/** * Usage-limit state per agent session — "this agent's runtime is out of * usage until …". Process-wide and dependency-free (like the spend-cap gate) * so the hot paths can ask it cheaply: the orchestrator restart service and * heartbeat monitor (no restarts while limited), delivery (no waiting on a * prompt that will not come, messages held), dispatch, and the status APIs. * * Why (2026-10-16): the orchestrator sat on Claude Code's weekly-limit menu * for ~38 hours. Crewly kept waiting for its prompt every ~17s, restarted it * 12 times in 15 minutes, and `GET /api/orchestrator/status` said "active and * ready" the whole time. * * Fed by the login/screen monitor (screen sweep + live output) through * {@link markUsageLimited}. A state lifts on its own at the reset time (plus * a short grace), or after {@link USAGE_LIMIT_STATE_CONSTANTS.NO_RESET_BACKOFF_MS} * when no reset time was given; it is set again if the limit is still there. * It is lifted early ({@link liftUsageLimit}) as soon as the agent works * again: a finished turn or tool call (Claude Code hooks), or fresh live * output that is not a limit message. * It is held in memory only: it describes the live screen, and after a Crewly * restart the screen is read again. * * Distinct from the runtime fallback's account-wide "exhausted runtime" * record: that one moves agents to another runtime; this one says what a * given session is doing right now. A session moved to another runtime is * cleared ({@link clearUsageLimit}). * * @module services/runtime-fallback/usage-limit-state */ /** Where a usage-limit state was seen. */ export type UsageLimitSource = 'menu' | 'screen' | 'output'; /** A session that is out of usage. */ export interface UsageLimitedInfo { /** Agent session */ sessionName: string; /** Runtime that is out of usage (null when unknown) */ runtime: string | null; /** Detection rule (logs only) */ ruleId: string; /** Where it was seen */ source: UsageLimitSource; /** ISO time of first detection */ detectedAt: string; /** ISO time the limit was last seen (every detection refreshes it) */ lastSeenAt: string; /** ISO time the runtime says the limit resets, when it says so */ until?: string; /** ISO time the session may be tried again (reset + grace, or the backoff) */ retryAt: string; /** ISO time Crewly answered the usage-limit menu, when it did */ menuAnsweredAt?: string; } /** Input of {@link markUsageLimited}. */ export interface MarkUsageLimitedInput { sessionName: string; runtime: string | null; ruleId: string; /** Reset time (epoch ms), or null when unknown */ resetAt: number | null; source: UsageLimitSource; } /** API view of a usage-limited session (the `usageLimited` status field). */ export interface UsageLimitedView { /** ISO reset time, when known */ until?: string; /** ISO time Crewly tries the session again */ retryAt: string; /** ISO time of first detection */ detectedAt: string; /** Runtime that is out of usage */ runtime: string | null; } /** * Record that a session is out of usage. A state already in force keeps its * first-detection time; a reset time seen later replaces the backoff, and a * later detection without one does not extend it. * * @param input - Session, runtime, rule, reset time, source * @param now - Current time (ms) * @returns The state and whether it is new (not in force before this call) * * @example * ```ts * markUsageLimited({ sessionName: 'crewly-orc', runtime: 'claude-code', ruleId: 'claude.hit_your_limit', resetAt, source: 'menu' }); * ``` */ export declare function markUsageLimited(input: MarkUsageLimitedInput, now?: number): { info: UsageLimitedInfo; isNew: boolean; }; /** * The usage-limit state in force for a session. A state past its retry time * is dropped. Never throws. * * @param sessionName - Agent session * @param now - Current time (ms) * @returns The state, or null when the session may run */ export declare function usageLimitOf(sessionName: string, now?: number): UsageLimitedInfo | null; /** * Milliseconds until a usage-limited session may be tried again. * * @param info - The state * @param now - Current time (ms) * @returns Milliseconds (at least 0) */ export declare function usageLimitRemainingMs(info: UsageLimitedInfo, now?: number): number; /** * Note that Crewly answered the usage-limit menu of a session. * * @param sessionName - Agent session * @param now - Current time (ms) */ export declare function noteUsageLimitMenuAnswered(sessionName: string, now?: number): void; /** * Forget a session's usage-limit state (it moved to another runtime, or the * owner changed its runtime). * * @param sessionName - Agent session * @returns True when a state was in force */ export declare function clearUsageLimit(sessionName: string): boolean; /** * Lift a session's usage-limit state because the agent works again: it * finished a turn, or wrote fresh output that is not a limit message. * Activity within {@link USAGE_LIMIT_STATE_CONSTANTS.LIFT_GRACE_MS} of the * limit last being seen, or of Crewly answering its menu, is the repaint of * that screen and does not count. * * @param sessionName - Agent session * @param now - Current time (ms) * @returns The lifted state, or null when none was in force or it was too soon * * @example * ```ts * if (liftUsageLimit('crewly-orc')) logger.info('Orchestrator works again'); * ``` */ export declare function liftUsageLimit(sessionName: string, now?: number): UsageLimitedInfo | null; /** * Every session that is usage-limited now. * * @param now - Current time (ms) * @returns States */ export declare function listUsageLimited(now?: number): UsageLimitedInfo[]; /** * The status-API view of a session's state (`usageLimited`), or undefined. * * @param sessionName - Agent session * @param now - Current time (ms) * @returns View, or undefined when the session is not limited */ export declare function usageLimitedView(sessionName: string, now?: number): UsageLimitedView | undefined; /** * One-line, owner-facing reason, e.g. "Orchestrator is out of Claude usage * until 2026-10-17T00:00:00.000Z". * * @param name - Who is limited ("Orchestrator", "Ella") * @param info - The state * @returns Reason text */ export declare function usageLimitReason(name: string, info: UsageLimitedInfo): string; /** Forget every state (tests). */ export declare function resetUsageLimitState(): void; //# sourceMappingURL=usage-limit-state.d.ts.map