@robota-sdk/agent-session 3.2.3 → 3.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["logger","logger","logger","logger","logger","logger","DEFAULT_REDACTED_VALUE"],"sources":["../src/synchronous-checkpoint.ts","../src/execution-root.ts","../src/turn-claim.ts","../src/session-base.ts","../src/conversation-transcript.ts","../src/require-hook-type-executors.ts","../src/compaction-orchestrator.ts","../src/context-window-tracker.ts","../src/abortable-approval.ts","../src/checkpointed-approval.ts","../src/auto-mode-gate.ts","../src/consent-scope.ts","../src/tool-argument-canonicalisation.ts","../src/permission-types.ts","../src/tool-hook-helpers.ts","../src/permission-denial-log.ts","../src/tool-permission-wrapper.ts","../src/permission-enforcer.ts","../src/session-components.ts","../src/load-session-for-update.ts","../src/compaction-tool-receipts.ts","../src/session-history-ops.ts","../src/session-lifecycle.ts","../src/session-tool-execution-bridge.ts","../src/session-run-observation.ts","../src/session-resume.ts","../src/session-execution-journal.ts","../src/session-run-options.ts","../src/session-run.ts","../src/session-recoverable.ts","../src/session-runtime-tools.ts","../src/session.ts","../src/session-artifact.ts","../src/scrub-sensitive.ts","../src/session-log-payload.ts","../src/session-logger.ts","../src/external-payload-file-reader.ts","../src/external-payload-resolver.ts","../src/session-log-validation.ts","../src/session-log-replay.ts","../src/checkpoint-tree.ts"],"sourcesContent":["/** A void port must acknowledge synchronously. Observe rejected async work without accepting it. */\nexport function assertSynchronousCheckpoint(result: unknown): void {\n if (\n ((typeof result === 'object' && result !== null) || typeof result === 'function') &&\n 'then' in result &&\n typeof result.then === 'function'\n ) {\n void Promise.resolve(result).catch(() => undefined);\n throw new Error(\n 'Session checkpoint must acknowledge synchronously; asynchronous persistence is unsupported',\n );\n }\n}\n","import { isAbsolutePath } from '@robota-sdk/agent-core';\n\n/**\n * The session's execution root.\n *\n * `Session` used to read `process.cwd()` in its constructor, and that ambient value became the\n * session's identity everywhere it matters — every hook input, `CLAUDE_PROJECT_DIR`, the permission\n * enforcer's root, the persisted record. A session could not be TOLD where it ran, so a subagent ran\n * in its parent's directory rather than its own workspace, while the subagent spawn contract had\n * declared `cwd` required all along.\n *\n * Making it a required TypeScript field is necessary and not sufficient. This package's tsconfig\n * excludes `*.test.ts`, and a JavaScript consumer is not type-checked at all — so without a runtime\n * check the field would simply be `undefined`, and the session would report a root it does not have\n * while everything downstream quietly used nothing. Silence is not success: refuse instead.\n */\nexport function requireExecutionRoot(cwd: unknown): string {\n if (typeof cwd !== 'string' || cwd.length === 0) {\n throw new Error(\n 'Session requires `cwd`: the absolute path this session executes in. It feeds ' +\n 'every hook input, CLAUDE_PROJECT_DIR, the permission root and the persisted record. Pass ' +\n '`process.cwd()` explicitly if that is genuinely what you mean.',\n );\n }\n // ABSOLUTE, not merely present. A relative root is resolved against `process.cwd()` by everything\n // downstream, so accepting one would let the ambient read this change removes back in through the\n // VALUE instead of through its absence — the same defect wearing a different shape.\n if (!isAbsolutePath(cwd)) {\n throw new Error(\n `Session requires an ABSOLUTE \\`cwd\\`; got ${JSON.stringify(cwd)}. A relative ` +\n 'root is resolved against the process directory downstream, which is the ambient value this ' +\n 'field exists to replace. Resolve it at your composition root.',\n );\n }\n return cwd;\n}\n","/**\n * Thrown when a turn is started on a session that is already running one.\n *\n * A distinct type rather than a bare `Error`, because the point of giving the session a claim is to\n * let consumers STOP maintaining their own busy flags — and a consumer that has to regex-match an\n * error message to tell \"busy, retry later\" apart from a provider failure has not been given\n * anything it can act on. Follows `CompactionError`, this package's existing precedent.\n */\nexport class SessionBusyError extends Error {\n /** Always `true`: the caller can run this turn later; nothing about the session is broken. */\n readonly recoverable = true;\n\n constructor(message: string) {\n super(message);\n this.name = 'SessionBusyError';\n }\n}\n\n/**\n * The identity of the turn a session is currently running.\n *\n * A session used to express \"is something running?\" as a bare `AbortController | null` field that\n * `run()` overwrote on entry. That field was doing three jobs at once — cancellation channel, busy\n * flag, and turn identity — and it could only do them for ONE turn, so a second concurrent `run()`\n * orphaned the first: `abort()` reached only whichever turn held the field, and the first turn to\n * finish cleared it in its `finally`, making `abort()` on the survivor a silent no-op. `isRunning()`\n * read the same field, so it answered about whichever turn happened to own it. That is why consumers\n * of this library grew their own busy flags rather than trusting it.\n *\n * The fix is to give the unit of work an OWNER. A claim is taken synchronously, it belongs to the\n * caller that took it, and ONLY that caller releases it.\n *\n * REFUSAL, not pre-emption. A session is a single conversation: two turns interleaving on it produce\n * a history neither of them wrote, and silently cancelling the first would discard work the caller\n * never asked to abandon. `claim()` throws {@link SessionBusyError}, whose message names the three\n * ways forward.\n */\nexport class TurnClaim {\n private controller: AbortController | null = null;\n private settlement: Promise<void> = Promise.resolve();\n private settle: (() => void) | undefined;\n\n /** Resolves when the currently claimed turn has finished unwinding. */\n get drained(): Promise<void> {\n return this.settlement;\n }\n\n /**\n * Take the claim for a new turn, or throw if one is already held.\n *\n * MUST be called before the first `await` in the turn — a check that yields first is not a claim,\n * it is a TOCTOU window, and two callers can pass it in the same tick.\n *\n * @throws {SessionBusyError} if a turn is already running, INCLUDING one that has been aborted but\n * has not finished unwinding. See {@link abort} for why that case is not an exception.\n */\n claim(): AbortController {\n if (this.controller !== null) {\n throw new SessionBusyError(\n 'This session is already running a turn. A session is a single conversation: await the ' +\n 'turn in flight, abort() it and await it, or use a separate session for concurrent work.',\n );\n }\n this.settlement = new Promise<void>((resolve) => {\n this.settle = resolve;\n });\n this.controller = new AbortController();\n return this.controller;\n }\n\n /**\n * Release the claim — but only if `controller` is still the one holding it.\n *\n * The ownership check is what stops the original defect from reappearing in a new shape: a turn\n * that released unconditionally in its `finally` could free a claim a LATER turn already took, and\n * `isRunning()` would then report idle while a turn was in flight.\n */\n release(controller: AbortController): void {\n if (this.controller === controller) {\n this.controller = null;\n this.settle?.();\n this.settle = undefined;\n }\n }\n\n /**\n * Signal the running turn to stop. Idempotent; a no-op if nothing is running.\n *\n * This does NOT release the claim, and that is deliberate. An earlier version cleared it here, so\n * `isRunning()` answered `false` the instant `abort()` returned — while the aborted turn was still\n * unwinding, still able to write history and finish tool calls. A new `run()` could then claim the\n * session and interleave with it: exactly the two-turns-on-one-session defect the claim\n * prevents, just moved behind the abort boundary. Review of the first draft caught it.\n *\n * A turn is not over when it is asked to stop; it is over when it has stopped. The claim is\n * therefore held until the owning turn's `finally` releases it, and until then `isRunning()` says\n * `true` and a further `run()` is refused. Cancel and restart is `abort()`, then AWAIT the turn,\n * then `run()` — which is what every caller in this repo already does.\n */\n abort(): void {\n this.controller?.abort();\n }\n\n isRunning(): boolean {\n return this.controller !== null;\n }\n}\n","import { requireExecutionRoot } from './execution-root.js';\nimport { TurnClaim } from './turn-claim.js';\n\nimport type { ContextWindowTracker, TAutoCompactThreshold } from './context-window-tracker.js';\nimport type { CompactionOrchestrator } from './compaction-orchestrator.js';\nimport type { PermissionEnforcer } from './permission-enforcer.js';\nimport type { IPermissionDenial } from './permission-denial-log.js';\nimport type {\n ConversationAgent,\n IAIProvider,\n IContextWindowState,\n IHistoryEntry,\n IToolSchema,\n TModelEffort,\n TModelEffortSelection,\n TPermissionMode,\n TToolParameters,\n TUniversalMessage,\n} from '@robota-sdk/agent-core';\n\nexport abstract class SessionBase {\n abstract assertInputAllowed(): void;\n protected abstract readonly agent: ConversationAgent;\n protected abstract readonly permissionEnforcer: PermissionEnforcer;\n protected abstract readonly contextTracker: ContextWindowTracker;\n protected abstract readonly compactionOrchestrator: CompactionOrchestrator;\n protected abstract permissionMode: TPermissionMode;\n protected abstract activePresetId: string;\n protected abstract parallelSubagentsEnabled: boolean;\n protected abstract readonly sessionId: string;\n protected abstract readonly aiProvider: IAIProvider;\n protected abstract readonly toolSchemas: IToolSchema[];\n protected abstract model: string;\n protected abstract systemMessage: string;\n protected abstract messageCount: number;\n /** The session's execution root — owned here, with the check that it was supplied. */\n protected readonly cwd: string;\n\n protected constructor(cwd: string) {\n this.cwd = requireExecutionRoot(cwd);\n }\n /**\n * The turn currently running, and its owner. Was a bare `AbortController | null` that\n * `run()` overwrote, which is why `abort()` and `isRunning()` below could answer about a turn that\n * was not the one in flight. See `turn-claim.ts`.\n */\n protected readonly turnClaim = new TurnClaim();\n private readonly permissionModeGuards = new Set<(next: TPermissionMode) => void>();\n\n getPermissionMode(): TPermissionMode {\n return this.permissionMode;\n }\n\n /** Change the active permission mode — future tool calls will use the new mode. */\n setPermissionMode(mode: TPermissionMode): void {\n for (const guard of this.permissionModeGuards) guard(mode);\n this.permissionMode = mode;\n }\n\n /** Register a synchronous policy check at the single session-mode mutation boundary. */\n addPermissionModeGuard(guard: (next: TPermissionMode) => void): () => void {\n this.permissionModeGuards.add(guard);\n return () => this.permissionModeGuards.delete(guard);\n }\n\n /** Read the active preset id (runtime state). */\n getActivePresetId(): string {\n return this.activePresetId;\n }\n\n /**\n * Set the active preset id. PURE STATE — this only records which preset is active;\n * it does not re-apply any preset options (permission/model/persona). Higher layers\n * own re-application.\n */\n setActivePresetId(id: string): void {\n this.activePresetId = id;\n }\n\n /** Whether subagent dispatch is currently allowed for this session (runtime gate). */\n getParallelSubagentsEnabled(): boolean {\n return this.parallelSubagentsEnabled;\n }\n\n /** Toggle subagent dispatch live. Only effective if the agent runtime was built at assembly. */\n setParallelSubagentsEnabled(enabled: boolean): void {\n this.parallelSubagentsEnabled = enabled;\n }\n\n getSessionId(): string {\n return this.sessionId;\n }\n\n /**\n * The session's execution root.\n *\n * Readable because a caller that derives something FROM the session — a fork, a subagent, a hook\n * input — must be able to ask which root this session actually runs in. Re-deriving it from\n * `process.cwd()` is how the two silently diverged.\n */\n getCwd(): string {\n return this.cwd;\n }\n\n getSystemMessage(): string {\n return this.systemMessage;\n }\n\n /**\n * Replace the active system message and propagate it so the next provider request carries it.\n * Records the live value on `this.systemMessage` (re-injected on compaction) and delegates to\n * `ConversationAgent.updateSystemPrompt`, which updates the single-source `config.systemMessage` and the live\n * conversation store head. The system prompt is an agent-level concern, not model config, so this\n * does not route through `setModel`. Used by persona application, the self-verification toggle, and\n * AGENTS.md/CLAUDE.md staleness refresh.\n */\n updateSystemMessage(newMessage: string): void {\n this.assertInputAllowed();\n this.systemMessage = newMessage;\n this.agent.updateSystemPrompt(newMessage);\n }\n\n /**\n * Re-apply model options to the live session (model/effort re-application seam).\n *\n * Propagates model/effort/temperature/maxOutputTokens to the agent via `agent.setModel` so the\n * next call reflects them, and updates `this.model` to keep `getModelId()` accurate. The preset\n * `maxOutputTokens` field maps to the agent's `maxTokens` channel. Absent fields are left untouched.\n */\n async applyModelOptions(options: {\n model?: string;\n effort?: TModelEffortSelection;\n temperature?: number;\n maxOutputTokens?: number;\n }): Promise<void> {\n // `setModel` requires the agent to be fully initialized. On a fresh interactive session the\n // agent initializes lazily on the first `run()`, so a live model change before any message\n // (e.g. `/preset` right after launch) would otherwise hit the \"must be fully initialized\"\n // guard. Bring the agent to a ready state first — idempotent and side-effect-free.\n await this.agent.ensureReady();\n const nextModel = options.model ?? this.model;\n // The system prompt is not model config; it is updated independently via updateSystemMessage.\n this.agent.setModel({\n provider: this.aiProvider.name,\n model: nextModel,\n ...(options.effort !== undefined && { effort: options.effort }),\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(options.maxOutputTokens !== undefined && { maxTokens: options.maxOutputTokens }),\n });\n this.setActiveModel(nextModel);\n }\n\n protected setActiveModel(model: string): void {\n this.model = model;\n this.contextTracker.setModel(model);\n this.compactionOrchestrator.setModel(model);\n }\n\n /** Read the selection for the next model call; provider default remains `auto`. */\n getModelEffort(): TModelEffortSelection {\n // Some lightweight session doubles intentionally implement only the execution surface. Keep\n // this read-only projection total for those callers; the real ConversationAgent instance exposes getModel.\n const getModel = (\n this.agent as ConversationAgent & { getModel?: () => { effort?: TModelEffortSelection } }\n ).getModel;\n if (getModel === undefined) return 'auto';\n try {\n return getModel.call(this.agent).effort ?? 'auto';\n } catch (error) {\n // Preserve the agent's own [LIFECYCLE] error when a destroyed agent is subsequently run.\n if (error instanceof Error && /disposed/i.test(error.message)) return 'auto';\n throw error;\n }\n }\n\n /** Run an operation with a temporary effort override and restore it on every exit path. */\n async withScopedModelEffort<T>(effort: TModelEffort, operation: () => Promise<T>): Promise<T> {\n const previous = this.getModelEffort();\n await this.applyModelOptions({ effort });\n try {\n return await operation();\n } finally {\n await this.applyModelOptions({ effort: previous });\n }\n }\n\n /**\n * Re-apply the agent's identity label to a LIVE session.\n *\n * A preset's `agentName` reached the agent only at construction, so\n * starting with a preset set the name while switching to the SAME preset mid-session left the old\n * one — one preset with two answers, decided by when it was chosen.\n *\n * Goes through `updateConfiguration`, the agent's own config seam: the agent's `name` reads THROUGH\n * its config, so writing the config is the whole rename and no copy is left stale.\n */\n async applyAgentName(name: string): Promise<void> {\n await this.agent.updateConfiguration({ name });\n }\n\n getToolSchemas(): IToolSchema[] {\n return this.toolSchemas;\n }\n\n getMessageCount(): number {\n return this.messageCount;\n }\n\n /** Get tools that have been session-approved (via \"Allow always\" choice). */\n /**\n * Re-apply a preset's tool lists to the live enforcer.\n *\n * The BASE it composes onto is the session's configured rules minus whatever a previous preset\n * contributed — which is why the enforcer keeps the original: an allowlist REPLACES the preset\n * layer's contribution rather than accumulating across successive `/preset` switches, while a\n * denial UNIONS because it must not be weakened by a later layer that forgot to repeat it.\n */\n applyPresetToolLists(preset: {\n allowedTools?: readonly string[];\n deniedTools?: readonly string[];\n }): void {\n this.permissionEnforcer.applyPresetToolLists(preset);\n }\n\n /**\n * The rules this session's gate reads right now — settings, preset lists and command auto-allows\n * together — so a subagent inherits what the parent actually enforces. Session-scoped\n * \"allow always\" consent is not included: it was given for this session's context.\n */\n getPermissionRules(): { allow: string[]; deny: string[]; ask: string[] } {\n const rules = this.permissionEnforcer.currentPermissionRules();\n return { allow: [...rules.allow], deny: [...rules.deny], ask: [...rules.ask] };\n }\n\n getSessionAllowedTools(): string[] {\n return this.permissionEnforcer.getSessionAllowedTools();\n }\n\n /**\n * Decide an action that reaches `toolName`'s effect by another route (a command that starts a\n * process), so that route cannot be a way around the tool's own permission: the PreToolUse hooks\n * and guardrails, then the rules, mode, remembered consent and the prompt. The command sandbox's\n * auto-approval never applies, because the action does not run inside that sandbox.\n */\n checkToolPermission(\n toolName: string,\n toolParameters: TToolParameters,\n signal?: AbortSignal,\n ): Promise<boolean> {\n return this.permissionEnforcer.checkDelegatedToolCall(toolName, toolParameters, signal);\n }\n\n /** `auto` mode hands decisions to a classifier, so a session without one cannot enter it. */\n protected requireClassifierFor(mode: TPermissionMode): void {\n if (mode === 'auto' && !this.permissionEnforcer.hasPermissionClassifier()) {\n throw new Error('Auto mode is unavailable: this session has no permission classifier.');\n }\n }\n\n /**\n * Let the call behind a classifier denial (by its index in the recent denials) run once when the\n * model tries it again. Returns the denial, or `undefined` when the index names no classifier\n * denial.\n */\n retryPermissionDenial(index: number): IPermissionDenial | undefined {\n return this.permissionEnforcer.allowRetryOfDenial(index);\n }\n\n /** The calls this session refused, most recent first. */\n getRecentPermissionDenials(): readonly IPermissionDenial[] {\n return this.permissionEnforcer.getRecentDenials();\n }\n\n clearSessionAllowedTools(): void {\n this.permissionEnforcer.clearSessionAllowedTools();\n }\n\n /** Abort the currently running execution. No-op if nothing is running. */\n abort(): void {\n this.turnClaim.abort();\n }\n\n isRunning(): boolean {\n return this.turnClaim.isRunning();\n }\n\n getContextState(): IContextWindowState {\n return this.contextTracker.getContextState();\n }\n\n /** Estimate context usage from current conversation history (used after session restore). */\n syncContextFromHistory(): void {\n this.contextTracker.updateFromHistory(this.agent.getHistory());\n }\n\n getAutoCompactThreshold(): TAutoCompactThreshold {\n return this.contextTracker.getAutoCompactThreshold();\n }\n\n setAutoCompactThreshold(threshold: number | false): void {\n this.contextTracker.setAutoCompactThreshold(threshold);\n }\n\n getHistory(): TUniversalMessage[] {\n return structuredClone(this.agent.getHistory());\n }\n\n getFullHistory(): IHistoryEntry[] {\n return structuredClone(this.agent.getFullHistory());\n }\n\n getSessionTokenUsage(): { inputTokens: number; outputTokens: number } | undefined {\n let inputTokens = 0;\n let outputTokens = 0;\n let found = false;\n for (const entry of this.getFullHistory()) {\n if (entry.category !== 'event' || entry.type !== 'usage-summary') continue;\n const snap = entry.data as { promptTokens?: number; completionTokens?: number } | undefined;\n inputTokens += snap?.promptTokens ?? 0;\n outputTokens += snap?.completionTokens ?? 0;\n found = true;\n }\n return found ? { inputTokens, outputTokens } : undefined;\n }\n\n getModelId(): string {\n return this.model;\n }\n\n /**\n * The tool schemas the model is offered at the next request.\n *\n * The offered set, not the registered one: a deferred tool that has not been loaded is absent,\n * because it is absent from the request. `/context` reads this to report what the tool schemas\n * actually cost, which is the only surface that makes deferral's saving observable.\n */\n getOfferedToolSchemas(): IToolSchema[] {\n return this.agent.getOfferedToolSchemas();\n }\n\n /** The provider the session sends its turns to now; a provider switch replaces it. */\n getProvider(): IAIProvider {\n return this.aiProvider;\n }\n\n getProviderId(): string {\n return this.aiProvider.name;\n }\n\n /** Add an event entry to history (not a chat message) */\n addHistoryEntry(entry: IHistoryEntry): void {\n this.assertInputAllowed();\n this.agent.addHistoryEntry(entry);\n }\n\n /** Inject a message into conversation history without execution (used for session restore). */\n injectMessage(\n role: 'user' | 'assistant' | 'system' | 'tool',\n content: string,\n options?: { toolCallId?: string; name?: string },\n ): void {\n this.assertInputAllowed();\n this.agent.injectMessage(role, content, options);\n }\n\n /**\n * Inject a full TUniversalMessage preserving all fields (toolCalls, toolCallId, null content).\n * Used during session restore to correctly reconstruct tool_use+tool_result pairs.\n */\n injectRawMessage(msg: TUniversalMessage): void {\n this.assertInputAllowed();\n this.agent.injectRawMessage(msg);\n }\n\n clearHistory(): void {\n this.assertInputAllowed();\n this.agent.clearHistory();\n this.contextTracker.reset();\n }\n}\n","/**\n * The one text rendering of a conversation that is handed to a model as prompt input.\n *\n * Compaction summarises it and the advisor reads it. Both need the same things kept: which tool the\n * model called with which arguments, what came back, and who wrote each user message — a message a\n * peer session sent is not the operator's own words and must not read as if it were.\n *\n * Every message is exactly one line: the label is written here and the content is JSON-encoded, so\n * no newline inside a message, a tool result or a peer's text can start a line of its own. That is\n * what keeps a peer from writing an unmarked `user:` line or a tool result from closing a block the\n * reader was told to trust.\n */\n\nimport {\n nonVisualObservationText,\n peerDriverOf,\n printablePeerDriver,\n} from '@robota-sdk/agent-core';\n\nimport type { TUniversalMessage } from '@robota-sdk/agent-core';\n\nfunction encode(content: unknown): string {\n return JSON.stringify(typeof content === 'string' ? content : (content ?? ''));\n}\n\nfunction encodeMessage(message: TUniversalMessage): string {\n const observations = (message.parts ?? []).flatMap((part) => {\n const observation = nonVisualObservationText(part);\n return observation === undefined ? [] : [observation];\n });\n return observations.length > 0\n ? JSON.stringify({ content: message.content, observations })\n : encode(message.content);\n}\n\n/**\n * The same peer attribution the model request carries (agent-core `peerDriverOf`), and the same\n * printable form of the id, which arrives from the sending side.\n */\nfunction userLabel(message: TUniversalMessage): string {\n const peer = peerDriverOf(message);\n return peer ? `user [from ${JSON.stringify(printablePeerDriver(peer))}]` : 'user';\n}\n\nfunction formatMessage(message: TUniversalMessage): string[] {\n switch (message.role) {\n case 'user':\n return [`${userLabel(message)}: ${encodeMessage(message)}`];\n case 'assistant': {\n const lines: string[] = [];\n if (message.content !== null && message.content !== '') {\n lines.push(`assistant: ${encodeMessage(message)}`);\n }\n for (const call of message.toolCalls ?? []) {\n lines.push(\n `assistant tool call ${JSON.stringify(call.function.name)} [${JSON.stringify(call.id)}]: ${encode(call.function.arguments)}`,\n );\n }\n return lines.length > 0 ? lines : [`assistant: \"\"`];\n }\n case 'tool':\n return [\n `tool result${message.name ? ` ${JSON.stringify(message.name)}` : ''} [${JSON.stringify(message.toolCallId)}]: ${encodeMessage(message)}`,\n ];\n case 'system':\n return [`system: ${encodeMessage(message)}`];\n }\n}\n\n/**\n * Render each message as one entry, in order. An entry is one line, except an assistant message\n * with several tool calls, which is one line per call. One entry per message so a caller that must\n * drop the oldest part of a long conversation can drop whole messages.\n */\nexport function formatConversationEntries(history: readonly TUniversalMessage[]): string[] {\n return history.map((message) => formatMessage(message).join('\\n'));\n}\n","import type { IHookTypeExecutor } from '@robota-sdk/agent-core';\n\n/**\n * The executors a session runs hook handlers through. REQUIRED, with no host default.\n *\n * The shell and HTTP executors drive a Node host, so a platform-neutral session cannot reach for\n * them itself; whoever composes the session says which handler types it can run. A JavaScript\n * consumer is not type-checked, so without a runtime check an omitted list would be `undefined` and\n * every configured hook would quietly run nothing — silence is not success, so refuse instead (the\n * same reasoning as `requireExecutionRoot`).\n */\nexport function requireHookTypeExecutors(\n executors: unknown,\n owner: string,\n): readonly IHookTypeExecutor[] {\n if (!Array.isArray(executors)) {\n throw new Error(\n `${owner} requires \\`hookTypeExecutors\\`: the executors that run a hook's handlers. On Node ` +\n 'pass `[new CommandExecutor(), new HttpExecutor()]` from `@robota-sdk/agent-core/node`; ' +\n 'pass `[]` to run no hook handlers.',\n );\n }\n return executors as readonly IHookTypeExecutor[];\n}\n","/**\n * CompactionOrchestrator — handles conversation compaction (summarization)\n * to free context window space.\n *\n * Extracted from Session to separate compaction logic from conversation management.\n */\n\nimport {\n callJournaledProvider,\n createAbortError,\n randomId,\n runHooks,\n} from '@robota-sdk/agent-core';\n\nimport { formatConversationEntries } from './conversation-transcript.js';\nimport { requireHookTypeExecutors } from './require-hook-type-executors.js';\n\nimport type { TCompactTrigger } from './session-types.js';\nimport type {\n IAIProvider,\n IExecutionJournal,\n TUniversalMessage,\n THooksConfig,\n IHookInput,\n IHookTypeExecutor,\n ISubprocessTraceEnv,\n} from '@robota-sdk/agent-core';\n\n/**\n * Thrown when a compaction summary is invalid (non-string or empty provider content).\n * Conversation history is append-only source data — callers must not clear or replace\n * it when this is thrown (see SPEC § Compaction Failure Contract).\n */\nexport class CompactionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CompactionError';\n }\n}\n\n/**\n * Default base template for the compaction summarization prompt — the one model-facing\n * prompt surface this package owns (declared in SPEC § Boundaries). Intentionally\n * domain-neutral: it must not assume a software-development conversation. Replaceable\n * wholesale via {@link ICompactionOptions.basePrompt}.\n */\nexport const DEFAULT_COMPACTION_PROMPT = [\n 'Summarize the following conversation concisely, preserving:',\n \"- User's original requests and goals\",\n '- Key decisions, conclusions, and important state',\n '- Identifiers, names, and references needed to continue the work',\n '- Current task status and next steps',\n \"Drop verbose intermediate outputs and exploratory work that didn't lead to results.\",\n].join('\\n');\n\nexport interface ICompactionOptions {\n sessionId: string;\n cwd: string;\n model: string;\n hooks?: Record<string, unknown>;\n compactInstructions?: string;\n /**\n * Replaces the entire base instruction template of the compaction prompt\n * (default: {@link DEFAULT_COMPACTION_PROMPT}). Focus instructions and the\n * formatted conversation are appended after it.\n */\n basePrompt?: string;\n /** The executors that run hook handlers; required, there is no host default. */\n hookTypeExecutors: readonly IHookTypeExecutor[];\n}\n\nexport class CompactionOrchestrator {\n private readonly sessionId: string;\n private readonly cwd: string;\n private model: string;\n private readonly hooks?: Record<string, unknown>;\n private readonly compactInstructions?: string;\n private readonly basePrompt?: string;\n private readonly hookTypeExecutors: readonly IHookTypeExecutor[];\n\n constructor(options: ICompactionOptions) {\n this.sessionId = options.sessionId;\n this.cwd = options.cwd;\n this.model = options.model;\n this.hooks = options.hooks;\n this.compactInstructions = options.compactInstructions;\n this.basePrompt = options.basePrompt;\n this.hookTypeExecutors = requireHookTypeExecutors(\n options.hookTypeExecutors,\n 'CompactionOrchestrator',\n );\n }\n\n setModel(model: string): void {\n this.model = model;\n }\n\n /**\n * Run compaction — summarize the conversation to free context space.\n * @param provider - The AI provider to use for summarization\n * @param history - The messages to summarise. Must not be empty: whether there is anything worth\n * compacting is the caller's judgement, made before it commits to replacing the conversation.\n * @param instructions - Optional focus instructions for the summary\n * @param signal - The turn's cancellation signal. Checked before the provider call\n * and again after it: an abort throws rather than returning, so the caller's existing\n * leave-history-untouched path covers a cancel as well as a failure.\n * @param hookTraceEnv - The prompt's trace for PreCompact's command hooks, only inside a prompt\n * @returns The generated summary string (always a non-empty string)\n * @throws {CompactionError} when `history` is empty, or when the provider returns a non-string or\n * empty summary — callers must leave the conversation history untouched in every such case\n */\n async compact(\n provider: IAIProvider,\n history: TUniversalMessage[],\n instructions?: string,\n signal?: AbortSignal,\n trigger: TCompactTrigger = 'manual',\n hookTraceEnv?: ISubprocessTraceEnv,\n executionJournal?: IExecutionJournal,\n ): Promise<string> {\n // FIRST, before the emptiness check. Review found that ordering the other way\n // returned a summary for an already-cancelled turn — and the caller replaces the conversation\n // with whatever this returns, so a cancel could still clear it and inject an empty summary.\n signal?.throwIfAborted();\n // This used to `return ''`, contradicting the contract two lines of docblock above it\n // (\"always a non-empty string\") — and the caller wrote that empty string over the conversation as\n // a summary. Deciding that an empty conversation is a no-op is the CALLER's judgement, made\n // before it commits to replacing anything; by the time execution is here, the caller has already\n // decided there is something to summarise, so an empty history means that decision was wrong.\n if (history.length === 0) {\n throw new CompactionError(\n 'Compaction was asked to summarise an empty history; conversation history preserved untouched',\n );\n }\n\n // Fire PreCompact hook\n const preHookInput: IHookInput = {\n session_id: this.sessionId,\n cwd: this.cwd,\n hook_event_name: 'PreCompact',\n trigger,\n };\n await runHooks(\n this.hooks as THooksConfig | undefined,\n 'PreCompact',\n preHookInput,\n this.hookTypeExecutors,\n hookTraceEnv,\n );\n\n // Build compaction prompt\n const compactPrompt = this.buildCompactionPrompt(history, instructions);\n\n // Call provider to generate summary\n const callId = randomId();\n const executionId = randomId();\n let route = provider.resolveModelRoute?.(this.model, executionId) ?? {\n provider: provider.name,\n model: this.model,\n };\n const summaryMessage = await callJournaledProvider(\n async (messages, options) => {\n try {\n return await provider.chat(messages, options);\n } catch (error) {\n if (signal?.aborted) throw createAbortError('Compaction cancelled', error);\n throw error;\n }\n },\n [\n {\n id: randomId(),\n role: 'user',\n content: compactPrompt,\n state: 'complete' as const,\n timestamp: new Date(),\n },\n ],\n {\n model: this.model,\n executionId,\n // Override defaults inside provider wrappers without mutating shared provider state.\n onTextDelta: () => {},\n onModelFallback: (notice) => {\n route = { ...notice.to };\n },\n toolChoice: 'none',\n // The history was sized to this model's window; a smaller one could not read it all.\n preserveContextWindow: true,\n ...(signal !== undefined ? { signal } : {}),\n },\n executionJournal\n ? {\n journal: executionJournal,\n executionId,\n callId,\n route: () => ({ providerId: route.provider, modelId: route.model }),\n }\n : undefined,\n );\n // The caller REPLACES the whole conversation with what this returns, so returning a\n // summary after a cancel is what destroyed it. Throwing puts an abort on the same path\n // already built for an invalid summary — history left untouched.\n signal?.throwIfAborted();\n if (typeof summaryMessage.content !== 'string' || summaryMessage.content.trim() === '') {\n throw new CompactionError(\n `Compaction produced an invalid summary (provider=${provider.name}, content type=${typeof summaryMessage.content}); conversation history preserved untouched`,\n );\n }\n\n return summaryMessage.content;\n }\n\n /** Build the compaction prompt from conversation history */\n private buildCompactionPrompt(history: TUniversalMessage[], instructions?: string): string {\n const instructionBlock = instructions ?? this.compactInstructions ?? '';\n const instructionSection = instructionBlock ? `\\nAdditional focus:\\n${instructionBlock}\\n` : '';\n\n const formattedHistory = formatConversationEntries(history).join('\\n');\n\n return [\n this.basePrompt ?? DEFAULT_COMPACTION_PROMPT,\n instructionSection,\n '',\n 'Conversation:',\n formattedHistory,\n ].join('\\n');\n }\n}\n","/**\n * ContextWindowTracker — tracks token usage and context window state.\n *\n * Extracted from Session to separate context monitoring from conversation management.\n */\n\nimport { estimateContextTokensFromMessages, getModelContextWindow } from '@robota-sdk/agent-core';\n\nimport type { IContextWindowState, TUniversalMessage } from '@robota-sdk/agent-core';\n\n/** Percentage conversion factor */\nconst PERCENT = 100;\n\n/** Auto-compact when context usage reaches this fraction */\nexport const AUTO_COMPACT_THRESHOLD = 0.835;\n\nexport type TAutoCompactThreshold = number | false;\n\nexport class ContextWindowTracker {\n private contextUsedTokens = 0;\n private contextMaxTokens: number;\n private readonly contextMaxTokensOverride: number | undefined;\n private autoCompactThreshold: TAutoCompactThreshold;\n\n constructor(\n model: string,\n contextMaxTokens?: number,\n autoCompactThreshold?: TAutoCompactThreshold,\n ) {\n this.contextMaxTokensOverride = contextMaxTokens;\n this.contextMaxTokens = contextMaxTokens ?? getModelContextWindow(model);\n this.autoCompactThreshold = normalizeAutoCompactThreshold(autoCompactThreshold);\n }\n\n /** Get current context window state */\n getContextState(): IContextWindowState {\n const usedPercentage = Math.min(\n PERCENT,\n (this.contextUsedTokens / this.contextMaxTokens) * PERCENT,\n );\n return {\n maxTokens: this.contextMaxTokens,\n usedTokens: this.contextUsedTokens,\n usedPercentage: Math.round(usedPercentage * PERCENT) / PERCENT,\n remainingPercentage: Math.round((PERCENT - usedPercentage) * PERCENT) / PERCENT,\n };\n }\n\n /** Whether auto-compaction threshold has been exceeded */\n shouldAutoCompact(requestMessages?: TUniversalMessage[]): boolean {\n if (this.autoCompactThreshold === false) {\n return false;\n }\n const usedTokens = requestMessages\n ? estimateContextTokensFromMessages(requestMessages).usedTokens\n : this.contextUsedTokens;\n return usedTokens >= this.contextMaxTokens * this.autoCompactThreshold;\n }\n\n /** Follow the active model unless the host supplied its own context budget. */\n setModel(model: string): void {\n this.contextMaxTokens = this.contextMaxTokensOverride ?? getModelContextWindow(model);\n }\n\n /** The auto-compaction policy for this tracker. */\n getAutoCompactThreshold(): TAutoCompactThreshold {\n return this.autoCompactThreshold;\n }\n\n /** Update the auto-compaction policy for this tracker. */\n setAutoCompactThreshold(autoCompactThreshold: TAutoCompactThreshold): void {\n this.autoCompactThreshold = normalizeAutoCompactThreshold(autoCompactThreshold);\n }\n\n /**\n * Estimate token usage from conversation history.\n *\n * Uses the shared core estimator (`estimateContextTokensFromMessages`) so session display,\n * /context, auto-compact, and core execution guards reason about the same effective token state.\n * That estimator prefers the provider's actual reported token count (which includes the system\n * prompt and tool schemas) over a raw serialized-history char heuristic, falling back to the\n * serialized estimate only when no provider usage is present on the latest message.\n */\n updateFromHistory(history: TUniversalMessage[]): void {\n this.contextUsedTokens = estimateContextTokensFromMessages(history).usedTokens;\n }\n\n /** Reset token tracking */\n reset(): void {\n this.contextUsedTokens = 0;\n }\n}\n\nfunction normalizeAutoCompactThreshold(\n autoCompactThreshold: TAutoCompactThreshold | undefined,\n): TAutoCompactThreshold {\n if (autoCompactThreshold === undefined) {\n return AUTO_COMPACT_THRESHOLD;\n }\n if (autoCompactThreshold === false) {\n return false;\n }\n if (\n !Number.isFinite(autoCompactThreshold) ||\n autoCompactThreshold <= 0 ||\n autoCompactThreshold > 1\n ) {\n throw new RangeError('autoCompactThreshold must be a number greater than 0 and at most 1.');\n }\n return autoCompactThreshold;\n}\n","import type {\n IPermissionAskContext,\n TPermissionHandler,\n TPermissionResult,\n} from './permission-types.js';\nimport type { ITerminalOutput, TToolArgs } from '@robota-sdk/agent-core';\n\n/**\n * Wait for an approval, or for the turn to be cancelled — whichever happens first.\n *\n * A human-approval prompt is a wait with no natural end. `abort()` reached the provider\n * and the tool-start check but not this, so a turn parked here ran until somebody answered, and since\n * the session's claim is held until the turn unwinds, the session stayed busy with no way\n * for the caller to clear it short of discarding it.\n *\n * A cancelled approval resolves to `false`, NOT to a rejection and never to `true`. Two reasons, and\n * the first is the load-bearing one:\n *\n * - **Fail closed.** If cancelling read as approval, aborting a turn would become a way to run an\n * unapproved tool. Denial is the same answer the enforcer already gives when no approver is\n * attached.\n * - The caller (`permission-enforcer`'s tool wrapper) must never throw — a throw there records an\n * assistant `tool_use` with no matching `tool_result` and corrupts the conversation.\n *\n * The listener is removed on every path, so an approval that arrives after the abort does not keep a\n * handler alive on a long-lived signal.\n */\nasync function raceAbort(\n approval: Promise<TPermissionResult>,\n signal?: AbortSignal,\n): Promise<TPermissionResult> {\n if (signal === undefined) return approval;\n if (signal.aborted) return false;\n\n let onAbort: (() => void) | undefined;\n try {\n return await Promise.race([\n approval,\n new Promise<TPermissionResult>((resolve) => {\n onAbort = (): void => resolve(false);\n signal.addEventListener('abort', onAbort, { once: true });\n }),\n ]);\n } finally {\n if (onAbort !== undefined) signal.removeEventListener('abort', onAbort);\n }\n}\n\n/**\n * What an approval RESULT means, in one place.\n *\n * The two prompt paths — a consumer `permissionHandler` and an injected `promptForApprovalFn` —\n * interpreted `allow-session` / `allow-project` identically, in two copies. Two readings of \"does\n * this answer grant permission\" that can drift is the shape this repository keeps removing; and the\n * caller now only has to know WHICH prompt to run, not what its answer implies.\n *\n * Side effects are returned rather than performed, so this stays a pure decision the enforcer applies.\n */\ninterface IApprovalOutcome {\n allowed: boolean;\n rememberForSession: boolean;\n rememberForProject: boolean;\n}\n\nfunction interpretApproval(result: TPermissionResult): IApprovalOutcome {\n if (result === 'allow-session') {\n return { allowed: true, rememberForSession: true, rememberForProject: false };\n }\n if (result === 'allow-project') {\n return { allowed: true, rememberForSession: true, rememberForProject: true };\n }\n return { allowed: result === true, rememberForSession: false, rememberForProject: false };\n}\n\n/**\n * The whole human-approval path: allow-list, cancellation, prompt, and what the answer means.\n *\n * Separate from the enforcer, and the seam is real\n * rather than convenient — this decides whether a human said yes, while the enforcer decides whether\n * a human is asked at all. Side effects come back as flags so the enforcer keeps ownership of its own\n * allow lists.\n *\n * A human-approval prompt is a wait with NO NATURAL END, which is why the signal matters here more\n * than anywhere else in the permission path: without it an aborted turn parked on a prompt ran until\n * somebody answered, and the session stayed claimed. Cancelling DENIES — a cancelled approval must\n * never read as approval, or aborting a turn becomes a way to run an unapproved tool.\n */\nexport interface IApprovalRequest {\n toolName: string;\n toolArgs: TToolArgs;\n alreadyAllowed: boolean;\n /** A consumer-supplied handler. Takes precedence over `injectedPrompt`, as it always did. */\n handler?: TPermissionHandler;\n injectedPrompt?: (\n terminal: ITerminalOutput,\n toolName: string,\n toolArgs: TToolArgs,\n ) => Promise<TPermissionResult>;\n terminal?: ITerminalOutput;\n signal?: AbortSignal;\n /** The tool call being decided, handed to `handler` so the prompt it raises can name the call. */\n executionId?: string;\n}\n\nexport async function decideApproval(request: IApprovalRequest): Promise<IApprovalOutcome> {\n const denied: IApprovalOutcome = {\n allowed: false,\n rememberForSession: false,\n rememberForProject: false,\n };\n if (request.alreadyAllowed) {\n return { allowed: true, rememberForSession: false, rememberForProject: false };\n }\n // A turn already cancelled asks nobody. Prompting here would put a question on screen\n // for work that is not going to happen.\n if (request.signal?.aborted === true) return denied;\n // Which prompt to run is this module's business too: the enforcer supplies the approvers it has\n // and does not decide between them.\n // The handler is given the same signal the wait observes, so it can withdraw the prompt itself:\n // racing the abort ends this wait, but only the handler can take the question off a screen. A call\n // with neither a signal nor an id asks exactly as before (two arguments), so a handler that counts\n // them sees no change.\n const context: IPermissionAskContext = {\n ...(request.signal ? { signal: request.signal } : {}),\n ...(request.executionId !== undefined ? { executionId: request.executionId } : {}),\n };\n const hasContext = request.signal !== undefined || request.executionId !== undefined;\n const prompt = request.handler\n ? (): Promise<TPermissionResult> =>\n hasContext\n ? request.handler!(request.toolName, request.toolArgs, context)\n : request.handler!(request.toolName, request.toolArgs)\n : request.injectedPrompt && request.terminal\n ? (): Promise<TPermissionResult> =>\n request.injectedPrompt!(request.terminal!, request.toolName, request.toolArgs)\n : undefined;\n // No approval mechanism available — deny by default.\n if (prompt === undefined) return denied;\n return interpretApproval(await raceAbort(prompt(), request.signal));\n}\n","import { ExecutionRecoveryError } from '@robota-sdk/agent-core';\nimport type { IToolContinuation, TToolArgs, TToolParameters } from '@robota-sdk/agent-core';\n\nconst kind = 'robota-session/approval';\n\nexport function hasCheckpointedApproval(continuation: IToolContinuation | undefined): boolean {\n return continuation?.waits.some((wait) => wait.request.kind === kind) ?? false;\n}\n\n/** Current policy is checked by the enforcer; this can answer only the exact saved action. */\nexport async function checkpointedApproval(\n context: {\n sessionId: string;\n cwd: string;\n toolName: string;\n toolArgs: TToolArgs;\n /** The host's canonical spelling of a directory, so a saved approval names the same one. */\n canonicalizePath: (path: string) => string;\n },\n continuation: IToolContinuation | undefined,\n requireApproval: boolean,\n): Promise<boolean | undefined> {\n const waits = continuation?.waits.filter((wait) => wait.request.kind === kind) ?? [];\n if (!requireApproval && !waits.length) return undefined;\n if (!continuation || continuation.action.toolName !== context.toolName)\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_INVALID',\n 'Checkpointed approval requires the current journaled action',\n );\n const cwd = context.canonicalizePath(context.cwd);\n for (const { request } of waits) {\n if (\n request.data.version !== 1 ||\n request.data.sessionId !== context.sessionId ||\n request.data.cwd !== cwd ||\n request.data.toolName !== context.toolName\n )\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_INVALID',\n 'Saved approval belongs to a different Session or tool',\n );\n }\n const answer = await continuation.request({\n kind,\n data: {\n version: 1,\n sessionId: context.sessionId,\n cwd,\n toolName: context.toolName,\n // The Core continuation port validates finite JSON before persisting this value.\n arguments: structuredClone(context.toolArgs) as TToolParameters,\n },\n });\n if (Object.keys(answer).length !== 1 || typeof answer.approved !== 'boolean')\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_INVALID',\n 'Approval response must contain only a boolean approved decision',\n );\n return answer.approved;\n}\n","/**\n * `auto` permission mode: a model classifier decides the calls the mode itself does not.\n *\n * The permission gate still runs first — deny rules, the ceiling, and the reads and workspace edits\n * the mode approves. What it would have asked a person about goes to the classifier instead, unless\n * the user's own `ask` rule names it. A block returns its reason to the model so it can take another\n * route. When the classifier keeps refusing, the mode pauses and asks a person, because a model\n * that is blocked over and over is either stuck or being steered, and neither is for the classifier\n * to resolve.\n */\n\nimport type { TToolArgs } from '@robota-sdk/agent-core';\n\n/** The call the classifier judges. */\nexport interface IClassifiedCall {\n readonly toolName: string;\n readonly toolArgs: TToolArgs;\n readonly cwd: string;\n}\n\nexport interface IClassifierVerdict {\n readonly decision: 'allow' | 'block';\n /** Short, for the model and the user: which rule, and why. */\n readonly reason: string;\n}\n\n/**\n * Judges one call. `undefined` means no usable verdict (an error, a refusal, output that does not\n * parse): the call is not run. It counts toward a run of refusals, so a classifier that cannot\n * answer hands the decision to a person instead of refusing every call.\n */\nexport interface IPermissionClassifier {\n classify(call: IClassifiedCall, signal?: AbortSignal): Promise<IClassifierVerdict | undefined>;\n}\n\n/** Refusals in a row (blocks or unusable verdicts), and blocks in the session, that pause the mode. */\nexport const CONSECUTIVE_BLOCK_LIMIT = 3;\nexport const TOTAL_BLOCK_LIMIT = 20;\n\n/** `reason` is for the user (`/permissions`); `message` is what the model is told. */\nexport type TAutoModeJudgement =\n | { readonly kind: 'allow' }\n | { readonly kind: 'block'; readonly reason: string; readonly message: string }\n | { readonly kind: 'unusable'; readonly reason: string; readonly message: string };\n\nfunction callKey(toolName: string, toolArgs: TToolArgs): string {\n return `${toolName}\\u0000${JSON.stringify(toolArgs)}`;\n}\n\nexport class AutoModeGate {\n private consecutive = 0;\n private total = 0;\n private paused = false;\n /** Calls a person allowed to be retried once after the classifier blocked them. */\n private readonly retries = new Set<string>();\n\n constructor(private readonly classifier: IPermissionClassifier) {}\n\n /** The mode asks a person until one approves. */\n isPaused(): boolean {\n return this.paused;\n }\n\n /** A person approved while paused: the classifier decides again. */\n resume(): void {\n this.paused = false;\n this.consecutive = 0;\n }\n\n /** Let this exact call through once, without the classifier. */\n grantRetry(toolName: string, toolArgs: TToolArgs): void {\n this.retries.add(callKey(toolName, toolArgs));\n }\n\n /** Consume a retry grant for this exact call, if there is one. */\n takeRetry(toolName: string, toolArgs: TToolArgs): boolean {\n return this.retries.delete(callKey(toolName, toolArgs));\n }\n\n async judge(call: IClassifiedCall, signal?: AbortSignal): Promise<TAutoModeJudgement> {\n let verdict: IClassifierVerdict | undefined;\n try {\n verdict = await this.classifier.classify(call, signal);\n } catch {\n // allow-fallback: an unusable verdict is a denial that is reported, not a crash\n verdict = undefined;\n }\n if (verdict === undefined) {\n // A turn the user cancelled is not the classifier failing to answer.\n if (signal?.aborted !== true) this.consecutive += 1;\n const pause = this.consecutive >= CONSECUTIVE_BLOCK_LIMIT;\n if (pause) this.paused = true;\n return {\n kind: 'unusable',\n reason: 'no usable verdict',\n message:\n 'The auto-mode classifier gave no usable verdict, so the call was not run. Try again, ' +\n 'or ask the user to approve it.' +\n (pause ? ' Auto mode is paused: the next calls ask the user.' : ''),\n };\n }\n if (verdict.decision === 'allow') {\n this.consecutive = 0;\n return { kind: 'allow' };\n }\n this.consecutive += 1;\n this.total += 1;\n const pause = this.consecutive >= CONSECUTIVE_BLOCK_LIMIT || this.total >= TOTAL_BLOCK_LIMIT;\n if (this.total >= TOTAL_BLOCK_LIMIT) this.total = 0;\n if (pause) this.paused = true;\n return {\n kind: 'block',\n reason: verdict.reason,\n message:\n `Blocked by the auto-mode classifier: ${verdict.reason}. Do not retry the same action; ` +\n 'take another approach or ask the user.' +\n (pause ? ' Auto mode is paused after repeated blocks: the next calls ask the user.' : ''),\n };\n }\n}\n","/**\n * What \"don't ask again\" GRANTS, as a permission pattern.\n *\n * Session and project consent used to be keyed on the tool NAME: approving `Bash` for `git status`\n * allowed every later `Bash`, approving `WebFetch` for one benign URL allowed every host, and the\n * project-level record (`Tool(*)`) outlived the session. The user was shown one argument and\n * granted all of them, and the prompt never said so.\n *\n * The consent record is now a pattern in the gate's own grammar, projected from the argument by the\n * kind the tool's permission profile declares, so the record, the prompt and the\n * `permissions.allow` rules all speak one language and are matched by one matcher:\n *\n * - `path` → the containing directory: `Read(/w/src/**)` — one approval covers a tree, not a file\n * and not the filesystem;\n * - `url` → the origin: `WebFetch(https://example.com/**)` — a host, not every host;\n * - `command` → the program: `Bash(git *)` — `argv[0]`, the projection the issue names;\n * - `text`, or no declared argument → the tool name: a search query or a glob pattern is not a\n * blast radius, and a tool that declares no argument cannot be narrowed at all.\n *\n * Exact-argument consent would prompt constantly; unbounded consent is what existed. A projection is\n * the design work, and it is stated here once so both prompt surfaces can print it verbatim.\n */\n\nimport { getToolPermissionProfile } from '@robota-sdk/agent-core';\n\nimport type { TToolArgs } from '@robota-sdk/agent-core';\n\n/** Lexical directory of a path, separators normalised; `/` → `/`, `a` → `.`. */\nfunction directoryOf(path: string): string {\n const slashed = path.replace(/\\\\/g, '/');\n const cut = slashed.lastIndexOf('/');\n if (cut < 0) return '.';\n if (cut === 0) return '/';\n return slashed.slice(0, cut);\n}\n\n/** The pattern text (inside the parentheses) consent covers, or undefined for name-only consent. */\nfunction scopeArgument(kind: string, value: string): string | undefined {\n switch (kind) {\n case 'path': {\n const dir = directoryOf(value);\n return dir === '/' ? '/**' : `${dir}/**`;\n }\n case 'url': {\n try {\n const url = new URL(value);\n return `${url.protocol}//${url.host}/**`;\n } catch {\n // allow-fallback: a URL the platform parser refuses is consented to EXACTLY as written —\n // the strict direction — never widened to the tool name.\n return value;\n }\n }\n case 'command': {\n const argv0 = value.trim().split(/\\s+/)[0];\n return argv0 ? `${argv0} *` : undefined;\n }\n default:\n return undefined;\n }\n}\n\n/** The permission pattern a \"don't ask again\" answer for this invocation grants. */\nexport function consentScopeFor(toolName: string, toolArgs: TToolArgs): string {\n const argument = getToolPermissionProfile(toolName).argument;\n if (argument === undefined) return toolName;\n const value = toolArgs[argument.key];\n if (typeof value !== 'string' || value === '') return toolName;\n const scoped = scopeArgument(argument.kind, value);\n return scoped === undefined ? toolName : `${toolName}(${scoped})`;\n}\n","/**\n * Canonicalise a tool invocation's arguments BEFORE the permission gate sees them.\n *\n * `Read`, `Write` and `Edit` declare `filePath` absolute, but nothing makes the model comply. A\n * relative `filePath` reaching the gate as written cannot be compared with an absolute pattern —\n * `Read(/w/**)` against `src/x` — and the gate answers that case \"unevaluable\" (a deny prompts, an\n * allow does not auto-approve) rather than guessing the base. The base is not a guess here: the\n * session's `cwd` is the containment root the tool itself anchors a relative path to, so resolving\n * against it produces the exact path the tool will open, and the gate judges that.\n *\n * Which argument is a path is what the tool's registered permission profile declares\n * (`argument.kind === 'path'`), so this module names no tool. The canonical parameters are what\n * the gate, the logs and the tool all receive — one input, not one for the decision and another\n * for the action.\n */\n\nimport { getToolPermissionProfile, isAbsolutePath, resolvePath } from '@robota-sdk/agent-core';\n\nimport type { TToolParameters } from '@robota-sdk/agent-core';\n\nexport function canonicaliseToolArguments(\n toolName: string,\n parameters: TToolParameters,\n cwd: string,\n): TToolParameters {\n const argument = getToolPermissionProfile(toolName).argument;\n if (argument === undefined || argument.kind !== 'path') return parameters;\n const value = parameters[argument.key];\n if (typeof value !== 'string' || value === '' || isAbsolutePath(value)) return parameters;\n const resolved = resolvePath(cwd, value);\n // A path relative to another drive's current directory stays as written, and the gate asks.\n if (!isAbsolutePath(resolved)) return parameters;\n return { ...parameters, [argument.key]: resolved };\n}\n","import type { IToolAppView, TUniversalMessagePart } from '@robota-sdk/agent-core';\nimport type { IPathProtectionPolicy, TResolveInWorkspace } from '@robota-sdk/agent-core';\n/**\n * Permission types — interfaces and type aliases for permission enforcement.\n */\n\nimport type { IPermissionClassifier } from './auto-mode-gate.js';\nimport type { ISessionLogger } from './session-logger.js';\nimport type { TPermissionMode, TToolArgs } from '@robota-sdk/agent-core';\nimport type {\n IHookTypeExecutor,\n ISpinner,\n ITerminalOutput,\n IToolResult,\n TBackgroundPermissionPolicy,\n} from '@robota-sdk/agent-core';\nimport type {\n TPermissionRequester,\n TPermissionResultValue,\n} from '@robota-sdk/agent-interface-session';\n\nexport type { ISpinner, ITerminalOutput };\nexport type { TPermissionRequester };\n\n/** The part of a sandbox client the permission gate consults. */\nexport interface ICommandSandboxApproval {\n /**\n * Whether `toolName` runs `shellCommand` inside the sandbox and the sandbox's settings let it\n * proceed without a prompt. Only a tool the sandbox actually wraps may answer yes.\n */\n autoApproves(toolName: string, shellCommand: string): boolean;\n}\n\n/**\n * Permission handler result (the union is OWNED by `agent-interface-session` as\n * `TPermissionResultValue`; this name is the session-layer alias, not a second declaration):\n * - true: allow this invocation\n * - false: deny this invocation\n * - 'allow-session': allow this invocation and auto-approve the CONSENT SCOPE — the pattern\n * `consentScopeFor` projects from this invocation's argument, e.g. `Bash(git *)` —\n * for the rest of the session\n * - 'allow-project': allow this invocation and persist that same scope pattern to the project's\n * local settings; the storage location is owned by the consuming layer (via `onProjectAllowTool`)\n */\nexport type TPermissionResult = TPermissionResultValue;\n\n/**\n * Context a caller MAY attach to a permission ask beyond the call itself.\n *\n * `requester` says WHO is asking, when it is not the person's own turn — a background agent's tool\n * call forwarded to the person, say — so a surface can show \"Background agent X wants to …\" instead\n * of an unattributed prompt. `signal` cancels the ASK itself (not the tool call): a background task\n * that is stopped or whose child process exits while a person has not yet answered must not leave\n * that answer parked forever, so aborting it here settles the parked prompt denied and dismisses it\n * on every attached surface — the same fail-closed direction `checkPermission`'s own `signal` denies\n * on. `executionId` names the tool call the ask is about — the id its `tool_start`/`tool_end`\n * carry — so a surface can hold one row from the request to the call's end. Every field is\n * optional so every existing 2-arg handler stays a valid implementation.\n */\nexport interface IPermissionAskContext {\n readonly requester?: TPermissionRequester;\n readonly signal?: AbortSignal;\n readonly executionId?: string;\n}\n\n/**\n * Custom permission handler — called when a tool needs user approval.\n * Returns true to allow, false to deny, or 'allow-session' to remember for the session.\n */\nexport type TPermissionHandler = (\n toolName: string,\n toolArgs: TToolArgs,\n context?: IPermissionAskContext,\n) => Promise<TPermissionResult>;\n\nexport interface IPermissionEnforcerOptions {\n readonly pathProtection?: IPathProtectionPolicy;\n sessionId: string;\n cwd: string;\n getPermissionMode: () => TPermissionMode;\n config: {\n /** `ask` patterns always ask, in every mode including bypassPermissions. */\n permissions: { allow: string[]; deny: string[]; ask?: string[] };\n hooks?: Record<string, unknown>;\n };\n /**\n * The OS sandbox the shell tools run under, when there is one: whether it confines a command and\n * lets it run without a prompt.\n */\n commandSandbox?: ICommandSandboxApproval;\n /**\n * Where `~` and `$HOME` point for the gate's path checks. Absent, the home directory is unknown:\n * a bare `~` is still refused as a critical target, but a path written under it is not expanded,\n * so it cannot match a protected path.\n */\n homeDirectory?: string;\n /**\n * Where a path a shell command names really is, symlinks followed (a host with a filesystem\n * supplies it). Absent, a command with a path operand is not read-only.\n */\n resolveInWorkspace?: TResolveInWorkspace;\n /** The host's canonical spelling of a directory; absent, a path is compared as written. */\n canonicalizePath?: (path: string) => string;\n /**\n * The rules BEFORE any preset contributed.\n *\n * Supplied by the composition root, never derived here. `config.permissions` already carries the\n * STARTUP preset's patterns, so capturing a base from it on the first live `/preset` would keep\n * the first preset's allowlist through every later switch — the accumulation the replace rule\n * exists to prevent, arriving through the base rather than through the merge. Absent ⇒ no preset\n * contributed, and `config.permissions` is itself the preset-free base.\n */\n presetFreePermissions?: { allow: readonly string[]; deny: readonly string[] };\n terminal: ITerminalOutput;\n permissionHandler?: TPermissionHandler;\n promptForApprovalFn?: (\n terminal: ITerminalOutput,\n toolName: string,\n toolArgs: TToolArgs,\n ) => Promise<TPermissionResult>;\n sessionLogger?: ISessionLogger;\n onToolExecution?: (event: {\n type: 'start' | 'end';\n toolName: string;\n toolArgs?: TToolArgs;\n success?: boolean;\n denied?: boolean;\n toolResultData?: string;\n toolResultParts?: TUniversalMessagePart[];\n executionId?: string;\n /** The source's view of the result, for a surface that can host one; never model context. */\n view?: IToolAppView;\n }) => void;\n /** The executors that run hook handlers; required, there is no host default. */\n hookTypeExecutors: readonly IHookTypeExecutor[];\n /** Absolute path to session transcript file — passed to PreToolUse hook inputs as transcript_path */\n transcriptPath?: string;\n /** Called when the user selects \"allow for project\" — persists the tool pattern to project settings. */\n onProjectAllowTool?: (toolName: string) => void;\n /**\n * A background/subagent task permission policy. It adds a ceiling (checked before bypass),\n * an ask-everything flag and the task's own lists to the one evaluator, so `deny`/`preapproved`/\n * `inherit-allowlist` still bind under a permissive mode. Absent → no policy constraints.\n */\n permissionPolicy?: TBackgroundPermissionPolicy;\n /**\n * The task's OWN declared allow/deny rules (distinct from the parent session's `config.permissions`\n * which `inherit-allowlist` inherits). `preapproved` consults these.\n */\n taskPermissions?: { allow?: readonly string[]; deny?: readonly string[] };\n /**\n * Judges, in `auto` mode, the calls the mode would otherwise ask a person about. Absent → the\n * session cannot enter `auto`.\n */\n permissionClassifier?: IPermissionClassifier;\n}\n\n/**\n * How a tool call ended, when it did not simply succeed.\n *\n * Three outcomes used to be indistinguishable from success at this type — a tool that THREW, a user\n * DENIAL, and a hook BLOCK — because each was returned as `{ success: true, data: '{\"success\":\n * false,…}' }`. Every consumer above had to parse English out of a string and guess, and all three\n * guesses were the same one.\n *\n * The framing the audit stated is the one kept here: **\"never throw\" is correct and \"encode the\n * failure as success\" is not — they are independent decisions.** Nothing below starts throwing.\n */\nexport type TToolFailureOutcome = 'threw' | 'denied' | 'hook-blocked';\n\n/**\n * The failure envelope. `success: false` is the part consumers branch on; `outcome` is the part they\n * branch on when they need to know WHICH failure, and neither requires reading `data`.\n *\n * `data` keeps the JSON string it always carried, because the model is shown that text and changing\n * what it sees is a separate decision from making the envelope honest.\n */\nexport interface IToolFailureResult extends IToolResult {\n success: false;\n outcome: TToolFailureOutcome;\n error: string;\n data: string;\n metadata: Record<string, never>;\n}\n\nexport function toolFailure(\n outcome: TToolFailureOutcome,\n error: string,\n /**\n * The payload, kept in the shape callers already parse.\n *\n * A CORRECTION, because the first version of this comment claimed something false and review\n * caught it. It said the model still sees `{ blocked: true, reason }` because `data` was\n * unchanged. It does not. `success: false` now reaches `ToolManager.executeTool`, which throws\n * `ToolExecutionError`; `ToolExecutionService` catches that and returns `{ success: false, error }`,\n * and the history writer renders a failed result as `Error: <message>`. So what the model reads\n * changed from the JSON payload to one error line.\n *\n * That change is INTENDED and is the point of the item: a blocked call is a failure, and the model\n * being told `Error: Blocked by hook — <reason>` is more honest than being handed a success-shaped\n * envelope it has to introspect. What was wrong was the claim that nothing changed, not the change.\n * The reason travels in `error`, so nothing is lost.\n */\n data?: unknown,\n): IToolFailureResult {\n return {\n success: false as const,\n outcome,\n error,\n data: JSON.stringify(data ?? { success: false, output: '', error }),\n metadata: {},\n };\n}\n\n/**\n * What a crash announcement looks like, named rather than widened.\n *\n * The first version typed `announce` as taking a `Record<string, unknown>`, which the real\n * `onToolExecution` cannot be assigned to — parameter positions are contravariant — so the call site\n * reached for `as never`. A cast at a boundary is the boundary's type being wrong; this is the\n * subset of the event this function actually emits.\n */\nexport interface IToolCrashAnnouncement {\n type: 'end';\n toolName: string;\n toolArgs: TToolArgs;\n success: false;\n executionId?: string;\n}\n\n/**\n * The crash path, as one call: announce the failure to the listener and return an honest envelope.\n *\n * It lives beside the envelope rather than in the enforcer because the enforcer is at its size\n * ceiling and this is the same subject — what a failed tool call looks like. Two things happen here\n * and both are the point: a catch that returned `success: true` AND emitted no end\n * event would leave a crash invisible to the caller and to anything watching.\n */\nexport function reportToolCrash(\n error: unknown,\n announce: ((event: IToolCrashAnnouncement) => void) | undefined,\n where: { toolName: string; toolArgs: TToolArgs; executionId?: string },\n): IToolFailureResult {\n const message = error instanceof Error ? error.message : String(error);\n announce?.({\n type: 'end',\n toolName: where.toolName,\n toolArgs: where.toolArgs,\n success: false,\n executionId: where.executionId,\n });\n return toolFailure('threw', message);\n}\n\n/** A refusal that tells the model why, so it can take another route. */\nexport interface IPermissionRefusal {\n readonly message: string;\n}\n\n/** Returned when the user denies a permission prompt. */\nexport const PERMISSION_DENIED_RESULT = toolFailure(\n 'denied',\n 'Permission denied. The user did not approve this action.',\n);\n\n/** Maximum chars for any single tool output. Matches Claude Code's 30K limit. */\nexport const MAX_TOOL_OUTPUT_CHARS = 30_000;\n","/**\n * Tool hook helpers — stateless utility functions for tool hook execution\n * and output truncation used by PermissionEnforcer.\n */\n\nimport { runHooks, createLogger, isEnforcing, wasToolResultAdmitted } from '@robota-sdk/agent-core';\n\nimport { canonicaliseToolArguments } from './tool-argument-canonicalisation.js';\n\nimport { MAX_TOOL_OUTPUT_CHARS, toolFailure } from './permission-types.js';\n\nimport type {\n IToolResult,\n TToolParameters,\n THooksConfig,\n IHookInput,\n IHookTypeExecutor,\n ISubprocessTraceEnv,\n} from '@robota-sdk/agent-core';\n\nconst logger = createLogger('ToolHookHelpers');\n\ntype TRunHooksResult = Awaited<ReturnType<typeof runHooks>>;\n\n/**\n * Truncate tool result data if it exceeds MAX_TOOL_OUTPUT_CHARS.\n * Uses middle-truncation: keeps first and last portions, removes middle.\n */\nexport function truncateToolResult(result: IToolResult): IToolResult {\n if (wasToolResultAdmitted(result)) return result;\n if (typeof result.data !== 'string') return result;\n if (result.data.length <= MAX_TOOL_OUTPUT_CHARS) return result;\n\n const halfLimit = Math.floor(MAX_TOOL_OUTPUT_CHARS / 2);\n const head = result.data.substring(0, halfLimit);\n const tail = result.data.substring(result.data.length - halfLimit);\n const originalSize = result.data.length;\n const truncatedData = `${head}\\n\\n[... output truncated: ${originalSize.toLocaleString()} chars total, showing first and last ${halfLimit.toLocaleString()} chars ...]\\n\\n${tail}`;\n\n return { ...result, data: truncatedData };\n}\n\n/** Build a hook input object for tool execution hooks */\nexport function buildHookInput(\n sessionId: string,\n cwd: string,\n toolName: string,\n parameters: TToolParameters,\n permissionMode?: string,\n transcriptPath?: string,\n): IHookInput {\n return {\n session_id: sessionId,\n cwd,\n hook_event_name: 'PreToolUse',\n tool_name: toolName,\n tool_input: parameters as Record<string, string | number | boolean | object>,\n ...(permissionMode !== undefined && { permission_mode: permissionMode }),\n ...(transcriptPath !== undefined && { transcript_path: transcriptPath }),\n };\n}\n\n/** A PreToolUse decision the permission gate applies; `deny` is a refusal and `defer` changes nothing. */\nexport type TPreToolHookDecision = 'allow' | 'ask';\n\n/** What the PreToolUse hooks decided about one call. */\nexport interface IPreToolGateOutcome {\n /** The denial to return instead of running the tool, or null to go on to the permission gate. */\n readonly refusal: IToolResult | null;\n readonly decision?: TPreToolHookDecision;\n readonly parameters?: TToolParameters;\n}\n\n/**\n * Run PreToolUse hooks; returns a denial IToolResult if blocked, or null to proceed. `hookTraceEnv`\n * names the prompt root, never the tool body's span.\n */\nexport async function runPreToolHook(\n hooks: Record<string, unknown> | undefined,\n hookInput: IHookInput,\n hookTypeExecutors: readonly IHookTypeExecutor[],\n hookTraceEnv?: ISubprocessTraceEnv,\n): Promise<IToolResult | null> {\n return (await runPreToolGate(hooks, hookInput, hookTypeExecutors, hookTraceEnv)).refusal;\n}\n\n/** Re-evaluate all enforcing hooks after a command rewrite before granting permission. */\nexport async function runPreToolGate(\n hooks: Record<string, unknown> | undefined,\n hookInput: IHookInput,\n hookTypeExecutors: readonly IHookTypeExecutor[],\n hookTraceEnv?: ISubprocessTraceEnv,\n): Promise<IPreToolGateOutcome> {\n let input = hookInput;\n let rewritten: TToolParameters | undefined;\n const seen = new Set([JSON.stringify(input.tool_input)]);\n for (let pass = 0; pass < 8; pass++) {\n const evaluated = await evaluatePreToolHooks(hooks, input, hookTypeExecutors, hookTraceEnv);\n if (evaluated.result !== null) return { refusal: evaluated.result };\n const { permissionDecision, updatedInput } = evaluated.hookResult;\n if (updatedInput !== undefined) {\n if (updatedInput === null || typeof updatedInput !== 'object' || Array.isArray(updatedInput))\n return {\n refusal: toolFailure('hook-blocked', 'PreToolUse updatedInput must be an object.'),\n };\n const parameters = canonicaliseToolArguments(\n input.tool_name ?? '',\n updatedInput as TToolParameters,\n input.cwd,\n );\n const encoded = JSON.stringify(parameters);\n if (encoded !== JSON.stringify(input.tool_input)) {\n if (seen.has(encoded)) break;\n seen.add(encoded);\n rewritten = parameters;\n input = { ...input, tool_input: parameters as IHookInput['tool_input'] };\n continue;\n }\n }\n return {\n refusal: null,\n ...(rewritten === undefined ? {} : { parameters: rewritten }),\n ...(permissionDecision === 'allow' || permissionDecision === 'ask'\n ? { decision: permissionDecision }\n : {}),\n };\n }\n const reason = 'PreToolUse input rewriting did not stabilize; no tool call was executed.';\n return { refusal: toolFailure('hook-blocked', reason, { blocked: true, reason }) };\n}\n\nasync function evaluatePreToolHooks(\n hooks: Record<string, unknown> | undefined,\n hookInput: IHookInput,\n hookTypeExecutors: readonly IHookTypeExecutor[],\n hookTraceEnv?: ISubprocessTraceEnv,\n): Promise<{ result: IToolResult | null; hookResult: TRunHooksResult }> {\n const hookResult = await runHooks(\n hooks as THooksConfig | undefined,\n 'PreToolUse',\n hookInput,\n hookTypeExecutors,\n hookTraceEnv,\n );\n if (hookResult.blocked) {\n // The third of the three outcomes the failure type names. `permission-types.ts` declares\n // `hook-blocked`, so this path must not return `success: true`: that would promise a\n // distinction the code does not make — in the file that exists to end exactly that.\n const reason = hookResult.reason ?? 'Blocked by hook';\n return { result: toolFailure('hook-blocked', reason, { blocked: true, reason }), hookResult };\n }\n\n // A hook that reached NO verdict is not a hook that approved. That distinction is\n // representable; this is where it starts costing something.\n //\n // Guarded by the policy rather than by a literal `true`, so the posture is stated in ONE place and\n // this boundary cannot drift from it. Note what this does NOT buy: the event is hardcoded here\n // because this function is the PreToolUse gate, so a future enforcing event needs its own boundary\n // and does not inherit anything — an earlier version of this comment claimed otherwise and review\n // caught it. What the indirection does buy is that flipping `PreToolUse` to advisory in the table\n // turns this gate off.\n //\n // The `errors` / `unknownHookTypes` gate is covered by\n // `__tests__/tool-hook-helpers.test.ts`, which fails if this handling is removed.\n //\n // The check stays HERE rather than inside `runHooks`, because the runner reports outcomes and must\n // not decide policy — the same split as between the decoder and the runner.\n if (isEnforcing('PreToolUse')) {\n // Bind the array, not `errors?.[0]`: narrowing the element does not narrow the collection, and\n // the count below needs the collection. The earlier shape needed a `?? 1` fallback that could\n // never be taken, which reads as though the array might be absent here.\n const failures = hookResult.errors;\n const failure = failures?.[0];\n // ONE binding for one field. Two bindings, each defended differently against `undefined`, is a\n // drift surface: the two branches below disagreed about whether the field could be absent.\n const unregistered = hookResult.unknownHookTypes ?? [];\n\n // A configured hook type with no registered executor ran NOTHING. If the gate proceeded after\n // the runner reported it, a config declaring a guardrail with no registry would silently\n // disable itself. Startup rejection of such a config is separate; this is the runtime half.\n //\n // Denying is deliberate and is the approved semantics: a PreToolUse hook the user wrote\n // as a gate must not be silently skipped. But note WHICH configs land here, because it is wider\n // than a mistake — `prompt`, `agent` and `guardrail` are accepted by the config schema while no\n // product surface supplies the `providerFactory` / `sessionFactory` / `guardrails` those\n // executors need, so such a config validates and can never run. That gap is the\n // reason this text says what to DO rather than only what happened.\n //\n // Built once and used by both branches. The earlier shape wrote the cause twice — a full\n // sentence in the standalone branch and a shorter one appended to the error branch — so the\n // operator with TWO faults got less guidance than the one with a single fault, which is exactly\n // backwards, and the two wordings were free to drift apart.\n const unregisteredReason =\n unregistered.length > 0\n ? `Hook type(s) with no registered executor: ${unregistered.join(', ')}. ` +\n 'Nothing evaluated this gate, so the tool call is denied rather than silently allowed. ' +\n 'Remove the hook from the PreToolUse configuration, or supply an executor for its type.'\n : '';\n\n if (failures !== undefined && failure !== undefined) {\n // The reason names the kind, the executor and the failure text, because a fail-closed gate\n // turns a misconfigured hook into a hard stop: whoever hits it needs enough to fix it.\n const others = failures.length - 1;\n const reason =\n `Hook could not evaluate (${failure.kind}, source: ${failure.source}): ${failure.reason}.` +\n // Naming only the first would hide that several gates failed; the count is the cheap half of\n // that, and the reason line stays one line.\n (others > 0 ? ` (+${others} more hook failure(s))` : '') +\n // Both causes in ONE reason. This branch returns before the unregistered branch, so a turn\n // carrying both used to report only the error — the operator fixed the named cause, retried,\n // and hit a second denial with no warning it was queued. A fail-closed gate that reveals its\n // reasons one per attempt is a gate you debug by being repeatedly stopped.\n (unregisteredReason !== '' ? ` Also unevaluated — ${unregisteredReason}` : '');\n return { result: toolFailure('hook-blocked', reason, { blocked: true, reason }), hookResult };\n }\n\n if (unregisteredReason !== '') {\n return {\n result: toolFailure('hook-blocked', unregisteredReason, {\n blocked: true,\n reason: unregisteredReason,\n }),\n hookResult,\n };\n }\n }\n\n return { result: null, hookResult };\n}\n\n/** Fire PostToolUse hooks (fire and forget) */\nexport function firePostToolHook(\n hooks: Record<string, unknown> | undefined,\n hookInput: IHookInput,\n result: IToolResult,\n hookTypeExecutors: readonly IHookTypeExecutor[],\n hookTraceEnv?: ISubprocessTraceEnv,\n): void {\n const postHookInput: IHookInput = {\n ...hookInput,\n hook_event_name: 'PostToolUse',\n tool_output: typeof result.data === 'string' ? result.data : JSON.stringify(result.data),\n };\n runHooks(\n hooks as THooksConfig | undefined,\n 'PostToolUse',\n postHookInput,\n hookTypeExecutors,\n hookTraceEnv,\n ).catch((error) => logger.warn('hook failed', { error }));\n}\n","/**\n * The calls this session refused, most recent first, so `/permissions` can show what was blocked\n * and why without the user scrolling back through the transcript.\n *\n * In memory and bounded: it answers \"what did I just block\", not an audit trail — the\n * `PermissionDecision` hook and the session log are the durable records.\n */\n\nimport { getToolPermissionProfile } from '@robota-sdk/agent-core';\n\nimport type { TToolArgs } from '@robota-sdk/agent-core';\n\n/**\n * Why a call was refused:\n * - `policy` — the gate answered deny: a deny rule, a background ceiling, or plan mode;\n * - `user` — a person was asked and declined, or the turn was cancelled while asking;\n * - `no-approver` — the call needed a person and none was attached;\n * - `classifier` — in `auto` mode, the classifier blocked the call or gave no usable verdict.\n */\nexport type TPermissionDenialReason = 'policy' | 'user' | 'no-approver' | 'classifier';\n\nexport interface IPermissionDenial {\n readonly toolName: string;\n /** The argument the tool's permission profile names (command, path, URL), when it has one. */\n readonly argument?: string;\n readonly reason: TPermissionDenialReason;\n /** The classifier's reason, for a `classifier` denial. */\n readonly detail?: string;\n /** Epoch milliseconds. */\n readonly at: number;\n}\n\nconst DEFAULT_CAPACITY = 20;\nconst ARGUMENT_DISPLAY_LIMIT = 200;\n\nfunction argumentOf(toolName: string, toolArgs: TToolArgs): string | undefined {\n const key = getToolPermissionProfile(toolName).argument?.key;\n if (key === undefined) return undefined;\n const value = toolArgs[key];\n if (typeof value !== 'string') return undefined;\n return value.length > ARGUMENT_DISPLAY_LIMIT\n ? `${value.slice(0, ARGUMENT_DISPLAY_LIMIT)}…`\n : value;\n}\n\nexport class PermissionDenialLog {\n private readonly entries: IPermissionDenial[] = [];\n /** The full call behind each entry, same order, so a denial can be retried exactly. */\n private readonly calls: { toolName: string; toolArgs: TToolArgs }[] = [];\n\n constructor(\n private readonly capacity: number = DEFAULT_CAPACITY,\n private readonly now: () => number = Date.now,\n ) {}\n\n record(\n toolName: string,\n toolArgs: TToolArgs,\n reason: TPermissionDenialReason,\n detail?: string,\n ): void {\n const argument = argumentOf(toolName, toolArgs);\n this.entries.unshift({\n toolName,\n ...(argument !== undefined ? { argument } : {}),\n reason,\n ...(detail !== undefined ? { detail } : {}),\n at: this.now(),\n });\n this.calls.unshift({ toolName, toolArgs });\n if (this.calls.length > this.capacity) this.calls.length = this.capacity;\n if (this.entries.length > this.capacity) this.entries.length = this.capacity;\n }\n\n list(): readonly IPermissionDenial[] {\n return [...this.entries];\n }\n\n /** The call behind the entry at `index` in {@link list}. */\n callAt(index: number): { toolName: string; toolArgs: TToolArgs } | undefined {\n return this.calls[index];\n }\n}\n","import { PERMISSION_DENIED_RESULT, reportToolCrash, toolFailure } from './permission-types.js';\nimport {\n createLogger,\n isExecutionControlError,\n DEFAULT_ABSTRACT_EVENT_SERVICE,\n isAbortFailure,\n TOOL_BODY_EVENTS,\n TOOL_PERMISSION_EVENTS,\n snapshotToolProvenance,\n withToolProvenance,\n} from '@robota-sdk/agent-core';\nimport { canonicaliseToolArguments } from './tool-argument-canonicalisation.js';\nimport {\n buildHookInput,\n firePostToolHook,\n runPreToolGate,\n truncateToolResult,\n} from './tool-hook-helpers.js';\n\nimport type { TPreToolHookDecision } from './tool-hook-helpers.js';\nimport type { IPermissionEnforcerOptions, IPermissionRefusal } from './permission-types.js';\nimport type { TSessionLogData } from './session-logger.js';\nimport type {\n IHookInput,\n IHookTypeExecutor,\n IEventService,\n IToolExecutionContext,\n IToolResult,\n IToolWithEventService,\n ITerminalOutput,\n TToolArgs,\n TToolParameters,\n TToolEffectAdmission,\n} from '@robota-sdk/agent-core';\n\nconst logger = createLogger('ToolBodyTrace');\n\nclass DeferredPermissionRefusal extends Error {\n constructor(readonly result: IToolResult) {\n super('Effective tool arguments were not authorized');\n }\n}\n\n/** Never let a permission observation break the tool_result it merely watches. */\nfunction emitPermissionDecision(\n context: IToolExecutionContext | undefined,\n decision: 'allowed' | 'denied' | 'hook-blocked',\n): void {\n try {\n context?.eventService?.emit(TOOL_PERMISSION_EVENTS.DECIDED, {\n timestamp: new Date(),\n executionId: context.executionId,\n decidedAt: new Date().toISOString(),\n decision,\n });\n } catch (error) {\n logger.warn(\n 'tool permission observation failed',\n error instanceof Error ? error : new Error(String(error)),\n );\n }\n}\n\n/** Exactly what the wrapper reads from the enforcer — no more, and named so it cannot quietly grow. */\nexport interface IToolWrapperDeps {\n readonly sessionId: string;\n readonly cwd: string;\n readonly config: IPermissionEnforcerOptions['config'];\n readonly terminal: ITerminalOutput;\n readonly transcriptPath?: string;\n readonly onToolExecution?: IPermissionEnforcerOptions['onToolExecution'];\n readonly hookTypeExecutors: readonly IHookTypeExecutor[];\n getPermissionMode: IPermissionEnforcerOptions['getPermissionMode'];\n log(event: string, detail: TSessionLogData): void;\n checkPermission(\n toolName: string,\n toolArgs: TToolArgs,\n signal?: AbortSignal,\n interaction?: IToolExecutionContext['permissionInteraction'],\n hookTraceEnv?: IToolExecutionContext['hookTraceEnv'],\n continuation?: IToolExecutionContext['continuation'],\n hookDecision?: TPreToolHookDecision,\n /** The call's id (`context.executionId`), so a prompt raised for it can name the call. */\n executionId?: string,\n ): Promise<boolean | IPermissionRefusal>;\n}\n\n/**\n * Wrap one tool so every call passes the permission gate, the hooks and the truncation limit.\n *\n * Extracted from `PermissionEnforcer` because the file-size ratchet refused to let it grow further,\n * and a ratchet that says \"split instead of extending\" is asking for exactly this. It takes what it\n * needs as `deps` rather than the enforcer itself: the ten members it reads are the honest surface\n * of this function, and naming them is what makes the extraction a boundary rather than a move.\n */\nexport function wrapToolWithPermission(\n tool: IToolWithEventService,\n enforcer: IToolWrapperDeps,\n): IToolWithEventService {\n const originalExecute = tool.execute.bind(tool);\n const originalExecuteWithAdmission = tool.executeWithAdmission?.bind(tool);\n const provenance = snapshotToolProvenance(tool.provenance);\n const onToolExecution: IToolWrapperDeps['onToolExecution'] = (event) => {\n if (event.type !== 'end' || !provenance) return enforcer.onToolExecution?.(event);\n const observation = withToolProvenance(\n { success: event.success ?? false, parts: event.toolResultParts },\n provenance,\n );\n enforcer.onToolExecution?.({ ...event, toolResultParts: observation.parts });\n };\n // What this session gave the tool with `setEventService`. The tool instance may be shared with\n // other sessions, and the service set on it is whichever session set one last, so each call\n // carries this one instead. Until the session sets one, calls carry the no-op service: its calls\n // must not reach a service another session set.\n let sessionEventService: IEventService = DEFAULT_ABSTRACT_EVENT_SERVICE;\n\n const wrappedTool = Object.create(tool) as IToolWithEventService;\n Object.defineProperty(wrappedTool, 'provenance', { value: provenance, enumerable: true });\n const execute = async (\n rawParameters: TToolParameters,\n context?: IToolExecutionContext,\n beforeEffect?: TToolEffectAdmission,\n ): Promise<IToolResult> => {\n // The gate, the hooks, the logs and the tool all see ONE canonical form of the\n // arguments — a relative path argument resolved against the session root — so a pattern judges\n // the path the tool will actually open. Canonicalising needs the tool's name, which is read\n // inside the try below, so until then this holds the raw form.\n let parameters: TToolParameters = rawParameters;\n // Ordinary tool failures become results. A journal failure must stop the execution owner,\n // preventing a later model call from treating a persistence failure as a retryable tool error.\n // Resolve the name inside the protected path so a malformed tool still produces a result.\n let toolName = '(unknown)';\n\n try {\n toolName = tool.getName();\n parameters = canonicaliseToolArguments(toolName, rawParameters, enforcer.cwd);\n enforcer.log('tool_call', {\n tool: toolName,\n args: parameters as Record<string, string | number | boolean | object>,\n });\n\n let hookInput = buildHookInput(\n enforcer.sessionId,\n enforcer.cwd,\n toolName,\n parameters,\n enforcer.getPermissionMode(),\n enforcer.transcriptPath,\n );\n\n const gate = await runPreToolGate(\n enforcer.config.hooks,\n hookInput,\n enforcer.hookTypeExecutors,\n context?.hookTraceEnv,\n );\n if (gate.refusal) {\n enforcer.log('tool_blocked', { tool: toolName, reason: 'hook' });\n emitPermissionDecision(context, 'hook-blocked');\n return gate.refusal;\n }\n\n if (gate.parameters !== undefined) {\n parameters = gate.parameters;\n hookInput = { ...hookInput, tool_input: parameters as IHookInput['tool_input'] };\n }\n\n // The turn's signal reaches this wrapper and stopped here.\n const verdict = await enforcer.checkPermission(\n toolName,\n parameters as TToolArgs,\n context?.signal,\n context?.permissionInteraction,\n context?.hookTraceEnv,\n context?.continuation,\n gate.decision,\n context?.executionId,\n );\n if (verdict !== true) {\n enforcer.log('tool_denied', { tool: toolName, reason: 'permission' });\n emitPermissionDecision(context, 'denied');\n onToolExecution({\n type: 'end',\n toolName,\n toolArgs: parameters as TToolArgs,\n success: false,\n denied: true,\n executionId: context?.executionId,\n });\n // A refusal that carries its reason (the auto-mode classifier) hands it to the model.\n return typeof verdict === 'object'\n ? toolFailure('denied', verdict.message)\n : PERMISSION_DENIED_RESULT;\n }\n\n emitPermissionDecision(context, 'allowed');\n context?.signal?.throwIfAborted();\n onToolExecution({\n type: 'start',\n toolName,\n toolArgs: structuredClone(parameters) as TToolArgs,\n executionId: context?.executionId,\n });\n context?.signal?.throwIfAborted();\n\n // The observation covers ONLY the awaited body, never approval, hooks, truncation or a\n // detached continuation. A pre-start denial/abort therefore has no tool-body span.\n let startedAtMs: number | undefined;\n const admit: TToolEffectAdmission = async (effective) => {\n context?.signal?.throwIfAborted();\n const approvedArguments = canonicaliseToolArguments(toolName, effective, enforcer.cwd);\n if (JSON.stringify(approvedArguments) !== JSON.stringify(effective)) {\n throw new DeferredPermissionRefusal(\n toolFailure(\n 'denied',\n 'Tool admission requires final canonical arguments; relative paths must be resolved before admission.',\n ),\n );\n }\n if (JSON.stringify(approvedArguments) !== JSON.stringify(parameters)) {\n const nextHookInput = buildHookInput(\n enforcer.sessionId,\n enforcer.cwd,\n toolName,\n approvedArguments,\n enforcer.getPermissionMode(),\n enforcer.transcriptPath,\n );\n const nextGate = await runPreToolGate(\n enforcer.config.hooks,\n nextHookInput,\n enforcer.hookTypeExecutors,\n context?.hookTraceEnv,\n );\n if (nextGate.refusal) throw new DeferredPermissionRefusal(nextGate.refusal);\n if (\n nextGate.parameters !== undefined &&\n JSON.stringify(nextGate.parameters) !== JSON.stringify(approvedArguments)\n )\n throw new DeferredPermissionRefusal(\n toolFailure(\n 'hook-blocked',\n 'A hook cannot replace arguments already settled by the tool; retry as a new call.',\n ),\n );\n const effectiveVerdict = await enforcer.checkPermission(\n toolName,\n approvedArguments as TToolArgs,\n context?.signal,\n context?.permissionInteraction,\n context?.hookTraceEnv,\n context?.continuation,\n nextGate.decision,\n context?.executionId,\n );\n if (effectiveVerdict !== true)\n throw new DeferredPermissionRefusal(\n typeof effectiveVerdict === 'object'\n ? toolFailure('denied', effectiveVerdict.message)\n : PERMISSION_DENIED_RESULT,\n );\n hookInput = nextHookInput;\n }\n parameters = structuredClone(effective);\n context?.signal?.throwIfAborted();\n await beforeEffect?.(parameters);\n context?.signal?.throwIfAborted();\n startedAtMs = Date.now();\n };\n let outcome: 'success' | 'failure' | 'interrupted' = 'failure';\n let result: IToolResult;\n try {\n // A call without a context was not made by an agent, so it has no session to carry.\n const toolContext =\n context === undefined\n ? undefined\n : { ...context, parameters, instanceEventService: sessionEventService };\n if (beforeEffect && originalExecuteWithAdmission) {\n result = await originalExecuteWithAdmission(\n parameters,\n toolContext as IToolExecutionContext,\n admit,\n );\n } else {\n if (beforeEffect) await admit(parameters);\n else startedAtMs = Date.now();\n context?.signal?.throwIfAborted();\n result = await originalExecute(parameters, toolContext as IToolExecutionContext);\n }\n outcome = context?.signal?.aborted ? 'interrupted' : result.success ? 'success' : 'failure';\n } catch (error) {\n outcome = context?.signal?.aborted || isAbortFailure(error) ? 'interrupted' : 'failure';\n throw error;\n } finally {\n try {\n if (startedAtMs !== undefined)\n context?.eventService?.emit(TOOL_BODY_EVENTS.COMPLETED, {\n timestamp: new Date(),\n executionId: context.executionId,\n startedAt: new Date(startedAtMs).toISOString(),\n endedAt: new Date(Math.max(Date.now(), startedAtMs)).toISOString(),\n outcome,\n toolName,\n ...(typeof context.toolBodyId === 'string' ? { toolBodyId: context.toolBodyId } : {}),\n });\n } catch (error) {\n // An observer must never turn a completed tool body into a missing tool_result.\n logger.warn(\n 'tool body observation failed',\n error instanceof Error ? error : new Error(String(error)),\n );\n }\n }\n\n // Truncate oversized tool output (matches 30K char limit)\n const truncatedResult = truncateToolResult(result);\n\n if (truncatedResult !== result && typeof result.data === 'string') {\n enforcer.terminal.writeLine(\n ` ⚠ Output truncated: ${result.data.length.toLocaleString()} chars total — model sees first and last 15,000 chars`,\n );\n }\n\n onToolExecution({\n type: 'end',\n toolName,\n toolArgs: parameters as TToolArgs,\n success: truncatedResult.success,\n toolResultData:\n typeof truncatedResult.data === 'string'\n ? truncatedResult.data\n : JSON.stringify(truncatedResult.data),\n toolResultParts: truncatedResult.parts,\n executionId: context?.executionId,\n // Display data for a surface only: it never joins `toolResultData`, which is what is logged.\n ...(truncatedResult.view ? { view: truncatedResult.view } : {}),\n });\n\n const dataSize =\n typeof truncatedResult.data === 'string'\n ? truncatedResult.data.length\n : (JSON.stringify(truncatedResult.data)?.length ?? 0);\n enforcer.log('tool_result', {\n tool: toolName,\n success: truncatedResult.success,\n dataChars: dataSize,\n truncated: truncatedResult !== result,\n });\n firePostToolHook(\n enforcer.config.hooks,\n hookInput,\n truncatedResult,\n enforcer.hookTypeExecutors,\n context?.hookTraceEnv,\n );\n return truncatedResult;\n } catch (err) {\n if (isExecutionControlError(err)) throw err;\n if (err instanceof DeferredPermissionRefusal) return err.result;\n // Beside the envelope it returns, in `permission-types.ts`.\n return reportToolCrash(err, onToolExecution, {\n toolName,\n toolArgs: parameters as TToolArgs,\n executionId: context?.executionId,\n });\n }\n };\n\n const executeAttributed: typeof execute = async (...args) =>\n withToolProvenance(await execute(...args), provenance);\n wrappedTool.execute = executeAttributed;\n wrappedTool.executeWithAdmission = executeAttributed;\n\n // Kept for the calls above, and still forwarded to the original tool, because\n // `Object.create(tool)` would otherwise shadow it onto the wrapper: a tool that reads only the\n // service set on it, such as an `AbstractTool` subclass, keeps receiving it.\n wrappedTool.setEventService = (eventService) => {\n sessionEventService = eventService ?? DEFAULT_ABSTRACT_EVENT_SERVICE;\n tool.setEventService(eventService);\n };\n\n return wrappedTool;\n}\n","/**\n * PermissionEnforcer — handles tool permission checking, hook execution,\n * and tool output truncation.\n *\n * Extracted from Session to separate permission/hook concerns from\n * conversation management.\n */\n\nimport {\n allowRulesForAutoMode,\n applyPresetToolLists,\n evaluatePermission,\n findInvalidPermissionPatterns,\n findPermissionPatternWarnings,\n getToolPermissionProfile,\n isToolDeniedOutright,\n matchesAnyPattern,\n projectPermissionPolicy,\n registerToolPermissionProfile,\n requiresFreshApproval,\n runHooks,\n} from '@robota-sdk/agent-core';\n\nimport { decideApproval } from './abortable-approval.js';\nimport { checkpointedApproval, hasCheckpointedApproval } from './checkpointed-approval.js';\nimport { AutoModeGate } from './auto-mode-gate.js';\nimport { consentScopeFor } from './consent-scope.js';\nimport { buildHookInput, runPreToolGate } from './tool-hook-helpers.js';\nimport { PermissionDenialLog } from './permission-denial-log.js';\nimport { wrapToolWithPermission } from './tool-permission-wrapper.js';\nimport { requireHookTypeExecutors } from './require-hook-type-executors.js';\n\nimport type {\n IPermissionEnforcerOptions,\n IPermissionRefusal,\n TPermissionHandler,\n TPermissionResult,\n ITerminalOutput,\n ISpinner,\n} from './permission-types.js';\nimport type { IPermissionDenial } from './permission-denial-log.js';\nimport type { ISessionLogger, TSessionLogData } from './session-logger.js';\nimport type { TPreToolHookDecision } from './tool-hook-helpers.js';\nimport type { IToolWrapperDeps } from './tool-permission-wrapper.js';\nimport type {\n IHookTypeExecutor,\n IToolExecutionContext,\n IToolWithEventService,\n TToolArgs,\n TToolParameters,\n THooksConfig,\n TResolveInWorkspace,\n} from '@robota-sdk/agent-core';\n\nexport type { TPermissionHandler, TPermissionResult, ITerminalOutput, ISpinner };\nexport type { IPermissionEnforcerOptions };\n\n/**\n * Throw naming every malformed permission pattern and why. Allow rules are held to\n * the narrower allow grammar.\n */\nfunction assertPermissionPatternsEvaluable(rules: {\n allow: readonly string[];\n restrictive: readonly string[];\n}): void {\n const problems = [\n ...findInvalidPermissionPatterns(rules.allow, 'allow'),\n ...findInvalidPermissionPatterns(rules.restrictive, 'deny'),\n ];\n if (problems.length === 0) return;\n const listed = problems.map(({ pattern, reason }) => `\"${pattern}\" ${reason}`).join('; ');\n throw new Error(\n `Invalid permission pattern(s) in permissions.allow/deny/ask: ${listed}. ` +\n 'Fix the pattern where it is configured.',\n );\n}\n\n/** How a decision's call will run, where that changes the answer. */\ninterface IDecisionScope {\n readonly continuation?: IToolExecutionContext['continuation'];\n /** `false` when the call will NOT run inside the command sandbox, so its approval cannot apply. */\n readonly sandboxed?: boolean;\n /** What the call's PreToolUse hooks decided, applied after the rules and mode. */\n readonly hookDecision?: TPreToolHookDecision;\n /** The tool call's id, so a prompt raised for it can name the call its `tool_start` will report. */\n readonly executionId?: string;\n}\n\nexport class PermissionEnforcer {\n private readonly sessionId: string;\n private readonly cwd: string;\n private readonly getPermissionMode: IPermissionEnforcerOptions['getPermissionMode'];\n private readonly config: IPermissionEnforcerOptions['config'];\n private readonly terminal: ITerminalOutput;\n private readonly permissionHandler?: TPermissionHandler;\n private readonly promptForApprovalFn?: IPermissionEnforcerOptions['promptForApprovalFn'];\n private readonly sessionLogger?: ISessionLogger;\n private readonly onToolExecution?: IPermissionEnforcerOptions['onToolExecution'];\n private readonly hookTypeExecutors: readonly IHookTypeExecutor[];\n private readonly transcriptPath?: string;\n /**\n * Consent is remembered as PATTERNS (`consentScopeFor`), not tool names, and read\n * back through the gate's own matcher — approving one argument does not allow every argument.\n */\n private readonly sessionAllowedTools = new Set<string>();\n /** The configured rules before any preset contributed — see {@link applyPresetToolLists}. */\n private readonly presetFreeRules: { allow: readonly string[]; deny: readonly string[] };\n private readonly onProjectAllowTool?: (toolName: string) => void;\n private readonly permissionPolicy?: IPermissionEnforcerOptions['permissionPolicy'];\n private readonly taskPermissions?: IPermissionEnforcerOptions['taskPermissions'];\n private readonly pathProtection: IPermissionEnforcerOptions['pathProtection'];\n private readonly homeDirectory: string | undefined;\n private readonly resolveInWorkspace: TResolveInWorkspace | undefined;\n private readonly canonicalizePath: (path: string) => string;\n private readonly commandSandbox?: IPermissionEnforcerOptions['commandSandbox'];\n private readonly denials = new PermissionDenialLog();\n /** A turn a peer's message started is in progress: the one place the reply to that peer exists. */\n private peerTurn = false;\n private checkpointedApprovals = false;\n private readonly autoMode?: AutoModeGate;\n\n constructor(options: IPermissionEnforcerOptions) {\n this.sessionId = options.sessionId;\n this.cwd = options.cwd;\n this.getPermissionMode = options.getPermissionMode;\n this.config = options.config;\n // Absent ⇒ no preset contributed, so the configured rules ARE the preset-free base. Copied, not\n // aliased: `applyPresetToolLists` writes back into `config.permissions`, and a shared array\n // would make the base track its own output.\n this.presetFreeRules = options.presetFreePermissions ?? {\n allow: [...options.config.permissions.allow],\n deny: [...options.config.permissions.deny],\n };\n // A pattern the gate could never evaluate is refused HERE, with the pattern and\n // the reason, before any turn — not discovered one unevaluable prompt at a time at the gate.\n assertPermissionPatternsEvaluable(this.configuredRules(options));\n this.terminal = options.terminal;\n this.permissionHandler = options.permissionHandler;\n this.promptForApprovalFn = options.promptForApprovalFn;\n this.sessionLogger = options.sessionLogger;\n this.onToolExecution = options.onToolExecution;\n this.hookTypeExecutors = requireHookTypeExecutors(\n options.hookTypeExecutors,\n 'PermissionEnforcer',\n );\n this.transcriptPath = options.transcriptPath;\n this.onProjectAllowTool = options.onProjectAllowTool;\n this.permissionPolicy = options.permissionPolicy;\n this.taskPermissions = options.taskPermissions;\n this.homeDirectory = options.homeDirectory;\n this.pathProtection = options.pathProtection;\n this.resolveInWorkspace = options.resolveInWorkspace;\n this.canonicalizePath = options.canonicalizePath ?? ((path) => path);\n this.commandSandbox = options.commandSandbox;\n if (options.permissionClassifier !== undefined) {\n this.autoMode = new AutoModeGate(options.permissionClassifier);\n }\n }\n\n /**\n * Start a turn, which a peer's message started when `peerTurn` is true. That decides only whether\n * the reply to the peer exists; every other call is decided exactly as in any turn.\n */\n beginTurn(peerTurn: boolean, checkpointedApprovals = false): void {\n this.peerTurn = peerTurn;\n this.checkpointedApprovals = checkpointedApprovals;\n }\n\n /** End the turn; the reply to a peer is gone until the next peer turn begins. */\n endTurn(): void {\n this.peerTurn = false;\n this.checkpointedApprovals = false;\n }\n\n /** Whether `auto` mode can run here: it needs a classifier to decide for it. */\n hasPermissionClassifier(): boolean {\n return this.autoMode !== undefined;\n }\n\n /**\n * Let the call behind a classifier denial run once, unjudged, when the model tries it again.\n * Returns the denial, or `undefined` when `index` names no classifier denial.\n */\n allowRetryOfDenial(index: number): IPermissionDenial | undefined {\n const denial = this.denials.list()[index];\n const call = this.denials.callAt(index);\n if (denial?.reason !== 'classifier' || call === undefined || this.autoMode === undefined) {\n return undefined;\n }\n this.autoMode.grantRetry(call.toolName, call.toolArgs);\n return denial;\n }\n\n /** Every configured pattern, split by the grammar it is held to. */\n private configuredRules(\n options: Pick<IPermissionEnforcerOptions, 'config' | 'taskPermissions'> = {\n config: this.config,\n ...(this.taskPermissions !== undefined ? { taskPermissions: this.taskPermissions } : {}),\n },\n ): { allow: string[]; restrictive: string[] } {\n return {\n allow: [...options.config.permissions.allow, ...(options.taskPermissions?.allow ?? [])],\n restrictive: [\n ...options.config.permissions.deny,\n ...(options.config.permissions.ask ?? []),\n ...(options.taskPermissions?.deny ?? []),\n ],\n };\n }\n\n /**\n * Whether the model is shown this tool at all. A bare-name deny (`Tool`, `Tool(*)`, a name glob)\n * removes it rather than offering it and refusing every call. Read live, so a\n * `/preset` that denies a tool hides it from the next round.\n */\n isToolVisible(toolName: string): boolean {\n // The reply exists in a peer turn alone; a peer turn is otherwise shown what any turn is.\n const profile = getToolPermissionProfile(toolName);\n if (!this.peerTurn && profile.repliesToPeer === true) return false;\n if (this.peerTurn && profile.notInPeerTurn === true) return false;\n return !isToolDeniedOutright(toolName, [\n ...this.config.permissions.deny,\n ...(this.taskPermissions?.deny ?? []),\n ]);\n }\n\n /**\n * Tell the gate each tool's parameter names — the schema is what makes `Tool(name:value)` a\n * parameter rule — then re-check the rules against them, before any turn runs.\n */\n private registerToolParameters(tools: readonly IToolWithEventService[]): void {\n for (const tool of tools) {\n // Read defensively: a tool without a schema has no parameters to name.\n const schema = (tool as Partial<Pick<IToolWithEventService, 'schema'>>).schema;\n if (schema === undefined) continue;\n const properties = schema.parameters?.properties ?? {};\n registerToolPermissionProfile(schema.name, { parameters: Object.keys(properties) });\n }\n const rules = this.configuredRules();\n assertPermissionPatternsEvaluable(rules);\n for (const { pattern, reason } of findPermissionPatternWarnings(rules.restrictive)) {\n this.terminal.writeLine(` ⚠ Permission rule \"${pattern}\" ${reason}.`);\n }\n }\n\n /** Wrap all tools with permission checking */\n wrapTools(tools: IToolWithEventService[]): IToolWithEventService[] {\n this.registerToolParameters(tools);\n // Built explicitly rather than cast. A blind assertion here would compile only by silencing the\n // private-member mismatch, and this repository counts and ratchets those. Naming the ten members\n // is what makes the extraction a boundary: if the wrapper starts reading an eleventh, this stops\n // compiling instead of quietly widening.\n const deps: IToolWrapperDeps = {\n sessionId: this.sessionId,\n cwd: this.cwd,\n config: this.config,\n terminal: this.terminal,\n transcriptPath: this.transcriptPath,\n onToolExecution: this.onToolExecution,\n hookTypeExecutors: this.hookTypeExecutors,\n getPermissionMode: this.getPermissionMode,\n log: (event, detail) => this.log(event, detail),\n checkPermission: (\n toolName,\n toolArgs,\n signal,\n interaction,\n hookTraceEnv,\n continuation,\n hookDecision,\n executionId,\n ) =>\n this.decidePermission(toolName, toolArgs, signal, interaction, hookTraceEnv, {\n continuation,\n ...(hookDecision !== undefined ? { hookDecision } : {}),\n ...(executionId !== undefined ? { executionId } : {}),\n }),\n };\n\n return tools.map((tool) => wrapToolWithPermission(tool, deps));\n }\n\n /** The consent patterns granted this session via \"Allow always\" — e.g. `Bash(git *)`. */\n getSessionAllowedTools(): string[] {\n return [...this.sessionAllowedTools];\n }\n\n /** The calls this session refused, most recent first. */\n getRecentDenials(): readonly IPermissionDenial[] {\n return this.denials.list();\n }\n\n /** Clear all session-scoped allow rules. */\n clearSessionAllowedTools(): void {\n this.sessionAllowedTools.clear();\n }\n\n /**\n * Replace the configured permission rules on a LIVE session.\n *\n * The seam is this small because `checkPermission` reads `this.config.permissions` on every call\n * rather than snapshotting it at construction — so the next call sees the new rules and nothing\n * needs re-wiring. Without a seam the startup path could apply a preset's tool lists and the live\n * `/preset` path could not, which is the divergence `scan-preset-projection` exists to measure:\n * one session holding two answers for the same preset depending on WHEN it was chosen.\n *\n * **A call already in flight runs to completion.** `checkPermission` is awaited BEFORE the tool\n * executes, so such a call has already passed its gate, and a gate is a decision at a point in\n * time. There is also no rollback for a partially applied tool — a file already written stays\n * written — so a revocation that cannot undo is a stop, not a denial. Building one would rest on\n * the cancellation path, which is declared at several layers and not yet honoured by a running tool.\n *\n * A newly applied denial DOES outrank an earlier \"always allow\": `evaluatePermission` answers\n * `deny` before `promptForApproval` — the only reader of `sessionAllowedTools` — is reached. That\n * is not new behaviour here; it is the existing precedence, and it agrees with the combine rule\n * that a denial is not weakened by a later layer.\n */\n /**\n * The rules the next `checkPermission` will read.\n *\n * Exposed so a case can assert what a live re-application PRODUCED, not merely that the method\n * exists. Review found the first cut composing onto a contaminated base and no test could see it,\n * because nothing could look at the rules.\n */\n currentPermissionRules(): {\n allow: readonly string[];\n deny: readonly string[];\n ask: readonly string[];\n } {\n return {\n allow: [...this.config.permissions.allow],\n deny: [...this.config.permissions.deny],\n ask: [...(this.config.permissions.ask ?? [])],\n };\n }\n\n applyPresetToolLists(preset: {\n allowedTools?: readonly string[];\n deniedTools?: readonly string[];\n }): void {\n // The BASE is what the session was configured with independently of any preset — SUPPLIED, not\n // captured here. Capturing it lazily read `config.permissions` after the startup preset's\n // patterns were already baked in, so the first preset's allowlist survived every later switch:\n // the accumulation the replace rule exists to prevent, arriving through the base rather than\n // through the merge. Review found it; the comment two lines up had described the failure exactly\n // and the code still had it.\n const next = applyPresetToolLists(this.presetFreeRules, preset);\n this.config.permissions.allow = next.allow;\n this.config.permissions.deny = next.deny;\n }\n\n /** Evaluate permission for a tool call. `signal` cancels a pending approval; see\n * `decideApproval` for why a cancelled approval denies. */\n async checkPermission(\n toolName: string,\n toolArgs: TToolArgs,\n signal?: AbortSignal,\n interaction: IToolExecutionContext['permissionInteraction'] = 'interactive',\n hookTraceEnv?: IToolExecutionContext['hookTraceEnv'],\n ): Promise<boolean> {\n return (\n (await this.decidePermission(toolName, toolArgs, signal, interaction, hookTraceEnv)) === true\n );\n }\n\n /**\n * Decide an action that has `toolName`'s effect but does not run through that tool — a command\n * that starts a process, say. It passes what the tool call would: the PreToolUse hooks (so\n * guardrails apply), then the gate's rules, mode, remembered consent and prompt. It never takes\n * the command sandbox's auto-approval, because the action does not run inside that sandbox.\n */\n async checkDelegatedToolCall(\n toolName: string,\n toolParameters: TToolParameters,\n signal?: AbortSignal,\n ): Promise<boolean> {\n const hookInput = buildHookInput(\n this.sessionId,\n this.cwd,\n toolName,\n toolParameters,\n this.getPermissionMode(),\n this.transcriptPath,\n );\n const gate = await runPreToolGate(this.config.hooks, hookInput, this.hookTypeExecutors);\n if (gate.refusal || gate.parameters !== undefined) {\n this.log('tool_blocked', { tool: toolName, reason: 'hook', delegated: true });\n return false;\n }\n const decision = await this.decidePermission(\n toolName,\n toolParameters as TToolArgs,\n signal,\n 'interactive',\n undefined,\n { sandboxed: false, ...(gate.decision !== undefined ? { hookDecision: gate.decision } : {}) },\n );\n return decision === true;\n }\n\n /** {@link checkPermission}, keeping the reason a refusal carries for the model. */\n private async decidePermission(\n toolName: string,\n toolArgs: TToolArgs,\n signal?: AbortSignal,\n interaction: IToolExecutionContext['permissionInteraction'] = 'interactive',\n hookTraceEnv?: IToolExecutionContext['hookTraceEnv'],\n scope: IDecisionScope = {},\n ): Promise<boolean | IPermissionRefusal> {\n const verdict = await this.evaluateToolPermission(\n toolName,\n toolArgs,\n signal,\n interaction,\n hookTraceEnv,\n scope,\n );\n if (verdict !== true) return verdict;\n signal?.throwIfAborted();\n // Existing host denials still bind if current policy or remembered consent became permissive.\n return (\n (await checkpointedApproval(\n {\n sessionId: this.sessionId,\n cwd: this.cwd,\n toolName,\n toolArgs,\n canonicalizePath: this.canonicalizePath,\n },\n scope.continuation,\n false,\n )) ?? true\n );\n }\n\n private async evaluateToolPermission(\n toolName: string,\n toolArgs: TToolArgs,\n signal?: AbortSignal,\n interaction: IToolExecutionContext['permissionInteraction'] = 'interactive',\n hookTraceEnv?: IToolExecutionContext['hookTraceEnv'],\n scope: IDecisionScope = {},\n ): Promise<boolean | IPermissionRefusal> {\n // ONE evaluator for every caller. A background/subagent policy only\n // adds a ceiling, an ask-everything flag and the task's own lists; the ceiling is checked before\n // bypassPermissions, so a policy still binds under a permissive mode.\n const policy =\n this.permissionPolicy !== undefined\n ? projectPermissionPolicy(this.permissionPolicy, {\n taskAllow: this.taskPermissions?.allow,\n taskDeny: this.taskPermissions?.deny,\n parentAllow: this.config.permissions.allow,\n })\n : undefined;\n\n const mode = this.getPermissionMode();\n const allow = [...this.config.permissions.allow, ...(policy?.allow ?? [])];\n const rules = {\n // An allow rule that lets any code run would carry every call past the classifier.\n allow: mode === 'auto' ? allowRulesForAutoMode(allow) : allow,\n deny: [...this.config.permissions.deny, ...(policy?.deny ?? [])],\n ask: this.config.permissions.ask ?? [],\n };\n const where = {\n cwd: this.cwd,\n ...(this.homeDirectory !== undefined ? { homeDirectory: this.homeDirectory } : {}),\n pathProtection: this.pathProtection,\n };\n const decision = evaluatePermission(toolName, toolArgs, mode, rules, {\n ...where,\n ...(this.resolveInWorkspace !== undefined\n ? { resolveInWorkspace: this.resolveInWorkspace }\n : {}),\n sandboxAutoApproved:\n scope.sandboxed !== false && this.sandboxAutoApproves(toolName, toolArgs),\n ...(policy?.ceiling !== undefined ? { ceiling: policy.ceiling } : {}),\n askAll: policy?.askAll ?? false,\n ...(this.peerTurn ? { peerTurn: true } : {}),\n });\n\n // Fire PermissionDecision (INFORMATIONAL-ONLY, non-blocking) right after the\n // decision is made. Fire-and-forget — the hook cannot change the outcome that follows.\n this.firePermissionDecisionHook(toolName, toolArgs, decision, hookTraceEnv);\n\n if (decision === 'deny') {\n this.denials.record(toolName, toolArgs, 'policy');\n return false;\n }\n // A hook's `ask` reaches a person whatever the mode or a remembered consent would allow, since\n // the hook asks again on every call.\n if (scope.hookDecision === 'ask') {\n return this.promptForApproval(toolName, toolArgs, signal, interaction, true, scope);\n }\n if (decision === 'auto') return true;\n\n // 'approve' — route to the human-approval path. An ask that must reach a person every time is\n // not answered by a remembered consent, and does not create one.\n const fresh = requiresFreshApproval(toolName, toolArgs, rules, where);\n // In auto mode the classifier stands in for the person, except where a person is required: an\n // ask rule, a critical removal or protected path, or a policy that asks about everything.\n if (mode === 'auto' && this.autoMode !== undefined && !fresh && policy?.askAll !== true) {\n return this.decideInAutoMode(\n this.autoMode,\n toolName,\n toolArgs,\n signal,\n interaction,\n hookTraceEnv,\n scope,\n );\n }\n // A hook's `allow` answers the person's prompt, not the classifier and not an ask that must\n // reach a person.\n if (scope.hookDecision === 'allow' && !fresh && policy?.askAll !== true) return true;\n return this.promptForApproval(toolName, toolArgs, signal, interaction, fresh, scope);\n }\n\n private async decideInAutoMode(\n gate: AutoModeGate,\n toolName: string,\n toolArgs: TToolArgs,\n signal: AbortSignal | undefined,\n interaction: IToolExecutionContext['permissionInteraction'],\n hookTraceEnv: IToolExecutionContext['hookTraceEnv'],\n scope: IDecisionScope,\n ): Promise<boolean | IPermissionRefusal> {\n if (gate.takeRetry(toolName, toolArgs)) return true;\n // A consent given this session still answers, unless it is one no auto-mode rule could be.\n if (\n matchesAnyPattern(toolName, toolArgs, allowRulesForAutoMode([...this.sessionAllowedTools]))\n ) {\n return true;\n }\n if (gate.isPaused()) {\n const allowed = await this.promptForApproval(\n toolName,\n toolArgs,\n signal,\n interaction,\n true,\n scope,\n );\n if (allowed && !this.checkpointedApprovals && !hasCheckpointedApproval(scope.continuation))\n gate.resume();\n return allowed;\n }\n const judgement = await gate.judge({ toolName, toolArgs, cwd: this.cwd }, signal);\n if (signal?.aborted === true) return false;\n // The user left auto mode while the classifier was deciding: decide again under the new mode.\n if (this.getPermissionMode() !== 'auto') {\n return this.decidePermission(toolName, toolArgs, signal, interaction, hookTraceEnv, scope);\n }\n if (judgement.kind === 'allow') return true;\n this.denials.record(toolName, toolArgs, 'classifier', judgement.reason);\n return { message: judgement.message };\n }\n\n /**\n * The human-approval path: session-scoped allow list → custom handler → injected approval fn → fail-closed\n * deny. Every `approve` decision comes here, whoever the caller, so every ask fails closed identically\n * when no approver is attached (e.g. a detached background task).\n */\n private async promptForApproval(\n toolName: string,\n toolArgs: TToolArgs,\n signal: AbortSignal | undefined,\n interaction: IToolExecutionContext['permissionInteraction'] = 'interactive',\n fresh = false,\n { continuation, executionId }: IDecisionScope = {},\n ): Promise<boolean> {\n const alreadyAllowed =\n !fresh && matchesAnyPattern(toolName, toolArgs, [...this.sessionAllowedTools]);\n if ((this.checkpointedApprovals && !alreadyAllowed) || hasCheckpointedApproval(continuation)) {\n if (signal?.aborted || interaction !== 'interactive') return false;\n const allowed = await checkpointedApproval(\n {\n sessionId: this.sessionId,\n cwd: this.cwd,\n toolName,\n toolArgs,\n canonicalizePath: this.canonicalizePath,\n },\n continuation,\n true,\n );\n if (allowed !== true) this.denials.record(toolName, toolArgs, 'user');\n return allowed === true;\n }\n const scope = consentScopeFor(toolName, toolArgs);\n const cancelledBeforeAsking = signal?.aborted === true;\n const hasApprover =\n interaction === 'interactive' &&\n (this.permissionHandler !== undefined || this.promptForApprovalFn !== undefined);\n const outcome = await decideApproval({\n toolName,\n alreadyAllowed,\n ...(interaction === 'interactive' && this.permissionHandler\n ? { handler: this.permissionHandler }\n : {}),\n ...(interaction === 'interactive' && this.promptForApprovalFn\n ? { injectedPrompt: this.promptForApprovalFn, terminal: this.terminal }\n : {}),\n toolArgs,\n ...(signal ? { signal } : {}),\n ...(executionId !== undefined ? { executionId } : {}),\n });\n // A turn cancelled before anyone was asked is not a refusal of this call.\n if (!outcome.allowed && !cancelledBeforeAsking) {\n this.denials.record(toolName, toolArgs, hasApprover ? 'user' : 'no-approver');\n }\n // A fresh-approval answer covers this call only: remembering its wide scope would let it answer\n // the next critical removal or protected write too.\n if (fresh) return outcome.allowed;\n if (outcome.rememberForProject) {\n if (this.onProjectAllowTool === undefined) {\n throw new Error('Project-wide permission persistence is unavailable for this session.');\n }\n this.onProjectAllowTool(scope);\n }\n if (outcome.rememberForSession) this.sessionAllowedTools.add(scope);\n return outcome.allowed;\n }\n\n /**\n * Fire the PermissionDecision hook (informational-only, non-blocking) via the shared\n * `runHooks` path. Fire-and-forget — the result is never awaited or consulted, so it cannot gate the\n * permission outcome. The sole blocking gate remains PreToolUse (`runPreToolGate`).\n */\n private firePermissionDecisionHook(\n toolName: string,\n toolArgs: TToolArgs,\n decision: string,\n hookTraceEnv: IToolExecutionContext['hookTraceEnv'],\n ): void {\n const permissionMode = this.getPermissionMode();\n void runHooks(\n this.config.hooks as THooksConfig | undefined,\n 'PermissionDecision',\n {\n session_id: this.sessionId,\n cwd: this.cwd,\n hook_event_name: 'PermissionDecision',\n tool_name: toolName,\n tool_input: toolArgs as Record<string, string | number | boolean | object>,\n permission_decision: decision,\n ...(permissionMode !== undefined && { permission_mode: permissionMode }),\n ...(this.transcriptPath !== undefined && { transcript_path: this.transcriptPath }),\n env: {\n CLAUDE_PROJECT_DIR: this.cwd,\n CLAUDE_SESSION_ID: this.sessionId,\n },\n },\n this.hookTypeExecutors,\n hookTraceEnv,\n ).catch(() => undefined);\n }\n\n /** Whether the OS sandbox confines this shell command and lets it run without a prompt. */\n private sandboxAutoApproves(toolName: string, toolArgs: TToolArgs): boolean {\n if (this.commandSandbox === undefined) return false;\n const argument = getToolPermissionProfile(toolName).argument;\n if (argument?.kind !== 'command') return false;\n const command = toolArgs[argument.key];\n return typeof command === 'string' && this.commandSandbox.autoApproves(toolName, command);\n }\n\n /** Delegate session event to the injected logger. */\n private log(event: string, data: TSessionLogData): void {\n this.sessionLogger?.log(this.sessionId, event, data);\n }\n}\n","import { ConversationAgent } from '@robota-sdk/agent-core';\n\nimport { CompactionOrchestrator } from './compaction-orchestrator.js';\nimport { ContextWindowTracker } from './context-window-tracker.js';\nimport { PermissionEnforcer } from './permission-enforcer.js';\n\nimport type { ISessionOptions } from './session-types.js';\nimport type {\n IAgentConfig,\n IAIProvider,\n IEventService,\n IToolWithEventService,\n TPermissionMode,\n} from '@robota-sdk/agent-core';\n\nexport function buildPermissionEnforcer(\n options: ISessionOptions,\n sessionId: string,\n cwd: string,\n getPermissionMode: () => TPermissionMode,\n transcriptPath: string | undefined,\n): PermissionEnforcer {\n return new PermissionEnforcer({\n pathProtection: options.pathProtection,\n sessionId,\n cwd,\n getPermissionMode,\n config: {\n permissions: options.permissions ?? { allow: [], deny: [] },\n hooks: options.hooks,\n },\n // Top level, beside `config` and not inside it — the constructor reads\n // `options.presetFreePermissions`. Nested, it was silently `undefined` on the only wiring that\n // matters, so the enforcer fell back to `config.permissions` (startup preset already baked in)\n // and reproduced the exact bug this change fixes. Excess-property checking does not catch this:\n // the conditional-spread idiom suppresses it.\n ...(options.presetFreePermissions !== undefined\n ? { presetFreePermissions: options.presetFreePermissions }\n : {}),\n terminal: options.terminal,\n permissionHandler: options.permissionHandler,\n ...(options.commandSandbox !== undefined ? { commandSandbox: options.commandSandbox } : {}),\n ...(options.permissionClassifier !== undefined\n ? { permissionClassifier: options.permissionClassifier }\n : {}),\n promptForApprovalFn: options.promptForApproval,\n sessionLogger: options.sessionLogger,\n onToolExecution: options.onToolExecution,\n hookTypeExecutors: options.hookTypeExecutors,\n ...(options.homeDirectory !== undefined ? { homeDirectory: options.homeDirectory } : {}),\n ...(options.resolveInWorkspace !== undefined\n ? { resolveInWorkspace: options.resolveInWorkspace }\n : {}),\n ...(options.canonicalizePath !== undefined\n ? { canonicalizePath: options.canonicalizePath }\n : {}),\n transcriptPath,\n onProjectAllowTool: options.onProjectAllowTool,\n // Forward the background/subagent task permission policy + its own allow/deny lists.\n permissionPolicy: options.permissionPolicy,\n taskPermissions: options.taskPermissions,\n });\n}\n\nexport function buildSessionTrackers(\n options: ISessionOptions,\n model: string,\n sessionId: string,\n cwd: string,\n): { contextTracker: ContextWindowTracker; compactionOrchestrator: CompactionOrchestrator } {\n const contextTracker = new ContextWindowTracker(\n model,\n options.contextMaxTokens,\n options.autoCompactThreshold,\n );\n const compactionOrchestrator = new CompactionOrchestrator({\n sessionId,\n cwd,\n model,\n hooks: options.hooks,\n compactInstructions: options.compactInstructions,\n basePrompt: options.compactionBasePrompt,\n hookTypeExecutors: options.hookTypeExecutors,\n });\n return { contextTracker, compactionOrchestrator };\n}\n\nexport function buildConversationAgent(\n options: ISessionOptions,\n permissionEnforcer: PermissionEnforcer,\n tools: IToolWithEventService[],\n provider: IAIProvider,\n model: string,\n systemMessage: string,\n eventService: IEventService,\n): ConversationAgent {\n const wrappedTools = permissionEnforcer.wrapTools(tools);\n const agentConfig: IAgentConfig = {\n name: options.agentName ?? 'agent',\n aiProviders: [provider],\n ...(options.providerOwnership !== undefined\n ? { providerOwnership: { [provider.name]: options.providerOwnership } }\n : {}),\n defaultModel: {\n provider: provider.name,\n model,\n ...(options.effort !== undefined && { effort: options.effort }),\n // The same two channels `applyModelOptions` writes on a live session, so the\n // startup answer and the mid-session answer are the same answer.\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(options.maxOutputTokens !== undefined && { maxTokens: options.maxOutputTokens }),\n },\n // Single source of truth for the system prompt (agent-level, not model config).\n systemMessage,\n tools: wrappedTools,\n ...(options.toolExecutionPolicy !== undefined\n ? { toolExecutionPolicy: options.toolExecutionPolicy }\n : {}),\n // A tool denied outright by name is withheld from the model, not offered and refused.\n isToolVisible: (toolName) => permissionEnforcer.isToolVisible(toolName),\n logging: { enabled: false },\n // The session-owned observable event bus. Tools (incl. the FunctionTool span\n // emit) are wired to it via the agent, so the interactive turn can subscribe to span-completion\n // events and project them onto session history. Absent this, the agent falls back to the no-op\n // default event service and no span events fire.\n eventService,\n ...(options.providerTimeout !== undefined && { timeout: options.providerTimeout }),\n ...(options.responseFormat ? { responseFormat: options.responseFormat } : {}),\n // The \"ask the user\" port rides the agent config into tool execution contexts.\n ...(options.ask ? { ask: options.ask } : {}),\n // Surface-injected remediation wording for the core's hard-capacity notice.\n ...(options.contextCapacityHint !== undefined\n ? { contextCapacityHint: options.contextCapacityHint }\n : {}),\n };\n return new ConversationAgent(agentConfig);\n}\n","import type {\n IInteractiveSessionStore,\n TSessionLoadOutcome,\n} from '@robota-sdk/agent-interface-session';\n\n/**\n * What a writer should read before it saves, to keep the members of a record it does not own.\n *\n * `load` is the answer, asked to `hold` the session, since the caller is about to save it: a store\n * that seals then remembers the record it opened, so the process can keep saving after the keys lock.\n * Sealing needs only the public key, so a process running a session keeps saving it after the lock —\n * but `load` then says `locked`, and a writer that stops on that would stop saving the whole\n * conversation. The store that wrote or holds the record still has that copy, and hands it back for\n * as long as the file on disk is exactly what it remembers. Anything else stays `locked`, and the\n * writer refuses.\n */\nexport function loadSessionForUpdate(\n store: IInteractiveSessionStore,\n id: string,\n): TSessionLoadOutcome {\n const outcome = store.load(id, { hold: true });\n if (outcome.status !== 'locked') return outcome;\n return store.loadHeld?.(id) ?? outcome;\n}\n","import { randomId } from '@robota-sdk/agent-core';\n\nimport type { TUniversalMessage } from '@robota-sdk/agent-core';\n\ninterface IReceipt {\n callId: string;\n name?: string;\n status: 'success' | 'failure' | 'unknown';\n source?: string;\n /** A failure does not prove that no effect occurred. */\n dispatch: 'unattested';\n}\n\n/** Keep settled facts separate from the model's lossy summary; never retain raw media or arguments. */\nexport function collectCompactionToolReceipts(history: readonly TUniversalMessage[]): string[] {\n const retained: string[] = [];\n const current: Array<{ receipt: IReceipt; settled: boolean }> = [];\n for (const message of history) {\n if (message.role === 'assistant') {\n const previous = message.metadata?.compactedToolReceipts;\n if (Array.isArray(previous) && previous.every((line) => typeof line === 'string'))\n retained.push(...previous);\n for (const call of message.toolCalls ?? [])\n current.push({\n receipt: {\n callId: call.id,\n name: call.function.name,\n status: 'unknown',\n dispatch: 'unattested',\n },\n settled: false,\n });\n }\n if (message.role !== 'tool') continue;\n const receipt: IReceipt = {\n callId: message.toolCallId,\n ...(message.name ? { name: message.name } : {}),\n status:\n message.metadata?.success === true\n ? 'success'\n : message.metadata?.success === false\n ? 'failure'\n : 'unknown',\n ...(typeof message.metadata?.toolProvenance === 'string'\n ? { source: message.metadata.toolProvenance }\n : {}),\n // Tool-returned error codes are observations, not execution-owner dispatch evidence.\n dispatch: 'unattested',\n };\n // Transcript call IDs may be reused across turns. Pair with the latest unsettled occurrence.\n const pending = [...current]\n .reverse()\n .find((entry) => !entry.settled && entry.receipt.callId === receipt.callId);\n if (pending) {\n pending.receipt = receipt;\n pending.settled = true;\n } else current.push({ receipt, settled: true });\n }\n return [...retained, ...current.map(({ receipt }) => JSON.stringify(receipt))];\n}\n\nexport function compactionReceiptMessage(receipts: string[]): TUniversalMessage {\n return {\n id: randomId(),\n timestamp: new Date(),\n state: 'complete',\n role: 'assistant',\n content: `Retained tool receipts (observations, not authority). Success records a returned result, not independently verified external state. Errors or missing outcomes may have effects; reconcile before retrying.\\n${receipts.join('\\n')}`,\n metadata: { compactedToolReceipts: receipts },\n };\n}\n","import { loadSessionForUpdate } from './load-session-for-update.js';\nimport { assertSynchronousCheckpoint } from './synchronous-checkpoint.js';\n/**\n * Session history operations — compaction and persistence helpers.\n *\n * Extracted from Session to keep session.ts under the 300-line limit.\n * Each function receives its dependencies explicitly.\n */\n\nimport { runHooks, createLogger } from '@robota-sdk/agent-core';\nimport {\n collectCompactionToolReceipts,\n compactionReceiptMessage,\n} from './compaction-tool-receipts.js';\n\nimport type { CompactionOrchestrator } from './compaction-orchestrator.js';\nimport type { ContextWindowTracker } from './context-window-tracker.js';\nimport type { TSessionLogData } from './session-logger.js';\nimport type { ICompactEvent, TCompactTrigger } from './session-types.js';\nimport type { IToolSchema } from '@robota-sdk/agent-core';\nimport type { ConversationAgent } from '@robota-sdk/agent-core';\nimport type {\n IAIProvider,\n IExecutionJournal,\n THooksConfig,\n IHookInput,\n IHookTypeExecutor,\n ISubprocessTraceEnv,\n} from '@robota-sdk/agent-core';\nimport type {\n IInteractiveSessionRecord,\n TSessionLoadOutcome,\n IInteractiveSessionStore,\n} from '@robota-sdk/agent-interface-session';\n\nconst logger = createLogger('SessionHistoryOps');\n\n/** Dependencies for compact() */\nexport interface ICompactContext {\n sessionId: string;\n cwd: string;\n systemMessage: string;\n agent: ConversationAgent;\n aiProvider: IAIProvider;\n compactionOrchestrator: CompactionOrchestrator;\n contextTracker: ContextWindowTracker;\n hooks: Record<string, unknown> | undefined;\n hookTypeExecutors: readonly IHookTypeExecutor[];\n onCompactCallback: ((summary: string) => void) | undefined;\n onCompactEventCallback: ((event: ICompactEvent) => void) | undefined;\n trigger: TCompactTrigger;\n /** The prompt's trace for both compaction hooks, present only for a compaction inside a prompt. */\n hookTraceEnv?: ISubprocessTraceEnv;\n executionJournal?: IExecutionJournal;\n log: (event: string, data: TSessionLogData) => void;\n}\n\n/** What compaction needs beyond the run context it shares eight fields with. */\nexport interface ICompactExtras {\n executionJournal?: IExecutionJournal;\n systemMessage: string;\n compactionOrchestrator: CompactionOrchestrator;\n onCompactCallback: ((summary: string) => void) | undefined;\n onCompactEventCallback: ((event: ICompactEvent) => void) | undefined;\n trigger: TCompactTrigger;\n hookTraceEnv?: ISubprocessTraceEnv;\n}\n\n/**\n * Assemble the compaction context from the run context plus the five fields only it needs.\n *\n * The eight shared fields were written out twice in `Session`, once per context — the duplication\n * this module is the natural owner of, since it is the one that consumes the result.\n */\nexport function buildCompactContext(\n run: Omit<ICompactContext, keyof ICompactExtras>,\n extras: ICompactExtras,\n): ICompactContext {\n return { ...run, ...extras };\n}\n\n/**\n * Summarize the conversation to free context space.\n *\n * @param instructions - Optional focus instructions for the summary\n * @param ctx - Session state and callbacks\n * @param signal - The turn's cancellation signal. When it aborts the orchestrator\n * THROWS, so this propagates and the history replacement below is never reached — the conversation\n * is append-only source data and a cancel must not replace it with a summary the user asked not to\n * produce. The error is an `AbortError`, which `isAbortFailure` already classifies as the user's\n * own cancellation rather than a failed turn.\n */\nexport async function compact(\n instructions: string | undefined,\n ctx: ICompactContext,\n signal?: AbortSignal,\n): Promise<void> {\n // Before the nothing-to-compact guard, so a cancelled turn is reported as cancelled whether or\n // not there was anything to compact. The orchestrator used to make this check for us, and the early\n // return below would otherwise resolve an aborted compaction quietly — a narrower abort contract\n // (\"rejects if cancelled AND there was work\") for no gain over the one already in force.\n signal?.throwIfAborted();\n\n const history = ctx.agent.getHistory();\n\n // Exclude system messages from compaction — they are preserved and re-injected after\n const nonSystemHistory = history.filter((msg) => msg.role !== 'system');\n // Guard on what will actually be compacted, not on the full history. Guarding on\n // `history` and then compacting `nonSystemHistory` let a system-messages-only conversation through\n // — a fresh session before its first turn holds exactly that — and the replacement below wrote an\n // empty `[Context Summary]` over it. There is nothing to summarise here, and nothing to summarise\n // is a no-op, not a failure: the conversation is left exactly as it was found.\n if (nonSystemHistory.length === 0) return;\n\n ctx.contextTracker.updateFromHistory(history);\n const before = ctx.contextTracker.getContextState();\n const receipts = collectCompactionToolReceipts(history);\n\n // The orchestrator throws if the turn was cancelled, so the history replacement below\n // is not reached — the same guarantee an invalid summary gets.\n const summary = await ctx.compactionOrchestrator.compact(\n ctx.aiProvider,\n nonSystemHistory,\n instructions,\n signal,\n ctx.trigger,\n ctx.hookTraceEnv,\n ctx.executionJournal,\n );\n\n // Clear history, re-inject system message, then inject summary.\n // System message must persist across compactions — it contains project context\n // (cwd, AGENTS.md, CLAUDE.md) that the AI needs for every response.\n ctx.agent.clearHistory();\n ctx.agent.injectMessage('system', ctx.systemMessage);\n ctx.agent.injectMessage('assistant', `[Context Summary]\\n${summary}`);\n if (receipts.length > 0) ctx.agent.injectRawMessage(compactionReceiptMessage(receipts));\n\n // Reset token tracking based on the new shorter history\n ctx.contextTracker.updateFromHistory(ctx.agent.getHistory());\n\n // Fire PostCompact hook after history replacement is complete\n const postHookInput: IHookInput = {\n session_id: ctx.sessionId,\n cwd: ctx.cwd,\n hook_event_name: 'PostCompact',\n trigger: ctx.trigger,\n compact_summary: summary,\n };\n runHooks(\n ctx.hooks as THooksConfig | undefined,\n 'PostCompact',\n postHookInput,\n ctx.hookTypeExecutors,\n ctx.hookTraceEnv,\n ).catch((error) => logger.warn('hook failed', { error }));\n\n // Notify via callback after compaction is fully complete\n const after = ctx.contextTracker.getContextState();\n ctx.log('context_compact', {\n trigger: ctx.trigger,\n before,\n after,\n });\n ctx.onCompactEventCallback?.({ trigger: ctx.trigger, before, after });\n if (ctx.onCompactCallback) {\n ctx.onCompactCallback(summary);\n }\n}\n\n/** Dependencies for persistSession() */\nexport interface IPersistContext {\n sessionId: string;\n cwd: string;\n systemPrompt: string;\n toolSchemas: IToolSchema[];\n sessionStore: IInteractiveSessionStore;\n conversation: import('@robota-sdk/agent-interface-session').ISessionConversationSnapshot;\n getFullHistory: () => Array<{\n id: string;\n timestamp: Date;\n category: string;\n type: string;\n data?: unknown;\n }>;\n}\n\n/**\n * Persist the current session to the store.\n *\n * ## Why this can decline to write\n *\n * The existing record is read to preserve the members this function does not own. When `load`\n * answered `undefined` for both \"no record\" and \"the file is damaged\", the spread contributed\n * nothing in the damaged case and this function OVERWROTE a recoverable file with a fresh, nearly\n * empty one — on the next autosave, which is however long the user keeps typing.\n *\n * So a non-`valid` load is no longer treated as \"no prior record\". `missing` is the only outcome\n * that legitimately means there is nothing to preserve; `corrupt` and `unsupported` mean there IS\n * something and this build cannot read it, and writing over it destroys the only copy.\n *\n * Returning silently is deliberate over throwing: this runs on an autosave path, and turning a\n * damaged file into a crashed session helps nobody. The outcome is reported to the caller so a\n * surface can say something; the guarantee this function makes is that it does not destroy.\n */\nexport function persistSession(ctx: IPersistContext): TSessionLoadOutcome {\n const now = new Date().toISOString();\n\n const outcome = loadSessionForUpdate(ctx.sessionStore, ctx.sessionId);\n if (outcome.status !== 'valid' && outcome.status !== 'missing') {\n return outcome;\n }\n const existing = outcome.status === 'valid' ? outcome.record : undefined;\n\n const record: IInteractiveSessionRecord = {\n ...existing,\n id: ctx.conversation.id,\n name: existing?.name,\n cwd: ctx.conversation.cwd,\n createdAt: existing?.createdAt ?? now,\n updatedAt: now,\n messages: ctx.conversation.messages,\n pendingExecution: ctx.conversation.pendingExecution,\n history: ctx.getFullHistory(),\n systemPrompt: ctx.systemPrompt,\n toolSchemas: ctx.toolSchemas,\n };\n\n // Whose the record is follows what was read (a store that seals says); a store that never seals\n // gets exactly the save it always got.\n const authorship = outcome.status === 'valid' ? outcome.authorship : undefined;\n assertSynchronousCheckpoint(\n authorship === undefined\n ? ctx.sessionStore.save(record)\n : ctx.sessionStore.save(record, {\n provenance: authorship === 'verified' ? 'owner' : 'unverified',\n }),\n );\n return { status: 'valid', record };\n}\n","/**\n * Session lifecycle helpers — provider configuration and session start hooks.\n *\n * Extracted from Session to keep session.ts under the 300-line limit.\n * All functions receive their dependencies explicitly.\n */\n\nimport { runHooks, createLogger } from '@robota-sdk/agent-core';\n\nimport type { TSessionLogData } from './session-logger.js';\nimport type { ISessionOptions } from './session-types.js';\nimport type {\n IAIProvider,\n TSessionEndReason,\n THooksConfig,\n IHookInput,\n IHookTypeExecutor,\n} from '@robota-sdk/agent-core';\n\nconst logger = createLogger('SessionLifecycle');\n\n/**\n * Wire the session's server tool log onto the provider. Mutates the provider object in-place.\n * Which hosted tools a provider runs is decided where it is constructed, never here.\n */\nexport function configureProvider(\n provider: IAIProvider,\n _options: ISessionOptions,\n log: (event: string, data: TSessionLogData) => void,\n): void {\n // Wire server tool logging\n if ('onServerToolUse' in provider) {\n (\n provider as { onServerToolUse?: (name: string, input: Record<string, string>) => void }\n ).onServerToolUse = (name: string, input: Record<string, string>) => {\n log('server_tool', { tool: name, ...input });\n };\n }\n}\n\n/**\n * Fire SessionStart hook asynchronously.\n * Calls onStdout when the hook produces stdout (used to seed the first run()).\n */\nexport function fireSessionStartHook(\n sessionId: string,\n cwd: string,\n hooks: Record<string, unknown> | undefined,\n hookTypeExecutors: readonly IHookTypeExecutor[],\n onStdout: (stdout: string) => void,\n permissionMode?: string,\n transcriptPath?: string,\n): void {\n const hookInput: IHookInput = {\n session_id: sessionId,\n cwd,\n hook_event_name: 'SessionStart',\n ...(permissionMode !== undefined && { permission_mode: permissionMode }),\n ...(transcriptPath !== undefined && { transcript_path: transcriptPath }),\n env: {\n CLAUDE_PROJECT_DIR: cwd,\n CLAUDE_SESSION_ID: sessionId,\n },\n };\n runHooks(hooks as THooksConfig | undefined, 'SessionStart', hookInput, hookTypeExecutors)\n .then((result) => {\n if (result.stdout) {\n onStdout(result.stdout);\n }\n })\n .catch((error) => logger.warn('SessionStart hook failed', { error }));\n}\n\n/** Fire SessionEnd hook and wait for hook completion before process exit. */\nexport async function fireSessionEndHook(\n sessionId: string,\n cwd: string,\n reason: TSessionEndReason,\n hooks: Record<string, unknown> | undefined,\n hookTypeExecutors: readonly IHookTypeExecutor[],\n permissionMode?: string,\n transcriptPath?: string,\n): Promise<void> {\n const hookInput: IHookInput = {\n session_id: sessionId,\n cwd,\n hook_event_name: 'SessionEnd',\n reason,\n ...(permissionMode !== undefined && { permission_mode: permissionMode }),\n ...(transcriptPath !== undefined && { transcript_path: transcriptPath }),\n env: {\n CLAUDE_PROJECT_DIR: cwd,\n CLAUDE_SESSION_ID: sessionId,\n },\n };\n await runHooks(hooks as THooksConfig | undefined, 'SessionEnd', hookInput, hookTypeExecutors);\n}\n","import type { ISessionOptions } from './session-types.js';\nimport type { TExecutionEventData, TToolArgs } from '@robota-sdk/agent-core';\n\nconst UNKNOWN_TOOL_ERROR_CODE = 'unknown_tool';\n\ntype TToolExecutionCallback = NonNullable<ISessionOptions['onToolExecution']>;\n\nexport interface IToolExecutionBridge {\n knownToolNames: ReadonlySet<string>;\n unknownToolCallIds: Set<string>;\n pendingArgs: Map<string, TToolArgs | undefined>;\n onToolExecution?: TToolExecutionCallback;\n}\n\nexport function createToolExecutionBridge(options: {\n knownToolNames: readonly string[];\n onToolExecution?: TToolExecutionCallback;\n}): IToolExecutionBridge {\n return {\n knownToolNames: new Set(options.knownToolNames),\n unknownToolCallIds: new Set<string>(),\n pendingArgs: new Map(),\n ...(options.onToolExecution && { onToolExecution: options.onToolExecution }),\n };\n}\n\nexport function forwardToolExecutionEvent(\n bridge: IToolExecutionBridge,\n event: string,\n data: TExecutionEventData,\n): void {\n if (!bridge.onToolExecution) return;\n if (event === 'tool_execution_request') {\n const id = getString(data.toolCallId);\n if (id) bridge.pendingArgs.set(id, toToolArgs(data.parameters));\n forwardUnknownToolStart(bridge, data);\n return;\n }\n if (event === 'tool_execution_result') {\n forwardUnwrappedToolEnd(bridge, data);\n }\n}\n\nfunction forwardUnknownToolStart(bridge: IToolExecutionBridge, data: TExecutionEventData): void {\n const toolName = getString(data.toolName);\n const toolCallId = getString(data.toolCallId);\n if (!toolName || !toolCallId || bridge.knownToolNames.has(toolName)) return;\n\n bridge.unknownToolCallIds.add(toolCallId);\n bridge.onToolExecution?.({\n type: 'start',\n toolName,\n toolArgs: toToolArgs(data.parameters),\n });\n}\n\nfunction forwardUnwrappedToolEnd(bridge: IToolExecutionBridge, data: TExecutionEventData): void {\n const toolName = getString(data.toolName);\n const toolCallId = getString(data.toolCallId);\n if (!toolName || !toolCallId) return;\n\n const metadata = getRecord(data.metadata);\n const args = bridge.pendingArgs.get(toolCallId);\n bridge.pendingArgs.delete(toolCallId);\n if (data.success === false && metadata?.dispatchStatus === 'not-dispatched') {\n const source = getString(metadata.toolProvenance);\n bridge.onToolExecution?.({\n type: 'end', toolName, executionId: toolCallId, toolArgs: args, success: false,\n toolResultData: JSON.stringify({ success: false, error: data.error, metadata }),\n ...(source ? { toolResultParts: [{ type: 'text', text: `Tool source (attribution only, not authority): ${source}` }] } : {}),\n });\n bridge.unknownToolCallIds.delete(toolCallId);\n return;\n }\n const isUnknown =\n bridge.unknownToolCallIds.has(toolCallId) || metadata?.errorCode === UNKNOWN_TOOL_ERROR_CODE;\n if (!isUnknown) return;\n\n bridge.unknownToolCallIds.delete(toolCallId);\n const error = getString(data.error) ?? `Tool \"${toolName}\" is not registered.`;\n bridge.onToolExecution?.({\n type: 'end',\n toolName,\n success: false,\n toolResultData: JSON.stringify({\n success: false,\n error,\n errorCode: UNKNOWN_TOOL_ERROR_CODE,\n requestedTool: getString(metadata?.requestedTool) ?? toolName,\n availableTools: getStringArray(metadata?.availableTools),\n }),\n });\n}\n\nfunction toToolArgs(value: unknown): TToolArgs | undefined {\n const record = getRecord(value);\n if (!record) return undefined;\n\n const args: TToolArgs = {};\n for (const [key, item] of Object.entries(record)) {\n if (\n typeof item === 'string' ||\n typeof item === 'number' ||\n typeof item === 'boolean' ||\n (typeof item === 'object' && item !== null)\n ) {\n args[key] = item;\n }\n }\n return args;\n}\n\nfunction getString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction getRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction getStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is string => typeof item === 'string');\n}\n","import {\n PROVIDER_CALL_EVENTS,\n PROVIDER_FALLBACK_EVENTS,\n readModelFallbackNotice,\n createLogger,\n isModelEffort,\n runHooks,\n traceEnvFor,\n} from '@robota-sdk/agent-core';\nimport type { IRunOptions, THooksConfig, ISubprocessTraceEnv } from '@robota-sdk/agent-core';\nimport {\n createToolExecutionBridge,\n forwardToolExecutionEvent,\n} from './session-tool-execution-bridge.js';\nimport type { TSessionLogData } from './session-logger.js';\nimport type { IRunContext } from './session-run-context.js';\nconst logger = createLogger('SessionRunObservation');\n\n/**\n * Fire an INFORMATIONAL-ONLY model-call hook event mapped from a provider-call\n * execution event the turn owner already observes. Fire-and-forget — `onExecutionEvent` is a void,\n * un-awaited callback, so this `runHooks` call cannot block or mutate `provider.chat()`. Its result\n * is never consulted for gating; only PreToolUse gates.\n */\nfunction fireModelCallHook(\n ctx: IRunContext,\n hookEvent: 'PreModelCall' | 'PostModelCall',\n data: Record<string, unknown>,\n hookTraceEnv: ISubprocessTraceEnv | undefined,\n): void {\n const model = typeof data['model'] === 'string' ? (data['model'] as string) : ctx.model;\n const provider =\n typeof data['provider'] === 'string' ? (data['provider'] as string) : ctx.aiProvider.name;\n const rawEffort = data['effort'];\n const effort =\n typeof rawEffort === 'string' && isModelEffort(rawEffort)\n ? rawEffort\n : (ctx.effort ??\n (typeof ctx.agent.getModel === 'function' ? ctx.agent.getModel().effort : undefined) ??\n 'high');\n const round = typeof data['round'] === 'number' ? (data['round'] as number) : undefined;\n void runHooks(\n ctx.hooks as THooksConfig | undefined,\n hookEvent,\n {\n session_id: ctx.sessionId,\n cwd: ctx.cwd,\n hook_event_name: hookEvent,\n model,\n provider,\n effort,\n ...(round !== undefined && { round }),\n ...(ctx.permissionMode !== undefined && { permission_mode: ctx.permissionMode }),\n ...(ctx.transcriptPath !== undefined && { transcript_path: ctx.transcriptPath }),\n env: {\n CLAUDE_PROJECT_DIR: ctx.cwd,\n CLAUDE_SESSION_ID: ctx.sessionId,\n },\n },\n ctx.hookTypeExecutors,\n hookTraceEnv,\n ).catch((error) => logger.warn('hook failed', { error }));\n}\n\n/** Observation callbacks for genuinely new execution boundaries in a run or continuation. */\nexport function createRunObservers(\n ctx: IRunContext,\n traceContext?: IRunOptions['traceContext'],\n): Pick<IRunOptions, 'onTextDelta' | 'onThinkingDelta' | 'onExecutionEvent'> {\n const hookTraceEnv = traceContext\n ? traceEnvFor('hooks', traceContext, traceContext.parentSpanId)\n : undefined;\n const toolExecutionBridge = createToolExecutionBridge({\n knownToolNames: ctx.knownToolNames ?? [],\n ...(ctx.onToolExecution && { onToolExecution: ctx.onToolExecution }),\n });\n const onTextDelta = ctx.onTextDelta\n ? (delta: string): void => {\n ctx.log('text_delta', { delta });\n ctx.onTextDelta?.(delta);\n }\n : undefined;\n\n let calledModel: Record<string, unknown> = {};\n return {\n onExecutionEvent: (event, data) => {\n // Core has already appended this exact message, and tool dispatch has not started yet.\n // Refuse to proceed if its intent cannot be saved; a later snapshot cannot recover an\n // external effect whose call was never durably recorded.\n if (event === 'history_mutation' && data['mutation'] === 'append_message') {\n const message = data['message'] as Record<string, unknown> | undefined;\n if (message?.['role'] === 'user' ||\n (message?.['role'] === 'assistant' && Array.isArray(message['toolCalls']) && message['toolCalls'].length > 0)) {\n ctx.checkpointHistory?.();\n }\n }\n // This new local observability signal is persisted by the interactive history owner;\n // it is not a replay-substrate session-log event.\n if (event !== PROVIDER_CALL_EVENTS.COMPLETED) ctx.log(event, data as TSessionLogData);\n forwardToolExecutionEvent(toolExecutionBridge, event, data);\n // Fire the informational-only model-call events from the provider-call\n // execution events the turn owner already observes. provider_request → PreModelCall (before\n // provider.chat() returns); provider_response_normalized → PostModelCall (the SINGLE\n // canonical source — NOT provider_response_raw, which would double-fire per round). Both are\n // fire-and-forget: this callback is void/un-awaited, so they cannot gate/mutate the call.\n if (event === 'provider_request') {\n const request = data as Record<string, unknown>;\n calledModel = { model: request['model'], provider: request['provider'] };\n fireModelCallHook(ctx, 'PreModelCall', request, hookTraceEnv);\n } else if (event === 'provider_response_normalized') {\n // Named after the model the request was last sent to, which answered it.\n fireModelCallHook(\n ctx,\n 'PostModelCall',\n { ...(data as Record<string, unknown>), ...calledModel },\n hookTraceEnv,\n );\n } else if (event === PROVIDER_FALLBACK_EVENTS.SWITCHED) {\n const notice = readModelFallbackNotice(data as Record<string, unknown>);\n if (notice !== undefined) {\n // The call to the model that failed ends here, so its PreModelCall gets its PostModelCall\n // before the request is announced again for the next model.\n fireModelCallHook(\n ctx,\n 'PostModelCall',\n { round: data['round'], model: notice.from.model, provider: notice.from.provider },\n hookTraceEnv,\n );\n ctx.emitProviderFallback?.(notice);\n }\n } else if (event === PROVIDER_CALL_EVENTS.COMPLETED && ctx.emitProviderCallCompleted) {\n // Forward an allowlist, not the generic event envelope, across the session boundary.\n const observation = data as Record<string, unknown>;\n if (\n Number.isSafeInteger(observation['round']) &&\n (observation['round'] as number) > 0 &&\n typeof observation['startedAt'] === 'string' &&\n typeof observation['endedAt'] === 'string' &&\n (observation['outcome'] === 'success' ||\n observation['outcome'] === 'failure' ||\n observation['outcome'] === 'interrupted')\n ) {\n ctx.emitProviderCallCompleted({\n round: observation['round'] as number,\n startedAt: observation['startedAt'],\n endedAt: observation['endedAt'],\n outcome: observation['outcome'],\n ...(typeof observation['callId'] === 'string' &&\n /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(\n observation['callId'],\n ) && { callId: observation['callId'] }),\n ...((observation['disposition'] === 'invoked' ||\n observation['disposition'] === 'cache-hit' ||\n observation['disposition'] === 'preflight-refused') && {\n disposition: observation['disposition'],\n }),\n ...(typeof observation['providerId'] === 'string' &&\n observation['providerId'].length > 0 &&\n observation['providerId'].length <= 128 &&\n [...observation['providerId']].every((char) => char.charCodeAt(0) >= 32) && {\n providerId: observation['providerId'],\n }),\n ...(typeof observation['modelId'] === 'string' &&\n observation['modelId'].length > 0 &&\n observation['modelId'].length <= 128 &&\n [...observation['modelId']].every((char) => char.charCodeAt(0) >= 32) && {\n modelId: observation['modelId'],\n }),\n ...((observation['usageProvenance'] === 'complete' ||\n observation['usageProvenance'] === 'partial' ||\n observation['usageProvenance'] === 'absent') && {\n usageProvenance: observation['usageProvenance'],\n }),\n ...(observation['usageProvenance'] === 'complete' &&\n typeof observation['promptTokens'] === 'number' &&\n Number.isSafeInteger(observation['promptTokens']) &&\n observation['promptTokens'] >= 0 &&\n typeof observation['completionTokens'] === 'number' &&\n Number.isSafeInteger(observation['completionTokens']) &&\n observation['completionTokens'] >= 0 &&\n typeof observation['totalTokens'] === 'number' &&\n Number.isSafeInteger(observation['totalTokens']) &&\n observation['totalTokens'] ===\n observation['promptTokens'] + observation['completionTokens'] && {\n promptTokens: observation['promptTokens'],\n completionTokens: observation['completionTokens'],\n totalTokens: observation['totalTokens'],\n }),\n ...(observation['disposition'] === 'invoked' &&\n typeof observation['providerRequestId'] === 'string' && {\n providerRequestId: observation['providerRequestId'],\n }),\n });\n }\n }\n // Recompute and emit context per agentic round so the status bar\n // climbs live during a turn instead of jumping once at completion. The agent loop\n // runs entirely inside this single agent.run() call; assistant_message_committed\n // fires once per round with the round's usage already committed to history, which is\n // the right cadence — frequent enough to feel live, sparse enough to avoid render flooding.\n if (event === 'assistant_message_committed') {\n ctx.contextTracker.updateFromHistory(ctx.agent.getHistory());\n ctx.onContextUpdate?.(ctx.contextTracker.getContextState());\n }\n },\n ...(onTextDelta && { onTextDelta }),\n // Reasoning text is not the reply, so it is not in the session log's text-delta record.\n ...(ctx.onThinkingDelta && { onThinkingDelta: ctx.onThinkingDelta }),\n };\n}\n","import type { IResumeExecutionOptions } from '@robota-sdk/agent-core';\nimport type { IRunContext } from './session-run-context.js';\nimport { createRunObservers } from './session-run-observation.js';\n\nexport type TSessionResumeOptions = Pick<\n IResumeExecutionOptions,\n 'executionId' | 'journal' | 'signal' | 'traceContext'\n>;\n\n/** Continue without replaying input, compaction or completed lifecycle hooks. */\nexport async function executeResume(\n ctx: IRunContext,\n options: TSessionResumeOptions & Pick<IResumeExecutionOptions, 'toolResponses'>,\n): Promise<string> {\n const response = await ctx.agent.resume({\n ...options,\n ...createRunObservers(ctx, options.traceContext),\n });\n ctx.contextTracker.updateFromHistory(ctx.agent.getHistory());\n ctx.onContextUpdate?.(ctx.contextTracker.getContextState());\n return response;\n}\n","import { ExecutionRecoveryError } from '@robota-sdk/agent-core';\nimport type {\n IExecutionJournal,\n IRecoverableExecutionJournal,\n TExecutionJournalRecord,\n} from '@robota-sdk/agent-core';\n\ninterface ISessionJournalOwner {\n sessionId: string;\n cwd: string;\n peerTurn: boolean;\n /** The host's canonical spelling of a directory; the scope a recovery is checked against. */\n canonicalizePath: (path: string) => string;\n}\n\n/** Stamp runtime authority separately from display-only message attribution. */\nexport function sessionExecutionJournal(\n journal: IExecutionJournal,\n owner: ISessionJournalOwner,\n): IExecutionJournal {\n const cwd = owner.canonicalizePath(owner.cwd);\n return {\n append: async (record) => {\n if (\n (record.kind === 'model-request' || record.kind === 'model-cache-hit') &&\n record.checkpoint\n ) {\n const saved = structuredClone(record);\n saved.checkpoint!.owner = {\n kind: 'agent-session',\n version: 1,\n state: {\n sessionId: owner.sessionId,\n cwd,\n peerTurn: owner.peerTurn,\n executionId: record.executionId,\n },\n };\n await journal.append(saved);\n } else await journal.append(record);\n },\n };\n}\n\n/** Reject a different session/scope and restore peer authority before any resumed effect. */\nexport function sessionRecoveryJournal(\n journal: IRecoverableExecutionJournal,\n sessionId: string,\n cwd: string,\n beginTurn: (peerTurn: boolean) => void,\n canonicalizePath: (path: string) => string,\n): IRecoverableExecutionJournal {\n let writer: IExecutionJournal | undefined;\n const scope = canonicalizePath(cwd);\n return {\n read: async (executionId) => {\n const records = structuredClone(await journal.read(executionId));\n let peerTurn: boolean | undefined;\n for (const record of records) {\n if (record.kind !== 'model-request' && record.kind !== 'model-cache-hit') continue;\n const owner = record.checkpoint?.owner;\n const state = owner?.state;\n if (\n !owner ||\n owner.kind !== 'agent-session' ||\n owner.version !== 1 ||\n !state ||\n state.sessionId !== sessionId ||\n state.cwd !== scope ||\n state.executionId !== executionId ||\n typeof state.peerTurn !== 'boolean' ||\n (peerTurn !== undefined && peerTurn !== state.peerTurn) ||\n (state.peerTurn && record.checkpoint?.continuation?.options.withholdHostedTools !== true)\n ) {\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_INVALID',\n 'Session continuation owner or scope is incompatible',\n );\n }\n peerTurn = state.peerTurn;\n }\n if (peerTurn === undefined)\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_INVALID',\n 'No Session owner checkpoint exists',\n );\n writer = sessionExecutionJournal(journal, { sessionId, cwd, peerTurn, canonicalizePath });\n beginTurn(peerTurn);\n return records;\n },\n append: async (record: TExecutionJournalRecord) => {\n if (!writer)\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_INVALID',\n 'Session owner has not been restored',\n );\n await writer.append(record);\n },\n };\n}\n","import type { ISessionRunOptions } from './session-types.js';\nimport type { IRunOptions } from '@robota-sdk/agent-core';\n\n/** The subset actually present, ready to spread into the agent-core run options. */\nexport function perTurnRunOptions(\n options?: ISessionRunOptions,\n): Pick<\n IRunOptions,\n | 'ephemeralSystemContext'\n | 'driverId'\n | 'turnSource'\n | 'toolChoice'\n | 'traceContext'\n | 'withholdHostedTools'\n | 'executionJournal'\n> {\n return {\n ...(options?.executionJournal !== undefined && { executionJournal: options.executionJournal }),\n ...(options?.ephemeralSystemContext !== undefined && {\n ephemeralSystemContext: options.ephemeralSystemContext,\n }),\n ...(options?.driverId !== undefined && { driverId: options.driverId }),\n ...(options?.turnSource !== undefined && { turnSource: options.turnSource }),\n ...(options?.toolChoice !== undefined && { toolChoice: options.toolChoice }),\n ...(options?.traceContext !== undefined && { traceContext: options.traceContext }),\n // What a message-triggered turn does is decided by the ordinary permissions, and a provider's\n // hosted tools run at the vendor without reaching them — so such a turn has none.\n ...(options?.peerTurn === true && { withholdHostedTools: true }),\n };\n}\n","/**\n * Session run — core execution logic for a single agent turn.\n *\n * Extracted from Session to keep session.ts under the 300-line limit.\n * Stateless: all mutable state is passed in via IRunContext.\n */\n\nimport {\n CONTEXT_ESTIMATE_CHARS_PER_TOKEN,\n createLogger,\n createUserMessage,\n createSystemMessage,\n getProviderCapabilities,\n runHooks,\n traceEnvFor,\n ExecutionSuspendedError,\n} from '@robota-sdk/agent-core';\n\nimport { perTurnRunOptions } from './session-run-options.js';\nimport { createRunObservers } from './session-run-observation.js';\n\nimport type { IRunContext } from './session-run-context.js';\nexport type { IRunContext } from './session-run-context.js';\nimport type { ISessionRunOptions } from './session-types.js';\nimport type { THooksConfig } from '@robota-sdk/agent-core';\n\nconst logger = createLogger('SessionRun');\n\n/**\n * Execute a single agent turn: run hooks, send message to AI, log results.\n *\n * @param message - The processed message to send to the AI\n * @param rawInput - Optional raw user input (used for hook prompt field)\n * @param ctx - Session state and callbacks\n * @param abortSignal - AbortSignal from the session's AbortController\n */\nexport async function executeRun(\n message: string,\n rawInput: string | undefined,\n ctx: IRunContext,\n abortSignal: AbortSignal,\n runOptions?: ISessionRunOptions,\n): Promise<string> {\n // Command hooks fired on this prompt's path name its root span; hooks elsewhere get nothing.\n const traceContext = runOptions?.traceContext;\n const hookTraceEnv = traceContext\n ? traceEnvFor('hooks', traceContext, traceContext.parentSpanId)\n : undefined;\n ctx.log('user', { content: message });\n\n // Fire UserPromptSubmit hook before AI processes input\n const hookResult = await runHooks(\n ctx.hooks as THooksConfig | undefined,\n 'UserPromptSubmit',\n {\n session_id: ctx.sessionId,\n cwd: ctx.cwd,\n hook_event_name: 'UserPromptSubmit',\n user_message: rawInput ?? message,\n prompt: rawInput ?? message,\n ...(ctx.permissionMode !== undefined && { permission_mode: ctx.permissionMode }),\n ...(ctx.transcriptPath !== undefined && { transcript_path: ctx.transcriptPath }),\n env: {\n CLAUDE_PROJECT_DIR: ctx.cwd,\n CLAUDE_SESSION_ID: ctx.sessionId,\n },\n },\n ctx.hookTypeExecutors,\n hookTraceEnv,\n );\n\n // Inject hook stdout into user message (e.g., plugin path info)\n const hookStdout = [ctx.sessionStartStdout, hookResult.stdout].filter(Boolean).join('\\n');\n const enrichedMessage = hookStdout\n ? `<system-reminder>\\n${hookStdout}\\n</system-reminder>\\n${message}`\n : message;\n\n // Budget the pending request without putting its new input into the history being summarized.\n const pendingInput = createUserMessage(enrichedMessage);\n const ephemeralSystemContext = runOptions?.ephemeralSystemContext;\n const pendingMessages = [\n pendingInput,\n ...(ephemeralSystemContext?.trim() ? [createSystemMessage(ephemeralSystemContext)] : []),\n ];\n ctx.contextTracker.updateFromHistory(ctx.agent.getHistory());\n if (ctx.contextTracker.shouldAutoCompact([...ctx.agent.getHistory(), ...pendingMessages])) {\n if (runOptions?.executionJournal) {\n await ctx.compact(abortSignal, hookTraceEnv, runOptions.executionJournal);\n } else {\n await (hookTraceEnv ? ctx.compact(abortSignal, hookTraceEnv) : ctx.compact(abortSignal));\n }\n }\n\n // Clear sessionStart stdout after first injection\n ctx.clearSessionStartStdout();\n\n const history = ctx.agent.getHistory();\n const historyJson = JSON.stringify(history);\n const providerCapabilities = getProviderCapabilities(ctx.aiProvider);\n ctx.log('pre_run', {\n historyLength: history.length,\n historyChars: historyJson.length,\n historyEstTokens: Math.ceil(historyJson.length / CONTEXT_ESTIMATE_CHARS_PER_TOKEN),\n input: enrichedMessage,\n history,\n model: ctx.model,\n provider: ctx.aiProvider.name,\n maxTokens: ctx.contextTracker.getContextState().maxTokens,\n nativeWebSearchSupported: providerCapabilities.nativeWebTools.webSearch.supported,\n nativeWebSearchEnabled: providerCapabilities.nativeWebTools.webSearch.enabled,\n nativeWebFetchSupported: providerCapabilities.nativeWebTools.webFetch.supported,\n nativeWebFetchEnabled: providerCapabilities.nativeWebTools.webFetch.enabled,\n });\n ctx.contextTracker.updateFromHistory([...history, ...pendingMessages]);\n ctx.onContextUpdate?.(ctx.contextTracker.getContextState());\n\n let response: string;\n try {\n response = await ctx.agent.run(enrichedMessage, {\n signal: abortSignal,\n maxExecutionRounds: ctx.maxTurns ?? 0,\n // Thin pass-through of the per-turn options to agent-core.\n ...perTurnRunOptions(runOptions),\n ...createRunObservers(ctx, traceContext),\n });\n\n // If execution was interrupted (abort fired during execution),\n // throw AbortError so the caller (useSubmitHandler) shows \"Cancelled.\"\n if (abortSignal.aborted) {\n throw new DOMException('Aborted', 'AbortError');\n }\n } catch (error) {\n if (error instanceof ExecutionSuspendedError) throw error;\n try {\n ctx.log('error', {\n message: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? (error.stack ?? '') : '',\n historyLength: ctx.agent.getHistory().length,\n });\n } catch {\n // Diagnostic failure cannot replace the execution error and its recovery classification.\n }\n runHooks(\n ctx.hooks as THooksConfig | undefined,\n 'StopFailure',\n {\n session_id: ctx.sessionId,\n cwd: ctx.cwd,\n hook_event_name: 'StopFailure',\n reason: error instanceof Error ? error.message : String(error),\n stop_hook_active: false,\n ...(ctx.permissionMode !== undefined && { permission_mode: ctx.permissionMode }),\n ...(ctx.transcriptPath !== undefined && { transcript_path: ctx.transcriptPath }),\n env: {\n CLAUDE_PROJECT_DIR: ctx.cwd,\n CLAUDE_SESSION_ID: ctx.sessionId,\n },\n },\n ctx.hookTypeExecutors,\n hookTraceEnv,\n ).catch((error) => logger.warn('hook failed', { error }));\n throw error;\n }\n\n // Log the response and full history structure\n const postHistory = ctx.agent.getHistory();\n const historyStructure = postHistory.map((msg) => {\n const hasToolCalls =\n 'toolCalls' in msg && Array.isArray(msg.toolCalls) && msg.toolCalls.length > 0;\n const toolCallNames = hasToolCalls\n ? (msg.toolCalls as Array<{ function: { name: string } }>).map((tc) => tc.function.name)\n : [];\n return {\n role: msg.role,\n contentLength: typeof msg.content === 'string' ? msg.content.length : 0,\n hasToolCalls,\n toolCallNames,\n ...(msg.metadata ? { metadata: msg.metadata } : {}),\n };\n });\n ctx.log('assistant', {\n content: response,\n historyLength: postHistory.length,\n estimatedChars: JSON.stringify(postHistory).length,\n history: postHistory,\n historyStructure,\n });\n\n // Update token usage from the latest assistant message metadata\n ctx.contextTracker.updateFromHistory(postHistory);\n\n const ctxState = ctx.contextTracker.getContextState();\n ctx.onContextUpdate?.(ctxState);\n ctx.log('context', {\n maxTokens: ctxState.maxTokens,\n usedTokens: ctxState.usedTokens,\n usedPercentage: ctxState.usedPercentage,\n remainingPercentage: ctxState.remainingPercentage,\n });\n\n // Fire Stop hook after AI response is complete (informational, fire and forget)\n runHooks(\n ctx.hooks as THooksConfig | undefined,\n 'Stop',\n {\n session_id: ctx.sessionId,\n cwd: ctx.cwd,\n hook_event_name: 'Stop',\n response: response.substring(0, 500),\n last_assistant_message: response,\n stop_hook_active: false,\n ...(ctx.permissionMode !== undefined && { permission_mode: ctx.permissionMode }),\n ...(ctx.transcriptPath !== undefined && { transcript_path: ctx.transcriptPath }),\n env: {\n CLAUDE_PROJECT_DIR: ctx.cwd,\n CLAUDE_SESSION_ID: ctx.sessionId,\n },\n },\n ctx.hookTypeExecutors,\n hookTraceEnv,\n ).catch((error) => logger.warn('hook failed', { error }));\n\n return response;\n}\n","import {\n ExecutionRecoveryError,\n ExecutionSuspendedError,\n createToolMessage,\n} from '@robota-sdk/agent-core';\nimport type {\n IRecoverableExecutionJournal,\n IResumeExecutionOptions,\n IToolWaitRequest,\n TUniversalMessage,\n} from '@robota-sdk/agent-core';\nimport type { ISessionRunOptions } from './session-types.js';\nimport type { TSessionResumeOptions } from './session-resume.js';\n\nexport interface ISessionRecoverableRunOptions extends ISessionRunOptions {\n executionJournal: IRecoverableExecutionJournal;\n}\nexport type TSessionRecoverableResumeOptions = TSessionResumeOptions &\n Pick<IResumeExecutionOptions, 'toolResponses'>;\nexport type TSessionExecutionResult =\n | { status: 'completed'; response: string }\n | { status: 'waiting'; requests: readonly IToolWaitRequest[] };\nimport type { ISessionPendingExecution } from '@robota-sdk/agent-interface-session';\nexport type { ISessionPendingExecution } from '@robota-sdk/agent-interface-session';\n\nexport function pendingExecution(\n error: ExecutionSuspendedError,\n): ISessionPendingExecution | undefined {\n const executionId = error.requests[0]?.executionId;\n return executionId ? { executionId, requests: structuredClone(error.requests) } : undefined;\n}\n\nfunction lastRound(history: readonly TUniversalMessage[]) {\n const index = history.findLastIndex((message) => message.role === 'assistant');\n const assistant = history[index];\n if (assistant?.role !== 'assistant') return undefined;\n const answered = new Set(\n history\n .slice(index + 1)\n .flatMap((message) => (message.role === 'tool' ? [message.toolCallId] : [])),\n );\n return {\n assistant,\n open: (assistant.toolCalls ?? []).filter((call) => !answered.has(call.id)),\n ending: history.slice(index + 1).every((message) => message.role === 'tool'),\n };\n}\n\n/**\n * After a journaled attempt fails, the execution whose round it left open at the end of history\n * stays pending, so the next input is never sent after unanswered calls. Only saved waits whose\n * effect was never admitted remain answerable.\n */\nexport function unfinishedExecution(\n history: readonly TUniversalMessage[],\n previous: ISessionPendingExecution | undefined,\n admitted: ReadonlySet<string>,\n): ISessionPendingExecution | undefined {\n const round = lastRound(history);\n const executionId = round?.assistant.metadata?.executionId;\n if (!round?.ending || !round.open.length || typeof executionId !== 'string') return undefined;\n const open = new Set(round.open.map((call) => call.id));\n const requests =\n previous?.executionId === executionId\n ? previous.requests.filter(\n (request) => open.has(request.toolCallId) && !admitted.has(request.actionId),\n )\n : [];\n return { executionId, requests: structuredClone(requests) };\n}\n\n/** Note each action whose effect admission is attempted: its outcome is no longer known not to exist. */\nexport function noteEffectAdmissions(\n journal: IRecoverableExecutionJournal,\n admitted: Set<string>,\n onAdmission?: () => void,\n): IRecoverableExecutionJournal {\n return {\n read: (executionId) => journal.read(executionId),\n append: (record) => {\n if (record.kind === 'tool-effect-start') {\n admitted.add(record.actionId);\n onAdmission?.();\n }\n return journal.append(record);\n },\n };\n}\n\n/**\n * Close the tool calls an abandoned round left open so the conversation stays well-formed. Nothing\n * runs and nothing is journaled; a call that awaited a response never started its effect.\n */\nexport function abandonedRoundResults(\n history: readonly TUniversalMessage[],\n pending: ISessionPendingExecution,\n liveWaits: boolean,\n): TUniversalMessage[] {\n const round = lastRound(history);\n if (!round || round.assistant.metadata?.executionId !== pending.executionId) return [];\n const waiting = new Set((liveWaits ? pending.requests : []).map((request) => request.toolCallId));\n return round.open.map((call) => {\n const error = waiting.has(call.id)\n ? 'Not run: the execution was abandoned while this call awaited a response.'\n : 'Execution abandoned before this call recorded a result; its outcome is unknown.';\n return createToolMessage(`Error: ${error}`, {\n toolCallId: call.id,\n name: call.function.name,\n metadata: { success: false, error, toolName: call.function.name },\n });\n });\n}\n\n/** Preserve typed persistence/recovery failures; only a durably parked execution becomes waiting. */\nexport async function recoverableSessionExecution(\n run: () => Promise<string>,\n journal: IRecoverableExecutionJournal,\n): Promise<TSessionExecutionResult> {\n if (!journal || typeof journal.append !== 'function' || typeof journal.read !== 'function')\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_INVALID',\n 'Recoverable Session execution requires a readable journal',\n );\n try {\n return { status: 'completed', response: await run() };\n } catch (error) {\n if (error instanceof ExecutionSuspendedError)\n return { status: 'waiting', requests: structuredClone(error.requests) };\n throw error;\n }\n}\n","import { randomId } from '@robota-sdk/agent-core';\n\nimport type { TurnClaim } from './turn-claim.js';\nimport type {\n ConversationAgent,\n IToolExecutionResult,\n IUserInteraction,\n TToolParameters,\n} from '@robota-sdk/agent-core';\nimport type { IRuntimeToolInvokeOptions } from '@robota-sdk/agent-interface-session';\n\n/** Direct calls share the session claim and retain their completion for graceful disposal. */\nexport class SessionRuntimeTools {\n private execution: Promise<IToolExecutionResult> | null = null;\n\n constructor(\n private readonly agent: ConversationAgent,\n private readonly claim: TurnClaim,\n private readonly sessionId: string,\n private readonly ask?: IUserInteraction['ask'],\n ) {}\n\n /**\n * `permissionInteraction` is `'deny'` unless the caller answers its own prompts; only then may\n * the call ask for permission or put a question through the session's ask port.\n */\n async invoke(\n name: string,\n parameters: TToolParameters,\n options?: IRuntimeToolInvokeOptions,\n ): Promise<IToolExecutionResult> {\n const controller = this.claim.claim();\n const unlink = linkCancellation(controller, options?.signal);\n try {\n controller.signal.throwIfAborted();\n this.execution = this.agent.invokeRuntimeTool(name, parameters, {\n toolName: name,\n parameters,\n executionId: options?.executionId ?? randomId(),\n sessionId: this.sessionId,\n signal: controller.signal,\n // A caller nobody is watching must not park on a prompt, so asking is the caller's choice;\n // a caller that answers its own prompts also gets the session's ask port.\n permissionInteraction: options?.permissionInteraction ?? 'deny',\n ...(options?.permissionInteraction === 'interactive' && this.ask !== undefined\n ? { ask: this.askUnder(controller.signal) }\n : {}),\n });\n return await this.execution;\n } finally {\n this.execution = null;\n unlink();\n this.claim.release(controller);\n }\n }\n\n /** The ask port for one direct call: a question it asks is withdrawn when the call is cancelled. */\n private askUnder(signal: AbortSignal): IUserInteraction['ask'] {\n const ask = this.ask;\n if (ask === undefined) throw new Error('No ask port');\n return (request, options) => ask(request, { signal: options?.signal ?? signal });\n }\n\n async drain(): Promise<void> {\n await this.execution;\n }\n}\n\n/** Link only this operation's signal; releasing it never cancels another claim. */\nexport function linkCancellation(controller: AbortController, signal?: AbortSignal): () => void {\n const abort = (): void => controller.abort(signal?.reason);\n if (signal?.aborted) abort();\n else signal?.addEventListener('abort', abort, { once: true });\n return () => signal?.removeEventListener('abort', abort);\n}\n","import { decodeInteractiveSessionRecord } from './session-record-codec/index.js';\nimport { assertSynchronousCheckpoint } from './synchronous-checkpoint.js';\nimport type { ISessionConversationSnapshot } from '@robota-sdk/agent-interface-session';\nimport {\n TRUST_TO_MODE,\n ObservableEventService,\n PROVIDER_CALL_EVENTS,\n PROVIDER_FALLBACK_EVENTS,\n ExecutionRecoveryError,\n ExecutionSuspendedError,\n} from '@robota-sdk/agent-core';\n\nimport { SessionBase } from './session-base.js';\nimport {\n buildPermissionEnforcer,\n buildConversationAgent,\n buildSessionTrackers,\n} from './session-components.js';\nimport { buildCompactContext, compact, persistSession } from './session-history-ops.js';\nimport { createSessionId } from './session-id.js';\nimport {\n configureProvider,\n fireSessionEndHook,\n fireSessionStartHook,\n} from './session-lifecycle.js';\nimport { requireHookTypeExecutors } from './require-hook-type-executors.js';\nimport { executeResume, type TSessionResumeOptions } from './session-resume.js';\nimport { sessionExecutionJournal, sessionRecoveryJournal } from './session-execution-journal.js';\nimport { executeRun } from './session-run.js';\nimport {\n abandonedRoundResults,\n noteEffectAdmissions,\n pendingExecution,\n recoverableSessionExecution,\n unfinishedExecution,\n} from './session-recoverable.js';\nimport type {\n ISessionPendingExecution,\n ISessionRecoverableRunOptions,\n TSessionRecoverableResumeOptions,\n TSessionExecutionResult,\n} from './session-recoverable.js';\nimport { SessionRuntimeTools, linkCancellation } from './session-runtime-tools.js';\nimport type { IRuntimeToolInvokeOptions } from '@robota-sdk/agent-interface-session';\n\nimport type { CompactionOrchestrator } from './compaction-orchestrator.js';\nimport type { ContextWindowTracker } from './context-window-tracker.js';\nimport type { PermissionEnforcer } from './permission-enforcer.js';\nimport type {\n TPermissionHandler,\n TPermissionResult,\n IPermissionAskContext,\n TPermissionRequester,\n ITerminalOutput,\n ISpinner,\n} from './permission-types.js';\nimport type { ISessionLogger, TSessionLogData } from './session-logger.js';\nimport type { IRunContext } from './session-run-context.js';\nimport type {\n ICompactEvent,\n ISessionOptions,\n ISessionShutdownOptions,\n ISessionRunOptions,\n TCompactTrigger,\n} from './session-types.js';\nimport type {\n IAIProvider,\n IProviderRegistrationOptions,\n IExecutionJournal,\n IContextWindowState,\n IEventService,\n IToolSchema,\n IToolExecutionResult,\n IToolWithEventService,\n TToolParameters,\n TPermissionMode,\n IHookTypeExecutor,\n ISubprocessTraceEnv,\n} from '@robota-sdk/agent-core';\nimport type { ConversationAgent } from '@robota-sdk/agent-core';\nimport type { IInteractiveSessionStore } from '@robota-sdk/agent-interface-session';\n\nexport type {\n ICompactEvent,\n TPermissionHandler,\n TPermissionResult,\n IPermissionAskContext,\n TPermissionRequester,\n ITerminalOutput,\n ISpinner,\n ISessionOptions,\n ISessionShutdownOptions,\n TCompactTrigger,\n};\nexport type { TAutoCompactThreshold } from './context-window-tracker.js';\n\n/** Wraps a ConversationAgent agent with project context, permission state, and optional persistence. */\nexport class Session extends SessionBase {\n protected readonly agent: ConversationAgent;\n /**\n * Session-owned observable event bus. Injected into the agent so tools (incl. the\n * `FunctionTool` span-completion emit) publish here; the interactive turn subscribes to it to\n * project per-operation spans onto session history. Exposed read-only via {@link getEventService}.\n */\n protected readonly eventService: IEventService = new ObservableEventService();\n protected readonly permissionEnforcer: PermissionEnforcer;\n protected readonly contextTracker: ContextWindowTracker;\n protected permissionMode: TPermissionMode;\n protected activePresetId: string;\n protected parallelSubagentsEnabled: boolean;\n protected readonly sessionId: string;\n protected aiProvider: IAIProvider;\n protected readonly toolSchemas: IToolSchema[];\n protected model: string;\n protected systemMessage: string;\n protected messageCount = 0;\n private readonly terminal: ITerminalOutput;\n private readonly sessionStore?: IInteractiveSessionStore;\n private readonly checkpointHistoryCallback?: () => void;\n private readonly hooks?: Record<string, unknown>;\n private readonly hookTypeExecutors: readonly IHookTypeExecutor[];\n private readonly canonicalizePath: (path: string) => string;\n private readonly onTextDeltaCallback?: (delta: string) => void;\n private readonly onThinkingDeltaCallback?: (delta: string) => void;\n private readonly onContextUpdateCallback?: (state: IContextWindowState) => void;\n private readonly onToolExecutionCallback?: ISessionOptions['onToolExecution'];\n private readonly onCompactCallback?: (summary: string) => void;\n private readonly onCompactEventCallback?: ISessionOptions['onCompactEvent'];\n private readonly sessionLogger?: ISessionLogger;\n private readonly maxTurns?: number;\n protected readonly compactionOrchestrator: CompactionOrchestrator;\n private readonly runtimeTools: SessionRuntimeTools;\n private readonly wrapAddedTools: ISessionOptions['wrapAddedTools'];\n /** Tools added while a turn ran, applied when the next one starts. */\n private readonly pendingTools: IToolWithEventService[] = [];\n /** The last tool change; the next one waits for it. */\n private toolChange: Promise<void> = Promise.resolve();\n private shuttingDown = false;\n /** Set while a journaled execution's round is open in history; cleared once it settles or is abandoned. */\n private recoveryState:\n | { kind: 'ready' }\n | { kind: 'pending'; pending: ISessionPendingExecution; liveWaits: boolean }\n | {\n kind: 'save-required';\n pending?: ISessionPendingExecution;\n executionId?: string;\n liveWaits: boolean;\n } = { kind: 'ready' };\n private checkpointing = false;\n\n private get pendingExecution(): ISessionPendingExecution | undefined {\n return this.recoveryState.kind === 'ready' ? undefined : this.recoveryState.pending;\n }\n private setPending(pending: ISessionPendingExecution | undefined, liveWaits = false): void {\n if (this.recoveryState.kind === 'save-required') return;\n this.recoveryState = pending ? { kind: 'pending', pending, liveWaits } : { kind: 'ready' };\n }\n private shutdownPromise: Promise<void> | null = null;\n /** Stdout collected from SessionStart hooks, injected on first run(). */\n private sessionStartStdout = '';\n /** Absolute path to the session transcript file, if file-backed storage is active. */\n private readonly transcriptPath: string | undefined;\n\n constructor(options: ISessionOptions) {\n super(options.cwd);\n const { tools, provider, systemMessage } = options;\n\n this.terminal = options.terminal;\n this.sessionStore = options.sessionStore;\n this.checkpointHistoryCallback = options.checkpointHistory;\n this.systemMessage = systemMessage;\n this.toolSchemas = tools.map((tool) => tool.schema);\n this.wrapAddedTools = options.wrapAddedTools;\n this.sessionLogger = options.sessionLogger;\n this.hooks = options.hooks;\n this.hookTypeExecutors = requireHookTypeExecutors(options.hookTypeExecutors, 'Session');\n this.canonicalizePath = options.canonicalizePath ?? ((path) => path);\n this.onTextDeltaCallback = options.onTextDelta;\n this.onThinkingDeltaCallback = options.onThinkingDelta;\n this.onContextUpdateCallback = options.onContextUpdate;\n this.onToolExecutionCallback = options.onToolExecution;\n this.onCompactCallback = options.onCompact;\n this.onCompactEventCallback = options.onCompactEvent;\n this.maxTurns = options.maxTurns;\n this.model = options.model ?? 'claude-sonnet-4-5';\n this.sessionId = options.sessionId ?? createSessionId();\n this.permissionMode =\n options.permissionMode ??\n (options.defaultTrustLevel ? TRUST_TO_MODE[options.defaultTrustLevel] : undefined) ??\n 'default';\n this.activePresetId = options.activePresetId ?? 'default';\n // Default true preserves the current behavior — subagent dispatch is allowed\n // unless a preset explicitly disables it.\n this.parallelSubagentsEnabled = options.enableParallelSubagents ?? true;\n this.transcriptPath = options.transcriptPath;\n this.log('session_init', {\n cwd: this.cwd,\n systemPromptLength: systemMessage.length,\n systemPrompt: systemMessage,\n toolSchemas: this.toolSchemas,\n model: this.model,\n provider: provider.name,\n });\n this.aiProvider = provider;\n configureProvider(provider, options, (event, data) => this.log(event, data));\n this.permissionEnforcer = buildPermissionEnforcer(\n options,\n this.sessionId,\n this.cwd,\n () => this.permissionMode,\n this.transcriptPath,\n );\n this.requireClassifierFor(this.permissionMode);\n this.addPermissionModeGuard((next) => this.requireClassifierFor(next));\n const { contextTracker, compactionOrchestrator } = buildSessionTrackers(\n options,\n this.model,\n this.sessionId,\n this.cwd,\n );\n this.contextTracker = contextTracker;\n this.compactionOrchestrator = compactionOrchestrator;\n this.agent = buildConversationAgent(\n options,\n this.permissionEnforcer,\n tools,\n provider,\n this.model,\n systemMessage,\n this.eventService,\n );\n this.runtimeTools = new SessionRuntimeTools(\n this.agent,\n this.turnClaim,\n this.sessionId,\n options.ask,\n );\n fireSessionStartHook(\n this.sessionId,\n this.cwd,\n this.hooks,\n this.hookTypeExecutors,\n (stdout) => void (this.sessionStartStdout = stdout),\n this.permissionMode,\n this.transcriptPath,\n );\n }\n\n /**\n * @param options.ephemeralSystemContext a transient system-role block included in this\n * turn's model call only, never persisted to history (thin pass-through to agent-core `IRunOptions`).\n * REJECTS with `SessionBusyError` if a turn is in flight; see `turn-claim.ts`.\n */\n async run(message: string, rawInput?: string, options?: ISessionRunOptions): Promise<string> {\n return this.runTurn(message, rawInput, options, false);\n }\n\n /** Run with durable approval requests instead of a live approval Promise. */\n runRecoverable(\n message: string,\n options: ISessionRecoverableRunOptions,\n rawInput?: string,\n ): Promise<TSessionExecutionResult> {\n return recoverableSessionExecution(\n () => this.runTurn(message, rawInput, options, true),\n options.executionJournal,\n );\n }\n\n private async runTurn(\n message: string,\n rawInput: string | undefined,\n value: ISessionRunOptions | undefined,\n checkpointedApprovals: boolean,\n ): Promise<string> {\n const options = value ? { ...value } : undefined;\n if (this.shuttingDown) throw new Error('[LIFECYCLE] Session is shutting down');\n this.assertInputAllowed();\n const controller = this.turnClaim.claim(); // Synchronously, before any await.\n const unlink = linkCancellation(controller, options?.signal);\n const { signal } = controller;\n // Whether the reply to a peer exists is decided per turn.\n this.permissionEnforcer.beginTurn(options?.peerTurn === true, checkpointedApprovals);\n try {\n signal.throwIfAborted();\n // Tools added while the last turn ran join at this boundary, before any request of this turn;\n // a change already in flight finishes first, so the turn never sees a list mid-update.\n await this.serializeToolChange(() => this.applyPendingTools());\n const runOptions = options?.executionJournal\n ? {\n ...options,\n executionJournal: sessionExecutionJournal(options.executionJournal, {\n sessionId: this.sessionId,\n cwd: this.cwd,\n peerTurn: options.peerTurn === true,\n canonicalizePath: this.canonicalizePath,\n }),\n }\n : options;\n const response = await executeRun(\n message,\n rawInput,\n this.buildRunContext(options?.executionJournal !== undefined),\n signal,\n runOptions,\n );\n this.setPending(undefined);\n this.commitConversation();\n this.messageCount += 1;\n return response;\n } catch (error) {\n if (error instanceof ExecutionSuspendedError) {\n this.setPending(pendingExecution(error), true);\n signal.throwIfAborted();\n } else if (options?.executionJournal) {\n this.setPending(unfinishedExecution(this.agent.getHistory(), undefined, new Set()));\n }\n this.saveAfterFailure(error);\n throw error;\n } finally {\n this.permissionEnforcer.endTurn();\n unlink();\n this.turnClaim.release(controller);\n }\n }\n\n /** Resume the original Session execution under current permissions without submitting input. */\n async resume(options: TSessionResumeOptions): Promise<string> {\n return this.resumeTurn(options, false);\n }\n\n /** Recheck current permissions and continue exact journaled responses without another input. */\n resumeRecoverable(options: TSessionRecoverableResumeOptions): Promise<TSessionExecutionResult> {\n return recoverableSessionExecution(() => this.resumeTurn(options, true), options.journal);\n }\n\n private async resumeTurn(\n value: TSessionRecoverableResumeOptions,\n checkpointedApprovals: boolean,\n ): Promise<string> {\n const options = { ...value, toolResponses: structuredClone(value.toolResponses) };\n if (this.shuttingDown) throw new Error('[LIFECYCLE] Session is shutting down');\n this.assertCheckpointSettled();\n if (this.pendingExecution && this.pendingExecution.executionId !== options.executionId)\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_CONFLICT',\n 'A different Session execution is pending',\n );\n const controller = this.turnClaim.claim();\n const unlink = linkCancellation(controller, options.signal);\n const admitted = new Set<string>();\n try {\n controller.signal.throwIfAborted();\n await this.serializeToolChange(() => this.applyPendingTools());\n const journal = sessionRecoveryJournal(\n noteEffectAdmissions(options.journal, admitted, () => {\n if (this.recoveryState.kind === 'pending') this.recoveryState.liveWaits = false;\n }),\n this.sessionId,\n this.cwd,\n (peerTurn) => this.permissionEnforcer.beginTurn(peerTurn, checkpointedApprovals),\n this.canonicalizePath,\n );\n const response = await executeResume(this.buildRunContext(true), {\n ...options,\n journal,\n signal: controller.signal,\n });\n this.setPending(undefined);\n this.commitConversation(options.executionId);\n return response;\n } catch (error) {\n if (error instanceof ExecutionSuspendedError) {\n this.setPending(pendingExecution(error), true);\n controller.signal.throwIfAborted();\n } else {\n // Settled rounds end the execution like an ordinary turn; a round left open keeps it pending.\n this.setPending(\n unfinishedExecution(this.agent.getHistory(), this.pendingExecution, admitted),\n );\n }\n this.saveAfterFailure(error);\n throw error;\n } finally {\n this.permissionEnforcer.endTurn();\n unlink();\n this.turnClaim.release(controller);\n }\n }\n\n /** The execution blocking new input, if any: parked on saved waits, or left open by a failure. */\n getPendingExecution(): ISessionPendingExecution | undefined {\n return structuredClone(this.pendingExecution);\n }\n\n /**\n * Give up a pending execution so the Session accepts new input. Nothing runs and nothing is\n * journaled: calls its parked round left open are closed in history as failed, and the journal's\n * records stay with the host. Resuming the execution afterwards is refused as a history conflict.\n */\n abandonPendingExecution(executionId: string): void {\n if (this.shuttingDown) throw new Error('[LIFECYCLE] Session is shutting down');\n if (this.recoveryState.kind === 'save-required') {\n if (this.recoveryState.executionId !== executionId || this.pendingExecution)\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_CONFLICT',\n 'Checkpoint the unresolved Session transition first',\n );\n this.checkpoint();\n return;\n }\n const pending = this.pendingExecution;\n if (!pending) return;\n if (pending.executionId !== executionId)\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_CONFLICT',\n 'A different Session execution is pending',\n );\n const controller = this.turnClaim.claim();\n try {\n const liveWaits = this.recoveryState.kind === 'pending' && this.recoveryState.liveWaits;\n for (const message of abandonedRoundResults(this.agent.getHistory(), pending, liveWaits))\n this.agent.injectRawMessage(message);\n this.setPending(undefined);\n this.commitConversation(executionId);\n } finally {\n this.turnClaim.release(controller);\n }\n }\n\n /** Refuse admission before callers resolve input or change scheduling state. */\n assertInputAllowed(): void {\n if (this.shuttingDown) throw new Error('[LIFECYCLE] Session is shutting down');\n this.assertCheckpointSettled();\n if (this.pendingExecution)\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_REQUIRED',\n 'Resume or abandon the pending Session execution before submitting new input',\n );\n }\n private assertCheckpointSettled(): void {\n if (this.checkpointing || this.recoveryState.kind === 'save-required')\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_REQUIRED',\n 'Call Session.checkpoint() to save the unresolved conversation transition',\n );\n }\n getConversationSnapshot(): ISessionConversationSnapshot {\n return structuredClone({\n id: this.sessionId,\n cwd: this.canonicalizePath(this.cwd),\n messages: this.agent.getHistory(),\n ...(this.pendingExecution ? { pendingExecution: this.pendingExecution } : {}),\n });\n }\n /** Install a complete same-owner snapshot on a pristine, idle Session. */\n restoreConversation(snapshot: ISessionConversationSnapshot): void {\n this.assertInputAllowed();\n if (\n this.turnClaim.isRunning() ||\n this.agent.getHistory().some((message) => message.role !== 'system')\n )\n throw new Error('Conversation restore requires a pristine idle Session');\n if (\n snapshot.id !== this.sessionId ||\n this.canonicalizePath(snapshot.cwd) !== this.canonicalizePath(this.cwd)\n )\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_CONFLICT',\n 'Conversation owner or workspace differs',\n );\n const decoded = decodeInteractiveSessionRecord({\n id: snapshot.id,\n cwd: snapshot.cwd,\n messages: snapshot.messages,\n ...(snapshot.pendingExecution === undefined\n ? {}\n : { pendingExecution: snapshot.pendingExecution }),\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n });\n if (decoded.status !== 'valid')\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_INVALID',\n 'Invalid conversation recovery snapshot',\n );\n const conversation = structuredClone(decoded.record);\n this.agent.clearHistory();\n for (const message of conversation.messages) this.agent.injectRawMessage(message);\n this.setPending(conversation.pendingExecution);\n this.syncContextFromHistory();\n }\n /** Retry only persistence, never model or tool execution. A sink must acknowledge synchronously. */\n checkpoint(): void {\n if (this.turnClaim.isRunning() || this.checkpointing)\n throw new Error('Cannot checkpoint during a Session transition');\n if (!this.checkpointHistoryCallback && !this.sessionStore)\n throw new Error('Session checkpoint requires a persistence sink');\n this.commitConversation(\n this.recoveryState.kind === 'save-required' ? this.recoveryState.executionId : undefined,\n );\n }\n private commitConversation(executionId?: string): void {\n if (!this.checkpointHistoryCallback && !this.sessionStore) return;\n if (this.checkpointing) throw new Error('Reentrant Session checkpoint');\n const pending = this.pendingExecution;\n const liveWaits = this.recoveryState.kind !== 'ready' && this.recoveryState.liveWaits;\n this.checkpointing = true;\n try {\n if (this.checkpointHistoryCallback)\n assertSynchronousCheckpoint(this.checkpointHistoryCallback());\n else this.persistSessionInternal(true);\n this.recoveryState = pending ? { kind: 'pending', pending, liveWaits } : { kind: 'ready' };\n } catch (error) {\n this.recoveryState = {\n kind: 'save-required',\n pending,\n liveWaits,\n executionId: executionId ?? pending?.executionId,\n };\n throw error;\n } finally {\n this.checkpointing = false;\n }\n }\n private saveAfterFailure(error: unknown): void {\n if (this.recoveryState.kind === 'save-required') return;\n try {\n this.commitConversation();\n } catch (saveError) {\n if (error instanceof ExecutionSuspendedError) throw saveError;\n try {\n this.log('error', {\n message: `Session checkpoint failed while preserving the execution error: ${saveError instanceof Error ? saveError.message : String(saveError)}`,\n stack: saveError instanceof Error ? (saveError.stack ?? '') : '',\n historyLength: this.agent.getHistory().length,\n });\n } catch {\n // allow-fallback: a secondary diagnostic must never replace the original execution failure.\n }\n }\n }\n\n /**\n * Make tools available from the next turn on — for a capability that became usable mid-session,\n * such as an MCP server connected after its sign-in. Each goes through the same wrappers and\n * permission gate as a tool present from the start. A tool whose name the session already has, or\n * has queued, is left out rather than replacing the one the conversation has been using.\n *\n * The tool list is part of what a provider caches a prompt by, and a turn's rounds must all see\n * the same list: while a turn runs, the tools wait and are applied when the next turn starts;\n * otherwise they are applied now. Calls are serialized, so two concurrent ones both land.\n * Resolves to the names that will be offered.\n */\n addTools(tools: readonly IToolWithEventService[]): Promise<readonly string[]> {\n this.assertInputAllowed();\n if (this.shuttingDown) {\n return Promise.reject(new Error('[LIFECYCLE] Session is shutting down'));\n }\n return this.serializeToolChange(async () => {\n const known = new Set([\n ...this.toolSchemas.map((schema) => schema.name),\n ...this.pendingTools.map((tool) => tool.schema.name),\n ]);\n const fresh: IToolWithEventService[] = [];\n for (const tool of tools) {\n if (known.has(tool.schema.name)) continue;\n known.add(tool.schema.name);\n fresh.push(tool);\n }\n if (fresh.length === 0) return [];\n this.pendingTools.push(...fresh);\n if (!this.turnClaim.isRunning()) await this.applyPendingTools();\n return fresh.map((tool) => tool.schema.name);\n });\n }\n\n /** Runs `change` after every tool change before it, so each reads the list the last one wrote. */\n private serializeToolChange<T>(change: () => Promise<T>): Promise<T> {\n const result = this.toolChange.then(change);\n this.toolChange = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n }\n\n /** Registers the queued tools with the agent. Only ever called inside `serializeToolChange`. */\n private async applyPendingTools(): Promise<void> {\n if (this.pendingTools.length === 0) return;\n const fresh = this.pendingTools.splice(0);\n await this.agent.ensureReady();\n const wrapped = this.permissionEnforcer.wrapTools(this.wrapAddedTools?.(fresh) ?? fresh);\n await this.agent.updateTools([...(this.agent.getConfig().tools ?? []), ...wrapped]);\n this.toolSchemas.push(...wrapped.map((tool) => tool.schema));\n }\n\n async listRuntimeTools(): Promise<IToolSchema[]> {\n if (this.shuttingDown) throw new Error('[LIFECYCLE] Session is shutting down');\n return this.agent.listRuntimeTools();\n }\n\n async invokeRuntimeTool(\n name: string,\n parameters: TToolParameters,\n options?: IRuntimeToolInvokeOptions,\n ): Promise<IToolExecutionResult> {\n if (this.shuttingDown) throw new Error('[LIFECYCLE] Session is shutting down');\n this.assertInputAllowed();\n return this.runtimeTools.invoke(name, parameters, options);\n }\n\n /**\n * The session-owned observable event bus the agent's tools publish to. The interactive\n * turn subscribes to it to collect span-completion events and project them onto session history.\n */\n getEventService(): IEventService {\n return this.eventService;\n }\n\n private log(event: string, data: TSessionLogData): void {\n this.sessionLogger?.log(this.sessionId, event, data);\n }\n\n private persistSessionInternal(strict = false): void {\n if (!this.sessionStore) return;\n const outcome = persistSession({\n sessionId: this.sessionId,\n cwd: this.cwd,\n systemPrompt: this.systemMessage,\n toolSchemas: this.toolSchemas,\n sessionStore: this.sessionStore,\n conversation: this.getConversationSnapshot(),\n getFullHistory: () => this.getFullHistory(),\n });\n if (strict && outcome.status !== 'valid') {\n throw new Error(`Session history could not be durably checkpointed: ${outcome.status}`);\n }\n }\n\n /**\n * Gracefully end the session and fire SessionEnd hooks once — **best-effort** (disposal\n * convention): never rejects, so `void session.shutdown()` cannot become an\n * unhandled rejection. Step failures are recorded to the session log and remaining steps\n * still run.\n */\n shutdown(options: ISessionShutdownOptions = {}): Promise<void> {\n if (this.shutdownPromise) return this.shutdownPromise;\n this.shuttingDown = true;\n const reason = options.reason ?? 'other';\n const step = async (label: string, run: () => Promise<void> | void): Promise<void> => {\n try {\n await run();\n } catch (error) {\n // allow-fallback: best-effort disposal IS the contract — the failure is logged and remaining shutdown steps still run (disposal convention)\n this.log('session_shutdown_step_error', {\n step: label,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n };\n this.shutdownPromise = (async () => {\n await step('abort', () => this.abort());\n await step('drain-direct-tool', () => this.runtimeTools.drain());\n await step('drain-turn', () => this.turnClaim.drained);\n this.log('session_shutdown', { reason });\n await step('persist', () => this.persistSessionInternal());\n await step('session-end-hook', () =>\n fireSessionEndHook(\n this.sessionId,\n this.cwd,\n reason,\n this.hooks,\n this.hookTypeExecutors,\n this.permissionMode,\n this.transcriptPath,\n ),\n );\n // (SPEC § Disposal Chain Contract): shutdown drives agent destruction —\n // plugins are disposed so no timers/listeners survive and the process can exit.\n await step('destroy-agent', async () => {\n await this.agent.destroy();\n });\n })();\n return this.shutdownPromise;\n }\n\n swapProvider(\n newProvider: IAIProvider,\n model: string,\n options?: IProviderRegistrationOptions,\n ): void {\n this.assertInputAllowed();\n this.agent.swapDefaultProvider(newProvider, model, options);\n if ('onServerToolUse' in newProvider) {\n (\n newProvider as { onServerToolUse?: (name: string, input: Record<string, string>) => void }\n ).onServerToolUse = (name: string, input: Record<string, string>) =>\n this.log('server_tool', { tool: name, ...input });\n }\n this.aiProvider = newProvider;\n this.setActiveModel(model);\n }\n\n async compact(\n instructions?: string,\n trigger: TCompactTrigger = 'manual',\n signal?: AbortSignal,\n executionJournal?: IExecutionJournal,\n ): Promise<void> {\n this.assertInputAllowed();\n const controller = this.turnClaim.claim();\n const unlink = linkCancellation(controller, signal);\n try {\n await this.compactWith(instructions, trigger, controller.signal, undefined, executionJournal);\n } finally {\n unlink();\n this.turnClaim.release(controller);\n }\n }\n\n /** `hookTraceEnv` reaches PreCompact only for a compaction inside a prompt (see `executeRun`). */\n private async compactWith(\n instructions: string | undefined,\n trigger: TCompactTrigger,\n signal?: AbortSignal,\n hookTraceEnv?: ISubprocessTraceEnv,\n executionJournal?: IExecutionJournal,\n ): Promise<void> {\n const extras = {\n systemMessage: this.systemMessage,\n compactionOrchestrator: this.compactionOrchestrator,\n onCompactCallback: this.onCompactCallback,\n onCompactEventCallback: this.onCompactEventCallback,\n trigger,\n ...(hookTraceEnv ? { hookTraceEnv } : {}),\n ...(executionJournal ? { executionJournal } : {}),\n };\n await compact(instructions, buildCompactContext(this.buildRunContext(), extras), signal);\n }\n\n private buildRunContext(journaled = false): IRunContext {\n return {\n sessionId: this.sessionId,\n cwd: this.cwd,\n model: this.model,\n effort: this.getModelEffort(),\n agent: this.agent,\n aiProvider: this.aiProvider,\n contextTracker: this.contextTracker,\n hooks: this.hooks,\n hookTypeExecutors: this.hookTypeExecutors,\n sessionStartStdout: this.sessionStartStdout,\n log: (event: string, data: TSessionLogData) => this.log(event, data),\n compact: (signal, hookTraceEnv, executionJournal) =>\n this.compactWith(undefined, 'auto', signal, hookTraceEnv, executionJournal),\n persistSession: () => this.persistSessionInternal(),\n checkpointHistory:\n this.checkpointHistoryCallback || this.sessionStore\n ? () => {\n if (journaled)\n this.setPending(\n unfinishedExecution(this.agent.getHistory(), this.pendingExecution, new Set()),\n );\n this.commitConversation();\n }\n : undefined,\n\n getSessionStore: () => !!this.sessionStore,\n clearSessionStartStdout: () => void (this.sessionStartStdout = ''),\n permissionMode: this.permissionMode,\n transcriptPath: this.transcriptPath,\n maxTurns: this.maxTurns,\n onTextDelta: this.onTextDeltaCallback,\n onThinkingDelta: this.onThinkingDeltaCallback,\n onContextUpdate: this.onContextUpdateCallback,\n onToolExecution: this.onToolExecutionCallback,\n emitProviderCallCompleted: (observation) =>\n this.eventService.emit(\n PROVIDER_CALL_EVENTS.COMPLETED,\n { timestamp: new Date(), ...observation },\n {\n ownerType: 'session',\n ownerId: this.sessionId,\n ownerPath: [{ type: 'session', id: this.sessionId }],\n },\n ),\n emitProviderFallback: (notice) =>\n this.eventService.emit(\n PROVIDER_FALLBACK_EVENTS.SWITCHED,\n {\n timestamp: new Date(),\n fromProvider: notice.from.provider,\n fromModel: notice.from.model,\n toProvider: notice.to.provider,\n toModel: notice.to.model,\n reason: notice.reason,\n },\n {\n ownerType: 'session',\n ownerId: this.sessionId,\n ownerPath: [{ type: 'session', id: this.sessionId }],\n },\n ),\n knownToolNames: this.toolSchemas.map((tool) => tool.name),\n };\n }\n}\n","/**\n * The neutral export/import envelope for a session, over `IInteractiveSessionRecord`.\n *\n * This is a RECORD-TRANSPORT sibling of the file-backed `session-store.ts` (SRP: transport vs file persistence).\n * It is the async, durable COMPLEMENT to the live P2P channel — export a thread to a portable artifact,\n * hand it off, import + resume it later on a second surface, with both peers possibly offline. No transport, no\n * pairing, no wire protocol.\n *\n * Two operations, specified separately and never conflated:\n * 1. Round-trip serialize (fidelity, local): `serializeSessionArtifact(record)` with no transform is\n * full-fidelity — `deserializeSessionArtifact(serializeSessionArtifact(record))` deep-equals the record.\n * (Precondition: `IInteractiveSessionRecord` is JSON-safe, as it is by construction — it is the on-disk file-store\n * shape; a non-JSON value like a `Date`/`Map`/class instance nested in a payload would not round-trip.)\n * 2. Export-for-share: `serializeSessionArtifact(record, { redact })` applies a caller-supplied, policy-free\n * `redact` transform BEFORE writing bytes. The envelope selects NO fields and owns NO field policy — the app\n * builds `redact` (composing the opt-in `scrubSensitiveKeys`). The no-transform form stays full-fidelity.\n *\n * Neutrality (mechanically fenced): this module is pure serialize/deserialize + a schema-version header +\n * the app-supplied `redact` seam. It carries NO link/cloud/upload/access-control and NO redaction FIELD policy.\n */\n\nimport {\n SESSION_RECORD_ENVELOPE_VERSION,\n decodeVersionedInteractiveSessionRecord,\n} from './session-record-codec/index.js';\n\nimport type { IVersionedInteractiveSessionRecord } from './session-record-codec/index.js';\nimport type { IInteractiveSessionRecord } from '@robota-sdk/agent-interface-session';\n\n/**\n * The envelope type and its version constant are the codec's (`session-record-codec/`).\n * They used to be declared here as `ISessionArtifact` and a local constant, which was a second name\n * and a second number for one shape — the envelope the codec decodes and the envelope this module\n * writes were always the same `{ schemaVersion, record }`. Nothing on disk changed when they were\n * unified, because there was nothing to change.\n */\n\nexport interface ISerializeSessionArtifactOptions {\n /**\n * SHARE-PATH ONLY. An app-supplied, policy-free transform applied to the record before serialization — the app\n * decides which trust-boundary fields to strip (composing the opt-in `scrubSensitiveKeys`). Omit for the\n * full-fidelity local round-trip.\n */\n redact?: (record: IInteractiveSessionRecord) => IInteractiveSessionRecord;\n}\n\n/** How many decode issues an error message carries before it elides the rest. */\nconst MAX_REPORTED_ISSUES = 5;\n\n/**\n * Parse the bytes, reporting a non-JSON body the same way a non-record body is reported.\n *\n * `JSON.parse` throwing and the decoder refusing are the same failure for a caller — the bytes are\n * not an artifact — so they are not two different exceptions to catch.\n */\nfunction parseArtifactBytes(bytes: string): unknown {\n try {\n return JSON.parse(bytes) as unknown;\n } catch {\n throw new Error('Invalid session artifact: the bytes are not JSON.');\n }\n}\n\n/**\n * Serialize a session record into a portable, versioned artifact. With no `redact`, this is the full-fidelity\n * round-trip form; with `redact`, the caller's transform is applied first (the share path).\n */\nexport function serializeSessionArtifact(\n record: IInteractiveSessionRecord,\n options: ISerializeSessionArtifactOptions = {},\n): string {\n const payload = options.redact ? options.redact(record) : record;\n const artifact: IVersionedInteractiveSessionRecord = {\n schemaVersion: SESSION_RECORD_ENVELOPE_VERSION,\n record: payload,\n };\n return JSON.stringify(artifact, null, 2);\n}\n\n/**\n * Parse a session artifact back into an `IInteractiveSessionRecord`, rejecting an artifact whose schema version this build\n * does not understand (so an incompatible artifact is never silently mis-imported).\n */\nexport function deserializeSessionArtifact(bytes: string): IInteractiveSessionRecord {\n const outcome = decodeVersionedInteractiveSessionRecord(parseArtifactBytes(bytes));\n if (outcome.status === 'unsupported') {\n throw new Error(\n `Unsupported session artifact schema version ${outcome.schemaVersion ?? '(absent or not a number)'} ` +\n `(this build reads ${SESSION_RECORD_ENVELOPE_VERSION}).`,\n );\n }\n if (outcome.status === 'corrupt') {\n // The paths are the point: an artifact that cannot be imported should tell its holder WHERE it\n // is wrong, not that it is wrong. Bounded, because a wholly unrelated payload produces an issue\n // per member and a thousand-line error informs nobody.\n const shown = outcome.issues.slice(0, MAX_REPORTED_ISSUES);\n const detail = shown\n .map((issue) => `${issue.path === '' ? '(root)' : issue.path}: ${issue.message}`)\n .join('; ');\n const elided =\n outcome.issues.length > shown.length\n ? ` (+${outcome.issues.length - shown.length} more)`\n : '';\n throw new Error(`Invalid session artifact: ${detail}${elided}`);\n }\n return outcome.record;\n}\n","/**\n * The single source of the sensitive-key redaction (SSOT).\n *\n * The recursive secret-key scrub used to live privately in `session-logger.ts`, logging-coupled and not exported.\n * It is extracted here so exactly ONE definition of \"which keys are sensitive\" exists, consumed by BOTH the file\n * session logger (persistence-time redaction) and the share-artifact `redact` transform (an opt-in\n * the app composes). This utility is a pure, mechanism-level key scrub only: it carries NO field/trust-boundary\n * policy (which of `cwd`/`sandboxSnapshotId`/… to strip is an app decision) and is NEVER forced into the\n * full-fidelity local round-trip.\n */\n\n/** Values a scrub can walk (mirrors the logger's log-value shape; avoids `any`/`unknown`). */\nexport type TScrubbableValue = string | number | boolean | object | null | undefined;\n\nconst DEFAULT_REDACTED_VALUE = '[REDACTED]';\n\n/**\n * Keys whose VALUE is a secret and must be redacted before persistence or sharing:\n * `apiKey`/`authorization`/`authToken`/`accessToken`/`refreshToken`/`secret`/`password`/`xApiKey`\n * (case/`-`/`_`-insensitive). `authToken` is how a provider's bearer credential is stored in\n * settings, so it must be redacted alongside `apiKey`; bare `token` stays unmatched (too broad).\n */\nexport const SENSITIVE_KEY_PATTERN =\n /^(api[-_]?key|authorization|auth[-_]?token|access[-_]?token|refresh[-_]?token|secret|password|x[-_]?api[-_]?key)$/i;\n\n/** True when a key's value should be redacted. The one predicate both the logger and the artifact scrub use. */\nexport function isSensitiveKey(key: string): boolean {\n return SENSITIVE_KEY_PATTERN.test(key);\n}\n\nfunction scrubValue(\n key: string | undefined,\n value: TScrubbableValue,\n redactedValue: string,\n): TScrubbableValue {\n if (key !== undefined && isSensitiveKey(key)) {\n return redactedValue;\n }\n if (Array.isArray(value)) {\n return value.map((item) => scrubValue(undefined, item as TScrubbableValue, redactedValue));\n }\n // A Date (or any non-plain object without own enumerable keys) is returned as-is by the Object.entries walk.\n if (value !== null && typeof value === 'object' && !(value instanceof Date)) {\n const record = value as Record<string, TScrubbableValue>;\n const out: Record<string, TScrubbableValue> = {};\n for (const [childKey, childValue] of Object.entries(record)) {\n out[childKey] = scrubValue(childKey, childValue, redactedValue);\n }\n return out;\n }\n return value;\n}\n\n/**\n * Deep-copy `value`, replacing any value whose KEY is sensitive with `redactedValue` (default `[REDACTED]`).\n * Pure — does not mutate the input. Returns the same shape (`T`).\n */\nexport function scrubSensitiveKeys<T>(value: T, redactedValue: string = DEFAULT_REDACTED_VALUE): T {\n return scrubValue(undefined, value as TScrubbableValue, redactedValue) as T;\n}\n","/**\n * Session log payload normalization — scrubbing and size-bounding what goes into a log line.\n *\n * Split out of `session-logger.ts` to keep each file under 300 lines. The split is by\n * responsibility, not by size alone: this module decides WHAT a line may contain (sensitive keys\n * redacted, oversized payloads written beside the log and referenced), while `session-logger.ts`\n * decides WHERE and WHEN the bytes are written.\n */\n\nimport { sha256Hex, utf8ByteLength } from '@robota-sdk/agent-core';\n\nimport { isSensitiveKey } from './scrub-sensitive.js';\n\nimport type { IExternalPayloadSink } from './session-log-sinks.js';\nimport type {\n IFileSessionLoggerOptions,\n TSessionLogData,\n TSessionLogValue,\n} from './session-log-reference-types.js';\n\n/** What decides a log line's content; where and when it is written is the logger's business. */\nexport type TLogNormalizationOptions = Required<\n Pick<IFileSessionLoggerOptions, 'externalPayloadThresholdBytes' | 'redactedValue'>\n>;\n\nexport function normalizeLogData(\n sessionId: string,\n data: TSessionLogData,\n options: TLogNormalizationOptions,\n externalPayloadSink: IExternalPayloadSink | undefined,\n): TSessionLogData {\n const normalized: TSessionLogData = {};\n for (const [key, value] of Object.entries(data)) {\n normalized[key] = normalizeLogValue(sessionId, key, value, options, externalPayloadSink);\n }\n return normalized;\n}\n\nfunction normalizeLogValue(\n sessionId: string,\n key: string,\n value: TSessionLogValue,\n options: TLogNormalizationOptions,\n externalPayloadSink: IExternalPayloadSink | undefined,\n): TSessionLogValue {\n if (isSensitiveKey(key)) {\n return options.redactedValue;\n }\n if (\n value === null ||\n value === undefined ||\n typeof value === 'string' ||\n typeof value === 'number'\n ) {\n return maybeExternalizePayload(sessionId, value, options, externalPayloadSink);\n }\n if (typeof value === 'boolean') {\n return value;\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (Array.isArray(value)) {\n const normalizedArray = value.map((item) =>\n normalizeLogValue(sessionId, key, item as TSessionLogValue, options, externalPayloadSink),\n );\n return maybeExternalizePayload(sessionId, normalizedArray, options, externalPayloadSink);\n }\n if (typeof value === 'object') {\n const record = value as Record<string, TSessionLogValue>;\n const normalizedRecord: Record<string, TSessionLogValue> = {};\n for (const [childKey, childValue] of Object.entries(record)) {\n normalizedRecord[childKey] = normalizeLogValue(\n sessionId,\n childKey,\n childValue,\n options,\n externalPayloadSink,\n );\n }\n return maybeExternalizePayload(sessionId, normalizedRecord, options, externalPayloadSink);\n }\n return String(value);\n}\n\nfunction maybeExternalizePayload(\n sessionId: string,\n value: TSessionLogValue,\n options: TLogNormalizationOptions,\n externalPayloadSink: IExternalPayloadSink | undefined,\n): TSessionLogValue {\n const serialized = JSON.stringify(value);\n if (serialized === undefined) {\n return value;\n }\n const byteLength = utf8ByteLength(serialized);\n if (byteLength <= options.externalPayloadThresholdBytes || externalPayloadSink === undefined) {\n return value;\n }\n\n const sha256 = sha256Hex(serialized);\n return externalPayloadSink.writeJson(sessionId, sha256, serialized);\n}\n\n/** No-op logger — used when logging is disabled. */\n","/**\n * Session Logger — pluggable logging interface for session events.\n *\n * ISessionLogger defines the contract. FileSessionLogger serializes JSONL through an\n * injected sink and never opens a path itself. Consumers can implement their\n * own (e.g., remote, database, silent) and inject via Session constructor.\n */\n\nimport { createLogger } from '@robota-sdk/agent-core';\n\nimport { isSafeSessionId } from './session-id.js';\nimport { SESSION_LOG_SCHEMA_VERSION } from './session-log-events.js';\nimport { normalizeLogData } from './session-log-payload.js';\n\nimport type { TLogNormalizationOptions } from './session-log-payload.js';\nimport type { ISessionLogSink } from './session-log-sinks.js';\nimport type {\n IFileSessionLoggerOptions,\n TRegisterExitFlush,\n TSessionLogData,\n} from './session-log-reference-types.js';\n\nexport type {\n IExternalPayloadReference,\n IFileSessionLoggerOptions,\n TRegisterExitFlush,\n TSessionLogData,\n TSessionLogValue,\n} from './session-log-reference-types.js';\n\nconst logger = createLogger('FileSessionLogger');\n\n/**\n * Events that arrive once per streamed token.\n *\n * `session-run.ts` logs one of these per text delta and this class answered each with a\n * blocking `appendFileSync` — a synchronous disk write per token on the streaming hot path. They are\n * buffered and written in one call; every OTHER event flushes the buffer before writing itself, so\n * ordering in the file is unchanged and no semantic event is ever delayed behind a stream.\n */\nconst HOT_PATH_EVENTS: ReadonlySet<string> = new Set(['text_delta']);\n\n/** Flush the delta buffer once it reaches this size, so a long stream cannot grow without bound. */\nconst HOT_PATH_FLUSH_BYTES = 64 * 1024;\n\n/**\n * Every logger with something buffered, flushed if the process exits mid-stream.\n *\n * Buffering trades a write per token for a write per batch, and the price is a window in which the\n * tail of a stream exists only in memory. A normal shutdown closes that window by itself — the\n * `session_shutdown` event is not a hot-path event, so it flushes before writing — but an abnormal\n * exit would not, and a replay log missing its last exchange is a worse defect than the one being\n * fixed. `exit` handlers may only do synchronous work, which is exactly what `flush` does. Whether\n * and how the process's exit is observed is the host's to say (`registerExitFlush`): a neutral\n * logger has no process to watch.\n */\nconst liveLoggers = new Set<FileSessionLogger>();\n/** Registrars already handed {@link flushLiveLoggers}: one exit listener per host, not per logger. */\nconst registrars = new WeakSet<TRegisterExitFlush>();\n\nfunction flushLiveLoggers(): void {\n for (const live of liveLoggers) {\n live.flush();\n }\n}\n\nfunction ensureExitFlush(\n loggerInstance: FileSessionLogger,\n registerExitFlush: TRegisterExitFlush | undefined,\n): void {\n liveLoggers.add(loggerInstance);\n if (registerExitFlush === undefined || registrars.has(registerExitFlush)) return;\n registrars.add(registerExitFlush);\n registerExitFlush(flushLiveLoggers);\n}\n\nconst BYTES_PER_KIB = 1024;\nconst DEFAULT_EXTERNAL_PAYLOAD_THRESHOLD_KIB = 32;\nconst DEFAULT_EXTERNAL_PAYLOAD_THRESHOLD_BYTES =\n DEFAULT_EXTERNAL_PAYLOAD_THRESHOLD_KIB * BYTES_PER_KIB;\nconst DEFAULT_REDACTED_VALUE = '[REDACTED]';\n\n/**\n * Session logger interface — injected into Session for pluggable logging.\n *\n * Implementations decide where and how to persist session events.\n * The Session class calls log() for every significant action.\n */\nexport interface ISessionLogger {\n /** Log a session event with structured data. */\n log(sessionId: string, event: string, data: TSessionLogData): void;\n /**\n * Write out anything buffered.\n *\n * Optional because an implementation that never buffers has nothing to do. A caller that needs\n * the log to be complete on disk — session end, or a reader about to parse it — calls this.\n */\n flush?(): void;\n}\n\n/**\n * Sink-driven session logger — writes JSONL through `ISessionLogSink`.\n *\n * This is the default implementation used by the CLI.\n * Each line is a self-contained JSON object with timestamp, sessionId, event, and data.\n */\nexport class FileSessionLogger implements ISessionLogger {\n private readonly options: TLogNormalizationOptions;\n private readonly registerExitFlush: TRegisterExitFlush | undefined;\n /** Buffered hot-path lines, per session file. Keyed by session id. */\n private readonly pending = new Map<string, string[]>();\n private pendingBytes = 0;\n\n constructor(\n private readonly sink: ISessionLogSink,\n options: IFileSessionLoggerOptions = {},\n ) {\n this.options = {\n externalPayloadThresholdBytes:\n options.externalPayloadThresholdBytes ?? DEFAULT_EXTERNAL_PAYLOAD_THRESHOLD_BYTES,\n redactedValue: options.redactedValue ?? DEFAULT_REDACTED_VALUE,\n };\n this.registerExitFlush = options.registerExitFlush;\n }\n\n log(sessionId: string, event: string, data: TSessionLogData): void {\n // `sessionId` becomes a path component below. This is a second sink on the same value the\n // session store guards, and a host can pass a remote-supplied id, so it must not rely on the\n // store having been called first. Logging must never break a\n // session, so a rejected id drops the line rather than throwing — the store raises the loud error.\n if (!isSafeSessionId(sessionId)) return;\n try {\n const normalizedData = normalizeLogData(\n sessionId,\n data,\n this.options,\n this.sink.externalPayloadSink,\n );\n const entry =\n JSON.stringify({\n ...normalizedData,\n schemaVersion: SESSION_LOG_SCHEMA_VERSION,\n timestamp: new Date().toISOString(),\n sessionId,\n event,\n }) + '\\n';\n\n if (HOT_PATH_EVENTS.has(event)) {\n this.buffer(sessionId, entry);\n return;\n }\n\n // Any other event flushes first, so the file's order is the order the events happened in.\n this.flush();\n this.write(sessionId, entry);\n } catch (error) {\n // allow-fallback: logging must never break a session (the same rule holds for a\n // rejected id). The failure must not be INVISIBLE — a log\n // that silently stops writing is indistinguishable from a session that produced no events.\n this.report(sessionId, event, error);\n }\n }\n\n /** Write out every buffered hot-path line. Safe to call when nothing is pending. */\n flush(): void {\n if (this.pending.size === 0) {\n liveLoggers.delete(this);\n return;\n }\n const batches = [...this.pending.entries()];\n this.pending.clear();\n this.pendingBytes = 0;\n liveLoggers.delete(this);\n for (const [sessionId, lines] of batches) {\n try {\n this.write(sessionId, lines.join(''));\n } catch (error) {\n this.report(sessionId, 'flush', error);\n }\n }\n }\n\n private buffer(sessionId: string, entry: string): void {\n ensureExitFlush(this, this.registerExitFlush);\n const lines = this.pending.get(sessionId) ?? [];\n lines.push(entry);\n this.pending.set(sessionId, lines);\n this.pendingBytes += entry.length;\n // A stream that never ends must not accumulate without bound; flushing on size keeps the\n // write count proportional to bytes rather than to tokens, which is the whole point.\n if (this.pendingBytes >= HOT_PATH_FLUSH_BYTES) {\n this.flush();\n }\n }\n\n private write(sessionId: string, text: string): void {\n this.sink.append(sessionId, text);\n }\n\n private report(sessionId: string, event: string, error: unknown): void {\n logger.warn('session log write failed', {\n sessionId,\n event,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\nexport class SilentSessionLogger implements ISessionLogger {\n log(): void {\n // intentionally empty\n }\n}\n","import { SessionLogPayloadResolutionError } from './external-payload-resolution-contracts.js';\n\nimport type { IExternalPayloadReference } from './session-logger.js';\n\nconst SHA256_PATTERN = /^[0-9a-f]{64}$/i;\n\nexport function validateExternalPayloadReference(\n value: Record<string, unknown>,\n): IExternalPayloadReference {\n if (!isValidSessionLogExternalPayloadReference(value)) {\n throw new SessionLogPayloadResolutionError(\n 'INVALID_REFERENCE',\n 'External payload reference has an invalid shape.',\n );\n }\n return {\n kind: 'external-payload',\n encoding: 'json',\n sha256: String(value.sha256).toLowerCase(),\n byteLength: Number(value.byteLength),\n relativePath: String(value.relativePath),\n };\n}\n\n/** Internal SSOT shared by the resolver and raw-log validator; not part of the package barrel. */\nexport function isValidSessionLogExternalPayloadReference(value: Record<string, unknown>): boolean {\n const keys = Object.keys(value).sort();\n const expectedKeys = ['byteLength', 'encoding', 'kind', 'relativePath', 'sha256'];\n return (\n keys.length === expectedKeys.length &&\n keys.every((key, index) => key === expectedKeys[index]) &&\n value.kind === 'external-payload' &&\n value.encoding === 'json' &&\n typeof value.sha256 === 'string' &&\n SHA256_PATTERN.test(value.sha256) &&\n typeof value.byteLength === 'number' &&\n Number.isSafeInteger(value.byteLength) &&\n value.byteLength >= 0 &&\n typeof value.relativePath === 'string' &&\n value.relativePath.trim().length > 0\n );\n}\n","import { sha256Hex } from '@robota-sdk/agent-core';\n\nimport { validateExternalPayloadReference } from './external-payload-file-reader.js';\nimport { SessionLogPayloadResolutionError } from './external-payload-resolution-contracts.js';\n\nimport type { ISessionLogPayloadResolutionOptions } from './external-payload-resolution-contracts.js';\nimport type { IExternalPayloadSource } from './external-payload-source-types.js';\nimport type { IExternalPayloadReference } from './session-logger.js';\n\nexport { SessionLogPayloadResolutionError } from './external-payload-resolution-contracts.js';\nexport type {\n ISessionLogPayloadResolutionErrorMetadata,\n ISessionLogPayloadResolutionOptions,\n TSessionLogPayloadResolutionErrorCode,\n} from './external-payload-resolution-contracts.js';\n\n// A leading byte-order mark is content, not framing: it must still make the JSON invalid, as it did\n// when the bytes were decoded with Node's `Buffer`.\nconst PAYLOAD_DECODER = new TextDecoder('utf-8', { ignoreBOM: true });\nconst DEFAULT_MAX_DEPTH = 32;\nconst DEFAULT_MAX_TOTAL_MIB = 64;\nconst BYTES_PER_KIB = 1024;\nconst KIB_PER_MIB = 1024;\nconst DEFAULT_MAX_TOTAL_BYTES = DEFAULT_MAX_TOTAL_MIB * KIB_PER_MIB * BYTES_PER_KIB;\n\ninterface IResolutionState {\n readonly maxDepth: number;\n readonly maxTotalBytes: number;\n readonly source: IExternalPayloadSource | undefined;\n totalBytes: number;\n readonly activePayloadPaths: Set<string>;\n readonly activeObjects: WeakSet<object>;\n}\n\n/**\n * Hydrate every external JSON payload reference in one value using one aggregate budget.\n * The input is treated as untrusted and the returned graph contains only JSON-compatible values.\n */\nexport function resolveSessionLogExternalPayloads(\n value: unknown,\n options: ISessionLogPayloadResolutionOptions,\n): unknown {\n const state = createResolutionState(options);\n return resolveValue(value, state, 0);\n}\n\nfunction createResolutionState(options: ISessionLogPayloadResolutionOptions): IResolutionState {\n const maxDepth = validateLimit('maxDepth', options.maxDepth ?? DEFAULT_MAX_DEPTH);\n const maxTotalBytes = validateLimit(\n 'maxTotalBytes',\n options.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES,\n );\n return {\n source: options.source,\n maxDepth,\n maxTotalBytes,\n totalBytes: 0,\n activePayloadPaths: new Set<string>(),\n activeObjects: new WeakSet<object>(),\n };\n}\n\nfunction validateLimit(name: string, value: number): number {\n if (!Number.isFinite(value) || !Number.isSafeInteger(value) || value < 0) {\n throw new SessionLogPayloadResolutionError(\n 'INVALID_LIMIT',\n `${name} must be a finite, non-negative safe integer.`,\n { actual: String(value) },\n );\n }\n return value;\n}\n\nfunction resolveValue(value: unknown, state: IResolutionState, referenceDepth: number): unknown {\n if (\n value === null ||\n typeof value === 'string' ||\n typeof value === 'boolean' ||\n (typeof value === 'number' && Number.isFinite(value))\n ) {\n return value;\n }\n if (typeof value === 'number') {\n throw invalidJsonValue('Non-finite numbers are not valid session-log JSON values.');\n }\n if (typeof value !== 'object') {\n throw invalidJsonValue(`Unsupported session-log JSON value type: ${typeof value}.`);\n }\n if (state.activeObjects.has(value)) {\n throw new SessionLogPayloadResolutionError(\n 'CIRCULAR_REFERENCE',\n 'Circular in-memory value encountered while resolving external payloads.',\n { depth: referenceDepth },\n );\n }\n if (isPotentialExternalPayloadReference(value)) {\n return resolveReference(value, state, referenceDepth);\n }\n if (Array.isArray(value)) {\n state.activeObjects.add(value);\n try {\n return value.map((item) => resolveValue(item, state, referenceDepth));\n } finally {\n state.activeObjects.delete(value);\n }\n }\n if (!isPlainRecord(value)) {\n throw invalidJsonValue('Session-log payload objects must be plain JSON records.');\n }\n state.activeObjects.add(value);\n try {\n return Object.fromEntries(\n Object.entries(value).map(([key, child]) => [\n key,\n resolveValue(child, state, referenceDepth),\n ]),\n );\n } finally {\n state.activeObjects.delete(value);\n }\n}\n\nfunction resolveReference(\n value: Record<string, unknown>,\n state: IResolutionState,\n referenceDepth: number,\n): unknown {\n const reference = validateExternalPayloadReference(value);\n if (referenceDepth >= state.maxDepth) {\n throw new SessionLogPayloadResolutionError(\n 'MAX_DEPTH_EXCEEDED',\n `External-payload reference depth exceeds the configured maximum of ${state.maxDepth}.`,\n { relativePath: reference.relativePath, depth: referenceDepth },\n );\n }\n if (state.source === undefined) {\n throw new SessionLogPayloadResolutionError(\n 'UNRESOLVED_REFERENCE',\n 'An external session-log payload requires an explicit payload source.',\n { relativePath: reference.relativePath, depth: referenceDepth },\n );\n }\n if (state.activePayloadPaths.has(reference.relativePath)) {\n throw new SessionLogPayloadResolutionError(\n 'CIRCULAR_REFERENCE',\n `External payload ${reference.relativePath} recursively references an active payload.`,\n { relativePath: reference.relativePath, depth: referenceDepth },\n );\n }\n\n const parsed = readExternalPayloadJson(reference, state);\n state.activePayloadPaths.add(reference.relativePath);\n try {\n return resolveValue(parsed, state, referenceDepth + 1);\n } finally {\n state.activePayloadPaths.delete(reference.relativePath);\n }\n}\n\nfunction readExternalPayloadJson(\n reference: IExternalPayloadReference,\n state: IResolutionState,\n): unknown {\n const remainingBytes = state.maxTotalBytes - state.totalBytes;\n const bytes = state.source?.readBytes(reference.relativePath, remainingBytes);\n if (bytes === undefined) {\n throw new SessionLogPayloadResolutionError(\n 'PAYLOAD_NOT_FOUND',\n `External payload was not found: ${reference.relativePath}.`,\n { relativePath: reference.relativePath },\n );\n }\n const nextTotalBytes = state.totalBytes + bytes.byteLength;\n if (!Number.isSafeInteger(nextTotalBytes) || nextTotalBytes > state.maxTotalBytes) {\n throw new SessionLogPayloadResolutionError(\n 'MAX_TOTAL_BYTES_EXCEEDED',\n `External-payload bytes exceed the configured maximum of ${state.maxTotalBytes}.`,\n { expected: state.maxTotalBytes, actual: nextTotalBytes },\n );\n }\n state.totalBytes = nextTotalBytes;\n if (bytes.byteLength !== reference.byteLength) {\n throw new SessionLogPayloadResolutionError(\n 'BYTE_LENGTH_MISMATCH',\n `External payload byte length does not match its reference: ${reference.relativePath}.`,\n {\n relativePath: reference.relativePath,\n expected: reference.byteLength,\n actual: bytes.byteLength,\n },\n );\n }\n const actualSha256 = sha256Hex(bytes);\n if (actualSha256 !== reference.sha256) {\n throw new SessionLogPayloadResolutionError(\n 'SHA256_MISMATCH',\n `External payload sha256 does not match its reference: ${reference.relativePath}.`,\n { relativePath: reference.relativePath, expected: reference.sha256, actual: actualSha256 },\n );\n }\n try {\n return JSON.parse(PAYLOAD_DECODER.decode(bytes)) as unknown;\n } catch (error) {\n throw new SessionLogPayloadResolutionError(\n 'INVALID_JSON',\n `External payload is not valid JSON: ${reference.relativePath}.`,\n { relativePath: reference.relativePath },\n error,\n );\n }\n}\n\nfunction invalidJsonValue(message: string): SessionLogPayloadResolutionError {\n return new SessionLogPayloadResolutionError('INVALID_JSON', message);\n}\n\nfunction isPotentialExternalPayloadReference(value: object): value is Record<string, unknown> {\n return !Array.isArray(value) && 'kind' in value && value.kind === 'external-payload';\n}\n\nfunction isPlainRecord(value: object): value is Record<string, unknown> {\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n","import { isValidSessionLogExternalPayloadReference } from './external-payload-file-reader.js';\nimport { decodeSessionLogEntries } from './session-log-codec/index.js';\n\nimport type { ISessionLogEntry } from './session-log-entry-types.js';\nimport type { TUniversalValue } from '@robota-sdk/agent-core';\n\nexport interface ISessionReplayValidationIssue {\n code:\n | 'PROVIDER_RESPONSE_RAW_MISSING'\n | 'PROVIDER_NATIVE_RAW_PAYLOAD_MISSING'\n | 'PROVIDER_RESPONSE_NORMALIZED_MISSING'\n | 'TOOL_RESULT_MISSING'\n | 'PAYLOAD_REFERENCE_INVALID'\n | 'UNRESOLVED_REPLAY_PAYLOAD';\n message: string;\n eventIndex?: number;\n executionId?: string;\n round?: number;\n toolCallId?: string;\n}\n\nexport interface ISessionReplayValidationResult {\n ok: boolean;\n issues: ISessionReplayValidationIssue[];\n}\n\nexport function validateSessionReplayLogEntries(\n entries: readonly ISessionLogEntry[],\n): ISessionReplayValidationResult {\n const issues: ISessionReplayValidationIssue[] = [];\n const providerEvents = createProviderReplayEventIndex();\n const toolEvents = createToolReplayEventIndex();\n\n decodeSessionLogEntries(entries).forEach((entry, index) => {\n collectPayloadReferenceIssues(entry, index, issues);\n collectUnresolvedReplayPayloadIssue(entry, index, issues);\n collectProviderReplayEvent(providerEvents, entry, index);\n collectToolReplayEvent(toolEvents, entry, index);\n });\n\n appendProviderReplayIssues(providerEvents, issues);\n appendToolReplayIssues(toolEvents, issues);\n\n return { ok: issues.length === 0, issues };\n}\n\ninterface IProviderReplayRequest {\n executionId: string;\n round: number;\n index: number;\n}\n\ninterface IProviderReplayEventIndex {\n requests: Map<string, IProviderReplayRequest>;\n nativeRawPayloads: Set<string>;\n rawResponses: Set<string>;\n normalizedResponses: Set<string>;\n}\n\ninterface IToolReplayRequest {\n executionId: string;\n toolCallId: string;\n index: number;\n}\n\ninterface IToolReplayEventIndex {\n requests: Map<string, IToolReplayRequest>;\n results: Set<string>;\n}\n\nfunction createProviderReplayEventIndex(): IProviderReplayEventIndex {\n return {\n requests: new Map<string, IProviderReplayRequest>(),\n nativeRawPayloads: new Set<string>(),\n rawResponses: new Set<string>(),\n normalizedResponses: new Set<string>(),\n };\n}\n\nfunction createToolReplayEventIndex(): IToolReplayEventIndex {\n return {\n requests: new Map<string, IToolReplayRequest>(),\n results: new Set<string>(),\n };\n}\n\nfunction collectProviderReplayEvent(\n events: IProviderReplayEventIndex,\n entry: ISessionLogEntry,\n index: number,\n): void {\n const key = providerKey(entry);\n if (!key) return;\n if (entry.event === 'provider_request') {\n events.requests.set(key.key, {\n executionId: key.executionId,\n round: key.round,\n index,\n });\n }\n if (entry.event === 'provider_response_raw') {\n events.rawResponses.add(key.key);\n }\n if (\n entry.event === 'provider_native_raw_payload' &&\n (entry.payloadKind === 'response' || entry.payloadKind === 'stream_event')\n ) {\n events.nativeRawPayloads.add(key.key);\n }\n if (\n entry.event === 'provider_response_normalized' &&\n !containsExternalPayloadReference(entry.response)\n ) {\n events.normalizedResponses.add(key.key);\n }\n}\n\nfunction collectUnresolvedReplayPayloadIssue(\n entry: ISessionLogEntry,\n eventIndex: number,\n issues: ISessionReplayValidationIssue[],\n): void {\n const replayValue =\n entry.event === 'history_mutation'\n ? entry.message\n : entry.event === 'provider_response_normalized'\n ? entry.response\n : undefined;\n if (!containsExternalPayloadReference(replayValue)) return;\n issues.push({\n code: 'UNRESOLVED_REPLAY_PAYLOAD',\n message: `Replay substrate ${entry.event} contains an unresolved external payload.`,\n eventIndex,\n executionId: typeof entry.executionId === 'string' ? entry.executionId : undefined,\n round: typeof entry.round === 'number' ? entry.round : undefined,\n });\n}\n\nfunction collectToolReplayEvent(\n events: IToolReplayEventIndex,\n entry: ISessionLogEntry,\n index: number,\n): void {\n const key = toolKey(entry);\n if (!key) return;\n if (entry.event === 'tool_execution_request') {\n events.requests.set(key.key, {\n executionId: key.executionId,\n toolCallId: key.toolCallId,\n index,\n });\n }\n if (entry.event === 'tool_execution_result') {\n events.results.add(key.key);\n }\n}\n\nfunction appendProviderReplayIssues(\n events: IProviderReplayEventIndex,\n issues: ISessionReplayValidationIssue[],\n): void {\n for (const [key, request] of events.requests) {\n if (!events.nativeRawPayloads.has(key)) {\n issues.push({\n code: 'PROVIDER_NATIVE_RAW_PAYLOAD_MISSING',\n message: `Provider request ${key} has no provider-native raw response or stream payload event.`,\n eventIndex: request.index,\n executionId: request.executionId,\n round: request.round,\n });\n }\n if (!events.rawResponses.has(key)) {\n issues.push({\n code: 'PROVIDER_RESPONSE_RAW_MISSING',\n message: `Provider request ${key} has no raw response event.`,\n eventIndex: request.index,\n executionId: request.executionId,\n round: request.round,\n });\n }\n if (!events.normalizedResponses.has(key)) {\n issues.push({\n code: 'PROVIDER_RESPONSE_NORMALIZED_MISSING',\n message: `Provider request ${key} has no normalized response event.`,\n eventIndex: request.index,\n executionId: request.executionId,\n round: request.round,\n });\n }\n }\n}\n\nfunction appendToolReplayIssues(\n events: IToolReplayEventIndex,\n issues: ISessionReplayValidationIssue[],\n): void {\n for (const [key, request] of events.requests) {\n if (!events.results.has(key)) {\n issues.push({\n code: 'TOOL_RESULT_MISSING',\n message: `Tool request ${key} has no terminal result event.`,\n eventIndex: request.index,\n executionId: request.executionId,\n toolCallId: request.toolCallId,\n });\n }\n }\n}\n\nfunction providerKey(\n entry: ISessionLogEntry,\n): { key: string; executionId: string; round: number } | undefined {\n if (typeof entry.executionId !== 'string') return undefined;\n const round = typeof entry.round === 'number' ? entry.round : Number(entry.round);\n if (!Number.isFinite(round)) return undefined;\n return { key: `${entry.executionId}:${round}`, executionId: entry.executionId, round };\n}\n\nfunction toolKey(\n entry: ISessionLogEntry,\n): { key: string; executionId: string; toolCallId: string } | undefined {\n if (typeof entry.executionId !== 'string') return undefined;\n const toolCallId =\n typeof entry.toolCallId === 'string'\n ? entry.toolCallId\n : typeof entry.toolExecutionId === 'string'\n ? entry.toolExecutionId\n : undefined;\n if (!toolCallId) return undefined;\n return { key: `${entry.executionId}:${toolCallId}`, executionId: entry.executionId, toolCallId };\n}\n\nfunction collectPayloadReferenceIssues(\n value: TUniversalValue,\n eventIndex: number,\n issues: ISessionReplayValidationIssue[],\n): void {\n if (Array.isArray(value)) {\n value.forEach((item) => collectPayloadReferenceIssues(item, eventIndex, issues));\n return;\n }\n if (!isRecord(value)) return;\n if (value.kind === 'external-payload') {\n if (!isValidSessionLogExternalPayloadReference(value)) {\n issues.push({\n code: 'PAYLOAD_REFERENCE_INVALID',\n message: 'External payload reference is missing required replay fields.',\n eventIndex,\n });\n }\n return;\n }\n Object.values(value).forEach((child) => collectPayloadReferenceIssues(child, eventIndex, issues));\n}\n\nfunction containsExternalPayloadReference(value: TUniversalValue): boolean {\n if (Array.isArray(value)) {\n return value.some((item) => containsExternalPayloadReference(item));\n }\n if (!isRecord(value)) return false;\n if (value.kind === 'external-payload') return true;\n return Object.values(value).some((child) => containsExternalPayloadReference(child));\n}\n\nfunction isRecord(value: TUniversalValue): value is Record<string, TUniversalValue> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","import { messageToHistoryEntry } from '@robota-sdk/agent-core';\n\nimport { resolveSessionLogExternalPayloads } from './external-payload-resolver.js';\nimport { decodeSessionLogEntries, SessionLogDecodeError } from './session-log-codec/index.js';\n\nimport type { ISessionLogPayloadResolutionOptions } from './external-payload-resolver.js';\nimport type {\n IExternalPayloadSource,\n ISessionLogSource,\n} from './external-payload-source-types.js';\nimport type { IHistoryEntry, TUniversalMessage } from '@robota-sdk/agent-core';\nimport type { ISessionLogEntry } from './session-log-entry-types.js';\n\nexport type { ISessionLogEntry } from './session-log-entry-types.js';\n\nexport interface ISessionReplayRecord {\n sessionId: string | undefined;\n cwd: string | undefined;\n createdAt: string | undefined;\n updatedAt: string | undefined;\n messages: TUniversalMessage[];\n history: IHistoryEntry[];\n backgroundTaskEvents: object[];\n backgroundJobGroupEvents: object[];\n memoryEvents: object[];\n}\n\nexport type ISessionLogLoadOptions = Omit<ISessionLogPayloadResolutionOptions, 'source'> & {\n readonly externalPayloadSource?: IExternalPayloadSource;\n};\n\nexport { validateSessionReplayLogEntries } from './session-log-validation.js';\nexport type {\n ISessionReplayValidationIssue,\n ISessionReplayValidationResult,\n} from './session-log-validation.js';\n\nexport function loadSessionLogEntries(\n source: ISessionLogSource,\n options: ISessionLogLoadOptions = {},\n): ISessionLogEntry[] {\n const text = source.readText();\n const parsedLines: { value: unknown; lineNumber: number }[] = [];\n if (text !== undefined) {\n text.split('\\n').forEach((line, index) => {\n if (line.trim().length === 0) return;\n try {\n parsedLines.push({ value: JSON.parse(line) as unknown, lineNumber: index + 1 });\n } catch (cause) {\n throw new SessionLogDecodeError(\n 'INVALID_JSON',\n [{ path: `line ${index + 1}`, message: 'expected valid JSON' }],\n { cause },\n );\n }\n });\n }\n const hydrated = resolveSessionLogExternalPayloads(\n parsedLines.map(({ value }) => value),\n {\n source: options.externalPayloadSource ?? source.externalPayloadSource,\n ...options,\n },\n );\n return decodeSessionLogEntries(hydrated, {\n lineNumbers: parsedLines.map(({ lineNumber }) => lineNumber),\n });\n}\n\nexport function replaySessionLogEntries(\n entries: readonly ISessionLogEntry[],\n): ISessionReplayRecord {\n const messages: TUniversalMessage[] = [];\n const history: IHistoryEntry[] = [];\n const auxiliaryEvents: IAuxiliaryReplayEvents = {\n backgroundTaskEvents: [],\n backgroundJobGroupEvents: [],\n memoryEvents: [],\n };\n let sessionId: string | undefined;\n let cwd: string | undefined;\n let createdAt: string | undefined;\n let updatedAt: string | undefined;\n\n for (const entry of decodeSessionLogEntries(entries)) {\n sessionId = sessionId ?? entry.sessionId;\n createdAt = createdAt ?? entry.timestamp;\n updatedAt = entry.timestamp;\n\n if (entry.event === 'session_init') {\n cwd = typeof entry.cwd === 'string' ? entry.cwd : cwd;\n }\n\n if (entry.event === 'history_mutation' && entry.mutation === 'append_message') {\n messages.push(entry.message);\n history.push(messageToHistoryEntry(entry.message));\n }\n\n collectAuxiliaryReplayEvent(entry, auxiliaryEvents);\n }\n\n return {\n sessionId,\n cwd,\n createdAt,\n updatedAt,\n messages,\n history,\n backgroundTaskEvents: auxiliaryEvents.backgroundTaskEvents,\n backgroundJobGroupEvents: auxiliaryEvents.backgroundJobGroupEvents,\n memoryEvents: auxiliaryEvents.memoryEvents,\n };\n}\n\ninterface IAuxiliaryReplayEvents {\n backgroundTaskEvents: object[];\n backgroundJobGroupEvents: object[];\n memoryEvents: object[];\n}\n\nfunction collectAuxiliaryReplayEvent(\n entry: ISessionLogEntry,\n auxiliaryEvents: IAuxiliaryReplayEvents,\n): void {\n if (entry.event === 'background_task_event') {\n pushObjectPayload(auxiliaryEvents.backgroundTaskEvents, entry, 'backgroundEvent', 'data');\n return;\n }\n if (entry.event === 'background_job_group_event') {\n pushObjectPayload(\n auxiliaryEvents.backgroundJobGroupEvents,\n entry,\n 'backgroundJobGroupEvent',\n 'data',\n );\n return;\n }\n if (entry.event === 'memory_event') {\n pushObjectPayload(auxiliaryEvents.memoryEvents, entry, 'memoryEvent', 'data');\n }\n}\n\nfunction getObjectPayload(entry: ISessionLogEntry, key: string): object | undefined {\n const value = entry[key];\n if (\n typeof value !== 'object' ||\n value === null ||\n Array.isArray(value) ||\n value instanceof Date\n ) {\n return undefined;\n }\n return value;\n}\n\nfunction pushObjectPayload(\n target: object[],\n entry: ISessionLogEntry,\n primaryKey: string,\n fallbackKey: string,\n): void {\n const payload = getObjectPayload(entry, primaryKey) ?? getObjectPayload(entry, fallbackKey);\n if (payload) target.push(payload);\n}\n","/**\n * Neutral checkpoint tree (branching time-travel).\n *\n * A pure, I/O-free branch-tree over opaque checkpoint-node ids — git-for-a-session with no file I/O,\n * no persistence, and no retention/prune policy. It lives beside the storage-neutral persistence\n * primitive (`SessionStore`/`IInteractiveSessionRecord`): same neutral-mechanism class (opaque payloads, no\n * product policy). The agent-framework checkpoint store consumes it over the existing one-way\n * `agent-framework → agent-session` edge; the reverse edge would be a cycle and is forbidden.\n *\n * Model: each node is `{ id, parentId }` (root has no `parentId`). Adding a checkpoint appends a child\n * of the active head. Forking moves the active head to a PAST node so the next append DIVERGES — the\n * original descendants stay reachable (a sibling branch). A \"branch\" is a leaf (a node with no\n * children); `switch` moves the active head to any existing node.\n */\n\nexport interface ICheckpointNode {\n id: string;\n /** Parent checkpoint id; absent for the root. */\n parentId?: string;\n}\n\nexport class CheckpointTree {\n private readonly nodes = new Map<string, ICheckpointNode>();\n /** Child adjacency (parentId → child ids in insertion order) for O(1) leaf/branch queries. */\n private readonly children = new Map<string, string[]>();\n private activeId: string | undefined;\n\n /**\n * Build a tree from explicit `{ id, parentId }` edges (e.g. reconstructed from persisted checkpoint\n * manifests). Nodes may arrive in any order — parents are linked by id. The active head is left at\n * the given `activeId` (or undefined). This is the delegation entry point a consumer store uses to\n * answer navigation queries (`listBranches`/`ancestors`) without the tree owning any persistence.\n */\n static fromNodes(nodes: ICheckpointNode[], activeId?: string): CheckpointTree {\n const tree = new CheckpointTree();\n // First pass: register every node so parent links resolve regardless of arrival order.\n for (const node of nodes) {\n if (tree.nodes.has(node.id)) throw new Error(`CheckpointTree: duplicate node \"${node.id}\"`);\n tree.nodes.set(node.id, node.parentId === undefined ? { id: node.id } : { ...node });\n }\n // Second pass: build child adjacency in the provided order.\n for (const node of nodes) {\n if (node.parentId !== undefined) {\n const siblings = tree.children.get(node.parentId) ?? [];\n siblings.push(node.id);\n tree.children.set(node.parentId, siblings);\n }\n }\n tree.activeId = activeId;\n return tree;\n }\n\n /**\n * Append a checkpoint as a child of the current active head and make it the new active head.\n * The first append (no active head) becomes the root. Ids must be unique.\n */\n addCheckpoint(id: string): void {\n if (this.nodes.has(id)) throw new Error(`CheckpointTree: duplicate checkpoint id \"${id}\"`);\n const parentId = this.activeId;\n this.nodes.set(id, parentId === undefined ? { id } : { id, parentId });\n if (parentId !== undefined) {\n const siblings = this.children.get(parentId) ?? [];\n siblings.push(id);\n this.children.set(parentId, siblings);\n }\n this.activeId = id;\n }\n\n /**\n * Fork from a PAST checkpoint: move the active head to `fromId` so the next `addCheckpoint` diverges\n * into a sibling branch, leaving `fromId`'s original descendants reachable. Returns `fromId`.\n */\n fork(fromId: string): string {\n if (!this.nodes.has(fromId)) throw new Error(`CheckpointTree: unknown checkpoint \"${fromId}\"`);\n this.activeId = fromId;\n return fromId;\n }\n\n /** Move the active head to an existing node (typically a branch leaf). */\n switch(nodeId: string): void {\n if (!this.nodes.has(nodeId)) throw new Error(`CheckpointTree: unknown checkpoint \"${nodeId}\"`);\n this.activeId = nodeId;\n }\n\n /** The current active head id (undefined for an empty tree). */\n activeLeaf(): string | undefined {\n return this.activeId;\n }\n\n /** All branch tips (leaf nodes with no children), in insertion order. */\n listBranches(): string[] {\n const leaves: string[] = [];\n for (const id of this.nodes.keys()) {\n if ((this.children.get(id)?.length ?? 0) === 0) leaves.push(id);\n }\n return leaves;\n }\n\n /**\n * The chain from `id` up to (and including) the root — nearest first. Empty if `id` is unknown. Only\n * REGISTERED nodes are included: a dangling `parentId` (edge to a node not in the tree — possible on\n * manifest drift/corruption) terminates the walk rather than emitting a phantom id, and so does an\n * edge back to a node already in the chain, which only damaged edges can form.\n */\n ancestors(id: string): string[] {\n const chain: string[] = [];\n const seen = new Set<string>();\n let cursor: string | undefined = id;\n while (cursor !== undefined && this.nodes.has(cursor) && !seen.has(cursor)) {\n chain.push(cursor);\n seen.add(cursor);\n cursor = this.nodes.get(cursor)!.parentId;\n }\n return chain;\n }\n\n /** Whether a checkpoint id exists in the tree. */\n has(id: string): boolean {\n return this.nodes.has(id);\n }\n\n /** The number of checkpoints in the tree. */\n get size(): number {\n return this.nodes.size;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["logger","logger","logger","logger","logger","logger","DEFAULT_REDACTED_VALUE"],"sources":["../src/synchronous-checkpoint.ts","../src/execution-root.ts","../src/turn-claim.ts","../src/session-base.ts","../src/conversation-transcript.ts","../src/require-hook-type-executors.ts","../src/compaction-orchestrator.ts","../src/context-window-tracker.ts","../src/abortable-approval.ts","../src/checkpointed-approval.ts","../src/auto-mode-gate.ts","../src/consent-scope.ts","../src/tool-argument-canonicalisation.ts","../src/permission-types.ts","../src/tool-hook-helpers.ts","../src/permission-denial-log.ts","../src/tool-permission-wrapper.ts","../src/permission-enforcer.ts","../src/session-components.ts","../src/load-session-for-update.ts","../src/compaction-tool-receipts.ts","../src/session-history-ops.ts","../src/session-lifecycle.ts","../src/session-tool-execution-bridge.ts","../src/session-run-observation.ts","../src/session-resume.ts","../src/session-execution-journal.ts","../src/session-run-options.ts","../src/session-run.ts","../src/session-recoverable.ts","../src/session-runtime-tools.ts","../src/session.ts","../src/session-artifact.ts","../src/scrub-sensitive.ts","../src/session-log-payload.ts","../src/session-logger.ts","../src/external-payload-file-reader.ts","../src/external-payload-resolver.ts","../src/session-log-validation.ts","../src/session-log-replay.ts","../src/checkpoint-tree.ts"],"sourcesContent":["/** A void port must acknowledge synchronously. Observe rejected async work without accepting it. */\nexport function assertSynchronousCheckpoint(result: unknown): void {\n if (\n ((typeof result === 'object' && result !== null) || typeof result === 'function') &&\n 'then' in result &&\n typeof result.then === 'function'\n ) {\n void Promise.resolve(result).catch(() => undefined);\n throw new Error(\n 'Session checkpoint must acknowledge synchronously; asynchronous persistence is unsupported',\n );\n }\n}\n","import { isAbsolutePath } from '@robota-sdk/agent-core';\n\n/**\n * The session's execution root.\n *\n * `Session` used to read `process.cwd()` in its constructor, and that ambient value became the\n * session's identity everywhere it matters — every hook input, `CLAUDE_PROJECT_DIR`, the permission\n * enforcer's root, the persisted record. A session could not be TOLD where it ran, so a subagent ran\n * in its parent's directory rather than its own workspace, while the subagent spawn contract had\n * declared `cwd` required all along.\n *\n * Making it a required TypeScript field is necessary and not sufficient. This package's tsconfig\n * excludes `*.test.ts`, and a JavaScript consumer is not type-checked at all — so without a runtime\n * check the field would simply be `undefined`, and the session would report a root it does not have\n * while everything downstream quietly used nothing. Silence is not success: refuse instead.\n */\nexport function requireExecutionRoot(cwd: unknown): string {\n if (typeof cwd !== 'string' || cwd.length === 0) {\n throw new Error(\n 'Session requires `cwd`: the absolute path this session executes in. It feeds ' +\n 'every hook input, CLAUDE_PROJECT_DIR, the permission root and the persisted record. Pass ' +\n '`process.cwd()` explicitly if that is genuinely what you mean.',\n );\n }\n // ABSOLUTE, not merely present. A relative root is resolved against `process.cwd()` by everything\n // downstream, so accepting one would let the ambient read this change removes back in through the\n // VALUE instead of through its absence — the same defect wearing a different shape.\n if (!isAbsolutePath(cwd)) {\n throw new Error(\n `Session requires an ABSOLUTE \\`cwd\\`; got ${JSON.stringify(cwd)}. A relative ` +\n 'root is resolved against the process directory downstream, which is the ambient value this ' +\n 'field exists to replace. Resolve it at your composition root.',\n );\n }\n return cwd;\n}\n","/**\n * Thrown when a turn is started on a session that is already running one.\n *\n * A distinct type rather than a bare `Error`, because the point of giving the session a claim is to\n * let consumers STOP maintaining their own busy flags — and a consumer that has to regex-match an\n * error message to tell \"busy, retry later\" apart from a provider failure has not been given\n * anything it can act on. Follows `CompactionError`, this package's existing precedent.\n */\nexport class SessionBusyError extends Error {\n /** Always `true`: the caller can run this turn later; nothing about the session is broken. */\n readonly recoverable = true;\n\n constructor(message: string) {\n super(message);\n this.name = 'SessionBusyError';\n }\n}\n\n/**\n * The identity of the turn a session is currently running.\n *\n * A session used to express \"is something running?\" as a bare `AbortController | null` field that\n * `run()` overwrote on entry. That field was doing three jobs at once — cancellation channel, busy\n * flag, and turn identity — and it could only do them for ONE turn, so a second concurrent `run()`\n * orphaned the first: `abort()` reached only whichever turn held the field, and the first turn to\n * finish cleared it in its `finally`, making `abort()` on the survivor a silent no-op. `isRunning()`\n * read the same field, so it answered about whichever turn happened to own it. That is why consumers\n * of this library grew their own busy flags rather than trusting it.\n *\n * The fix is to give the unit of work an OWNER. A claim is taken synchronously, it belongs to the\n * caller that took it, and ONLY that caller releases it.\n *\n * REFUSAL, not pre-emption. A session is a single conversation: two turns interleaving on it produce\n * a history neither of them wrote, and silently cancelling the first would discard work the caller\n * never asked to abandon. `claim()` throws {@link SessionBusyError}, whose message names the three\n * ways forward.\n */\nexport class TurnClaim {\n private controller: AbortController | null = null;\n private settlement: Promise<void> = Promise.resolve();\n private settle: (() => void) | undefined;\n\n /** Resolves when the currently claimed turn has finished unwinding. */\n get drained(): Promise<void> {\n return this.settlement;\n }\n\n /**\n * Take the claim for a new turn, or throw if one is already held.\n *\n * MUST be called before the first `await` in the turn — a check that yields first is not a claim,\n * it is a TOCTOU window, and two callers can pass it in the same tick.\n *\n * @throws {SessionBusyError} if a turn is already running, INCLUDING one that has been aborted but\n * has not finished unwinding. See {@link abort} for why that case is not an exception.\n */\n claim(): AbortController {\n if (this.controller !== null) {\n throw new SessionBusyError(\n 'This session is already running a turn. A session is a single conversation: await the ' +\n 'turn in flight, abort() it and await it, or use a separate session for concurrent work.',\n );\n }\n this.settlement = new Promise<void>((resolve) => {\n this.settle = resolve;\n });\n this.controller = new AbortController();\n return this.controller;\n }\n\n /**\n * Release the claim — but only if `controller` is still the one holding it.\n *\n * The ownership check is what stops the original defect from reappearing in a new shape: a turn\n * that released unconditionally in its `finally` could free a claim a LATER turn already took, and\n * `isRunning()` would then report idle while a turn was in flight.\n */\n release(controller: AbortController): void {\n if (this.controller === controller) {\n this.controller = null;\n this.settle?.();\n this.settle = undefined;\n }\n }\n\n /**\n * Signal the running turn to stop. Idempotent; a no-op if nothing is running.\n *\n * This does NOT release the claim, and that is deliberate. An earlier version cleared it here, so\n * `isRunning()` answered `false` the instant `abort()` returned — while the aborted turn was still\n * unwinding, still able to write history and finish tool calls. A new `run()` could then claim the\n * session and interleave with it: exactly the two-turns-on-one-session defect the claim\n * prevents, just moved behind the abort boundary. Review of the first draft caught it.\n *\n * A turn is not over when it is asked to stop; it is over when it has stopped. The claim is\n * therefore held until the owning turn's `finally` releases it, and until then `isRunning()` says\n * `true` and a further `run()` is refused. Cancel and restart is `abort()`, then AWAIT the turn,\n * then `run()` — which is what every caller in this repo already does.\n */\n abort(): void {\n this.controller?.abort();\n }\n\n isRunning(): boolean {\n return this.controller !== null;\n }\n}\n","import { requireExecutionRoot } from './execution-root.js';\nimport { TurnClaim } from './turn-claim.js';\n\nimport type { ContextWindowTracker, TAutoCompactThreshold } from './context-window-tracker.js';\nimport type { CompactionOrchestrator } from './compaction-orchestrator.js';\nimport type { PermissionEnforcer } from './permission-enforcer.js';\nimport type { IPermissionDenial } from './permission-denial-log.js';\nimport type {\n ConversationAgent,\n IAIProvider,\n IContextWindowState,\n IHistoryEntry,\n IToolSchema,\n TModelEffort,\n TModelEffortSelection,\n TPermissionMode,\n TToolParameters,\n TUniversalMessage,\n} from '@robota-sdk/agent-core';\n\nexport abstract class SessionBase {\n abstract assertInputAllowed(): void;\n protected abstract readonly agent: ConversationAgent;\n protected abstract readonly permissionEnforcer: PermissionEnforcer;\n protected abstract readonly contextTracker: ContextWindowTracker;\n protected abstract readonly compactionOrchestrator: CompactionOrchestrator;\n protected abstract permissionMode: TPermissionMode;\n protected abstract activePresetId: string;\n protected abstract parallelSubagentsEnabled: boolean;\n protected abstract readonly sessionId: string;\n protected abstract readonly aiProvider: IAIProvider;\n protected abstract readonly toolSchemas: IToolSchema[];\n protected abstract model: string;\n protected abstract systemMessage: string;\n protected abstract messageCount: number;\n /** The session's execution root — owned here, with the check that it was supplied. */\n protected readonly cwd: string;\n\n protected constructor(cwd: string) {\n this.cwd = requireExecutionRoot(cwd);\n }\n /**\n * The turn currently running, and its owner. Was a bare `AbortController | null` that\n * `run()` overwrote, which is why `abort()` and `isRunning()` below could answer about a turn that\n * was not the one in flight. See `turn-claim.ts`.\n */\n protected readonly turnClaim = new TurnClaim();\n private readonly permissionModeGuards = new Set<(next: TPermissionMode) => void>();\n\n getPermissionMode(): TPermissionMode {\n return this.permissionMode;\n }\n\n /** Change the active permission mode — future tool calls will use the new mode. */\n setPermissionMode(mode: TPermissionMode): void {\n for (const guard of this.permissionModeGuards) guard(mode);\n this.permissionMode = mode;\n }\n\n /** Register a synchronous policy check at the single session-mode mutation boundary. */\n addPermissionModeGuard(guard: (next: TPermissionMode) => void): () => void {\n this.permissionModeGuards.add(guard);\n return () => this.permissionModeGuards.delete(guard);\n }\n\n /** Read the active preset id (runtime state). */\n getActivePresetId(): string {\n return this.activePresetId;\n }\n\n /**\n * Set the active preset id. PURE STATE — this only records which preset is active;\n * it does not re-apply any preset options (permission/model/persona). Higher layers\n * own re-application.\n */\n setActivePresetId(id: string): void {\n this.activePresetId = id;\n }\n\n /** Whether subagent dispatch is currently allowed for this session (runtime gate). */\n getParallelSubagentsEnabled(): boolean {\n return this.parallelSubagentsEnabled;\n }\n\n /** Toggle subagent dispatch live. Only effective if the agent runtime was built at assembly. */\n setParallelSubagentsEnabled(enabled: boolean): void {\n this.parallelSubagentsEnabled = enabled;\n }\n\n getSessionId(): string {\n return this.sessionId;\n }\n\n /**\n * The session's execution root.\n *\n * Readable because a caller that derives something FROM the session — a fork, a subagent, a hook\n * input — must be able to ask which root this session actually runs in. Re-deriving it from\n * `process.cwd()` is how the two silently diverged.\n */\n getCwd(): string {\n return this.cwd;\n }\n\n getSystemMessage(): string {\n return this.systemMessage;\n }\n\n /**\n * Replace the active system message and propagate it so the next provider request carries it.\n * Records the live value on `this.systemMessage` (re-injected on compaction) and delegates to\n * `ConversationAgent.updateSystemPrompt`, which updates the single-source `config.systemMessage` and the live\n * conversation store head. The system prompt is an agent-level concern, not model config, so this\n * does not route through `setModel`. Used by persona application, the self-verification toggle, and\n * AGENTS.md/CLAUDE.md staleness refresh.\n */\n updateSystemMessage(newMessage: string): void {\n this.assertInputAllowed();\n this.systemMessage = newMessage;\n this.agent.updateSystemPrompt(newMessage);\n }\n\n /**\n * Re-apply model options to the live session (model/effort re-application seam).\n *\n * Propagates model/effort/temperature/maxOutputTokens to the agent via `agent.setModel` so the\n * next call reflects them, and updates `this.model` to keep `getModelId()` accurate. The preset\n * `maxOutputTokens` field maps to the agent's `maxTokens` channel. Absent fields are left untouched.\n */\n async applyModelOptions(options: {\n model?: string;\n effort?: TModelEffortSelection;\n temperature?: number;\n maxOutputTokens?: number;\n }): Promise<void> {\n // `setModel` requires the agent to be fully initialized. On a fresh interactive session the\n // agent initializes lazily on the first `run()`, so a live model change before any message\n // (e.g. `/preset` right after launch) would otherwise hit the \"must be fully initialized\"\n // guard. Bring the agent to a ready state first — idempotent and side-effect-free.\n await this.agent.ensureReady();\n const nextModel = options.model ?? this.model;\n // The system prompt is not model config; it is updated independently via updateSystemMessage.\n this.agent.setModel({\n provider: this.aiProvider.name,\n model: nextModel,\n ...(options.effort !== undefined && { effort: options.effort }),\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(options.maxOutputTokens !== undefined && { maxTokens: options.maxOutputTokens }),\n });\n this.setActiveModel(nextModel);\n }\n\n protected setActiveModel(model: string): void {\n this.model = model;\n this.contextTracker.setModel(model);\n this.compactionOrchestrator.setModel(model);\n }\n\n /** Read the selection for the next model call; provider default remains `auto`. */\n getModelEffort(): TModelEffortSelection {\n // Some lightweight session doubles intentionally implement only the execution surface. Keep\n // this read-only projection total for those callers; the real ConversationAgent instance exposes getModel.\n const getModel = (\n this.agent as ConversationAgent & { getModel?: () => { effort?: TModelEffortSelection } }\n ).getModel;\n if (getModel === undefined) return 'auto';\n try {\n return getModel.call(this.agent).effort ?? 'auto';\n } catch (error) {\n // Preserve the agent's own [LIFECYCLE] error when a destroyed agent is subsequently run.\n if (error instanceof Error && /disposed/i.test(error.message)) return 'auto';\n throw error;\n }\n }\n\n /** Run an operation with a temporary effort override and restore it on every exit path. */\n async withScopedModelEffort<T>(effort: TModelEffort, operation: () => Promise<T>): Promise<T> {\n const previous = this.getModelEffort();\n await this.applyModelOptions({ effort });\n try {\n return await operation();\n } finally {\n await this.applyModelOptions({ effort: previous });\n }\n }\n\n /**\n * Re-apply the agent's identity label to a LIVE session.\n *\n * A preset's `agentName` reached the agent only at construction, so\n * starting with a preset set the name while switching to the SAME preset mid-session left the old\n * one — one preset with two answers, decided by when it was chosen.\n *\n * Goes through `updateConfiguration`, the agent's own config seam: the agent's `name` reads THROUGH\n * its config, so writing the config is the whole rename and no copy is left stale.\n */\n async applyAgentName(name: string): Promise<void> {\n await this.agent.updateConfiguration({ name });\n }\n\n getToolSchemas(): IToolSchema[] {\n return this.toolSchemas;\n }\n\n getMessageCount(): number {\n return this.messageCount;\n }\n\n /** Get tools that have been session-approved (via \"Allow always\" choice). */\n /**\n * Re-apply a preset's tool lists to the live enforcer.\n *\n * The BASE it composes onto is the session's configured rules minus whatever a previous preset\n * contributed — which is why the enforcer keeps the original: an allowlist REPLACES the preset\n * layer's contribution rather than accumulating across successive `/preset` switches, while a\n * denial UNIONS because it must not be weakened by a later layer that forgot to repeat it.\n */\n applyPresetToolLists(preset: {\n allowedTools?: readonly string[];\n deniedTools?: readonly string[];\n }): void {\n this.permissionEnforcer.applyPresetToolLists(preset);\n }\n\n /**\n * The rules this session's gate reads right now — settings, preset lists and command auto-allows\n * together — so a subagent inherits what the parent actually enforces. Session-scoped\n * \"allow always\" consent is not included: it was given for this session's context.\n */\n getPermissionRules(): { allow: string[]; deny: string[]; ask: string[] } {\n const rules = this.permissionEnforcer.currentPermissionRules();\n return { allow: [...rules.allow], deny: [...rules.deny], ask: [...rules.ask] };\n }\n\n getSessionAllowedTools(): string[] {\n return this.permissionEnforcer.getSessionAllowedTools();\n }\n\n /**\n * Decide an action that reaches `toolName`'s effect by another route (a command that starts a\n * process), so that route cannot be a way around the tool's own permission: the PreToolUse hooks\n * and guardrails, then the rules, mode, remembered consent and the prompt. The command sandbox's\n * auto-approval never applies, because the action does not run inside that sandbox.\n */\n checkToolPermission(\n toolName: string,\n toolParameters: TToolParameters,\n signal?: AbortSignal,\n ): Promise<boolean> {\n return this.permissionEnforcer.checkDelegatedToolCall(toolName, toolParameters, signal);\n }\n\n /** `auto` mode hands decisions to a classifier, so a session without one cannot enter it. */\n protected requireClassifierFor(mode: TPermissionMode): void {\n if (mode === 'auto' && !this.permissionEnforcer.hasPermissionClassifier()) {\n throw new Error('Auto mode is unavailable: this session has no permission classifier.');\n }\n }\n\n /**\n * Let the call behind a classifier denial (by its index in the recent denials) run once when the\n * model tries it again. Returns the denial, or `undefined` when the index names no classifier\n * denial.\n */\n retryPermissionDenial(index: number): IPermissionDenial | undefined {\n return this.permissionEnforcer.allowRetryOfDenial(index);\n }\n\n /** The calls this session refused, most recent first. */\n getRecentPermissionDenials(): readonly IPermissionDenial[] {\n return this.permissionEnforcer.getRecentDenials();\n }\n\n clearSessionAllowedTools(): void {\n this.permissionEnforcer.clearSessionAllowedTools();\n }\n\n /** Abort the currently running execution. No-op if nothing is running. */\n abort(): void {\n this.turnClaim.abort();\n }\n\n isRunning(): boolean {\n return this.turnClaim.isRunning();\n }\n\n getContextState(): IContextWindowState {\n return this.contextTracker.getContextState();\n }\n\n /** Estimate context usage from current conversation history (used after session restore). */\n syncContextFromHistory(): void {\n this.contextTracker.updateFromHistory(this.agent.getHistory());\n }\n\n getAutoCompactThreshold(): TAutoCompactThreshold {\n return this.contextTracker.getAutoCompactThreshold();\n }\n\n setAutoCompactThreshold(threshold: number | false): void {\n this.contextTracker.setAutoCompactThreshold(threshold);\n }\n\n getHistory(): TUniversalMessage[] {\n return structuredClone(this.agent.getHistory());\n }\n\n getFullHistory(): IHistoryEntry[] {\n return structuredClone(this.agent.getFullHistory());\n }\n\n getSessionTokenUsage(): { inputTokens: number; outputTokens: number } | undefined {\n let inputTokens = 0;\n let outputTokens = 0;\n let found = false;\n for (const entry of this.getFullHistory()) {\n if (entry.category !== 'event' || entry.type !== 'usage-summary') continue;\n const snap = entry.data as { promptTokens?: number; completionTokens?: number } | undefined;\n inputTokens += snap?.promptTokens ?? 0;\n outputTokens += snap?.completionTokens ?? 0;\n found = true;\n }\n return found ? { inputTokens, outputTokens } : undefined;\n }\n\n getModelId(): string {\n return this.model;\n }\n\n /**\n * The tool schemas the model is offered at the next request.\n *\n * The offered set, not the registered one: a deferred tool that has not been loaded is absent,\n * because it is absent from the request. `/context` reads this to report what the tool schemas\n * actually cost, which is the only surface that makes deferral's saving observable.\n */\n getOfferedToolSchemas(): IToolSchema[] {\n return this.agent.getOfferedToolSchemas();\n }\n\n /** The provider the session sends its turns to now; a provider switch replaces it. */\n getProvider(): IAIProvider {\n return this.aiProvider;\n }\n\n getProviderId(): string {\n return this.aiProvider.name;\n }\n\n /** Add an event entry to history (not a chat message) */\n addHistoryEntry(entry: IHistoryEntry): void {\n this.assertInputAllowed();\n this.agent.addHistoryEntry(entry);\n }\n\n /** Inject a message into conversation history without execution (used for session restore). */\n injectMessage(\n role: 'user' | 'assistant' | 'system' | 'tool',\n content: string,\n options?: { toolCallId?: string; name?: string },\n ): void {\n this.assertInputAllowed();\n this.agent.injectMessage(role, content, options);\n }\n\n /**\n * Inject a full TUniversalMessage preserving all fields (toolCalls, toolCallId, null content).\n * Used during session restore to correctly reconstruct tool_use+tool_result pairs.\n */\n injectRawMessage(msg: TUniversalMessage): void {\n this.assertInputAllowed();\n this.agent.injectRawMessage(msg);\n }\n\n clearHistory(): void {\n this.assertInputAllowed();\n this.agent.clearHistory();\n this.contextTracker.reset();\n }\n}\n","/**\n * The one text rendering of a conversation that is handed to a model as prompt input.\n *\n * Compaction summarises it and the advisor reads it. Both need the same things kept: which tool the\n * model called with which arguments, what came back, and who wrote each user message — a message a\n * peer session sent is not the operator's own words and must not read as if it were.\n *\n * Every message is exactly one line: the label is written here and the content is JSON-encoded, so\n * no newline inside a message, a tool result or a peer's text can start a line of its own. That is\n * what keeps a peer from writing an unmarked `user:` line or a tool result from closing a block the\n * reader was told to trust.\n */\n\nimport {\n nonVisualObservationText,\n peerDriverOf,\n printablePeerDriver,\n} from '@robota-sdk/agent-core';\n\nimport type { TUniversalMessage } from '@robota-sdk/agent-core';\n\nfunction encode(content: unknown): string {\n return JSON.stringify(typeof content === 'string' ? content : (content ?? ''));\n}\n\nfunction encodeMessage(message: TUniversalMessage): string {\n const observations = (message.parts ?? []).flatMap((part) => {\n const observation = nonVisualObservationText(part);\n return observation === undefined ? [] : [observation];\n });\n return observations.length > 0\n ? JSON.stringify({ content: message.content, observations })\n : encode(message.content);\n}\n\n/**\n * The same peer attribution the model request carries (agent-core `peerDriverOf`), and the same\n * printable form of the id, which arrives from the sending side.\n */\nfunction userLabel(message: TUniversalMessage): string {\n const peer = peerDriverOf(message);\n return peer ? `user [from ${JSON.stringify(printablePeerDriver(peer))}]` : 'user';\n}\n\nfunction formatMessage(message: TUniversalMessage): string[] {\n switch (message.role) {\n case 'user':\n return [`${userLabel(message)}: ${encodeMessage(message)}`];\n case 'assistant': {\n const lines: string[] = [];\n if (message.content !== null && message.content !== '') {\n lines.push(`assistant: ${encodeMessage(message)}`);\n }\n for (const call of message.toolCalls ?? []) {\n lines.push(\n `assistant tool call ${JSON.stringify(call.function.name)} [${JSON.stringify(call.id)}]: ${encode(call.function.arguments)}`,\n );\n }\n return lines.length > 0 ? lines : [`assistant: \"\"`];\n }\n case 'tool':\n return [\n `tool result${message.name ? ` ${JSON.stringify(message.name)}` : ''} [${JSON.stringify(message.toolCallId)}]: ${encodeMessage(message)}`,\n ];\n case 'system':\n return [`system: ${encodeMessage(message)}`];\n }\n}\n\n/**\n * Render each message as one entry, in order. An entry is one line, except an assistant message\n * with several tool calls, which is one line per call. One entry per message so a caller that must\n * drop the oldest part of a long conversation can drop whole messages.\n */\nexport function formatConversationEntries(history: readonly TUniversalMessage[]): string[] {\n return history.map((message) => formatMessage(message).join('\\n'));\n}\n","import type { IHookTypeExecutor } from '@robota-sdk/agent-core';\n\n/**\n * The executors a session runs hook handlers through. REQUIRED, with no host default.\n *\n * The shell and HTTP executors drive a Node host, so a platform-neutral session cannot reach for\n * them itself; whoever composes the session says which handler types it can run. A JavaScript\n * consumer is not type-checked, so without a runtime check an omitted list would be `undefined` and\n * every configured hook would quietly run nothing — silence is not success, so refuse instead (the\n * same reasoning as `requireExecutionRoot`).\n */\nexport function requireHookTypeExecutors(\n executors: unknown,\n owner: string,\n): readonly IHookTypeExecutor[] {\n if (!Array.isArray(executors)) {\n throw new Error(\n `${owner} requires \\`hookTypeExecutors\\`: the executors that run a hook's handlers. On Node ` +\n 'pass `[new CommandExecutor(), new HttpExecutor()]` from `@robota-sdk/agent-core/node`; ' +\n 'pass `[]` to run no hook handlers.',\n );\n }\n return executors as readonly IHookTypeExecutor[];\n}\n","/**\n * CompactionOrchestrator — handles conversation compaction (summarization)\n * to free context window space.\n *\n * Extracted from Session to separate compaction logic from conversation management.\n */\n\nimport {\n callJournaledProvider,\n createAbortError,\n randomId,\n runHooks,\n} from '@robota-sdk/agent-core';\n\nimport { formatConversationEntries } from './conversation-transcript.js';\nimport { requireHookTypeExecutors } from './require-hook-type-executors.js';\n\nimport type { TCompactTrigger } from './session-types.js';\nimport type {\n IAIProvider,\n IExecutionJournal,\n TUniversalMessage,\n THooksConfig,\n IHookInput,\n IHookTypeExecutor,\n ISubprocessTraceEnv,\n} from '@robota-sdk/agent-core';\n\n/**\n * Thrown when a compaction summary is invalid (non-string or empty provider content).\n * Conversation history is append-only source data — callers must not clear or replace\n * it when this is thrown (see SPEC § Compaction Failure Contract).\n */\nexport class CompactionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CompactionError';\n }\n}\n\n/**\n * Default base template for the compaction summarization prompt — the one model-facing\n * prompt surface this package owns (declared in SPEC § Boundaries). Intentionally\n * domain-neutral: it must not assume a software-development conversation. Replaceable\n * wholesale via {@link ICompactionOptions.basePrompt}.\n */\nexport const DEFAULT_COMPACTION_PROMPT = [\n 'Summarize the following conversation concisely, preserving:',\n \"- User's original requests and goals\",\n '- Key decisions, conclusions, and important state',\n '- Identifiers, names, and references needed to continue the work',\n '- Current task status and next steps',\n \"Drop verbose intermediate outputs and exploratory work that didn't lead to results.\",\n].join('\\n');\n\nexport interface ICompactionOptions {\n sessionId: string;\n cwd: string;\n model: string;\n hooks?: Record<string, unknown>;\n compactInstructions?: string;\n /**\n * Replaces the entire base instruction template of the compaction prompt\n * (default: {@link DEFAULT_COMPACTION_PROMPT}). Focus instructions and the\n * formatted conversation are appended after it.\n */\n basePrompt?: string;\n /** The executors that run hook handlers; required, there is no host default. */\n hookTypeExecutors: readonly IHookTypeExecutor[];\n}\n\nexport class CompactionOrchestrator {\n private readonly sessionId: string;\n private readonly cwd: string;\n private model: string;\n private readonly hooks?: Record<string, unknown>;\n private readonly compactInstructions?: string;\n private readonly basePrompt?: string;\n private readonly hookTypeExecutors: readonly IHookTypeExecutor[];\n\n constructor(options: ICompactionOptions) {\n this.sessionId = options.sessionId;\n this.cwd = options.cwd;\n this.model = options.model;\n this.hooks = options.hooks;\n this.compactInstructions = options.compactInstructions;\n this.basePrompt = options.basePrompt;\n this.hookTypeExecutors = requireHookTypeExecutors(\n options.hookTypeExecutors,\n 'CompactionOrchestrator',\n );\n }\n\n setModel(model: string): void {\n this.model = model;\n }\n\n /**\n * Run compaction — summarize the conversation to free context space.\n * @param provider - The AI provider to use for summarization\n * @param history - The messages to summarise. Must not be empty: whether there is anything worth\n * compacting is the caller's judgement, made before it commits to replacing the conversation.\n * @param instructions - Optional focus instructions for the summary\n * @param signal - The turn's cancellation signal. Checked before the provider call\n * and again after it: an abort throws rather than returning, so the caller's existing\n * leave-history-untouched path covers a cancel as well as a failure.\n * @param hookTraceEnv - The prompt's trace for PreCompact's command hooks, only inside a prompt\n * @returns The generated summary string (always a non-empty string)\n * @throws {CompactionError} when `history` is empty, or when the provider returns a non-string or\n * empty summary — callers must leave the conversation history untouched in every such case\n */\n async compact(\n provider: IAIProvider,\n history: TUniversalMessage[],\n instructions?: string,\n signal?: AbortSignal,\n trigger: TCompactTrigger = 'manual',\n hookTraceEnv?: ISubprocessTraceEnv,\n executionJournal?: IExecutionJournal,\n ): Promise<string> {\n // FIRST, before the emptiness check. Review found that ordering the other way\n // returned a summary for an already-cancelled turn — and the caller replaces the conversation\n // with whatever this returns, so a cancel could still clear it and inject an empty summary.\n signal?.throwIfAborted();\n // This used to `return ''`, contradicting the contract two lines of docblock above it\n // (\"always a non-empty string\") — and the caller wrote that empty string over the conversation as\n // a summary. Deciding that an empty conversation is a no-op is the CALLER's judgement, made\n // before it commits to replacing anything; by the time execution is here, the caller has already\n // decided there is something to summarise, so an empty history means that decision was wrong.\n if (history.length === 0) {\n throw new CompactionError(\n 'Compaction was asked to summarise an empty history; conversation history preserved untouched',\n );\n }\n\n // Fire PreCompact hook\n const preHookInput: IHookInput = {\n session_id: this.sessionId,\n cwd: this.cwd,\n hook_event_name: 'PreCompact',\n trigger,\n };\n await runHooks(\n this.hooks as THooksConfig | undefined,\n 'PreCompact',\n preHookInput,\n this.hookTypeExecutors,\n hookTraceEnv,\n );\n\n // Build compaction prompt\n const compactPrompt = this.buildCompactionPrompt(history, instructions);\n\n // Call provider to generate summary\n const callId = randomId();\n const executionId = randomId();\n let route = provider.resolveModelRoute?.(this.model, executionId) ?? {\n provider: provider.name,\n model: this.model,\n };\n const summaryMessage = await callJournaledProvider(\n async (messages, options) => {\n try {\n return await provider.chat(messages, options);\n } catch (error) {\n if (signal?.aborted) throw createAbortError('Compaction cancelled', error);\n throw error;\n }\n },\n [\n {\n id: randomId(),\n role: 'user',\n content: compactPrompt,\n state: 'complete' as const,\n timestamp: new Date(),\n },\n ],\n {\n model: this.model,\n executionId,\n // Override defaults inside provider wrappers without mutating shared provider state.\n onTextDelta: () => {},\n onModelFallback: (notice) => {\n route = { ...notice.to };\n },\n toolChoice: 'none',\n // The history was sized to this model's window; a smaller one could not read it all.\n preserveContextWindow: true,\n ...(signal !== undefined ? { signal } : {}),\n },\n executionJournal\n ? {\n journal: executionJournal,\n executionId,\n callId,\n route: () => ({ providerId: route.provider, modelId: route.model }),\n }\n : undefined,\n );\n // The caller REPLACES the whole conversation with what this returns, so returning a\n // summary after a cancel is what destroyed it. Throwing puts an abort on the same path\n // already built for an invalid summary — history left untouched.\n signal?.throwIfAborted();\n if (typeof summaryMessage.content !== 'string' || summaryMessage.content.trim() === '') {\n throw new CompactionError(\n `Compaction produced an invalid summary (provider=${provider.name}, content type=${typeof summaryMessage.content}); conversation history preserved untouched`,\n );\n }\n\n return summaryMessage.content;\n }\n\n /** Build the compaction prompt from conversation history */\n private buildCompactionPrompt(history: TUniversalMessage[], instructions?: string): string {\n const instructionBlock = instructions ?? this.compactInstructions ?? '';\n const instructionSection = instructionBlock ? `\\nAdditional focus:\\n${instructionBlock}\\n` : '';\n\n const formattedHistory = formatConversationEntries(history).join('\\n');\n\n return [\n this.basePrompt ?? DEFAULT_COMPACTION_PROMPT,\n instructionSection,\n '',\n 'Conversation:',\n formattedHistory,\n ].join('\\n');\n }\n}\n","/**\n * ContextWindowTracker — tracks token usage and context window state.\n *\n * Extracted from Session to separate context monitoring from conversation management.\n */\n\nimport { estimateContextTokensFromMessages, getModelContextWindow } from '@robota-sdk/agent-core';\n\nimport type { IContextWindowState, TUniversalMessage } from '@robota-sdk/agent-core';\n\n/** Percentage conversion factor */\nconst PERCENT = 100;\n\n/** Auto-compact when context usage reaches this fraction */\nexport const AUTO_COMPACT_THRESHOLD = 0.835;\n\nexport type TAutoCompactThreshold = number | false;\n\nexport class ContextWindowTracker {\n private contextUsedTokens = 0;\n private contextMaxTokens: number;\n private readonly contextMaxTokensOverride: number | undefined;\n private autoCompactThreshold: TAutoCompactThreshold;\n\n constructor(\n model: string,\n contextMaxTokens?: number,\n autoCompactThreshold?: TAutoCompactThreshold,\n ) {\n this.contextMaxTokensOverride = contextMaxTokens;\n this.contextMaxTokens = contextMaxTokens ?? getModelContextWindow(model);\n this.autoCompactThreshold = normalizeAutoCompactThreshold(autoCompactThreshold);\n }\n\n /** Get current context window state */\n getContextState(): IContextWindowState {\n const usedPercentage = Math.min(\n PERCENT,\n (this.contextUsedTokens / this.contextMaxTokens) * PERCENT,\n );\n return {\n maxTokens: this.contextMaxTokens,\n usedTokens: this.contextUsedTokens,\n usedPercentage: Math.round(usedPercentage * PERCENT) / PERCENT,\n remainingPercentage: Math.round((PERCENT - usedPercentage) * PERCENT) / PERCENT,\n };\n }\n\n /** Whether auto-compaction threshold has been exceeded */\n shouldAutoCompact(requestMessages?: TUniversalMessage[]): boolean {\n if (this.autoCompactThreshold === false) {\n return false;\n }\n const usedTokens = requestMessages\n ? estimateContextTokensFromMessages(requestMessages).usedTokens\n : this.contextUsedTokens;\n return usedTokens >= this.contextMaxTokens * this.autoCompactThreshold;\n }\n\n /** Follow the active model unless the host supplied its own context budget. */\n setModel(model: string): void {\n this.contextMaxTokens = this.contextMaxTokensOverride ?? getModelContextWindow(model);\n }\n\n /** The auto-compaction policy for this tracker. */\n getAutoCompactThreshold(): TAutoCompactThreshold {\n return this.autoCompactThreshold;\n }\n\n /** Update the auto-compaction policy for this tracker. */\n setAutoCompactThreshold(autoCompactThreshold: TAutoCompactThreshold): void {\n this.autoCompactThreshold = normalizeAutoCompactThreshold(autoCompactThreshold);\n }\n\n /**\n * Estimate token usage from conversation history.\n *\n * Uses the shared core estimator (`estimateContextTokensFromMessages`) so session display,\n * /context, auto-compact, and core execution guards reason about the same effective token state.\n * That estimator prefers the provider's actual reported token count (which includes the system\n * prompt and tool schemas) over a raw serialized-history char heuristic, falling back to the\n * serialized estimate only when no provider usage is present on the latest message.\n */\n updateFromHistory(history: TUniversalMessage[]): void {\n this.contextUsedTokens = estimateContextTokensFromMessages(history).usedTokens;\n }\n\n /** Reset token tracking */\n reset(): void {\n this.contextUsedTokens = 0;\n }\n}\n\nfunction normalizeAutoCompactThreshold(\n autoCompactThreshold: TAutoCompactThreshold | undefined,\n): TAutoCompactThreshold {\n if (autoCompactThreshold === undefined) {\n return AUTO_COMPACT_THRESHOLD;\n }\n if (autoCompactThreshold === false) {\n return false;\n }\n if (\n !Number.isFinite(autoCompactThreshold) ||\n autoCompactThreshold <= 0 ||\n autoCompactThreshold > 1\n ) {\n throw new RangeError('autoCompactThreshold must be a number greater than 0 and at most 1.');\n }\n return autoCompactThreshold;\n}\n","import type {\n IPermissionAskContext,\n TPermissionHandler,\n TPermissionResult,\n} from './permission-types.js';\nimport type { ITerminalOutput, TToolArgs } from '@robota-sdk/agent-core';\n\n/**\n * Wait for an approval, or for the turn to be cancelled — whichever happens first.\n *\n * A human-approval prompt is a wait with no natural end. `abort()` reached the provider\n * and the tool-start check but not this, so a turn parked here ran until somebody answered, and since\n * the session's claim is held until the turn unwinds, the session stayed busy with no way\n * for the caller to clear it short of discarding it.\n *\n * A cancelled approval resolves to `false`, NOT to a rejection and never to `true`. Two reasons, and\n * the first is the load-bearing one:\n *\n * - **Fail closed.** If cancelling read as approval, aborting a turn would become a way to run an\n * unapproved tool. Denial is the same answer the enforcer already gives when no approver is\n * attached.\n * - The caller (`permission-enforcer`'s tool wrapper) must never throw — a throw there records an\n * assistant `tool_use` with no matching `tool_result` and corrupts the conversation.\n *\n * The listener is removed on every path, so an approval that arrives after the abort does not keep a\n * handler alive on a long-lived signal.\n */\nasync function raceAbort(\n approval: Promise<TPermissionResult>,\n signal?: AbortSignal,\n): Promise<TPermissionResult> {\n if (signal === undefined) return approval;\n if (signal.aborted) return false;\n\n let onAbort: (() => void) | undefined;\n try {\n return await Promise.race([\n approval,\n new Promise<TPermissionResult>((resolve) => {\n onAbort = (): void => resolve(false);\n signal.addEventListener('abort', onAbort, { once: true });\n }),\n ]);\n } finally {\n if (onAbort !== undefined) signal.removeEventListener('abort', onAbort);\n }\n}\n\n/**\n * What an approval RESULT means, in one place.\n *\n * The two prompt paths — a consumer `permissionHandler` and an injected `promptForApprovalFn` —\n * interpreted `allow-session` / `allow-project` identically, in two copies. Two readings of \"does\n * this answer grant permission\" that can drift is the shape this repository keeps removing; and the\n * caller now only has to know WHICH prompt to run, not what its answer implies.\n *\n * Side effects are returned rather than performed, so this stays a pure decision the enforcer applies.\n */\ninterface IApprovalOutcome {\n allowed: boolean;\n rememberForSession: boolean;\n rememberForProject: boolean;\n}\n\nfunction interpretApproval(result: TPermissionResult): IApprovalOutcome {\n if (result === 'allow-session') {\n return { allowed: true, rememberForSession: true, rememberForProject: false };\n }\n if (result === 'allow-project') {\n return { allowed: true, rememberForSession: true, rememberForProject: true };\n }\n return { allowed: result === true, rememberForSession: false, rememberForProject: false };\n}\n\n/**\n * The whole human-approval path: allow-list, cancellation, prompt, and what the answer means.\n *\n * Separate from the enforcer, and the seam is real\n * rather than convenient — this decides whether a human said yes, while the enforcer decides whether\n * a human is asked at all. Side effects come back as flags so the enforcer keeps ownership of its own\n * allow lists.\n *\n * A human-approval prompt is a wait with NO NATURAL END, which is why the signal matters here more\n * than anywhere else in the permission path: without it an aborted turn parked on a prompt ran until\n * somebody answered, and the session stayed claimed. Cancelling DENIES — a cancelled approval must\n * never read as approval, or aborting a turn becomes a way to run an unapproved tool.\n */\nexport interface IApprovalRequest {\n toolName: string;\n toolArgs: TToolArgs;\n alreadyAllowed: boolean;\n /** A consumer-supplied handler. Takes precedence over `injectedPrompt`, as it always did. */\n handler?: TPermissionHandler;\n injectedPrompt?: (\n terminal: ITerminalOutput,\n toolName: string,\n toolArgs: TToolArgs,\n ) => Promise<TPermissionResult>;\n terminal?: ITerminalOutput;\n signal?: AbortSignal;\n /** The tool call being decided, handed to `handler` so the prompt it raises can name the call. */\n executionId?: string;\n}\n\nexport async function decideApproval(request: IApprovalRequest): Promise<IApprovalOutcome> {\n const denied: IApprovalOutcome = {\n allowed: false,\n rememberForSession: false,\n rememberForProject: false,\n };\n if (request.alreadyAllowed) {\n return { allowed: true, rememberForSession: false, rememberForProject: false };\n }\n // A turn already cancelled asks nobody. Prompting here would put a question on screen\n // for work that is not going to happen.\n if (request.signal?.aborted === true) return denied;\n // Which prompt to run is this module's business too: the enforcer supplies the approvers it has\n // and does not decide between them.\n // The handler is given the same signal the wait observes, so it can withdraw the prompt itself:\n // racing the abort ends this wait, but only the handler can take the question off a screen. A call\n // with neither a signal nor an id asks exactly as before (two arguments), so a handler that counts\n // them sees no change.\n const context: IPermissionAskContext = {\n ...(request.signal ? { signal: request.signal } : {}),\n ...(request.executionId !== undefined ? { executionId: request.executionId } : {}),\n };\n const hasContext = request.signal !== undefined || request.executionId !== undefined;\n const prompt = request.handler\n ? (): Promise<TPermissionResult> =>\n hasContext\n ? request.handler!(request.toolName, request.toolArgs, context)\n : request.handler!(request.toolName, request.toolArgs)\n : request.injectedPrompt && request.terminal\n ? (): Promise<TPermissionResult> =>\n request.injectedPrompt!(request.terminal!, request.toolName, request.toolArgs)\n : undefined;\n // No approval mechanism available — deny by default.\n if (prompt === undefined) return denied;\n return interpretApproval(await raceAbort(prompt(), request.signal));\n}\n","import { ExecutionRecoveryError } from '@robota-sdk/agent-core';\nimport type { IToolContinuation, TToolArgs, TToolParameters } from '@robota-sdk/agent-core';\n\nconst kind = 'robota-session/approval';\n\nexport function hasCheckpointedApproval(continuation: IToolContinuation | undefined): boolean {\n return continuation?.waits.some((wait) => wait.request.kind === kind) ?? false;\n}\n\n/** Current policy is checked by the enforcer; this can answer only the exact saved action. */\nexport async function checkpointedApproval(\n context: {\n sessionId: string;\n cwd: string;\n toolName: string;\n toolArgs: TToolArgs;\n /** The host's canonical spelling of a directory, so a saved approval names the same one. */\n canonicalizePath: (path: string) => string;\n },\n continuation: IToolContinuation | undefined,\n requireApproval: boolean,\n): Promise<boolean | undefined> {\n const waits = continuation?.waits.filter((wait) => wait.request.kind === kind) ?? [];\n if (!requireApproval && !waits.length) return undefined;\n if (!continuation || continuation.action.toolName !== context.toolName)\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_INVALID',\n 'Checkpointed approval requires the current journaled action',\n );\n const cwd = context.canonicalizePath(context.cwd);\n for (const { request } of waits) {\n if (\n request.data.version !== 1 ||\n request.data.sessionId !== context.sessionId ||\n request.data.cwd !== cwd ||\n request.data.toolName !== context.toolName\n )\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_INVALID',\n 'Saved approval belongs to a different Session or tool',\n );\n }\n const answer = await continuation.request({\n kind,\n data: {\n version: 1,\n sessionId: context.sessionId,\n cwd,\n toolName: context.toolName,\n // The Core continuation port validates finite JSON before persisting this value.\n arguments: structuredClone(context.toolArgs) as TToolParameters,\n },\n });\n if (Object.keys(answer).length !== 1 || typeof answer.approved !== 'boolean')\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_INVALID',\n 'Approval response must contain only a boolean approved decision',\n );\n return answer.approved;\n}\n","/**\n * `auto` permission mode: a model classifier decides the calls the mode itself does not.\n *\n * The permission gate still runs first — deny rules, the ceiling, and the reads and workspace edits\n * the mode approves. What it would have asked a person about goes to the classifier instead, unless\n * the user's own `ask` rule names it. A block returns its reason to the model so it can take another\n * route. When the classifier keeps refusing, the mode pauses and asks a person, because a model\n * that is blocked over and over is either stuck or being steered, and neither is for the classifier\n * to resolve.\n */\n\nimport type { TToolArgs } from '@robota-sdk/agent-core';\n\n/** The call the classifier judges. */\nexport interface IClassifiedCall {\n readonly toolName: string;\n readonly toolArgs: TToolArgs;\n readonly cwd: string;\n}\n\nexport interface IClassifierVerdict {\n readonly decision: 'allow' | 'block';\n /** Short, for the model and the user: which rule, and why. */\n readonly reason: string;\n}\n\n/**\n * Judges one call. `undefined` means no usable verdict (an error, a refusal, output that does not\n * parse): the call is not run. It counts toward a run of refusals, so a classifier that cannot\n * answer hands the decision to a person instead of refusing every call.\n */\nexport interface IPermissionClassifier {\n classify(call: IClassifiedCall, signal?: AbortSignal): Promise<IClassifierVerdict | undefined>;\n}\n\n/** Refusals in a row (blocks or unusable verdicts), and blocks in the session, that pause the mode. */\nexport const CONSECUTIVE_BLOCK_LIMIT = 3;\nexport const TOTAL_BLOCK_LIMIT = 20;\n\n/** `reason` is for the user (`/permissions`); `message` is what the model is told. */\nexport type TAutoModeJudgement =\n | { readonly kind: 'allow' }\n | { readonly kind: 'block'; readonly reason: string; readonly message: string }\n | { readonly kind: 'unusable'; readonly reason: string; readonly message: string };\n\nfunction callKey(toolName: string, toolArgs: TToolArgs): string {\n return `${toolName}\\u0000${JSON.stringify(toolArgs)}`;\n}\n\nexport class AutoModeGate {\n private consecutive = 0;\n private total = 0;\n private paused = false;\n /** Calls a person allowed to be retried once after the classifier blocked them. */\n private readonly retries = new Set<string>();\n\n constructor(private readonly classifier: IPermissionClassifier) {}\n\n /** The mode asks a person until one approves. */\n isPaused(): boolean {\n return this.paused;\n }\n\n /** A person approved while paused: the classifier decides again. */\n resume(): void {\n this.paused = false;\n this.consecutive = 0;\n }\n\n /** Let this exact call through once, without the classifier. */\n grantRetry(toolName: string, toolArgs: TToolArgs): void {\n this.retries.add(callKey(toolName, toolArgs));\n }\n\n /** Consume a retry grant for this exact call, if there is one. */\n takeRetry(toolName: string, toolArgs: TToolArgs): boolean {\n return this.retries.delete(callKey(toolName, toolArgs));\n }\n\n async judge(call: IClassifiedCall, signal?: AbortSignal): Promise<TAutoModeJudgement> {\n let verdict: IClassifierVerdict | undefined;\n try {\n verdict = await this.classifier.classify(call, signal);\n } catch {\n // allow-fallback: an unusable verdict is a denial that is reported, not a crash\n verdict = undefined;\n }\n if (verdict === undefined) {\n // A turn the user cancelled is not the classifier failing to answer.\n if (signal?.aborted !== true) this.consecutive += 1;\n const pause = this.consecutive >= CONSECUTIVE_BLOCK_LIMIT;\n if (pause) this.paused = true;\n return {\n kind: 'unusable',\n reason: 'no usable verdict',\n message:\n 'The auto-mode classifier gave no usable verdict, so the call was not run. Try again, ' +\n 'or ask the user to approve it.' +\n (pause ? ' Auto mode is paused: the next calls ask the user.' : ''),\n };\n }\n if (verdict.decision === 'allow') {\n this.consecutive = 0;\n return { kind: 'allow' };\n }\n this.consecutive += 1;\n this.total += 1;\n const pause = this.consecutive >= CONSECUTIVE_BLOCK_LIMIT || this.total >= TOTAL_BLOCK_LIMIT;\n if (this.total >= TOTAL_BLOCK_LIMIT) this.total = 0;\n if (pause) this.paused = true;\n return {\n kind: 'block',\n reason: verdict.reason,\n message:\n `Blocked by the auto-mode classifier: ${verdict.reason}. Do not retry the same action; ` +\n 'take another approach or ask the user.' +\n (pause ? ' Auto mode is paused after repeated blocks: the next calls ask the user.' : ''),\n };\n }\n}\n","/**\n * What \"don't ask again\" GRANTS, as a permission pattern.\n *\n * Session and project consent used to be keyed on the tool NAME: approving `Bash` for `git status`\n * allowed every later `Bash`, approving `WebFetch` for one benign URL allowed every host, and the\n * project-level record (`Tool(*)`) outlived the session. The user was shown one argument and\n * granted all of them, and the prompt never said so.\n *\n * The consent record is now a pattern in the gate's own grammar, projected from the argument by the\n * kind the tool's permission profile declares, so the record, the prompt and the\n * `permissions.allow` rules all speak one language and are matched by one matcher:\n *\n * - `path` → the containing directory: `Read(/w/src/**)` — one approval covers a tree, not a file\n * and not the filesystem;\n * - `url` → the origin: `WebFetch(https://example.com/**)` — a host, not every host;\n * - `command` → the program: `Bash(git *)` — `argv[0]`, the projection the issue names;\n * - `text`, or no declared argument → the tool name: a search query or a glob pattern is not a\n * blast radius, and a tool that declares no argument cannot be narrowed at all.\n *\n * Exact-argument consent would prompt constantly; unbounded consent is what existed. A projection is\n * the design work, and it is stated here once so both prompt surfaces can print it verbatim.\n */\n\nimport { getToolPermissionProfile } from '@robota-sdk/agent-core';\n\nimport type { TToolArgs } from '@robota-sdk/agent-core';\n\n/** Lexical directory of a path, separators normalised; `/` → `/`, `a` → `.`. */\nfunction directoryOf(path: string): string {\n const slashed = path.replace(/\\\\/g, '/');\n const cut = slashed.lastIndexOf('/');\n if (cut < 0) return '.';\n if (cut === 0) return '/';\n return slashed.slice(0, cut);\n}\n\n/** The pattern text (inside the parentheses) consent covers, or undefined for name-only consent. */\nfunction scopeArgument(kind: string, value: string): string | undefined {\n switch (kind) {\n case 'path': {\n const dir = directoryOf(value);\n return dir === '/' ? '/**' : `${dir}/**`;\n }\n case 'url': {\n try {\n const url = new URL(value);\n return `${url.protocol}//${url.host}/**`;\n } catch {\n // allow-fallback: a URL the platform parser refuses is consented to EXACTLY as written —\n // the strict direction — never widened to the tool name.\n return value;\n }\n }\n case 'command': {\n const argv0 = value.trim().split(/\\s+/)[0];\n return argv0 ? `${argv0} *` : undefined;\n }\n default:\n return undefined;\n }\n}\n\n/** The permission pattern a \"don't ask again\" answer for this invocation grants. */\nexport function consentScopeFor(toolName: string, toolArgs: TToolArgs): string {\n const argument = getToolPermissionProfile(toolName).argument;\n if (argument === undefined) return toolName;\n const value = toolArgs[argument.key];\n if (typeof value !== 'string' || value === '') return toolName;\n const scoped = scopeArgument(argument.kind, value);\n return scoped === undefined ? toolName : `${toolName}(${scoped})`;\n}\n","/**\n * Canonicalise a tool invocation's arguments BEFORE the permission gate sees them.\n *\n * `Read`, `Write` and `Edit` declare `filePath` absolute, but nothing makes the model comply. A\n * relative `filePath` reaching the gate as written cannot be compared with an absolute pattern —\n * `Read(/w/**)` against `src/x` — and the gate answers that case \"unevaluable\" (a deny prompts, an\n * allow does not auto-approve) rather than guessing the base. The base is not a guess here: the\n * session's `cwd` is the containment root the tool itself anchors a relative path to, so resolving\n * against it produces the exact path the tool will open, and the gate judges that.\n *\n * Which argument is a path is what the tool's registered permission profile declares\n * (`argument.kind === 'path'`), so this module names no tool. The canonical parameters are what\n * the gate, the logs and the tool all receive — one input, not one for the decision and another\n * for the action.\n */\n\nimport { getToolPermissionProfile, isAbsolutePath, resolvePath } from '@robota-sdk/agent-core';\n\nimport type { TToolParameters } from '@robota-sdk/agent-core';\n\nexport function canonicaliseToolArguments(\n toolName: string,\n parameters: TToolParameters,\n cwd: string,\n): TToolParameters {\n const argument = getToolPermissionProfile(toolName).argument;\n if (argument === undefined || argument.kind !== 'path') return parameters;\n const value = parameters[argument.key];\n if (typeof value !== 'string' || value === '' || isAbsolutePath(value)) return parameters;\n const resolved = resolvePath(cwd, value);\n // A path relative to another drive's current directory stays as written, and the gate asks.\n if (!isAbsolutePath(resolved)) return parameters;\n return { ...parameters, [argument.key]: resolved };\n}\n","import type { IToolAppView, TUniversalMessagePart } from '@robota-sdk/agent-core';\nimport type { IPathProtectionPolicy, TResolveInWorkspace } from '@robota-sdk/agent-core';\n/**\n * Permission types — interfaces and type aliases for permission enforcement.\n */\n\nimport type { IPermissionClassifier } from './auto-mode-gate.js';\nimport type { ISessionLogger } from './session-logger.js';\nimport type { TPermissionMode, TToolArgs } from '@robota-sdk/agent-core';\nimport type {\n IHookTypeExecutor,\n ISpinner,\n ITerminalOutput,\n IToolResult,\n TBackgroundPermissionPolicy,\n} from '@robota-sdk/agent-core';\nimport type {\n TPermissionRequester,\n TPermissionResultValue,\n} from '@robota-sdk/agent-interface/session';\n\nexport type { ISpinner, ITerminalOutput };\nexport type { TPermissionRequester };\n\n/** The part of a sandbox client the permission gate consults. */\nexport interface ICommandSandboxApproval {\n /**\n * Whether `toolName` runs `shellCommand` inside the sandbox and the sandbox's settings let it\n * proceed without a prompt. Only a tool the sandbox actually wraps may answer yes.\n */\n autoApproves(toolName: string, shellCommand: string): boolean;\n}\n\n/**\n * Permission handler result (the union is OWNED by `agent-interface/session` as\n * `TPermissionResultValue`; this name is the session-layer alias, not a second declaration):\n * - true: allow this invocation\n * - false: deny this invocation\n * - 'allow-session': allow this invocation and auto-approve the CONSENT SCOPE — the pattern\n * `consentScopeFor` projects from this invocation's argument, e.g. `Bash(git *)` —\n * for the rest of the session\n * - 'allow-project': allow this invocation and persist that same scope pattern to the project's\n * local settings; the storage location is owned by the consuming layer (via `onProjectAllowTool`)\n */\nexport type TPermissionResult = TPermissionResultValue;\n\n/**\n * Context a caller MAY attach to a permission ask beyond the call itself.\n *\n * `requester` says WHO is asking, when it is not the person's own turn — a background agent's tool\n * call forwarded to the person, say — so a surface can show \"Background agent X wants to …\" instead\n * of an unattributed prompt. `signal` cancels the ASK itself (not the tool call): a background task\n * that is stopped or whose child process exits while a person has not yet answered must not leave\n * that answer parked forever, so aborting it here settles the parked prompt denied and dismisses it\n * on every attached surface — the same fail-closed direction `checkPermission`'s own `signal` denies\n * on. `executionId` names the tool call the ask is about — the id its `tool_start`/`tool_end`\n * carry — so a surface can hold one row from the request to the call's end. Every field is\n * optional so every existing 2-arg handler stays a valid implementation.\n */\nexport interface IPermissionAskContext {\n readonly requester?: TPermissionRequester;\n readonly signal?: AbortSignal;\n readonly executionId?: string;\n}\n\n/**\n * Custom permission handler — called when a tool needs user approval.\n * Returns true to allow, false to deny, or 'allow-session' to remember for the session.\n */\nexport type TPermissionHandler = (\n toolName: string,\n toolArgs: TToolArgs,\n context?: IPermissionAskContext,\n) => Promise<TPermissionResult>;\n\nexport interface IPermissionEnforcerOptions {\n readonly pathProtection?: IPathProtectionPolicy;\n sessionId: string;\n cwd: string;\n getPermissionMode: () => TPermissionMode;\n config: {\n /** `ask` patterns always ask, in every mode including bypassPermissions. */\n permissions: { allow: string[]; deny: string[]; ask?: string[] };\n hooks?: Record<string, unknown>;\n };\n /**\n * The OS sandbox the shell tools run under, when there is one: whether it confines a command and\n * lets it run without a prompt.\n */\n commandSandbox?: ICommandSandboxApproval;\n /**\n * Where `~` and `$HOME` point for the gate's path checks. Absent, the home directory is unknown:\n * a bare `~` is still refused as a critical target, but a path written under it is not expanded,\n * so it cannot match a protected path.\n */\n homeDirectory?: string;\n /**\n * Where a path a shell command names really is, symlinks followed (a host with a filesystem\n * supplies it). Absent, a command with a path operand is not read-only.\n */\n resolveInWorkspace?: TResolveInWorkspace;\n /** The host's canonical spelling of a directory; absent, a path is compared as written. */\n canonicalizePath?: (path: string) => string;\n /**\n * The rules BEFORE any preset contributed.\n *\n * Supplied by the composition root, never derived here. `config.permissions` already carries the\n * STARTUP preset's patterns, so capturing a base from it on the first live `/preset` would keep\n * the first preset's allowlist through every later switch — the accumulation the replace rule\n * exists to prevent, arriving through the base rather than through the merge. Absent ⇒ no preset\n * contributed, and `config.permissions` is itself the preset-free base.\n */\n presetFreePermissions?: { allow: readonly string[]; deny: readonly string[] };\n terminal: ITerminalOutput;\n permissionHandler?: TPermissionHandler;\n promptForApprovalFn?: (\n terminal: ITerminalOutput,\n toolName: string,\n toolArgs: TToolArgs,\n ) => Promise<TPermissionResult>;\n sessionLogger?: ISessionLogger;\n onToolExecution?: (event: {\n type: 'start' | 'end';\n toolName: string;\n toolArgs?: TToolArgs;\n success?: boolean;\n denied?: boolean;\n toolResultData?: string;\n toolResultParts?: TUniversalMessagePart[];\n executionId?: string;\n /** The source's view of the result, for a surface that can host one; never model context. */\n view?: IToolAppView;\n }) => void;\n /** The executors that run hook handlers; required, there is no host default. */\n hookTypeExecutors: readonly IHookTypeExecutor[];\n /** Absolute path to session transcript file — passed to PreToolUse hook inputs as transcript_path */\n transcriptPath?: string;\n /** Called when the user selects \"allow for project\" — persists the tool pattern to project settings. */\n onProjectAllowTool?: (toolName: string) => void;\n /**\n * A background/subagent task permission policy. It adds a ceiling (checked before bypass),\n * an ask-everything flag and the task's own lists to the one evaluator, so `deny`/`preapproved`/\n * `inherit-allowlist` still bind under a permissive mode. Absent → no policy constraints.\n */\n permissionPolicy?: TBackgroundPermissionPolicy;\n /**\n * The task's OWN declared allow/deny rules (distinct from the parent session's `config.permissions`\n * which `inherit-allowlist` inherits). `preapproved` consults these.\n */\n taskPermissions?: { allow?: readonly string[]; deny?: readonly string[] };\n /**\n * Judges, in `auto` mode, the calls the mode would otherwise ask a person about. Absent → the\n * session cannot enter `auto`.\n */\n permissionClassifier?: IPermissionClassifier;\n}\n\n/**\n * How a tool call ended, when it did not simply succeed.\n *\n * Three outcomes used to be indistinguishable from success at this type — a tool that THREW, a user\n * DENIAL, and a hook BLOCK — because each was returned as `{ success: true, data: '{\"success\":\n * false,…}' }`. Every consumer above had to parse English out of a string and guess, and all three\n * guesses were the same one.\n *\n * The framing the audit stated is the one kept here: **\"never throw\" is correct and \"encode the\n * failure as success\" is not — they are independent decisions.** Nothing below starts throwing.\n */\nexport type TToolFailureOutcome = 'threw' | 'denied' | 'hook-blocked';\n\n/**\n * The failure envelope. `success: false` is the part consumers branch on; `outcome` is the part they\n * branch on when they need to know WHICH failure, and neither requires reading `data`.\n *\n * `data` keeps the JSON string it always carried, because the model is shown that text and changing\n * what it sees is a separate decision from making the envelope honest.\n */\nexport interface IToolFailureResult extends IToolResult {\n success: false;\n outcome: TToolFailureOutcome;\n error: string;\n data: string;\n metadata: Record<string, never>;\n}\n\nexport function toolFailure(\n outcome: TToolFailureOutcome,\n error: string,\n /**\n * The payload, kept in the shape callers already parse.\n *\n * A CORRECTION, because the first version of this comment claimed something false and review\n * caught it. It said the model still sees `{ blocked: true, reason }` because `data` was\n * unchanged. It does not. `success: false` now reaches `ToolManager.executeTool`, which throws\n * `ToolExecutionError`; `ToolExecutionService` catches that and returns `{ success: false, error }`,\n * and the history writer renders a failed result as `Error: <message>`. So what the model reads\n * changed from the JSON payload to one error line.\n *\n * That change is INTENDED and is the point of the item: a blocked call is a failure, and the model\n * being told `Error: Blocked by hook — <reason>` is more honest than being handed a success-shaped\n * envelope it has to introspect. What was wrong was the claim that nothing changed, not the change.\n * The reason travels in `error`, so nothing is lost.\n */\n data?: unknown,\n): IToolFailureResult {\n return {\n success: false as const,\n outcome,\n error,\n data: JSON.stringify(data ?? { success: false, output: '', error }),\n metadata: {},\n };\n}\n\n/**\n * What a crash announcement looks like, named rather than widened.\n *\n * The first version typed `announce` as taking a `Record<string, unknown>`, which the real\n * `onToolExecution` cannot be assigned to — parameter positions are contravariant — so the call site\n * reached for `as never`. A cast at a boundary is the boundary's type being wrong; this is the\n * subset of the event this function actually emits.\n */\nexport interface IToolCrashAnnouncement {\n type: 'end';\n toolName: string;\n toolArgs: TToolArgs;\n success: false;\n executionId?: string;\n}\n\n/**\n * The crash path, as one call: announce the failure to the listener and return an honest envelope.\n *\n * It lives beside the envelope rather than in the enforcer because the enforcer is at its size\n * ceiling and this is the same subject — what a failed tool call looks like. Two things happen here\n * and both are the point: a catch that returned `success: true` AND emitted no end\n * event would leave a crash invisible to the caller and to anything watching.\n */\nexport function reportToolCrash(\n error: unknown,\n announce: ((event: IToolCrashAnnouncement) => void) | undefined,\n where: { toolName: string; toolArgs: TToolArgs; executionId?: string },\n): IToolFailureResult {\n const message = error instanceof Error ? error.message : String(error);\n announce?.({\n type: 'end',\n toolName: where.toolName,\n toolArgs: where.toolArgs,\n success: false,\n executionId: where.executionId,\n });\n return toolFailure('threw', message);\n}\n\n/** A refusal that tells the model why, so it can take another route. */\nexport interface IPermissionRefusal {\n readonly message: string;\n}\n\n/** Returned when the user denies a permission prompt. */\nexport const PERMISSION_DENIED_RESULT = toolFailure(\n 'denied',\n 'Permission denied. The user did not approve this action.',\n);\n\n/** Maximum chars for any single tool output. Matches Claude Code's 30K limit. */\nexport const MAX_TOOL_OUTPUT_CHARS = 30_000;\n","/**\n * Tool hook helpers — stateless utility functions for tool hook execution\n * and output truncation used by PermissionEnforcer.\n */\n\nimport { runHooks, createLogger, isEnforcing, wasToolResultAdmitted } from '@robota-sdk/agent-core';\n\nimport { canonicaliseToolArguments } from './tool-argument-canonicalisation.js';\n\nimport { MAX_TOOL_OUTPUT_CHARS, toolFailure } from './permission-types.js';\n\nimport type {\n IToolResult,\n TToolParameters,\n THooksConfig,\n IHookInput,\n IHookTypeExecutor,\n ISubprocessTraceEnv,\n} from '@robota-sdk/agent-core';\n\nconst logger = createLogger('ToolHookHelpers');\n\ntype TRunHooksResult = Awaited<ReturnType<typeof runHooks>>;\n\n/**\n * Truncate tool result data if it exceeds MAX_TOOL_OUTPUT_CHARS.\n * Uses middle-truncation: keeps first and last portions, removes middle.\n */\nexport function truncateToolResult(result: IToolResult): IToolResult {\n if (wasToolResultAdmitted(result)) return result;\n if (typeof result.data !== 'string') return result;\n if (result.data.length <= MAX_TOOL_OUTPUT_CHARS) return result;\n\n const halfLimit = Math.floor(MAX_TOOL_OUTPUT_CHARS / 2);\n const head = result.data.substring(0, halfLimit);\n const tail = result.data.substring(result.data.length - halfLimit);\n const originalSize = result.data.length;\n const truncatedData = `${head}\\n\\n[... output truncated: ${originalSize.toLocaleString()} chars total, showing first and last ${halfLimit.toLocaleString()} chars ...]\\n\\n${tail}`;\n\n return { ...result, data: truncatedData };\n}\n\n/** Build a hook input object for tool execution hooks */\nexport function buildHookInput(\n sessionId: string,\n cwd: string,\n toolName: string,\n parameters: TToolParameters,\n permissionMode?: string,\n transcriptPath?: string,\n): IHookInput {\n return {\n session_id: sessionId,\n cwd,\n hook_event_name: 'PreToolUse',\n tool_name: toolName,\n tool_input: parameters as Record<string, string | number | boolean | object>,\n ...(permissionMode !== undefined && { permission_mode: permissionMode }),\n ...(transcriptPath !== undefined && { transcript_path: transcriptPath }),\n };\n}\n\n/** A PreToolUse decision the permission gate applies; `deny` is a refusal and `defer` changes nothing. */\nexport type TPreToolHookDecision = 'allow' | 'ask';\n\n/** What the PreToolUse hooks decided about one call. */\nexport interface IPreToolGateOutcome {\n /** The denial to return instead of running the tool, or null to go on to the permission gate. */\n readonly refusal: IToolResult | null;\n readonly decision?: TPreToolHookDecision;\n readonly parameters?: TToolParameters;\n}\n\n/**\n * Run PreToolUse hooks; returns a denial IToolResult if blocked, or null to proceed. `hookTraceEnv`\n * names the prompt root, never the tool body's span.\n */\nexport async function runPreToolHook(\n hooks: Record<string, unknown> | undefined,\n hookInput: IHookInput,\n hookTypeExecutors: readonly IHookTypeExecutor[],\n hookTraceEnv?: ISubprocessTraceEnv,\n): Promise<IToolResult | null> {\n return (await runPreToolGate(hooks, hookInput, hookTypeExecutors, hookTraceEnv)).refusal;\n}\n\n/** Re-evaluate all enforcing hooks after a command rewrite before granting permission. */\nexport async function runPreToolGate(\n hooks: Record<string, unknown> | undefined,\n hookInput: IHookInput,\n hookTypeExecutors: readonly IHookTypeExecutor[],\n hookTraceEnv?: ISubprocessTraceEnv,\n): Promise<IPreToolGateOutcome> {\n let input = hookInput;\n let rewritten: TToolParameters | undefined;\n const seen = new Set([JSON.stringify(input.tool_input)]);\n for (let pass = 0; pass < 8; pass++) {\n const evaluated = await evaluatePreToolHooks(hooks, input, hookTypeExecutors, hookTraceEnv);\n if (evaluated.result !== null) return { refusal: evaluated.result };\n const { permissionDecision, updatedInput } = evaluated.hookResult;\n if (updatedInput !== undefined) {\n if (updatedInput === null || typeof updatedInput !== 'object' || Array.isArray(updatedInput))\n return {\n refusal: toolFailure('hook-blocked', 'PreToolUse updatedInput must be an object.'),\n };\n const parameters = canonicaliseToolArguments(\n input.tool_name ?? '',\n updatedInput as TToolParameters,\n input.cwd,\n );\n const encoded = JSON.stringify(parameters);\n if (encoded !== JSON.stringify(input.tool_input)) {\n if (seen.has(encoded)) break;\n seen.add(encoded);\n rewritten = parameters;\n input = { ...input, tool_input: parameters as IHookInput['tool_input'] };\n continue;\n }\n }\n return {\n refusal: null,\n ...(rewritten === undefined ? {} : { parameters: rewritten }),\n ...(permissionDecision === 'allow' || permissionDecision === 'ask'\n ? { decision: permissionDecision }\n : {}),\n };\n }\n const reason = 'PreToolUse input rewriting did not stabilize; no tool call was executed.';\n return { refusal: toolFailure('hook-blocked', reason, { blocked: true, reason }) };\n}\n\nasync function evaluatePreToolHooks(\n hooks: Record<string, unknown> | undefined,\n hookInput: IHookInput,\n hookTypeExecutors: readonly IHookTypeExecutor[],\n hookTraceEnv?: ISubprocessTraceEnv,\n): Promise<{ result: IToolResult | null; hookResult: TRunHooksResult }> {\n const hookResult = await runHooks(\n hooks as THooksConfig | undefined,\n 'PreToolUse',\n hookInput,\n hookTypeExecutors,\n hookTraceEnv,\n );\n if (hookResult.blocked) {\n // The third of the three outcomes the failure type names. `permission-types.ts` declares\n // `hook-blocked`, so this path must not return `success: true`: that would promise a\n // distinction the code does not make — in the file that exists to end exactly that.\n const reason = hookResult.reason ?? 'Blocked by hook';\n return { result: toolFailure('hook-blocked', reason, { blocked: true, reason }), hookResult };\n }\n\n // A hook that reached NO verdict is not a hook that approved. That distinction is\n // representable; this is where it starts costing something.\n //\n // Guarded by the policy rather than by a literal `true`, so the posture is stated in ONE place and\n // this boundary cannot drift from it. Note what this does NOT buy: the event is hardcoded here\n // because this function is the PreToolUse gate, so a future enforcing event needs its own boundary\n // and does not inherit anything — an earlier version of this comment claimed otherwise and review\n // caught it. What the indirection does buy is that flipping `PreToolUse` to advisory in the table\n // turns this gate off.\n //\n // The `errors` / `unknownHookTypes` gate is covered by\n // `__tests__/tool-hook-helpers.test.ts`, which fails if this handling is removed.\n //\n // The check stays HERE rather than inside `runHooks`, because the runner reports outcomes and must\n // not decide policy — the same split as between the decoder and the runner.\n if (isEnforcing('PreToolUse')) {\n // Bind the array, not `errors?.[0]`: narrowing the element does not narrow the collection, and\n // the count below needs the collection. The earlier shape needed a `?? 1` fallback that could\n // never be taken, which reads as though the array might be absent here.\n const failures = hookResult.errors;\n const failure = failures?.[0];\n // ONE binding for one field. Two bindings, each defended differently against `undefined`, is a\n // drift surface: the two branches below disagreed about whether the field could be absent.\n const unregistered = hookResult.unknownHookTypes ?? [];\n\n // A configured hook type with no registered executor ran NOTHING. If the gate proceeded after\n // the runner reported it, a config declaring a guardrail with no registry would silently\n // disable itself. Startup rejection of such a config is separate; this is the runtime half.\n //\n // Denying is deliberate and is the approved semantics: a PreToolUse hook the user wrote\n // as a gate must not be silently skipped. But note WHICH configs land here, because it is wider\n // than a mistake — `prompt`, `agent` and `guardrail` are accepted by the config schema while no\n // product surface supplies the `providerFactory` / `sessionFactory` / `guardrails` those\n // executors need, so such a config validates and can never run. That gap is the\n // reason this text says what to DO rather than only what happened.\n //\n // Built once and used by both branches. The earlier shape wrote the cause twice — a full\n // sentence in the standalone branch and a shorter one appended to the error branch — so the\n // operator with TWO faults got less guidance than the one with a single fault, which is exactly\n // backwards, and the two wordings were free to drift apart.\n const unregisteredReason =\n unregistered.length > 0\n ? `Hook type(s) with no registered executor: ${unregistered.join(', ')}. ` +\n 'Nothing evaluated this gate, so the tool call is denied rather than silently allowed. ' +\n 'Remove the hook from the PreToolUse configuration, or supply an executor for its type.'\n : '';\n\n if (failures !== undefined && failure !== undefined) {\n // The reason names the kind, the executor and the failure text, because a fail-closed gate\n // turns a misconfigured hook into a hard stop: whoever hits it needs enough to fix it.\n const others = failures.length - 1;\n const reason =\n `Hook could not evaluate (${failure.kind}, source: ${failure.source}): ${failure.reason}.` +\n // Naming only the first would hide that several gates failed; the count is the cheap half of\n // that, and the reason line stays one line.\n (others > 0 ? ` (+${others} more hook failure(s))` : '') +\n // Both causes in ONE reason. This branch returns before the unregistered branch, so a turn\n // carrying both used to report only the error — the operator fixed the named cause, retried,\n // and hit a second denial with no warning it was queued. A fail-closed gate that reveals its\n // reasons one per attempt is a gate you debug by being repeatedly stopped.\n (unregisteredReason !== '' ? ` Also unevaluated — ${unregisteredReason}` : '');\n return { result: toolFailure('hook-blocked', reason, { blocked: true, reason }), hookResult };\n }\n\n if (unregisteredReason !== '') {\n return {\n result: toolFailure('hook-blocked', unregisteredReason, {\n blocked: true,\n reason: unregisteredReason,\n }),\n hookResult,\n };\n }\n }\n\n return { result: null, hookResult };\n}\n\n/** Fire PostToolUse hooks (fire and forget) */\nexport function firePostToolHook(\n hooks: Record<string, unknown> | undefined,\n hookInput: IHookInput,\n result: IToolResult,\n hookTypeExecutors: readonly IHookTypeExecutor[],\n hookTraceEnv?: ISubprocessTraceEnv,\n): void {\n const postHookInput: IHookInput = {\n ...hookInput,\n hook_event_name: 'PostToolUse',\n tool_output: typeof result.data === 'string' ? result.data : JSON.stringify(result.data),\n };\n runHooks(\n hooks as THooksConfig | undefined,\n 'PostToolUse',\n postHookInput,\n hookTypeExecutors,\n hookTraceEnv,\n ).catch((error) => logger.warn('hook failed', { error }));\n}\n","/**\n * The calls this session refused, most recent first, so `/permissions` can show what was blocked\n * and why without the user scrolling back through the transcript.\n *\n * In memory and bounded: it answers \"what did I just block\", not an audit trail — the\n * `PermissionDecision` hook and the session log are the durable records.\n */\n\nimport { getToolPermissionProfile } from '@robota-sdk/agent-core';\n\nimport type { TToolArgs } from '@robota-sdk/agent-core';\n\n/**\n * Why a call was refused:\n * - `policy` — the gate answered deny: a deny rule, a background ceiling, or plan mode;\n * - `user` — a person was asked and declined, or the turn was cancelled while asking;\n * - `no-approver` — the call needed a person and none was attached;\n * - `classifier` — in `auto` mode, the classifier blocked the call or gave no usable verdict.\n */\nexport type TPermissionDenialReason = 'policy' | 'user' | 'no-approver' | 'classifier';\n\nexport interface IPermissionDenial {\n readonly toolName: string;\n /** The argument the tool's permission profile names (command, path, URL), when it has one. */\n readonly argument?: string;\n readonly reason: TPermissionDenialReason;\n /** The classifier's reason, for a `classifier` denial. */\n readonly detail?: string;\n /** Epoch milliseconds. */\n readonly at: number;\n}\n\nconst DEFAULT_CAPACITY = 20;\nconst ARGUMENT_DISPLAY_LIMIT = 200;\n\nfunction argumentOf(toolName: string, toolArgs: TToolArgs): string | undefined {\n const key = getToolPermissionProfile(toolName).argument?.key;\n if (key === undefined) return undefined;\n const value = toolArgs[key];\n if (typeof value !== 'string') return undefined;\n return value.length > ARGUMENT_DISPLAY_LIMIT\n ? `${value.slice(0, ARGUMENT_DISPLAY_LIMIT)}…`\n : value;\n}\n\nexport class PermissionDenialLog {\n private readonly entries: IPermissionDenial[] = [];\n /** The full call behind each entry, same order, so a denial can be retried exactly. */\n private readonly calls: { toolName: string; toolArgs: TToolArgs }[] = [];\n\n constructor(\n private readonly capacity: number = DEFAULT_CAPACITY,\n private readonly now: () => number = Date.now,\n ) {}\n\n record(\n toolName: string,\n toolArgs: TToolArgs,\n reason: TPermissionDenialReason,\n detail?: string,\n ): void {\n const argument = argumentOf(toolName, toolArgs);\n this.entries.unshift({\n toolName,\n ...(argument !== undefined ? { argument } : {}),\n reason,\n ...(detail !== undefined ? { detail } : {}),\n at: this.now(),\n });\n this.calls.unshift({ toolName, toolArgs });\n if (this.calls.length > this.capacity) this.calls.length = this.capacity;\n if (this.entries.length > this.capacity) this.entries.length = this.capacity;\n }\n\n list(): readonly IPermissionDenial[] {\n return [...this.entries];\n }\n\n /** The call behind the entry at `index` in {@link list}. */\n callAt(index: number): { toolName: string; toolArgs: TToolArgs } | undefined {\n return this.calls[index];\n }\n}\n","import { PERMISSION_DENIED_RESULT, reportToolCrash, toolFailure } from './permission-types.js';\nimport {\n createLogger,\n isExecutionControlError,\n DEFAULT_ABSTRACT_EVENT_SERVICE,\n isAbortFailure,\n TOOL_BODY_EVENTS,\n TOOL_PERMISSION_EVENTS,\n snapshotToolProvenance,\n withToolProvenance,\n} from '@robota-sdk/agent-core';\nimport { canonicaliseToolArguments } from './tool-argument-canonicalisation.js';\nimport {\n buildHookInput,\n firePostToolHook,\n runPreToolGate,\n truncateToolResult,\n} from './tool-hook-helpers.js';\n\nimport type { TPreToolHookDecision } from './tool-hook-helpers.js';\nimport type { IPermissionEnforcerOptions, IPermissionRefusal } from './permission-types.js';\nimport type { TSessionLogData } from './session-logger.js';\nimport type {\n IHookInput,\n IHookTypeExecutor,\n IEventService,\n IToolExecutionContext,\n IToolResult,\n IToolWithEventService,\n ITerminalOutput,\n TToolArgs,\n TToolParameters,\n TToolEffectAdmission,\n} from '@robota-sdk/agent-core';\n\nconst logger = createLogger('ToolBodyTrace');\n\nclass DeferredPermissionRefusal extends Error {\n constructor(readonly result: IToolResult) {\n super('Effective tool arguments were not authorized');\n }\n}\n\n/** Never let a permission observation break the tool_result it merely watches. */\nfunction emitPermissionDecision(\n context: IToolExecutionContext | undefined,\n decision: 'allowed' | 'denied' | 'hook-blocked',\n): void {\n try {\n context?.eventService?.emit(TOOL_PERMISSION_EVENTS.DECIDED, {\n timestamp: new Date(),\n executionId: context.executionId,\n decidedAt: new Date().toISOString(),\n decision,\n });\n } catch (error) {\n logger.warn(\n 'tool permission observation failed',\n error instanceof Error ? error : new Error(String(error)),\n );\n }\n}\n\n/** Exactly what the wrapper reads from the enforcer — no more, and named so it cannot quietly grow. */\nexport interface IToolWrapperDeps {\n readonly sessionId: string;\n readonly cwd: string;\n readonly config: IPermissionEnforcerOptions['config'];\n readonly terminal: ITerminalOutput;\n readonly transcriptPath?: string;\n readonly onToolExecution?: IPermissionEnforcerOptions['onToolExecution'];\n readonly hookTypeExecutors: readonly IHookTypeExecutor[];\n getPermissionMode: IPermissionEnforcerOptions['getPermissionMode'];\n log(event: string, detail: TSessionLogData): void;\n checkPermission(\n toolName: string,\n toolArgs: TToolArgs,\n signal?: AbortSignal,\n interaction?: IToolExecutionContext['permissionInteraction'],\n hookTraceEnv?: IToolExecutionContext['hookTraceEnv'],\n continuation?: IToolExecutionContext['continuation'],\n hookDecision?: TPreToolHookDecision,\n /** The call's id (`context.executionId`), so a prompt raised for it can name the call. */\n executionId?: string,\n ): Promise<boolean | IPermissionRefusal>;\n}\n\n/**\n * Wrap one tool so every call passes the permission gate, the hooks and the truncation limit.\n *\n * Extracted from `PermissionEnforcer` because the file-size ratchet refused to let it grow further,\n * and a ratchet that says \"split instead of extending\" is asking for exactly this. It takes what it\n * needs as `deps` rather than the enforcer itself: the ten members it reads are the honest surface\n * of this function, and naming them is what makes the extraction a boundary rather than a move.\n */\nexport function wrapToolWithPermission(\n tool: IToolWithEventService,\n enforcer: IToolWrapperDeps,\n): IToolWithEventService {\n const originalExecute = tool.execute.bind(tool);\n const originalExecuteWithAdmission = tool.executeWithAdmission?.bind(tool);\n const provenance = snapshotToolProvenance(tool.provenance);\n const onToolExecution: IToolWrapperDeps['onToolExecution'] = (event) => {\n if (event.type !== 'end' || !provenance) return enforcer.onToolExecution?.(event);\n const observation = withToolProvenance(\n { success: event.success ?? false, parts: event.toolResultParts },\n provenance,\n );\n enforcer.onToolExecution?.({ ...event, toolResultParts: observation.parts });\n };\n // What this session gave the tool with `setEventService`. The tool instance may be shared with\n // other sessions, and the service set on it is whichever session set one last, so each call\n // carries this one instead. Until the session sets one, calls carry the no-op service: its calls\n // must not reach a service another session set.\n let sessionEventService: IEventService = DEFAULT_ABSTRACT_EVENT_SERVICE;\n\n const wrappedTool = Object.create(tool) as IToolWithEventService;\n Object.defineProperty(wrappedTool, 'provenance', { value: provenance, enumerable: true });\n const execute = async (\n rawParameters: TToolParameters,\n context?: IToolExecutionContext,\n beforeEffect?: TToolEffectAdmission,\n ): Promise<IToolResult> => {\n // The gate, the hooks, the logs and the tool all see ONE canonical form of the\n // arguments — a relative path argument resolved against the session root — so a pattern judges\n // the path the tool will actually open. Canonicalising needs the tool's name, which is read\n // inside the try below, so until then this holds the raw form.\n let parameters: TToolParameters = rawParameters;\n // Ordinary tool failures become results. A journal failure must stop the execution owner,\n // preventing a later model call from treating a persistence failure as a retryable tool error.\n // Resolve the name inside the protected path so a malformed tool still produces a result.\n let toolName = '(unknown)';\n\n try {\n toolName = tool.getName();\n parameters = canonicaliseToolArguments(toolName, rawParameters, enforcer.cwd);\n enforcer.log('tool_call', {\n tool: toolName,\n args: parameters as Record<string, string | number | boolean | object>,\n });\n\n let hookInput = buildHookInput(\n enforcer.sessionId,\n enforcer.cwd,\n toolName,\n parameters,\n enforcer.getPermissionMode(),\n enforcer.transcriptPath,\n );\n\n const gate = await runPreToolGate(\n enforcer.config.hooks,\n hookInput,\n enforcer.hookTypeExecutors,\n context?.hookTraceEnv,\n );\n if (gate.refusal) {\n enforcer.log('tool_blocked', { tool: toolName, reason: 'hook' });\n emitPermissionDecision(context, 'hook-blocked');\n return gate.refusal;\n }\n\n if (gate.parameters !== undefined) {\n parameters = gate.parameters;\n hookInput = { ...hookInput, tool_input: parameters as IHookInput['tool_input'] };\n }\n\n // The turn's signal reaches this wrapper and stopped here.\n const verdict = await enforcer.checkPermission(\n toolName,\n parameters as TToolArgs,\n context?.signal,\n context?.permissionInteraction,\n context?.hookTraceEnv,\n context?.continuation,\n gate.decision,\n context?.executionId,\n );\n if (verdict !== true) {\n enforcer.log('tool_denied', { tool: toolName, reason: 'permission' });\n emitPermissionDecision(context, 'denied');\n onToolExecution({\n type: 'end',\n toolName,\n toolArgs: parameters as TToolArgs,\n success: false,\n denied: true,\n executionId: context?.executionId,\n });\n // A refusal that carries its reason (the auto-mode classifier) hands it to the model.\n return typeof verdict === 'object'\n ? toolFailure('denied', verdict.message)\n : PERMISSION_DENIED_RESULT;\n }\n\n emitPermissionDecision(context, 'allowed');\n context?.signal?.throwIfAborted();\n onToolExecution({\n type: 'start',\n toolName,\n toolArgs: structuredClone(parameters) as TToolArgs,\n executionId: context?.executionId,\n });\n context?.signal?.throwIfAborted();\n\n // The observation covers ONLY the awaited body, never approval, hooks, truncation or a\n // detached continuation. A pre-start denial/abort therefore has no tool-body span.\n let startedAtMs: number | undefined;\n const admit: TToolEffectAdmission = async (effective) => {\n context?.signal?.throwIfAborted();\n const approvedArguments = canonicaliseToolArguments(toolName, effective, enforcer.cwd);\n if (JSON.stringify(approvedArguments) !== JSON.stringify(effective)) {\n throw new DeferredPermissionRefusal(\n toolFailure(\n 'denied',\n 'Tool admission requires final canonical arguments; relative paths must be resolved before admission.',\n ),\n );\n }\n if (JSON.stringify(approvedArguments) !== JSON.stringify(parameters)) {\n const nextHookInput = buildHookInput(\n enforcer.sessionId,\n enforcer.cwd,\n toolName,\n approvedArguments,\n enforcer.getPermissionMode(),\n enforcer.transcriptPath,\n );\n const nextGate = await runPreToolGate(\n enforcer.config.hooks,\n nextHookInput,\n enforcer.hookTypeExecutors,\n context?.hookTraceEnv,\n );\n if (nextGate.refusal) throw new DeferredPermissionRefusal(nextGate.refusal);\n if (\n nextGate.parameters !== undefined &&\n JSON.stringify(nextGate.parameters) !== JSON.stringify(approvedArguments)\n )\n throw new DeferredPermissionRefusal(\n toolFailure(\n 'hook-blocked',\n 'A hook cannot replace arguments already settled by the tool; retry as a new call.',\n ),\n );\n const effectiveVerdict = await enforcer.checkPermission(\n toolName,\n approvedArguments as TToolArgs,\n context?.signal,\n context?.permissionInteraction,\n context?.hookTraceEnv,\n context?.continuation,\n nextGate.decision,\n context?.executionId,\n );\n if (effectiveVerdict !== true)\n throw new DeferredPermissionRefusal(\n typeof effectiveVerdict === 'object'\n ? toolFailure('denied', effectiveVerdict.message)\n : PERMISSION_DENIED_RESULT,\n );\n hookInput = nextHookInput;\n }\n parameters = structuredClone(effective);\n context?.signal?.throwIfAborted();\n await beforeEffect?.(parameters);\n context?.signal?.throwIfAborted();\n startedAtMs = Date.now();\n };\n let outcome: 'success' | 'failure' | 'interrupted' = 'failure';\n let result: IToolResult;\n try {\n // A call without a context was not made by an agent, so it has no session to carry.\n const toolContext =\n context === undefined\n ? undefined\n : { ...context, parameters, instanceEventService: sessionEventService };\n if (beforeEffect && originalExecuteWithAdmission) {\n result = await originalExecuteWithAdmission(\n parameters,\n toolContext as IToolExecutionContext,\n admit,\n );\n } else {\n if (beforeEffect) await admit(parameters);\n else startedAtMs = Date.now();\n context?.signal?.throwIfAborted();\n result = await originalExecute(parameters, toolContext as IToolExecutionContext);\n }\n outcome = context?.signal?.aborted ? 'interrupted' : result.success ? 'success' : 'failure';\n } catch (error) {\n outcome = context?.signal?.aborted || isAbortFailure(error) ? 'interrupted' : 'failure';\n throw error;\n } finally {\n try {\n if (startedAtMs !== undefined)\n context?.eventService?.emit(TOOL_BODY_EVENTS.COMPLETED, {\n timestamp: new Date(),\n executionId: context.executionId,\n startedAt: new Date(startedAtMs).toISOString(),\n endedAt: new Date(Math.max(Date.now(), startedAtMs)).toISOString(),\n outcome,\n toolName,\n ...(typeof context.toolBodyId === 'string' ? { toolBodyId: context.toolBodyId } : {}),\n });\n } catch (error) {\n // An observer must never turn a completed tool body into a missing tool_result.\n logger.warn(\n 'tool body observation failed',\n error instanceof Error ? error : new Error(String(error)),\n );\n }\n }\n\n // Truncate oversized tool output (matches 30K char limit)\n const truncatedResult = truncateToolResult(result);\n\n if (truncatedResult !== result && typeof result.data === 'string') {\n enforcer.terminal.writeLine(\n ` ⚠ Output truncated: ${result.data.length.toLocaleString()} chars total — model sees first and last 15,000 chars`,\n );\n }\n\n onToolExecution({\n type: 'end',\n toolName,\n toolArgs: parameters as TToolArgs,\n success: truncatedResult.success,\n toolResultData:\n typeof truncatedResult.data === 'string'\n ? truncatedResult.data\n : JSON.stringify(truncatedResult.data),\n toolResultParts: truncatedResult.parts,\n executionId: context?.executionId,\n // Display data for a surface only: it never joins `toolResultData`, which is what is logged.\n ...(truncatedResult.view ? { view: truncatedResult.view } : {}),\n });\n\n const dataSize =\n typeof truncatedResult.data === 'string'\n ? truncatedResult.data.length\n : (JSON.stringify(truncatedResult.data)?.length ?? 0);\n enforcer.log('tool_result', {\n tool: toolName,\n success: truncatedResult.success,\n dataChars: dataSize,\n truncated: truncatedResult !== result,\n });\n firePostToolHook(\n enforcer.config.hooks,\n hookInput,\n truncatedResult,\n enforcer.hookTypeExecutors,\n context?.hookTraceEnv,\n );\n return truncatedResult;\n } catch (err) {\n if (isExecutionControlError(err)) throw err;\n if (err instanceof DeferredPermissionRefusal) return err.result;\n // Beside the envelope it returns, in `permission-types.ts`.\n return reportToolCrash(err, onToolExecution, {\n toolName,\n toolArgs: parameters as TToolArgs,\n executionId: context?.executionId,\n });\n }\n };\n\n const executeAttributed: typeof execute = async (...args) =>\n withToolProvenance(await execute(...args), provenance);\n wrappedTool.execute = executeAttributed;\n wrappedTool.executeWithAdmission = executeAttributed;\n\n // Kept for the calls above, and still forwarded to the original tool, because\n // `Object.create(tool)` would otherwise shadow it onto the wrapper: a tool that reads only the\n // service set on it, such as an `AbstractTool` subclass, keeps receiving it.\n wrappedTool.setEventService = (eventService) => {\n sessionEventService = eventService ?? DEFAULT_ABSTRACT_EVENT_SERVICE;\n tool.setEventService(eventService);\n };\n\n return wrappedTool;\n}\n","/**\n * PermissionEnforcer — handles tool permission checking, hook execution,\n * and tool output truncation.\n *\n * Extracted from Session to separate permission/hook concerns from\n * conversation management.\n */\n\nimport {\n allowRulesForAutoMode,\n applyPresetToolLists,\n evaluatePermission,\n findInvalidPermissionPatterns,\n findPermissionPatternWarnings,\n getToolPermissionProfile,\n isToolDeniedOutright,\n matchesAnyPattern,\n projectPermissionPolicy,\n registerToolPermissionProfile,\n requiresFreshApproval,\n runHooks,\n} from '@robota-sdk/agent-core';\n\nimport { decideApproval } from './abortable-approval.js';\nimport { checkpointedApproval, hasCheckpointedApproval } from './checkpointed-approval.js';\nimport { AutoModeGate } from './auto-mode-gate.js';\nimport { consentScopeFor } from './consent-scope.js';\nimport { buildHookInput, runPreToolGate } from './tool-hook-helpers.js';\nimport { PermissionDenialLog } from './permission-denial-log.js';\nimport { wrapToolWithPermission } from './tool-permission-wrapper.js';\nimport { requireHookTypeExecutors } from './require-hook-type-executors.js';\n\nimport type {\n IPermissionEnforcerOptions,\n IPermissionRefusal,\n TPermissionHandler,\n TPermissionResult,\n ITerminalOutput,\n ISpinner,\n} from './permission-types.js';\nimport type { IPermissionDenial } from './permission-denial-log.js';\nimport type { ISessionLogger, TSessionLogData } from './session-logger.js';\nimport type { TPreToolHookDecision } from './tool-hook-helpers.js';\nimport type { IToolWrapperDeps } from './tool-permission-wrapper.js';\nimport type {\n IHookTypeExecutor,\n IToolExecutionContext,\n IToolWithEventService,\n TToolArgs,\n TToolParameters,\n THooksConfig,\n TResolveInWorkspace,\n} from '@robota-sdk/agent-core';\n\nexport type { TPermissionHandler, TPermissionResult, ITerminalOutput, ISpinner };\nexport type { IPermissionEnforcerOptions };\n\n/**\n * Throw naming every malformed permission pattern and why. Allow rules are held to\n * the narrower allow grammar.\n */\nfunction assertPermissionPatternsEvaluable(rules: {\n allow: readonly string[];\n restrictive: readonly string[];\n}): void {\n const problems = [\n ...findInvalidPermissionPatterns(rules.allow, 'allow'),\n ...findInvalidPermissionPatterns(rules.restrictive, 'deny'),\n ];\n if (problems.length === 0) return;\n const listed = problems.map(({ pattern, reason }) => `\"${pattern}\" ${reason}`).join('; ');\n throw new Error(\n `Invalid permission pattern(s) in permissions.allow/deny/ask: ${listed}. ` +\n 'Fix the pattern where it is configured.',\n );\n}\n\n/** How a decision's call will run, where that changes the answer. */\ninterface IDecisionScope {\n readonly continuation?: IToolExecutionContext['continuation'];\n /** `false` when the call will NOT run inside the command sandbox, so its approval cannot apply. */\n readonly sandboxed?: boolean;\n /** What the call's PreToolUse hooks decided, applied after the rules and mode. */\n readonly hookDecision?: TPreToolHookDecision;\n /** The tool call's id, so a prompt raised for it can name the call its `tool_start` will report. */\n readonly executionId?: string;\n}\n\nexport class PermissionEnforcer {\n private readonly sessionId: string;\n private readonly cwd: string;\n private readonly getPermissionMode: IPermissionEnforcerOptions['getPermissionMode'];\n private readonly config: IPermissionEnforcerOptions['config'];\n private readonly terminal: ITerminalOutput;\n private readonly permissionHandler?: TPermissionHandler;\n private readonly promptForApprovalFn?: IPermissionEnforcerOptions['promptForApprovalFn'];\n private readonly sessionLogger?: ISessionLogger;\n private readonly onToolExecution?: IPermissionEnforcerOptions['onToolExecution'];\n private readonly hookTypeExecutors: readonly IHookTypeExecutor[];\n private readonly transcriptPath?: string;\n /**\n * Consent is remembered as PATTERNS (`consentScopeFor`), not tool names, and read\n * back through the gate's own matcher — approving one argument does not allow every argument.\n */\n private readonly sessionAllowedTools = new Set<string>();\n /** The configured rules before any preset contributed — see {@link applyPresetToolLists}. */\n private readonly presetFreeRules: { allow: readonly string[]; deny: readonly string[] };\n private readonly onProjectAllowTool?: (toolName: string) => void;\n private readonly permissionPolicy?: IPermissionEnforcerOptions['permissionPolicy'];\n private readonly taskPermissions?: IPermissionEnforcerOptions['taskPermissions'];\n private readonly pathProtection: IPermissionEnforcerOptions['pathProtection'];\n private readonly homeDirectory: string | undefined;\n private readonly resolveInWorkspace: TResolveInWorkspace | undefined;\n private readonly canonicalizePath: (path: string) => string;\n private readonly commandSandbox?: IPermissionEnforcerOptions['commandSandbox'];\n private readonly denials = new PermissionDenialLog();\n /** A turn a peer's message started is in progress: the one place the reply to that peer exists. */\n private peerTurn = false;\n private checkpointedApprovals = false;\n private readonly autoMode?: AutoModeGate;\n\n constructor(options: IPermissionEnforcerOptions) {\n this.sessionId = options.sessionId;\n this.cwd = options.cwd;\n this.getPermissionMode = options.getPermissionMode;\n this.config = options.config;\n // Absent ⇒ no preset contributed, so the configured rules ARE the preset-free base. Copied, not\n // aliased: `applyPresetToolLists` writes back into `config.permissions`, and a shared array\n // would make the base track its own output.\n this.presetFreeRules = options.presetFreePermissions ?? {\n allow: [...options.config.permissions.allow],\n deny: [...options.config.permissions.deny],\n };\n // A pattern the gate could never evaluate is refused HERE, with the pattern and\n // the reason, before any turn — not discovered one unevaluable prompt at a time at the gate.\n assertPermissionPatternsEvaluable(this.configuredRules(options));\n this.terminal = options.terminal;\n this.permissionHandler = options.permissionHandler;\n this.promptForApprovalFn = options.promptForApprovalFn;\n this.sessionLogger = options.sessionLogger;\n this.onToolExecution = options.onToolExecution;\n this.hookTypeExecutors = requireHookTypeExecutors(\n options.hookTypeExecutors,\n 'PermissionEnforcer',\n );\n this.transcriptPath = options.transcriptPath;\n this.onProjectAllowTool = options.onProjectAllowTool;\n this.permissionPolicy = options.permissionPolicy;\n this.taskPermissions = options.taskPermissions;\n this.homeDirectory = options.homeDirectory;\n this.pathProtection = options.pathProtection;\n this.resolveInWorkspace = options.resolveInWorkspace;\n this.canonicalizePath = options.canonicalizePath ?? ((path) => path);\n this.commandSandbox = options.commandSandbox;\n if (options.permissionClassifier !== undefined) {\n this.autoMode = new AutoModeGate(options.permissionClassifier);\n }\n }\n\n /**\n * Start a turn, which a peer's message started when `peerTurn` is true. That decides only whether\n * the reply to the peer exists; every other call is decided exactly as in any turn.\n */\n beginTurn(peerTurn: boolean, checkpointedApprovals = false): void {\n this.peerTurn = peerTurn;\n this.checkpointedApprovals = checkpointedApprovals;\n }\n\n /** End the turn; the reply to a peer is gone until the next peer turn begins. */\n endTurn(): void {\n this.peerTurn = false;\n this.checkpointedApprovals = false;\n }\n\n /** Whether `auto` mode can run here: it needs a classifier to decide for it. */\n hasPermissionClassifier(): boolean {\n return this.autoMode !== undefined;\n }\n\n /**\n * Let the call behind a classifier denial run once, unjudged, when the model tries it again.\n * Returns the denial, or `undefined` when `index` names no classifier denial.\n */\n allowRetryOfDenial(index: number): IPermissionDenial | undefined {\n const denial = this.denials.list()[index];\n const call = this.denials.callAt(index);\n if (denial?.reason !== 'classifier' || call === undefined || this.autoMode === undefined) {\n return undefined;\n }\n this.autoMode.grantRetry(call.toolName, call.toolArgs);\n return denial;\n }\n\n /** Every configured pattern, split by the grammar it is held to. */\n private configuredRules(\n options: Pick<IPermissionEnforcerOptions, 'config' | 'taskPermissions'> = {\n config: this.config,\n ...(this.taskPermissions !== undefined ? { taskPermissions: this.taskPermissions } : {}),\n },\n ): { allow: string[]; restrictive: string[] } {\n return {\n allow: [...options.config.permissions.allow, ...(options.taskPermissions?.allow ?? [])],\n restrictive: [\n ...options.config.permissions.deny,\n ...(options.config.permissions.ask ?? []),\n ...(options.taskPermissions?.deny ?? []),\n ],\n };\n }\n\n /**\n * Whether the model is shown this tool at all. A bare-name deny (`Tool`, `Tool(*)`, a name glob)\n * removes it rather than offering it and refusing every call. Read live, so a\n * `/preset` that denies a tool hides it from the next round.\n */\n isToolVisible(toolName: string): boolean {\n // The reply exists in a peer turn alone; a peer turn is otherwise shown what any turn is.\n const profile = getToolPermissionProfile(toolName);\n if (!this.peerTurn && profile.repliesToPeer === true) return false;\n if (this.peerTurn && profile.notInPeerTurn === true) return false;\n return !isToolDeniedOutright(toolName, [\n ...this.config.permissions.deny,\n ...(this.taskPermissions?.deny ?? []),\n ]);\n }\n\n /**\n * Tell the gate each tool's parameter names — the schema is what makes `Tool(name:value)` a\n * parameter rule — then re-check the rules against them, before any turn runs.\n */\n private registerToolParameters(tools: readonly IToolWithEventService[]): void {\n for (const tool of tools) {\n // Read defensively: a tool without a schema has no parameters to name.\n const schema = (tool as Partial<Pick<IToolWithEventService, 'schema'>>).schema;\n if (schema === undefined) continue;\n const properties = schema.parameters?.properties ?? {};\n registerToolPermissionProfile(schema.name, { parameters: Object.keys(properties) });\n }\n const rules = this.configuredRules();\n assertPermissionPatternsEvaluable(rules);\n for (const { pattern, reason } of findPermissionPatternWarnings(rules.restrictive)) {\n this.terminal.writeLine(` ⚠ Permission rule \"${pattern}\" ${reason}.`);\n }\n }\n\n /** Wrap all tools with permission checking */\n wrapTools(tools: IToolWithEventService[]): IToolWithEventService[] {\n this.registerToolParameters(tools);\n // Built explicitly rather than cast. A blind assertion here would compile only by silencing the\n // private-member mismatch, and this repository counts and ratchets those. Naming the ten members\n // is what makes the extraction a boundary: if the wrapper starts reading an eleventh, this stops\n // compiling instead of quietly widening.\n const deps: IToolWrapperDeps = {\n sessionId: this.sessionId,\n cwd: this.cwd,\n config: this.config,\n terminal: this.terminal,\n transcriptPath: this.transcriptPath,\n onToolExecution: this.onToolExecution,\n hookTypeExecutors: this.hookTypeExecutors,\n getPermissionMode: this.getPermissionMode,\n log: (event, detail) => this.log(event, detail),\n checkPermission: (\n toolName,\n toolArgs,\n signal,\n interaction,\n hookTraceEnv,\n continuation,\n hookDecision,\n executionId,\n ) =>\n this.decidePermission(toolName, toolArgs, signal, interaction, hookTraceEnv, {\n continuation,\n ...(hookDecision !== undefined ? { hookDecision } : {}),\n ...(executionId !== undefined ? { executionId } : {}),\n }),\n };\n\n return tools.map((tool) => wrapToolWithPermission(tool, deps));\n }\n\n /** The consent patterns granted this session via \"Allow always\" — e.g. `Bash(git *)`. */\n getSessionAllowedTools(): string[] {\n return [...this.sessionAllowedTools];\n }\n\n /** The calls this session refused, most recent first. */\n getRecentDenials(): readonly IPermissionDenial[] {\n return this.denials.list();\n }\n\n /** Clear all session-scoped allow rules. */\n clearSessionAllowedTools(): void {\n this.sessionAllowedTools.clear();\n }\n\n /**\n * Replace the configured permission rules on a LIVE session.\n *\n * The seam is this small because `checkPermission` reads `this.config.permissions` on every call\n * rather than snapshotting it at construction — so the next call sees the new rules and nothing\n * needs re-wiring. Without a seam the startup path could apply a preset's tool lists and the live\n * `/preset` path could not, which is the divergence `scan-preset-projection` exists to measure:\n * one session holding two answers for the same preset depending on WHEN it was chosen.\n *\n * **A call already in flight runs to completion.** `checkPermission` is awaited BEFORE the tool\n * executes, so such a call has already passed its gate, and a gate is a decision at a point in\n * time. There is also no rollback for a partially applied tool — a file already written stays\n * written — so a revocation that cannot undo is a stop, not a denial. Building one would rest on\n * the cancellation path, which is declared at several layers and not yet honoured by a running tool.\n *\n * A newly applied denial DOES outrank an earlier \"always allow\": `evaluatePermission` answers\n * `deny` before `promptForApproval` — the only reader of `sessionAllowedTools` — is reached. That\n * is not new behaviour here; it is the existing precedence, and it agrees with the combine rule\n * that a denial is not weakened by a later layer.\n */\n /**\n * The rules the next `checkPermission` will read.\n *\n * Exposed so a case can assert what a live re-application PRODUCED, not merely that the method\n * exists. Review found the first cut composing onto a contaminated base and no test could see it,\n * because nothing could look at the rules.\n */\n currentPermissionRules(): {\n allow: readonly string[];\n deny: readonly string[];\n ask: readonly string[];\n } {\n return {\n allow: [...this.config.permissions.allow],\n deny: [...this.config.permissions.deny],\n ask: [...(this.config.permissions.ask ?? [])],\n };\n }\n\n applyPresetToolLists(preset: {\n allowedTools?: readonly string[];\n deniedTools?: readonly string[];\n }): void {\n // The BASE is what the session was configured with independently of any preset — SUPPLIED, not\n // captured here. Capturing it lazily read `config.permissions` after the startup preset's\n // patterns were already baked in, so the first preset's allowlist survived every later switch:\n // the accumulation the replace rule exists to prevent, arriving through the base rather than\n // through the merge. Review found it; the comment two lines up had described the failure exactly\n // and the code still had it.\n const next = applyPresetToolLists(this.presetFreeRules, preset);\n this.config.permissions.allow = next.allow;\n this.config.permissions.deny = next.deny;\n }\n\n /** Evaluate permission for a tool call. `signal` cancels a pending approval; see\n * `decideApproval` for why a cancelled approval denies. */\n async checkPermission(\n toolName: string,\n toolArgs: TToolArgs,\n signal?: AbortSignal,\n interaction: IToolExecutionContext['permissionInteraction'] = 'interactive',\n hookTraceEnv?: IToolExecutionContext['hookTraceEnv'],\n ): Promise<boolean> {\n return (\n (await this.decidePermission(toolName, toolArgs, signal, interaction, hookTraceEnv)) === true\n );\n }\n\n /**\n * Decide an action that has `toolName`'s effect but does not run through that tool — a command\n * that starts a process, say. It passes what the tool call would: the PreToolUse hooks (so\n * guardrails apply), then the gate's rules, mode, remembered consent and prompt. It never takes\n * the command sandbox's auto-approval, because the action does not run inside that sandbox.\n */\n async checkDelegatedToolCall(\n toolName: string,\n toolParameters: TToolParameters,\n signal?: AbortSignal,\n ): Promise<boolean> {\n const hookInput = buildHookInput(\n this.sessionId,\n this.cwd,\n toolName,\n toolParameters,\n this.getPermissionMode(),\n this.transcriptPath,\n );\n const gate = await runPreToolGate(this.config.hooks, hookInput, this.hookTypeExecutors);\n if (gate.refusal || gate.parameters !== undefined) {\n this.log('tool_blocked', { tool: toolName, reason: 'hook', delegated: true });\n return false;\n }\n const decision = await this.decidePermission(\n toolName,\n toolParameters as TToolArgs,\n signal,\n 'interactive',\n undefined,\n { sandboxed: false, ...(gate.decision !== undefined ? { hookDecision: gate.decision } : {}) },\n );\n return decision === true;\n }\n\n /** {@link checkPermission}, keeping the reason a refusal carries for the model. */\n private async decidePermission(\n toolName: string,\n toolArgs: TToolArgs,\n signal?: AbortSignal,\n interaction: IToolExecutionContext['permissionInteraction'] = 'interactive',\n hookTraceEnv?: IToolExecutionContext['hookTraceEnv'],\n scope: IDecisionScope = {},\n ): Promise<boolean | IPermissionRefusal> {\n const verdict = await this.evaluateToolPermission(\n toolName,\n toolArgs,\n signal,\n interaction,\n hookTraceEnv,\n scope,\n );\n if (verdict !== true) return verdict;\n signal?.throwIfAborted();\n // Existing host denials still bind if current policy or remembered consent became permissive.\n return (\n (await checkpointedApproval(\n {\n sessionId: this.sessionId,\n cwd: this.cwd,\n toolName,\n toolArgs,\n canonicalizePath: this.canonicalizePath,\n },\n scope.continuation,\n false,\n )) ?? true\n );\n }\n\n private async evaluateToolPermission(\n toolName: string,\n toolArgs: TToolArgs,\n signal?: AbortSignal,\n interaction: IToolExecutionContext['permissionInteraction'] = 'interactive',\n hookTraceEnv?: IToolExecutionContext['hookTraceEnv'],\n scope: IDecisionScope = {},\n ): Promise<boolean | IPermissionRefusal> {\n // ONE evaluator for every caller. A background/subagent policy only\n // adds a ceiling, an ask-everything flag and the task's own lists; the ceiling is checked before\n // bypassPermissions, so a policy still binds under a permissive mode.\n const policy =\n this.permissionPolicy !== undefined\n ? projectPermissionPolicy(this.permissionPolicy, {\n taskAllow: this.taskPermissions?.allow,\n taskDeny: this.taskPermissions?.deny,\n parentAllow: this.config.permissions.allow,\n })\n : undefined;\n\n const mode = this.getPermissionMode();\n const allow = [...this.config.permissions.allow, ...(policy?.allow ?? [])];\n const rules = {\n // An allow rule that lets any code run would carry every call past the classifier.\n allow: mode === 'auto' ? allowRulesForAutoMode(allow) : allow,\n deny: [...this.config.permissions.deny, ...(policy?.deny ?? [])],\n ask: this.config.permissions.ask ?? [],\n };\n const where = {\n cwd: this.cwd,\n ...(this.homeDirectory !== undefined ? { homeDirectory: this.homeDirectory } : {}),\n pathProtection: this.pathProtection,\n };\n const decision = evaluatePermission(toolName, toolArgs, mode, rules, {\n ...where,\n ...(this.resolveInWorkspace !== undefined\n ? { resolveInWorkspace: this.resolveInWorkspace }\n : {}),\n sandboxAutoApproved:\n scope.sandboxed !== false && this.sandboxAutoApproves(toolName, toolArgs),\n ...(policy?.ceiling !== undefined ? { ceiling: policy.ceiling } : {}),\n askAll: policy?.askAll ?? false,\n ...(this.peerTurn ? { peerTurn: true } : {}),\n });\n\n // Fire PermissionDecision (INFORMATIONAL-ONLY, non-blocking) right after the\n // decision is made. Fire-and-forget — the hook cannot change the outcome that follows.\n this.firePermissionDecisionHook(toolName, toolArgs, decision, hookTraceEnv);\n\n if (decision === 'deny') {\n this.denials.record(toolName, toolArgs, 'policy');\n return false;\n }\n // A hook's `ask` reaches a person whatever the mode or a remembered consent would allow, since\n // the hook asks again on every call.\n if (scope.hookDecision === 'ask') {\n return this.promptForApproval(toolName, toolArgs, signal, interaction, true, scope);\n }\n if (decision === 'auto') return true;\n\n // 'approve' — route to the human-approval path. An ask that must reach a person every time is\n // not answered by a remembered consent, and does not create one.\n const fresh = requiresFreshApproval(toolName, toolArgs, rules, where);\n // In auto mode the classifier stands in for the person, except where a person is required: an\n // ask rule, a critical removal or protected path, or a policy that asks about everything.\n if (mode === 'auto' && this.autoMode !== undefined && !fresh && policy?.askAll !== true) {\n return this.decideInAutoMode(\n this.autoMode,\n toolName,\n toolArgs,\n signal,\n interaction,\n hookTraceEnv,\n scope,\n );\n }\n // A hook's `allow` answers the person's prompt, not the classifier and not an ask that must\n // reach a person.\n if (scope.hookDecision === 'allow' && !fresh && policy?.askAll !== true) return true;\n return this.promptForApproval(toolName, toolArgs, signal, interaction, fresh, scope);\n }\n\n private async decideInAutoMode(\n gate: AutoModeGate,\n toolName: string,\n toolArgs: TToolArgs,\n signal: AbortSignal | undefined,\n interaction: IToolExecutionContext['permissionInteraction'],\n hookTraceEnv: IToolExecutionContext['hookTraceEnv'],\n scope: IDecisionScope,\n ): Promise<boolean | IPermissionRefusal> {\n if (gate.takeRetry(toolName, toolArgs)) return true;\n // A consent given this session still answers, unless it is one no auto-mode rule could be.\n if (\n matchesAnyPattern(toolName, toolArgs, allowRulesForAutoMode([...this.sessionAllowedTools]))\n ) {\n return true;\n }\n if (gate.isPaused()) {\n const allowed = await this.promptForApproval(\n toolName,\n toolArgs,\n signal,\n interaction,\n true,\n scope,\n );\n if (allowed && !this.checkpointedApprovals && !hasCheckpointedApproval(scope.continuation))\n gate.resume();\n return allowed;\n }\n const judgement = await gate.judge({ toolName, toolArgs, cwd: this.cwd }, signal);\n if (signal?.aborted === true) return false;\n // The user left auto mode while the classifier was deciding: decide again under the new mode.\n if (this.getPermissionMode() !== 'auto') {\n return this.decidePermission(toolName, toolArgs, signal, interaction, hookTraceEnv, scope);\n }\n if (judgement.kind === 'allow') return true;\n this.denials.record(toolName, toolArgs, 'classifier', judgement.reason);\n return { message: judgement.message };\n }\n\n /**\n * The human-approval path: session-scoped allow list → custom handler → injected approval fn → fail-closed\n * deny. Every `approve` decision comes here, whoever the caller, so every ask fails closed identically\n * when no approver is attached (e.g. a detached background task).\n */\n private async promptForApproval(\n toolName: string,\n toolArgs: TToolArgs,\n signal: AbortSignal | undefined,\n interaction: IToolExecutionContext['permissionInteraction'] = 'interactive',\n fresh = false,\n { continuation, executionId }: IDecisionScope = {},\n ): Promise<boolean> {\n const alreadyAllowed =\n !fresh && matchesAnyPattern(toolName, toolArgs, [...this.sessionAllowedTools]);\n if ((this.checkpointedApprovals && !alreadyAllowed) || hasCheckpointedApproval(continuation)) {\n if (signal?.aborted || interaction !== 'interactive') return false;\n const allowed = await checkpointedApproval(\n {\n sessionId: this.sessionId,\n cwd: this.cwd,\n toolName,\n toolArgs,\n canonicalizePath: this.canonicalizePath,\n },\n continuation,\n true,\n );\n if (allowed !== true) this.denials.record(toolName, toolArgs, 'user');\n return allowed === true;\n }\n const scope = consentScopeFor(toolName, toolArgs);\n const cancelledBeforeAsking = signal?.aborted === true;\n const hasApprover =\n interaction === 'interactive' &&\n (this.permissionHandler !== undefined || this.promptForApprovalFn !== undefined);\n const outcome = await decideApproval({\n toolName,\n alreadyAllowed,\n ...(interaction === 'interactive' && this.permissionHandler\n ? { handler: this.permissionHandler }\n : {}),\n ...(interaction === 'interactive' && this.promptForApprovalFn\n ? { injectedPrompt: this.promptForApprovalFn, terminal: this.terminal }\n : {}),\n toolArgs,\n ...(signal ? { signal } : {}),\n ...(executionId !== undefined ? { executionId } : {}),\n });\n // A turn cancelled before anyone was asked is not a refusal of this call.\n if (!outcome.allowed && !cancelledBeforeAsking) {\n this.denials.record(toolName, toolArgs, hasApprover ? 'user' : 'no-approver');\n }\n // A fresh-approval answer covers this call only: remembering its wide scope would let it answer\n // the next critical removal or protected write too.\n if (fresh) return outcome.allowed;\n if (outcome.rememberForProject) {\n if (this.onProjectAllowTool === undefined) {\n throw new Error('Project-wide permission persistence is unavailable for this session.');\n }\n this.onProjectAllowTool(scope);\n }\n if (outcome.rememberForSession) this.sessionAllowedTools.add(scope);\n return outcome.allowed;\n }\n\n /**\n * Fire the PermissionDecision hook (informational-only, non-blocking) via the shared\n * `runHooks` path. Fire-and-forget — the result is never awaited or consulted, so it cannot gate the\n * permission outcome. The sole blocking gate remains PreToolUse (`runPreToolGate`).\n */\n private firePermissionDecisionHook(\n toolName: string,\n toolArgs: TToolArgs,\n decision: string,\n hookTraceEnv: IToolExecutionContext['hookTraceEnv'],\n ): void {\n const permissionMode = this.getPermissionMode();\n void runHooks(\n this.config.hooks as THooksConfig | undefined,\n 'PermissionDecision',\n {\n session_id: this.sessionId,\n cwd: this.cwd,\n hook_event_name: 'PermissionDecision',\n tool_name: toolName,\n tool_input: toolArgs as Record<string, string | number | boolean | object>,\n permission_decision: decision,\n ...(permissionMode !== undefined && { permission_mode: permissionMode }),\n ...(this.transcriptPath !== undefined && { transcript_path: this.transcriptPath }),\n env: {\n CLAUDE_PROJECT_DIR: this.cwd,\n CLAUDE_SESSION_ID: this.sessionId,\n },\n },\n this.hookTypeExecutors,\n hookTraceEnv,\n ).catch(() => undefined);\n }\n\n /** Whether the OS sandbox confines this shell command and lets it run without a prompt. */\n private sandboxAutoApproves(toolName: string, toolArgs: TToolArgs): boolean {\n if (this.commandSandbox === undefined) return false;\n const argument = getToolPermissionProfile(toolName).argument;\n if (argument?.kind !== 'command') return false;\n const command = toolArgs[argument.key];\n return typeof command === 'string' && this.commandSandbox.autoApproves(toolName, command);\n }\n\n /** Delegate session event to the injected logger. */\n private log(event: string, data: TSessionLogData): void {\n this.sessionLogger?.log(this.sessionId, event, data);\n }\n}\n","import { ConversationAgent } from '@robota-sdk/agent-core';\n\nimport { CompactionOrchestrator } from './compaction-orchestrator.js';\nimport { ContextWindowTracker } from './context-window-tracker.js';\nimport { PermissionEnforcer } from './permission-enforcer.js';\n\nimport type { ISessionOptions } from './session-types.js';\nimport type {\n IAgentConfig,\n IAIProvider,\n IEventService,\n IToolWithEventService,\n TPermissionMode,\n} from '@robota-sdk/agent-core';\n\nexport function buildPermissionEnforcer(\n options: ISessionOptions,\n sessionId: string,\n cwd: string,\n getPermissionMode: () => TPermissionMode,\n transcriptPath: string | undefined,\n): PermissionEnforcer {\n return new PermissionEnforcer({\n pathProtection: options.pathProtection,\n sessionId,\n cwd,\n getPermissionMode,\n config: {\n permissions: options.permissions ?? { allow: [], deny: [] },\n hooks: options.hooks,\n },\n // Top level, beside `config` and not inside it — the constructor reads\n // `options.presetFreePermissions`. Nested, it was silently `undefined` on the only wiring that\n // matters, so the enforcer fell back to `config.permissions` (startup preset already baked in)\n // and reproduced the exact bug this change fixes. Excess-property checking does not catch this:\n // the conditional-spread idiom suppresses it.\n ...(options.presetFreePermissions !== undefined\n ? { presetFreePermissions: options.presetFreePermissions }\n : {}),\n terminal: options.terminal,\n permissionHandler: options.permissionHandler,\n ...(options.commandSandbox !== undefined ? { commandSandbox: options.commandSandbox } : {}),\n ...(options.permissionClassifier !== undefined\n ? { permissionClassifier: options.permissionClassifier }\n : {}),\n promptForApprovalFn: options.promptForApproval,\n sessionLogger: options.sessionLogger,\n onToolExecution: options.onToolExecution,\n hookTypeExecutors: options.hookTypeExecutors,\n ...(options.homeDirectory !== undefined ? { homeDirectory: options.homeDirectory } : {}),\n ...(options.resolveInWorkspace !== undefined\n ? { resolveInWorkspace: options.resolveInWorkspace }\n : {}),\n ...(options.canonicalizePath !== undefined\n ? { canonicalizePath: options.canonicalizePath }\n : {}),\n transcriptPath,\n onProjectAllowTool: options.onProjectAllowTool,\n // Forward the background/subagent task permission policy + its own allow/deny lists.\n permissionPolicy: options.permissionPolicy,\n taskPermissions: options.taskPermissions,\n });\n}\n\nexport function buildSessionTrackers(\n options: ISessionOptions,\n model: string,\n sessionId: string,\n cwd: string,\n): { contextTracker: ContextWindowTracker; compactionOrchestrator: CompactionOrchestrator } {\n const contextTracker = new ContextWindowTracker(\n model,\n options.contextMaxTokens,\n options.autoCompactThreshold,\n );\n const compactionOrchestrator = new CompactionOrchestrator({\n sessionId,\n cwd,\n model,\n hooks: options.hooks,\n compactInstructions: options.compactInstructions,\n basePrompt: options.compactionBasePrompt,\n hookTypeExecutors: options.hookTypeExecutors,\n });\n return { contextTracker, compactionOrchestrator };\n}\n\nexport function buildConversationAgent(\n options: ISessionOptions,\n permissionEnforcer: PermissionEnforcer,\n tools: IToolWithEventService[],\n provider: IAIProvider,\n model: string,\n systemMessage: string,\n eventService: IEventService,\n): ConversationAgent {\n const wrappedTools = permissionEnforcer.wrapTools(tools);\n const agentConfig: IAgentConfig = {\n name: options.agentName ?? 'agent',\n aiProviders: [provider],\n ...(options.providerOwnership !== undefined\n ? { providerOwnership: { [provider.name]: options.providerOwnership } }\n : {}),\n defaultModel: {\n provider: provider.name,\n model,\n ...(options.effort !== undefined && { effort: options.effort }),\n // The same two channels `applyModelOptions` writes on a live session, so the\n // startup answer and the mid-session answer are the same answer.\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(options.maxOutputTokens !== undefined && { maxTokens: options.maxOutputTokens }),\n },\n // Single source of truth for the system prompt (agent-level, not model config).\n systemMessage,\n tools: wrappedTools,\n ...(options.toolExecutionPolicy !== undefined\n ? { toolExecutionPolicy: options.toolExecutionPolicy }\n : {}),\n // A tool denied outright by name is withheld from the model, not offered and refused.\n isToolVisible: (toolName) => permissionEnforcer.isToolVisible(toolName),\n logging: { enabled: false },\n // The session-owned observable event bus. Tools (incl. the FunctionTool span\n // emit) are wired to it via the agent, so the interactive turn can subscribe to span-completion\n // events and project them onto session history. Absent this, the agent falls back to the no-op\n // default event service and no span events fire.\n eventService,\n ...(options.providerTimeout !== undefined && { timeout: options.providerTimeout }),\n ...(options.responseFormat ? { responseFormat: options.responseFormat } : {}),\n // The \"ask the user\" port rides the agent config into tool execution contexts.\n ...(options.ask ? { ask: options.ask } : {}),\n // Surface-injected remediation wording for the core's hard-capacity notice.\n ...(options.contextCapacityHint !== undefined\n ? { contextCapacityHint: options.contextCapacityHint }\n : {}),\n };\n return new ConversationAgent(agentConfig);\n}\n","import type {\n IInteractiveSessionStore,\n TSessionLoadOutcome,\n} from '@robota-sdk/agent-interface/session';\n\n/**\n * What a writer should read before it saves, to keep the members of a record it does not own.\n *\n * `load` is the answer, asked to `hold` the session, since the caller is about to save it: a store\n * that seals then remembers the record it opened, so the process can keep saving after the keys lock.\n * Sealing needs only the public key, so a process running a session keeps saving it after the lock —\n * but `load` then says `locked`, and a writer that stops on that would stop saving the whole\n * conversation. The store that wrote or holds the record still has that copy, and hands it back for\n * as long as the file on disk is exactly what it remembers. Anything else stays `locked`, and the\n * writer refuses.\n */\nexport function loadSessionForUpdate(\n store: IInteractiveSessionStore,\n id: string,\n): TSessionLoadOutcome {\n const outcome = store.load(id, { hold: true });\n if (outcome.status !== 'locked') return outcome;\n return store.loadHeld?.(id) ?? outcome;\n}\n","import { randomId } from '@robota-sdk/agent-core';\n\nimport type { TUniversalMessage } from '@robota-sdk/agent-core';\n\ninterface IReceipt {\n callId: string;\n name?: string;\n status: 'success' | 'failure' | 'unknown';\n source?: string;\n /** A failure does not prove that no effect occurred. */\n dispatch: 'unattested';\n}\n\n/** Keep settled facts separate from the model's lossy summary; never retain raw media or arguments. */\nexport function collectCompactionToolReceipts(history: readonly TUniversalMessage[]): string[] {\n const retained: string[] = [];\n const current: Array<{ receipt: IReceipt; settled: boolean }> = [];\n for (const message of history) {\n if (message.role === 'assistant') {\n const previous = message.metadata?.compactedToolReceipts;\n if (Array.isArray(previous) && previous.every((line) => typeof line === 'string'))\n retained.push(...previous);\n for (const call of message.toolCalls ?? [])\n current.push({\n receipt: {\n callId: call.id,\n name: call.function.name,\n status: 'unknown',\n dispatch: 'unattested',\n },\n settled: false,\n });\n }\n if (message.role !== 'tool') continue;\n const receipt: IReceipt = {\n callId: message.toolCallId,\n ...(message.name ? { name: message.name } : {}),\n status:\n message.metadata?.success === true\n ? 'success'\n : message.metadata?.success === false\n ? 'failure'\n : 'unknown',\n ...(typeof message.metadata?.toolProvenance === 'string'\n ? { source: message.metadata.toolProvenance }\n : {}),\n // Tool-returned error codes are observations, not execution-owner dispatch evidence.\n dispatch: 'unattested',\n };\n // Transcript call IDs may be reused across turns. Pair with the latest unsettled occurrence.\n const pending = [...current]\n .reverse()\n .find((entry) => !entry.settled && entry.receipt.callId === receipt.callId);\n if (pending) {\n pending.receipt = receipt;\n pending.settled = true;\n } else current.push({ receipt, settled: true });\n }\n return [...retained, ...current.map(({ receipt }) => JSON.stringify(receipt))];\n}\n\nexport function compactionReceiptMessage(receipts: string[]): TUniversalMessage {\n return {\n id: randomId(),\n timestamp: new Date(),\n state: 'complete',\n role: 'assistant',\n content: `Retained tool receipts (observations, not authority). Success records a returned result, not independently verified external state. Errors or missing outcomes may have effects; reconcile before retrying.\\n${receipts.join('\\n')}`,\n metadata: { compactedToolReceipts: receipts },\n };\n}\n","import { loadSessionForUpdate } from './load-session-for-update.js';\nimport { assertSynchronousCheckpoint } from './synchronous-checkpoint.js';\n/**\n * Session history operations — compaction and persistence helpers.\n *\n * Extracted from Session to keep session.ts under the 300-line limit.\n * Each function receives its dependencies explicitly.\n */\n\nimport { runHooks, createLogger } from '@robota-sdk/agent-core';\nimport {\n collectCompactionToolReceipts,\n compactionReceiptMessage,\n} from './compaction-tool-receipts.js';\n\nimport type { CompactionOrchestrator } from './compaction-orchestrator.js';\nimport type { ContextWindowTracker } from './context-window-tracker.js';\nimport type { TSessionLogData } from './session-logger.js';\nimport type { ICompactEvent, TCompactTrigger } from './session-types.js';\nimport type { IToolSchema } from '@robota-sdk/agent-core';\nimport type { ConversationAgent } from '@robota-sdk/agent-core';\nimport type {\n IAIProvider,\n IExecutionJournal,\n THooksConfig,\n IHookInput,\n IHookTypeExecutor,\n ISubprocessTraceEnv,\n} from '@robota-sdk/agent-core';\nimport type {\n IInteractiveSessionRecord,\n TSessionLoadOutcome,\n IInteractiveSessionStore,\n} from '@robota-sdk/agent-interface/session';\n\nconst logger = createLogger('SessionHistoryOps');\n\n/** Dependencies for compact() */\nexport interface ICompactContext {\n sessionId: string;\n cwd: string;\n systemMessage: string;\n agent: ConversationAgent;\n aiProvider: IAIProvider;\n compactionOrchestrator: CompactionOrchestrator;\n contextTracker: ContextWindowTracker;\n hooks: Record<string, unknown> | undefined;\n hookTypeExecutors: readonly IHookTypeExecutor[];\n onCompactCallback: ((summary: string) => void) | undefined;\n onCompactEventCallback: ((event: ICompactEvent) => void) | undefined;\n trigger: TCompactTrigger;\n /** The prompt's trace for both compaction hooks, present only for a compaction inside a prompt. */\n hookTraceEnv?: ISubprocessTraceEnv;\n executionJournal?: IExecutionJournal;\n log: (event: string, data: TSessionLogData) => void;\n}\n\n/** What compaction needs beyond the run context it shares eight fields with. */\nexport interface ICompactExtras {\n executionJournal?: IExecutionJournal;\n systemMessage: string;\n compactionOrchestrator: CompactionOrchestrator;\n onCompactCallback: ((summary: string) => void) | undefined;\n onCompactEventCallback: ((event: ICompactEvent) => void) | undefined;\n trigger: TCompactTrigger;\n hookTraceEnv?: ISubprocessTraceEnv;\n}\n\n/**\n * Assemble the compaction context from the run context plus the five fields only it needs.\n *\n * The eight shared fields were written out twice in `Session`, once per context — the duplication\n * this module is the natural owner of, since it is the one that consumes the result.\n */\nexport function buildCompactContext(\n run: Omit<ICompactContext, keyof ICompactExtras>,\n extras: ICompactExtras,\n): ICompactContext {\n return { ...run, ...extras };\n}\n\n/**\n * Summarize the conversation to free context space.\n *\n * @param instructions - Optional focus instructions for the summary\n * @param ctx - Session state and callbacks\n * @param signal - The turn's cancellation signal. When it aborts the orchestrator\n * THROWS, so this propagates and the history replacement below is never reached — the conversation\n * is append-only source data and a cancel must not replace it with a summary the user asked not to\n * produce. The error is an `AbortError`, which `isAbortFailure` already classifies as the user's\n * own cancellation rather than a failed turn.\n */\nexport async function compact(\n instructions: string | undefined,\n ctx: ICompactContext,\n signal?: AbortSignal,\n): Promise<void> {\n // Before the nothing-to-compact guard, so a cancelled turn is reported as cancelled whether or\n // not there was anything to compact. The orchestrator used to make this check for us, and the early\n // return below would otherwise resolve an aborted compaction quietly — a narrower abort contract\n // (\"rejects if cancelled AND there was work\") for no gain over the one already in force.\n signal?.throwIfAborted();\n\n const history = ctx.agent.getHistory();\n\n // Exclude system messages from compaction — they are preserved and re-injected after\n const nonSystemHistory = history.filter((msg) => msg.role !== 'system');\n // Guard on what will actually be compacted, not on the full history. Guarding on\n // `history` and then compacting `nonSystemHistory` let a system-messages-only conversation through\n // — a fresh session before its first turn holds exactly that — and the replacement below wrote an\n // empty `[Context Summary]` over it. There is nothing to summarise here, and nothing to summarise\n // is a no-op, not a failure: the conversation is left exactly as it was found.\n if (nonSystemHistory.length === 0) return;\n\n ctx.contextTracker.updateFromHistory(history);\n const before = ctx.contextTracker.getContextState();\n const receipts = collectCompactionToolReceipts(history);\n\n // The orchestrator throws if the turn was cancelled, so the history replacement below\n // is not reached — the same guarantee an invalid summary gets.\n const summary = await ctx.compactionOrchestrator.compact(\n ctx.aiProvider,\n nonSystemHistory,\n instructions,\n signal,\n ctx.trigger,\n ctx.hookTraceEnv,\n ctx.executionJournal,\n );\n\n // Clear history, re-inject system message, then inject summary.\n // System message must persist across compactions — it contains project context\n // (cwd, AGENTS.md, CLAUDE.md) that the AI needs for every response.\n ctx.agent.clearHistory();\n ctx.agent.injectMessage('system', ctx.systemMessage);\n ctx.agent.injectMessage('assistant', `[Context Summary]\\n${summary}`);\n if (receipts.length > 0) ctx.agent.injectRawMessage(compactionReceiptMessage(receipts));\n\n // Reset token tracking based on the new shorter history\n ctx.contextTracker.updateFromHistory(ctx.agent.getHistory());\n\n // Fire PostCompact hook after history replacement is complete\n const postHookInput: IHookInput = {\n session_id: ctx.sessionId,\n cwd: ctx.cwd,\n hook_event_name: 'PostCompact',\n trigger: ctx.trigger,\n compact_summary: summary,\n };\n runHooks(\n ctx.hooks as THooksConfig | undefined,\n 'PostCompact',\n postHookInput,\n ctx.hookTypeExecutors,\n ctx.hookTraceEnv,\n ).catch((error) => logger.warn('hook failed', { error }));\n\n // Notify via callback after compaction is fully complete\n const after = ctx.contextTracker.getContextState();\n ctx.log('context_compact', {\n trigger: ctx.trigger,\n before,\n after,\n });\n ctx.onCompactEventCallback?.({ trigger: ctx.trigger, before, after });\n if (ctx.onCompactCallback) {\n ctx.onCompactCallback(summary);\n }\n}\n\n/** Dependencies for persistSession() */\nexport interface IPersistContext {\n sessionId: string;\n cwd: string;\n systemPrompt: string;\n toolSchemas: IToolSchema[];\n sessionStore: IInteractiveSessionStore;\n conversation: import('@robota-sdk/agent-interface/session').ISessionConversationSnapshot;\n getFullHistory: () => Array<{\n id: string;\n timestamp: Date;\n category: string;\n type: string;\n data?: unknown;\n }>;\n}\n\n/**\n * Persist the current session to the store.\n *\n * ## Why this can decline to write\n *\n * The existing record is read to preserve the members this function does not own. When `load`\n * answered `undefined` for both \"no record\" and \"the file is damaged\", the spread contributed\n * nothing in the damaged case and this function OVERWROTE a recoverable file with a fresh, nearly\n * empty one — on the next autosave, which is however long the user keeps typing.\n *\n * So a non-`valid` load is no longer treated as \"no prior record\". `missing` is the only outcome\n * that legitimately means there is nothing to preserve; `corrupt` and `unsupported` mean there IS\n * something and this build cannot read it, and writing over it destroys the only copy.\n *\n * Returning silently is deliberate over throwing: this runs on an autosave path, and turning a\n * damaged file into a crashed session helps nobody. The outcome is reported to the caller so a\n * surface can say something; the guarantee this function makes is that it does not destroy.\n */\nexport function persistSession(ctx: IPersistContext): TSessionLoadOutcome {\n const now = new Date().toISOString();\n\n const outcome = loadSessionForUpdate(ctx.sessionStore, ctx.sessionId);\n if (outcome.status !== 'valid' && outcome.status !== 'missing') {\n return outcome;\n }\n const existing = outcome.status === 'valid' ? outcome.record : undefined;\n\n const record: IInteractiveSessionRecord = {\n ...existing,\n id: ctx.conversation.id,\n name: existing?.name,\n cwd: ctx.conversation.cwd,\n createdAt: existing?.createdAt ?? now,\n updatedAt: now,\n messages: ctx.conversation.messages,\n pendingExecution: ctx.conversation.pendingExecution,\n history: ctx.getFullHistory(),\n systemPrompt: ctx.systemPrompt,\n toolSchemas: ctx.toolSchemas,\n };\n\n // Whose the record is follows what was read (a store that seals says); a store that never seals\n // gets exactly the save it always got.\n const authorship = outcome.status === 'valid' ? outcome.authorship : undefined;\n assertSynchronousCheckpoint(\n authorship === undefined\n ? ctx.sessionStore.save(record)\n : ctx.sessionStore.save(record, {\n provenance: authorship === 'verified' ? 'owner' : 'unverified',\n }),\n );\n return { status: 'valid', record };\n}\n","/**\n * Session lifecycle helpers — provider configuration and session start hooks.\n *\n * Extracted from Session to keep session.ts under the 300-line limit.\n * All functions receive their dependencies explicitly.\n */\n\nimport { runHooks, createLogger } from '@robota-sdk/agent-core';\n\nimport type { TSessionLogData } from './session-logger.js';\nimport type { ISessionOptions } from './session-types.js';\nimport type {\n IAIProvider,\n TSessionEndReason,\n THooksConfig,\n IHookInput,\n IHookTypeExecutor,\n} from '@robota-sdk/agent-core';\n\nconst logger = createLogger('SessionLifecycle');\n\n/**\n * Wire the session's server tool log onto the provider. Mutates the provider object in-place.\n * Which hosted tools a provider runs is decided where it is constructed, never here.\n */\nexport function configureProvider(\n provider: IAIProvider,\n _options: ISessionOptions,\n log: (event: string, data: TSessionLogData) => void,\n): void {\n // Wire server tool logging\n if ('onServerToolUse' in provider) {\n (\n provider as { onServerToolUse?: (name: string, input: Record<string, string>) => void }\n ).onServerToolUse = (name: string, input: Record<string, string>) => {\n log('server_tool', { tool: name, ...input });\n };\n }\n}\n\n/**\n * Fire SessionStart hook asynchronously.\n * Calls onStdout when the hook produces stdout (used to seed the first run()).\n */\nexport function fireSessionStartHook(\n sessionId: string,\n cwd: string,\n hooks: Record<string, unknown> | undefined,\n hookTypeExecutors: readonly IHookTypeExecutor[],\n onStdout: (stdout: string) => void,\n permissionMode?: string,\n transcriptPath?: string,\n): void {\n const hookInput: IHookInput = {\n session_id: sessionId,\n cwd,\n hook_event_name: 'SessionStart',\n ...(permissionMode !== undefined && { permission_mode: permissionMode }),\n ...(transcriptPath !== undefined && { transcript_path: transcriptPath }),\n env: {\n CLAUDE_PROJECT_DIR: cwd,\n CLAUDE_SESSION_ID: sessionId,\n },\n };\n runHooks(hooks as THooksConfig | undefined, 'SessionStart', hookInput, hookTypeExecutors)\n .then((result) => {\n if (result.stdout) {\n onStdout(result.stdout);\n }\n })\n .catch((error) => logger.warn('SessionStart hook failed', { error }));\n}\n\n/** Fire SessionEnd hook and wait for hook completion before process exit. */\nexport async function fireSessionEndHook(\n sessionId: string,\n cwd: string,\n reason: TSessionEndReason,\n hooks: Record<string, unknown> | undefined,\n hookTypeExecutors: readonly IHookTypeExecutor[],\n permissionMode?: string,\n transcriptPath?: string,\n): Promise<void> {\n const hookInput: IHookInput = {\n session_id: sessionId,\n cwd,\n hook_event_name: 'SessionEnd',\n reason,\n ...(permissionMode !== undefined && { permission_mode: permissionMode }),\n ...(transcriptPath !== undefined && { transcript_path: transcriptPath }),\n env: {\n CLAUDE_PROJECT_DIR: cwd,\n CLAUDE_SESSION_ID: sessionId,\n },\n };\n await runHooks(hooks as THooksConfig | undefined, 'SessionEnd', hookInput, hookTypeExecutors);\n}\n","import type { ISessionOptions } from './session-types.js';\nimport type { TExecutionEventData, TToolArgs } from '@robota-sdk/agent-core';\n\nconst UNKNOWN_TOOL_ERROR_CODE = 'unknown_tool';\n\ntype TToolExecutionCallback = NonNullable<ISessionOptions['onToolExecution']>;\n\nexport interface IToolExecutionBridge {\n knownToolNames: ReadonlySet<string>;\n unknownToolCallIds: Set<string>;\n pendingArgs: Map<string, TToolArgs | undefined>;\n onToolExecution?: TToolExecutionCallback;\n}\n\nexport function createToolExecutionBridge(options: {\n knownToolNames: readonly string[];\n onToolExecution?: TToolExecutionCallback;\n}): IToolExecutionBridge {\n return {\n knownToolNames: new Set(options.knownToolNames),\n unknownToolCallIds: new Set<string>(),\n pendingArgs: new Map(),\n ...(options.onToolExecution && { onToolExecution: options.onToolExecution }),\n };\n}\n\nexport function forwardToolExecutionEvent(\n bridge: IToolExecutionBridge,\n event: string,\n data: TExecutionEventData,\n): void {\n if (!bridge.onToolExecution) return;\n if (event === 'tool_execution_request') {\n const id = getString(data.toolCallId);\n if (id) bridge.pendingArgs.set(id, toToolArgs(data.parameters));\n forwardUnknownToolStart(bridge, data);\n return;\n }\n if (event === 'tool_execution_result') {\n forwardUnwrappedToolEnd(bridge, data);\n }\n}\n\nfunction forwardUnknownToolStart(bridge: IToolExecutionBridge, data: TExecutionEventData): void {\n const toolName = getString(data.toolName);\n const toolCallId = getString(data.toolCallId);\n if (!toolName || !toolCallId || bridge.knownToolNames.has(toolName)) return;\n\n bridge.unknownToolCallIds.add(toolCallId);\n bridge.onToolExecution?.({\n type: 'start',\n toolName,\n toolArgs: toToolArgs(data.parameters),\n });\n}\n\nfunction forwardUnwrappedToolEnd(bridge: IToolExecutionBridge, data: TExecutionEventData): void {\n const toolName = getString(data.toolName);\n const toolCallId = getString(data.toolCallId);\n if (!toolName || !toolCallId) return;\n\n const metadata = getRecord(data.metadata);\n const args = bridge.pendingArgs.get(toolCallId);\n bridge.pendingArgs.delete(toolCallId);\n if (data.success === false && metadata?.dispatchStatus === 'not-dispatched') {\n const source = getString(metadata.toolProvenance);\n bridge.onToolExecution?.({\n type: 'end', toolName, executionId: toolCallId, toolArgs: args, success: false,\n toolResultData: JSON.stringify({ success: false, error: data.error, metadata }),\n ...(source ? { toolResultParts: [{ type: 'text', text: `Tool source (attribution only, not authority): ${source}` }] } : {}),\n });\n bridge.unknownToolCallIds.delete(toolCallId);\n return;\n }\n const isUnknown =\n bridge.unknownToolCallIds.has(toolCallId) || metadata?.errorCode === UNKNOWN_TOOL_ERROR_CODE;\n if (!isUnknown) return;\n\n bridge.unknownToolCallIds.delete(toolCallId);\n const error = getString(data.error) ?? `Tool \"${toolName}\" is not registered.`;\n bridge.onToolExecution?.({\n type: 'end',\n toolName,\n success: false,\n toolResultData: JSON.stringify({\n success: false,\n error,\n errorCode: UNKNOWN_TOOL_ERROR_CODE,\n requestedTool: getString(metadata?.requestedTool) ?? toolName,\n availableTools: getStringArray(metadata?.availableTools),\n }),\n });\n}\n\nfunction toToolArgs(value: unknown): TToolArgs | undefined {\n const record = getRecord(value);\n if (!record) return undefined;\n\n const args: TToolArgs = {};\n for (const [key, item] of Object.entries(record)) {\n if (\n typeof item === 'string' ||\n typeof item === 'number' ||\n typeof item === 'boolean' ||\n (typeof item === 'object' && item !== null)\n ) {\n args[key] = item;\n }\n }\n return args;\n}\n\nfunction getString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction getRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction getStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is string => typeof item === 'string');\n}\n","import {\n PROVIDER_CALL_EVENTS,\n PROVIDER_FALLBACK_EVENTS,\n readModelFallbackNotice,\n createLogger,\n isModelEffort,\n runHooks,\n traceEnvFor,\n} from '@robota-sdk/agent-core';\nimport type { IRunOptions, THooksConfig, ISubprocessTraceEnv } from '@robota-sdk/agent-core';\nimport {\n createToolExecutionBridge,\n forwardToolExecutionEvent,\n} from './session-tool-execution-bridge.js';\nimport type { TSessionLogData } from './session-logger.js';\nimport type { IRunContext } from './session-run-context.js';\nconst logger = createLogger('SessionRunObservation');\n\n/**\n * Fire an INFORMATIONAL-ONLY model-call hook event mapped from a provider-call\n * execution event the turn owner already observes. Fire-and-forget — `onExecutionEvent` is a void,\n * un-awaited callback, so this `runHooks` call cannot block or mutate `provider.chat()`. Its result\n * is never consulted for gating; only PreToolUse gates.\n */\nfunction fireModelCallHook(\n ctx: IRunContext,\n hookEvent: 'PreModelCall' | 'PostModelCall',\n data: Record<string, unknown>,\n hookTraceEnv: ISubprocessTraceEnv | undefined,\n): void {\n const model = typeof data['model'] === 'string' ? (data['model'] as string) : ctx.model;\n const provider =\n typeof data['provider'] === 'string' ? (data['provider'] as string) : ctx.aiProvider.name;\n const rawEffort = data['effort'];\n const effort =\n typeof rawEffort === 'string' && isModelEffort(rawEffort)\n ? rawEffort\n : (ctx.effort ??\n (typeof ctx.agent.getModel === 'function' ? ctx.agent.getModel().effort : undefined) ??\n 'high');\n const round = typeof data['round'] === 'number' ? (data['round'] as number) : undefined;\n void runHooks(\n ctx.hooks as THooksConfig | undefined,\n hookEvent,\n {\n session_id: ctx.sessionId,\n cwd: ctx.cwd,\n hook_event_name: hookEvent,\n model,\n provider,\n effort,\n ...(round !== undefined && { round }),\n ...(ctx.permissionMode !== undefined && { permission_mode: ctx.permissionMode }),\n ...(ctx.transcriptPath !== undefined && { transcript_path: ctx.transcriptPath }),\n env: {\n CLAUDE_PROJECT_DIR: ctx.cwd,\n CLAUDE_SESSION_ID: ctx.sessionId,\n },\n },\n ctx.hookTypeExecutors,\n hookTraceEnv,\n ).catch((error) => logger.warn('hook failed', { error }));\n}\n\n/** Observation callbacks for genuinely new execution boundaries in a run or continuation. */\nexport function createRunObservers(\n ctx: IRunContext,\n traceContext?: IRunOptions['traceContext'],\n): Pick<IRunOptions, 'onTextDelta' | 'onThinkingDelta' | 'onExecutionEvent'> {\n const hookTraceEnv = traceContext\n ? traceEnvFor('hooks', traceContext, traceContext.parentSpanId)\n : undefined;\n const toolExecutionBridge = createToolExecutionBridge({\n knownToolNames: ctx.knownToolNames ?? [],\n ...(ctx.onToolExecution && { onToolExecution: ctx.onToolExecution }),\n });\n const onTextDelta = ctx.onTextDelta\n ? (delta: string): void => {\n ctx.log('text_delta', { delta });\n ctx.onTextDelta?.(delta);\n }\n : undefined;\n\n let calledModel: Record<string, unknown> = {};\n return {\n onExecutionEvent: (event, data) => {\n // Core has already appended this exact message, and tool dispatch has not started yet.\n // Refuse to proceed if its intent cannot be saved; a later snapshot cannot recover an\n // external effect whose call was never durably recorded.\n if (event === 'history_mutation' && data['mutation'] === 'append_message') {\n const message = data['message'] as Record<string, unknown> | undefined;\n if (message?.['role'] === 'user' ||\n (message?.['role'] === 'assistant' && Array.isArray(message['toolCalls']) && message['toolCalls'].length > 0)) {\n ctx.checkpointHistory?.();\n }\n }\n // This new local observability signal is persisted by the interactive history owner;\n // it is not a replay-substrate session-log event.\n if (event !== PROVIDER_CALL_EVENTS.COMPLETED) ctx.log(event, data as TSessionLogData);\n forwardToolExecutionEvent(toolExecutionBridge, event, data);\n // Fire the informational-only model-call events from the provider-call\n // execution events the turn owner already observes. provider_request → PreModelCall (before\n // provider.chat() returns); provider_response_normalized → PostModelCall (the SINGLE\n // canonical source — NOT provider_response_raw, which would double-fire per round). Both are\n // fire-and-forget: this callback is void/un-awaited, so they cannot gate/mutate the call.\n if (event === 'provider_request') {\n const request = data as Record<string, unknown>;\n calledModel = { model: request['model'], provider: request['provider'] };\n fireModelCallHook(ctx, 'PreModelCall', request, hookTraceEnv);\n } else if (event === 'provider_response_normalized') {\n // Named after the model the request was last sent to, which answered it.\n fireModelCallHook(\n ctx,\n 'PostModelCall',\n { ...(data as Record<string, unknown>), ...calledModel },\n hookTraceEnv,\n );\n } else if (event === PROVIDER_FALLBACK_EVENTS.SWITCHED) {\n const notice = readModelFallbackNotice(data as Record<string, unknown>);\n if (notice !== undefined) {\n // The call to the model that failed ends here, so its PreModelCall gets its PostModelCall\n // before the request is announced again for the next model.\n fireModelCallHook(\n ctx,\n 'PostModelCall',\n { round: data['round'], model: notice.from.model, provider: notice.from.provider },\n hookTraceEnv,\n );\n ctx.emitProviderFallback?.(notice);\n }\n } else if (event === PROVIDER_CALL_EVENTS.COMPLETED && ctx.emitProviderCallCompleted) {\n // Forward an allowlist, not the generic event envelope, across the session boundary.\n const observation = data as Record<string, unknown>;\n if (\n Number.isSafeInteger(observation['round']) &&\n (observation['round'] as number) > 0 &&\n typeof observation['startedAt'] === 'string' &&\n typeof observation['endedAt'] === 'string' &&\n (observation['outcome'] === 'success' ||\n observation['outcome'] === 'failure' ||\n observation['outcome'] === 'interrupted')\n ) {\n ctx.emitProviderCallCompleted({\n round: observation['round'] as number,\n startedAt: observation['startedAt'],\n endedAt: observation['endedAt'],\n outcome: observation['outcome'],\n ...(typeof observation['callId'] === 'string' &&\n /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(\n observation['callId'],\n ) && { callId: observation['callId'] }),\n ...((observation['disposition'] === 'invoked' ||\n observation['disposition'] === 'cache-hit' ||\n observation['disposition'] === 'preflight-refused') && {\n disposition: observation['disposition'],\n }),\n ...(typeof observation['providerId'] === 'string' &&\n observation['providerId'].length > 0 &&\n observation['providerId'].length <= 128 &&\n [...observation['providerId']].every((char) => char.charCodeAt(0) >= 32) && {\n providerId: observation['providerId'],\n }),\n ...(typeof observation['modelId'] === 'string' &&\n observation['modelId'].length > 0 &&\n observation['modelId'].length <= 128 &&\n [...observation['modelId']].every((char) => char.charCodeAt(0) >= 32) && {\n modelId: observation['modelId'],\n }),\n ...((observation['usageProvenance'] === 'complete' ||\n observation['usageProvenance'] === 'partial' ||\n observation['usageProvenance'] === 'absent') && {\n usageProvenance: observation['usageProvenance'],\n }),\n ...(observation['usageProvenance'] === 'complete' &&\n typeof observation['promptTokens'] === 'number' &&\n Number.isSafeInteger(observation['promptTokens']) &&\n observation['promptTokens'] >= 0 &&\n typeof observation['completionTokens'] === 'number' &&\n Number.isSafeInteger(observation['completionTokens']) &&\n observation['completionTokens'] >= 0 &&\n typeof observation['totalTokens'] === 'number' &&\n Number.isSafeInteger(observation['totalTokens']) &&\n observation['totalTokens'] ===\n observation['promptTokens'] + observation['completionTokens'] && {\n promptTokens: observation['promptTokens'],\n completionTokens: observation['completionTokens'],\n totalTokens: observation['totalTokens'],\n }),\n ...(observation['disposition'] === 'invoked' &&\n typeof observation['providerRequestId'] === 'string' && {\n providerRequestId: observation['providerRequestId'],\n }),\n });\n }\n }\n // Recompute and emit context per agentic round so the status bar\n // climbs live during a turn instead of jumping once at completion. The agent loop\n // runs entirely inside this single agent.run() call; assistant_message_committed\n // fires once per round with the round's usage already committed to history, which is\n // the right cadence — frequent enough to feel live, sparse enough to avoid render flooding.\n if (event === 'assistant_message_committed') {\n ctx.contextTracker.updateFromHistory(ctx.agent.getHistory());\n ctx.onContextUpdate?.(ctx.contextTracker.getContextState());\n }\n },\n ...(onTextDelta && { onTextDelta }),\n // Reasoning text is not the reply, so it is not in the session log's text-delta record.\n ...(ctx.onThinkingDelta && { onThinkingDelta: ctx.onThinkingDelta }),\n };\n}\n","import type { IResumeExecutionOptions } from '@robota-sdk/agent-core';\nimport type { IRunContext } from './session-run-context.js';\nimport { createRunObservers } from './session-run-observation.js';\n\nexport type TSessionResumeOptions = Pick<\n IResumeExecutionOptions,\n 'executionId' | 'journal' | 'signal' | 'traceContext'\n>;\n\n/** Continue without replaying input, compaction or completed lifecycle hooks. */\nexport async function executeResume(\n ctx: IRunContext,\n options: TSessionResumeOptions & Pick<IResumeExecutionOptions, 'toolResponses'>,\n): Promise<string> {\n const response = await ctx.agent.resume({\n ...options,\n ...createRunObservers(ctx, options.traceContext),\n });\n ctx.contextTracker.updateFromHistory(ctx.agent.getHistory());\n ctx.onContextUpdate?.(ctx.contextTracker.getContextState());\n return response;\n}\n","import { ExecutionRecoveryError } from '@robota-sdk/agent-core';\nimport type {\n IExecutionJournal,\n IRecoverableExecutionJournal,\n TExecutionJournalRecord,\n} from '@robota-sdk/agent-core';\n\ninterface ISessionJournalOwner {\n sessionId: string;\n cwd: string;\n peerTurn: boolean;\n /** The host's canonical spelling of a directory; the scope a recovery is checked against. */\n canonicalizePath: (path: string) => string;\n}\n\n/** Stamp runtime authority separately from display-only message attribution. */\nexport function sessionExecutionJournal(\n journal: IExecutionJournal,\n owner: ISessionJournalOwner,\n): IExecutionJournal {\n const cwd = owner.canonicalizePath(owner.cwd);\n return {\n append: async (record) => {\n if (\n (record.kind === 'model-request' || record.kind === 'model-cache-hit') &&\n record.checkpoint\n ) {\n const saved = structuredClone(record);\n saved.checkpoint!.owner = {\n kind: 'agent-session',\n version: 1,\n state: {\n sessionId: owner.sessionId,\n cwd,\n peerTurn: owner.peerTurn,\n executionId: record.executionId,\n },\n };\n await journal.append(saved);\n } else await journal.append(record);\n },\n };\n}\n\n/** Reject a different session/scope and restore peer authority before any resumed effect. */\nexport function sessionRecoveryJournal(\n journal: IRecoverableExecutionJournal,\n sessionId: string,\n cwd: string,\n beginTurn: (peerTurn: boolean) => void,\n canonicalizePath: (path: string) => string,\n): IRecoverableExecutionJournal {\n let writer: IExecutionJournal | undefined;\n const scope = canonicalizePath(cwd);\n return {\n read: async (executionId) => {\n const records = structuredClone(await journal.read(executionId));\n let peerTurn: boolean | undefined;\n for (const record of records) {\n if (record.kind !== 'model-request' && record.kind !== 'model-cache-hit') continue;\n const owner = record.checkpoint?.owner;\n const state = owner?.state;\n if (\n !owner ||\n owner.kind !== 'agent-session' ||\n owner.version !== 1 ||\n !state ||\n state.sessionId !== sessionId ||\n state.cwd !== scope ||\n state.executionId !== executionId ||\n typeof state.peerTurn !== 'boolean' ||\n (peerTurn !== undefined && peerTurn !== state.peerTurn) ||\n (state.peerTurn && record.checkpoint?.continuation?.options.withholdHostedTools !== true)\n ) {\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_INVALID',\n 'Session continuation owner or scope is incompatible',\n );\n }\n peerTurn = state.peerTurn;\n }\n if (peerTurn === undefined)\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_INVALID',\n 'No Session owner checkpoint exists',\n );\n writer = sessionExecutionJournal(journal, { sessionId, cwd, peerTurn, canonicalizePath });\n beginTurn(peerTurn);\n return records;\n },\n append: async (record: TExecutionJournalRecord) => {\n if (!writer)\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_INVALID',\n 'Session owner has not been restored',\n );\n await writer.append(record);\n },\n };\n}\n","import type { ISessionRunOptions } from './session-types.js';\nimport type { IRunOptions } from '@robota-sdk/agent-core';\n\n/** The subset actually present, ready to spread into the agent-core run options. */\nexport function perTurnRunOptions(\n options?: ISessionRunOptions,\n): Pick<\n IRunOptions,\n | 'ephemeralSystemContext'\n | 'driverId'\n | 'turnSource'\n | 'toolChoice'\n | 'traceContext'\n | 'withholdHostedTools'\n | 'executionJournal'\n> {\n return {\n ...(options?.executionJournal !== undefined && { executionJournal: options.executionJournal }),\n ...(options?.ephemeralSystemContext !== undefined && {\n ephemeralSystemContext: options.ephemeralSystemContext,\n }),\n ...(options?.driverId !== undefined && { driverId: options.driverId }),\n ...(options?.turnSource !== undefined && { turnSource: options.turnSource }),\n ...(options?.toolChoice !== undefined && { toolChoice: options.toolChoice }),\n ...(options?.traceContext !== undefined && { traceContext: options.traceContext }),\n // What a message-triggered turn does is decided by the ordinary permissions, and a provider's\n // hosted tools run at the vendor without reaching them — so such a turn has none.\n ...(options?.peerTurn === true && { withholdHostedTools: true }),\n };\n}\n","/**\n * Session run — core execution logic for a single agent turn.\n *\n * Extracted from Session to keep session.ts under the 300-line limit.\n * Stateless: all mutable state is passed in via IRunContext.\n */\n\nimport {\n CONTEXT_ESTIMATE_CHARS_PER_TOKEN,\n createLogger,\n createUserMessage,\n createSystemMessage,\n getProviderCapabilities,\n runHooks,\n traceEnvFor,\n ExecutionSuspendedError,\n} from '@robota-sdk/agent-core';\n\nimport { perTurnRunOptions } from './session-run-options.js';\nimport { createRunObservers } from './session-run-observation.js';\n\nimport type { IRunContext } from './session-run-context.js';\nexport type { IRunContext } from './session-run-context.js';\nimport type { ISessionRunOptions } from './session-types.js';\nimport type { THooksConfig } from '@robota-sdk/agent-core';\n\nconst logger = createLogger('SessionRun');\n\n/**\n * Execute a single agent turn: run hooks, send message to AI, log results.\n *\n * @param message - The processed message to send to the AI\n * @param rawInput - Optional raw user input (used for hook prompt field)\n * @param ctx - Session state and callbacks\n * @param abortSignal - AbortSignal from the session's AbortController\n */\nexport async function executeRun(\n message: string,\n rawInput: string | undefined,\n ctx: IRunContext,\n abortSignal: AbortSignal,\n runOptions?: ISessionRunOptions,\n): Promise<string> {\n // Command hooks fired on this prompt's path name its root span; hooks elsewhere get nothing.\n const traceContext = runOptions?.traceContext;\n const hookTraceEnv = traceContext\n ? traceEnvFor('hooks', traceContext, traceContext.parentSpanId)\n : undefined;\n ctx.log('user', { content: message });\n\n // Fire UserPromptSubmit hook before AI processes input\n const hookResult = await runHooks(\n ctx.hooks as THooksConfig | undefined,\n 'UserPromptSubmit',\n {\n session_id: ctx.sessionId,\n cwd: ctx.cwd,\n hook_event_name: 'UserPromptSubmit',\n user_message: rawInput ?? message,\n prompt: rawInput ?? message,\n ...(ctx.permissionMode !== undefined && { permission_mode: ctx.permissionMode }),\n ...(ctx.transcriptPath !== undefined && { transcript_path: ctx.transcriptPath }),\n env: {\n CLAUDE_PROJECT_DIR: ctx.cwd,\n CLAUDE_SESSION_ID: ctx.sessionId,\n },\n },\n ctx.hookTypeExecutors,\n hookTraceEnv,\n );\n\n // Inject hook stdout into user message (e.g., plugin path info)\n const hookStdout = [ctx.sessionStartStdout, hookResult.stdout].filter(Boolean).join('\\n');\n const enrichedMessage = hookStdout\n ? `<system-reminder>\\n${hookStdout}\\n</system-reminder>\\n${message}`\n : message;\n\n // Budget the pending request without putting its new input into the history being summarized.\n const pendingInput = createUserMessage(enrichedMessage);\n const ephemeralSystemContext = runOptions?.ephemeralSystemContext;\n const pendingMessages = [\n pendingInput,\n ...(ephemeralSystemContext?.trim() ? [createSystemMessage(ephemeralSystemContext)] : []),\n ];\n ctx.contextTracker.updateFromHistory(ctx.agent.getHistory());\n if (ctx.contextTracker.shouldAutoCompact([...ctx.agent.getHistory(), ...pendingMessages])) {\n if (runOptions?.executionJournal) {\n await ctx.compact(abortSignal, hookTraceEnv, runOptions.executionJournal);\n } else {\n await (hookTraceEnv ? ctx.compact(abortSignal, hookTraceEnv) : ctx.compact(abortSignal));\n }\n }\n\n // Clear sessionStart stdout after first injection\n ctx.clearSessionStartStdout();\n\n const history = ctx.agent.getHistory();\n const historyJson = JSON.stringify(history);\n const providerCapabilities = getProviderCapabilities(ctx.aiProvider);\n ctx.log('pre_run', {\n historyLength: history.length,\n historyChars: historyJson.length,\n historyEstTokens: Math.ceil(historyJson.length / CONTEXT_ESTIMATE_CHARS_PER_TOKEN),\n input: enrichedMessage,\n history,\n model: ctx.model,\n provider: ctx.aiProvider.name,\n maxTokens: ctx.contextTracker.getContextState().maxTokens,\n nativeWebSearchSupported: providerCapabilities.nativeWebTools.webSearch.supported,\n nativeWebSearchEnabled: providerCapabilities.nativeWebTools.webSearch.enabled,\n nativeWebFetchSupported: providerCapabilities.nativeWebTools.webFetch.supported,\n nativeWebFetchEnabled: providerCapabilities.nativeWebTools.webFetch.enabled,\n });\n ctx.contextTracker.updateFromHistory([...history, ...pendingMessages]);\n ctx.onContextUpdate?.(ctx.contextTracker.getContextState());\n\n let response: string;\n try {\n response = await ctx.agent.run(enrichedMessage, {\n signal: abortSignal,\n maxExecutionRounds: ctx.maxTurns ?? 0,\n // Thin pass-through of the per-turn options to agent-core.\n ...perTurnRunOptions(runOptions),\n ...createRunObservers(ctx, traceContext),\n });\n\n // If execution was interrupted (abort fired during execution),\n // throw AbortError so the caller (useSubmitHandler) shows \"Cancelled.\"\n if (abortSignal.aborted) {\n throw new DOMException('Aborted', 'AbortError');\n }\n } catch (error) {\n if (error instanceof ExecutionSuspendedError) throw error;\n try {\n ctx.log('error', {\n message: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? (error.stack ?? '') : '',\n historyLength: ctx.agent.getHistory().length,\n });\n } catch {\n // Diagnostic failure cannot replace the execution error and its recovery classification.\n }\n runHooks(\n ctx.hooks as THooksConfig | undefined,\n 'StopFailure',\n {\n session_id: ctx.sessionId,\n cwd: ctx.cwd,\n hook_event_name: 'StopFailure',\n reason: error instanceof Error ? error.message : String(error),\n stop_hook_active: false,\n ...(ctx.permissionMode !== undefined && { permission_mode: ctx.permissionMode }),\n ...(ctx.transcriptPath !== undefined && { transcript_path: ctx.transcriptPath }),\n env: {\n CLAUDE_PROJECT_DIR: ctx.cwd,\n CLAUDE_SESSION_ID: ctx.sessionId,\n },\n },\n ctx.hookTypeExecutors,\n hookTraceEnv,\n ).catch((error) => logger.warn('hook failed', { error }));\n throw error;\n }\n\n // Log the response and full history structure\n const postHistory = ctx.agent.getHistory();\n const historyStructure = postHistory.map((msg) => {\n const hasToolCalls =\n 'toolCalls' in msg && Array.isArray(msg.toolCalls) && msg.toolCalls.length > 0;\n const toolCallNames = hasToolCalls\n ? (msg.toolCalls as Array<{ function: { name: string } }>).map((tc) => tc.function.name)\n : [];\n return {\n role: msg.role,\n contentLength: typeof msg.content === 'string' ? msg.content.length : 0,\n hasToolCalls,\n toolCallNames,\n ...(msg.metadata ? { metadata: msg.metadata } : {}),\n };\n });\n ctx.log('assistant', {\n content: response,\n historyLength: postHistory.length,\n estimatedChars: JSON.stringify(postHistory).length,\n history: postHistory,\n historyStructure,\n });\n\n // Update token usage from the latest assistant message metadata\n ctx.contextTracker.updateFromHistory(postHistory);\n\n const ctxState = ctx.contextTracker.getContextState();\n ctx.onContextUpdate?.(ctxState);\n ctx.log('context', {\n maxTokens: ctxState.maxTokens,\n usedTokens: ctxState.usedTokens,\n usedPercentage: ctxState.usedPercentage,\n remainingPercentage: ctxState.remainingPercentage,\n });\n\n // Fire Stop hook after AI response is complete (informational, fire and forget)\n runHooks(\n ctx.hooks as THooksConfig | undefined,\n 'Stop',\n {\n session_id: ctx.sessionId,\n cwd: ctx.cwd,\n hook_event_name: 'Stop',\n response: response.substring(0, 500),\n last_assistant_message: response,\n stop_hook_active: false,\n ...(ctx.permissionMode !== undefined && { permission_mode: ctx.permissionMode }),\n ...(ctx.transcriptPath !== undefined && { transcript_path: ctx.transcriptPath }),\n env: {\n CLAUDE_PROJECT_DIR: ctx.cwd,\n CLAUDE_SESSION_ID: ctx.sessionId,\n },\n },\n ctx.hookTypeExecutors,\n hookTraceEnv,\n ).catch((error) => logger.warn('hook failed', { error }));\n\n return response;\n}\n","import {\n ExecutionRecoveryError,\n ExecutionSuspendedError,\n createToolMessage,\n} from '@robota-sdk/agent-core';\nimport type {\n IRecoverableExecutionJournal,\n IResumeExecutionOptions,\n IToolWaitRequest,\n TUniversalMessage,\n} from '@robota-sdk/agent-core';\nimport type { ISessionRunOptions } from './session-types.js';\nimport type { TSessionResumeOptions } from './session-resume.js';\n\nexport interface ISessionRecoverableRunOptions extends ISessionRunOptions {\n executionJournal: IRecoverableExecutionJournal;\n}\nexport type TSessionRecoverableResumeOptions = TSessionResumeOptions &\n Pick<IResumeExecutionOptions, 'toolResponses'>;\nexport type TSessionExecutionResult =\n | { status: 'completed'; response: string }\n | { status: 'waiting'; requests: readonly IToolWaitRequest[] };\nimport type { ISessionPendingExecution } from '@robota-sdk/agent-interface/session';\nexport type { ISessionPendingExecution } from '@robota-sdk/agent-interface/session';\n\nexport function pendingExecution(\n error: ExecutionSuspendedError,\n): ISessionPendingExecution | undefined {\n const executionId = error.requests[0]?.executionId;\n return executionId ? { executionId, requests: structuredClone(error.requests) } : undefined;\n}\n\nfunction lastRound(history: readonly TUniversalMessage[]) {\n const index = history.findLastIndex((message) => message.role === 'assistant');\n const assistant = history[index];\n if (assistant?.role !== 'assistant') return undefined;\n const answered = new Set(\n history\n .slice(index + 1)\n .flatMap((message) => (message.role === 'tool' ? [message.toolCallId] : [])),\n );\n return {\n assistant,\n open: (assistant.toolCalls ?? []).filter((call) => !answered.has(call.id)),\n ending: history.slice(index + 1).every((message) => message.role === 'tool'),\n };\n}\n\n/**\n * After a journaled attempt fails, the execution whose round it left open at the end of history\n * stays pending, so the next input is never sent after unanswered calls. Only saved waits whose\n * effect was never admitted remain answerable.\n */\nexport function unfinishedExecution(\n history: readonly TUniversalMessage[],\n previous: ISessionPendingExecution | undefined,\n admitted: ReadonlySet<string>,\n): ISessionPendingExecution | undefined {\n const round = lastRound(history);\n const executionId = round?.assistant.metadata?.executionId;\n if (!round?.ending || !round.open.length || typeof executionId !== 'string') return undefined;\n const open = new Set(round.open.map((call) => call.id));\n const requests =\n previous?.executionId === executionId\n ? previous.requests.filter(\n (request) => open.has(request.toolCallId) && !admitted.has(request.actionId),\n )\n : [];\n return { executionId, requests: structuredClone(requests) };\n}\n\n/** Note each action whose effect admission is attempted: its outcome is no longer known not to exist. */\nexport function noteEffectAdmissions(\n journal: IRecoverableExecutionJournal,\n admitted: Set<string>,\n onAdmission?: () => void,\n): IRecoverableExecutionJournal {\n return {\n read: (executionId) => journal.read(executionId),\n append: (record) => {\n if (record.kind === 'tool-effect-start') {\n admitted.add(record.actionId);\n onAdmission?.();\n }\n return journal.append(record);\n },\n };\n}\n\n/**\n * Close the tool calls an abandoned round left open so the conversation stays well-formed. Nothing\n * runs and nothing is journaled; a call that awaited a response never started its effect.\n */\nexport function abandonedRoundResults(\n history: readonly TUniversalMessage[],\n pending: ISessionPendingExecution,\n liveWaits: boolean,\n): TUniversalMessage[] {\n const round = lastRound(history);\n if (!round || round.assistant.metadata?.executionId !== pending.executionId) return [];\n const waiting = new Set((liveWaits ? pending.requests : []).map((request) => request.toolCallId));\n return round.open.map((call) => {\n const error = waiting.has(call.id)\n ? 'Not run: the execution was abandoned while this call awaited a response.'\n : 'Execution abandoned before this call recorded a result; its outcome is unknown.';\n return createToolMessage(`Error: ${error}`, {\n toolCallId: call.id,\n name: call.function.name,\n metadata: { success: false, error, toolName: call.function.name },\n });\n });\n}\n\n/** Preserve typed persistence/recovery failures; only a durably parked execution becomes waiting. */\nexport async function recoverableSessionExecution(\n run: () => Promise<string>,\n journal: IRecoverableExecutionJournal,\n): Promise<TSessionExecutionResult> {\n if (!journal || typeof journal.append !== 'function' || typeof journal.read !== 'function')\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_INVALID',\n 'Recoverable Session execution requires a readable journal',\n );\n try {\n return { status: 'completed', response: await run() };\n } catch (error) {\n if (error instanceof ExecutionSuspendedError)\n return { status: 'waiting', requests: structuredClone(error.requests) };\n throw error;\n }\n}\n","import { randomId } from '@robota-sdk/agent-core';\n\nimport type { TurnClaim } from './turn-claim.js';\nimport type {\n ConversationAgent,\n IToolExecutionResult,\n IUserInteraction,\n TToolParameters,\n} from '@robota-sdk/agent-core';\nimport type { IRuntimeToolInvokeOptions } from '@robota-sdk/agent-interface/session';\n\n/** Direct calls share the session claim and retain their completion for graceful disposal. */\nexport class SessionRuntimeTools {\n private execution: Promise<IToolExecutionResult> | null = null;\n\n constructor(\n private readonly agent: ConversationAgent,\n private readonly claim: TurnClaim,\n private readonly sessionId: string,\n private readonly ask?: IUserInteraction['ask'],\n ) {}\n\n /**\n * `permissionInteraction` is `'deny'` unless the caller answers its own prompts; only then may\n * the call ask for permission or put a question through the session's ask port.\n */\n async invoke(\n name: string,\n parameters: TToolParameters,\n options?: IRuntimeToolInvokeOptions,\n ): Promise<IToolExecutionResult> {\n const controller = this.claim.claim();\n const unlink = linkCancellation(controller, options?.signal);\n try {\n controller.signal.throwIfAborted();\n this.execution = this.agent.invokeRuntimeTool(name, parameters, {\n toolName: name,\n parameters,\n executionId: options?.executionId ?? randomId(),\n sessionId: this.sessionId,\n signal: controller.signal,\n // A caller nobody is watching must not park on a prompt, so asking is the caller's choice;\n // a caller that answers its own prompts also gets the session's ask port.\n permissionInteraction: options?.permissionInteraction ?? 'deny',\n ...(options?.permissionInteraction === 'interactive' && this.ask !== undefined\n ? { ask: this.askUnder(controller.signal) }\n : {}),\n });\n return await this.execution;\n } finally {\n this.execution = null;\n unlink();\n this.claim.release(controller);\n }\n }\n\n /** The ask port for one direct call: a question it asks is withdrawn when the call is cancelled. */\n private askUnder(signal: AbortSignal): IUserInteraction['ask'] {\n const ask = this.ask;\n if (ask === undefined) throw new Error('No ask port');\n return (request, options) => ask(request, { signal: options?.signal ?? signal });\n }\n\n async drain(): Promise<void> {\n await this.execution;\n }\n}\n\n/** Link only this operation's signal; releasing it never cancels another claim. */\nexport function linkCancellation(controller: AbortController, signal?: AbortSignal): () => void {\n const abort = (): void => controller.abort(signal?.reason);\n if (signal?.aborted) abort();\n else signal?.addEventListener('abort', abort, { once: true });\n return () => signal?.removeEventListener('abort', abort);\n}\n","import { decodeInteractiveSessionRecord } from './session-record-codec/index.js';\nimport { assertSynchronousCheckpoint } from './synchronous-checkpoint.js';\nimport type { ISessionConversationSnapshot } from '@robota-sdk/agent-interface/session';\nimport {\n TRUST_TO_MODE,\n ObservableEventService,\n PROVIDER_CALL_EVENTS,\n PROVIDER_FALLBACK_EVENTS,\n ExecutionRecoveryError,\n ExecutionSuspendedError,\n} from '@robota-sdk/agent-core';\n\nimport { SessionBase } from './session-base.js';\nimport {\n buildPermissionEnforcer,\n buildConversationAgent,\n buildSessionTrackers,\n} from './session-components.js';\nimport { buildCompactContext, compact, persistSession } from './session-history-ops.js';\nimport { createSessionId } from './session-id.js';\nimport {\n configureProvider,\n fireSessionEndHook,\n fireSessionStartHook,\n} from './session-lifecycle.js';\nimport { requireHookTypeExecutors } from './require-hook-type-executors.js';\nimport { executeResume, type TSessionResumeOptions } from './session-resume.js';\nimport { sessionExecutionJournal, sessionRecoveryJournal } from './session-execution-journal.js';\nimport { executeRun } from './session-run.js';\nimport {\n abandonedRoundResults,\n noteEffectAdmissions,\n pendingExecution,\n recoverableSessionExecution,\n unfinishedExecution,\n} from './session-recoverable.js';\nimport type {\n ISessionPendingExecution,\n ISessionRecoverableRunOptions,\n TSessionRecoverableResumeOptions,\n TSessionExecutionResult,\n} from './session-recoverable.js';\nimport { SessionRuntimeTools, linkCancellation } from './session-runtime-tools.js';\nimport type { IRuntimeToolInvokeOptions } from '@robota-sdk/agent-interface/session';\n\nimport type { CompactionOrchestrator } from './compaction-orchestrator.js';\nimport type { ContextWindowTracker } from './context-window-tracker.js';\nimport type { PermissionEnforcer } from './permission-enforcer.js';\nimport type {\n TPermissionHandler,\n TPermissionResult,\n IPermissionAskContext,\n TPermissionRequester,\n ITerminalOutput,\n ISpinner,\n} from './permission-types.js';\nimport type { ISessionLogger, TSessionLogData } from './session-logger.js';\nimport type { IRunContext } from './session-run-context.js';\nimport type {\n ICompactEvent,\n ISessionOptions,\n ISessionShutdownOptions,\n ISessionRunOptions,\n TCompactTrigger,\n} from './session-types.js';\nimport type {\n IAIProvider,\n IProviderRegistrationOptions,\n IExecutionJournal,\n IContextWindowState,\n IEventService,\n IToolSchema,\n IToolExecutionResult,\n IToolWithEventService,\n TToolParameters,\n TPermissionMode,\n IHookTypeExecutor,\n ISubprocessTraceEnv,\n} from '@robota-sdk/agent-core';\nimport type { ConversationAgent } from '@robota-sdk/agent-core';\nimport type { IInteractiveSessionStore } from '@robota-sdk/agent-interface/session';\n\nexport type {\n ICompactEvent,\n TPermissionHandler,\n TPermissionResult,\n IPermissionAskContext,\n TPermissionRequester,\n ITerminalOutput,\n ISpinner,\n ISessionOptions,\n ISessionShutdownOptions,\n TCompactTrigger,\n};\nexport type { TAutoCompactThreshold } from './context-window-tracker.js';\n\n/** Wraps a ConversationAgent agent with project context, permission state, and optional persistence. */\nexport class Session extends SessionBase {\n protected readonly agent: ConversationAgent;\n /**\n * Session-owned observable event bus. Injected into the agent so tools (incl. the\n * `FunctionTool` span-completion emit) publish here; the interactive turn subscribes to it to\n * project per-operation spans onto session history. Exposed read-only via {@link getEventService}.\n */\n protected readonly eventService: IEventService = new ObservableEventService();\n protected readonly permissionEnforcer: PermissionEnforcer;\n protected readonly contextTracker: ContextWindowTracker;\n protected permissionMode: TPermissionMode;\n protected activePresetId: string;\n protected parallelSubagentsEnabled: boolean;\n protected readonly sessionId: string;\n protected aiProvider: IAIProvider;\n protected readonly toolSchemas: IToolSchema[];\n protected model: string;\n protected systemMessage: string;\n protected messageCount = 0;\n private readonly terminal: ITerminalOutput;\n private readonly sessionStore?: IInteractiveSessionStore;\n private readonly checkpointHistoryCallback?: () => void;\n private readonly hooks?: Record<string, unknown>;\n private readonly hookTypeExecutors: readonly IHookTypeExecutor[];\n private readonly canonicalizePath: (path: string) => string;\n private readonly onTextDeltaCallback?: (delta: string) => void;\n private readonly onThinkingDeltaCallback?: (delta: string) => void;\n private readonly onContextUpdateCallback?: (state: IContextWindowState) => void;\n private readonly onToolExecutionCallback?: ISessionOptions['onToolExecution'];\n private readonly onCompactCallback?: (summary: string) => void;\n private readonly onCompactEventCallback?: ISessionOptions['onCompactEvent'];\n private readonly sessionLogger?: ISessionLogger;\n private readonly maxTurns?: number;\n protected readonly compactionOrchestrator: CompactionOrchestrator;\n private readonly runtimeTools: SessionRuntimeTools;\n private readonly wrapAddedTools: ISessionOptions['wrapAddedTools'];\n /** Tools added while a turn ran, applied when the next one starts. */\n private readonly pendingTools: IToolWithEventService[] = [];\n /** The last tool change; the next one waits for it. */\n private toolChange: Promise<void> = Promise.resolve();\n private shuttingDown = false;\n /** Set while a journaled execution's round is open in history; cleared once it settles or is abandoned. */\n private recoveryState:\n | { kind: 'ready' }\n | { kind: 'pending'; pending: ISessionPendingExecution; liveWaits: boolean }\n | {\n kind: 'save-required';\n pending?: ISessionPendingExecution;\n executionId?: string;\n liveWaits: boolean;\n } = { kind: 'ready' };\n private checkpointing = false;\n\n private get pendingExecution(): ISessionPendingExecution | undefined {\n return this.recoveryState.kind === 'ready' ? undefined : this.recoveryState.pending;\n }\n private setPending(pending: ISessionPendingExecution | undefined, liveWaits = false): void {\n if (this.recoveryState.kind === 'save-required') return;\n this.recoveryState = pending ? { kind: 'pending', pending, liveWaits } : { kind: 'ready' };\n }\n private shutdownPromise: Promise<void> | null = null;\n /** Stdout collected from SessionStart hooks, injected on first run(). */\n private sessionStartStdout = '';\n /** Absolute path to the session transcript file, if file-backed storage is active. */\n private readonly transcriptPath: string | undefined;\n\n constructor(options: ISessionOptions) {\n super(options.cwd);\n const { tools, provider, systemMessage } = options;\n\n this.terminal = options.terminal;\n this.sessionStore = options.sessionStore;\n this.checkpointHistoryCallback = options.checkpointHistory;\n this.systemMessage = systemMessage;\n this.toolSchemas = tools.map((tool) => tool.schema);\n this.wrapAddedTools = options.wrapAddedTools;\n this.sessionLogger = options.sessionLogger;\n this.hooks = options.hooks;\n this.hookTypeExecutors = requireHookTypeExecutors(options.hookTypeExecutors, 'Session');\n this.canonicalizePath = options.canonicalizePath ?? ((path) => path);\n this.onTextDeltaCallback = options.onTextDelta;\n this.onThinkingDeltaCallback = options.onThinkingDelta;\n this.onContextUpdateCallback = options.onContextUpdate;\n this.onToolExecutionCallback = options.onToolExecution;\n this.onCompactCallback = options.onCompact;\n this.onCompactEventCallback = options.onCompactEvent;\n this.maxTurns = options.maxTurns;\n this.model = options.model ?? 'claude-sonnet-4-5';\n this.sessionId = options.sessionId ?? createSessionId();\n this.permissionMode =\n options.permissionMode ??\n (options.defaultTrustLevel ? TRUST_TO_MODE[options.defaultTrustLevel] : undefined) ??\n 'default';\n this.activePresetId = options.activePresetId ?? 'default';\n // Default true preserves the current behavior — subagent dispatch is allowed\n // unless a preset explicitly disables it.\n this.parallelSubagentsEnabled = options.enableParallelSubagents ?? true;\n this.transcriptPath = options.transcriptPath;\n this.log('session_init', {\n cwd: this.cwd,\n systemPromptLength: systemMessage.length,\n systemPrompt: systemMessage,\n toolSchemas: this.toolSchemas,\n model: this.model,\n provider: provider.name,\n });\n this.aiProvider = provider;\n configureProvider(provider, options, (event, data) => this.log(event, data));\n this.permissionEnforcer = buildPermissionEnforcer(\n options,\n this.sessionId,\n this.cwd,\n () => this.permissionMode,\n this.transcriptPath,\n );\n this.requireClassifierFor(this.permissionMode);\n this.addPermissionModeGuard((next) => this.requireClassifierFor(next));\n const { contextTracker, compactionOrchestrator } = buildSessionTrackers(\n options,\n this.model,\n this.sessionId,\n this.cwd,\n );\n this.contextTracker = contextTracker;\n this.compactionOrchestrator = compactionOrchestrator;\n this.agent = buildConversationAgent(\n options,\n this.permissionEnforcer,\n tools,\n provider,\n this.model,\n systemMessage,\n this.eventService,\n );\n this.runtimeTools = new SessionRuntimeTools(\n this.agent,\n this.turnClaim,\n this.sessionId,\n options.ask,\n );\n fireSessionStartHook(\n this.sessionId,\n this.cwd,\n this.hooks,\n this.hookTypeExecutors,\n (stdout) => void (this.sessionStartStdout = stdout),\n this.permissionMode,\n this.transcriptPath,\n );\n }\n\n /**\n * @param options.ephemeralSystemContext a transient system-role block included in this\n * turn's model call only, never persisted to history (thin pass-through to agent-core `IRunOptions`).\n * REJECTS with `SessionBusyError` if a turn is in flight; see `turn-claim.ts`.\n */\n async run(message: string, rawInput?: string, options?: ISessionRunOptions): Promise<string> {\n return this.runTurn(message, rawInput, options, false);\n }\n\n /** Run with durable approval requests instead of a live approval Promise. */\n runRecoverable(\n message: string,\n options: ISessionRecoverableRunOptions,\n rawInput?: string,\n ): Promise<TSessionExecutionResult> {\n return recoverableSessionExecution(\n () => this.runTurn(message, rawInput, options, true),\n options.executionJournal,\n );\n }\n\n private async runTurn(\n message: string,\n rawInput: string | undefined,\n value: ISessionRunOptions | undefined,\n checkpointedApprovals: boolean,\n ): Promise<string> {\n const options = value ? { ...value } : undefined;\n if (this.shuttingDown) throw new Error('[LIFECYCLE] Session is shutting down');\n this.assertInputAllowed();\n const controller = this.turnClaim.claim(); // Synchronously, before any await.\n const unlink = linkCancellation(controller, options?.signal);\n const { signal } = controller;\n // Whether the reply to a peer exists is decided per turn.\n this.permissionEnforcer.beginTurn(options?.peerTurn === true, checkpointedApprovals);\n try {\n signal.throwIfAborted();\n // Tools added while the last turn ran join at this boundary, before any request of this turn;\n // a change already in flight finishes first, so the turn never sees a list mid-update.\n await this.serializeToolChange(() => this.applyPendingTools());\n const runOptions = options?.executionJournal\n ? {\n ...options,\n executionJournal: sessionExecutionJournal(options.executionJournal, {\n sessionId: this.sessionId,\n cwd: this.cwd,\n peerTurn: options.peerTurn === true,\n canonicalizePath: this.canonicalizePath,\n }),\n }\n : options;\n const response = await executeRun(\n message,\n rawInput,\n this.buildRunContext(options?.executionJournal !== undefined),\n signal,\n runOptions,\n );\n this.setPending(undefined);\n this.commitConversation();\n this.messageCount += 1;\n return response;\n } catch (error) {\n if (error instanceof ExecutionSuspendedError) {\n this.setPending(pendingExecution(error), true);\n signal.throwIfAborted();\n } else if (options?.executionJournal) {\n this.setPending(unfinishedExecution(this.agent.getHistory(), undefined, new Set()));\n }\n this.saveAfterFailure(error);\n throw error;\n } finally {\n this.permissionEnforcer.endTurn();\n unlink();\n this.turnClaim.release(controller);\n }\n }\n\n /** Resume the original Session execution under current permissions without submitting input. */\n async resume(options: TSessionResumeOptions): Promise<string> {\n return this.resumeTurn(options, false);\n }\n\n /** Recheck current permissions and continue exact journaled responses without another input. */\n resumeRecoverable(options: TSessionRecoverableResumeOptions): Promise<TSessionExecutionResult> {\n return recoverableSessionExecution(() => this.resumeTurn(options, true), options.journal);\n }\n\n private async resumeTurn(\n value: TSessionRecoverableResumeOptions,\n checkpointedApprovals: boolean,\n ): Promise<string> {\n const options = { ...value, toolResponses: structuredClone(value.toolResponses) };\n if (this.shuttingDown) throw new Error('[LIFECYCLE] Session is shutting down');\n this.assertCheckpointSettled();\n if (this.pendingExecution && this.pendingExecution.executionId !== options.executionId)\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_CONFLICT',\n 'A different Session execution is pending',\n );\n const controller = this.turnClaim.claim();\n const unlink = linkCancellation(controller, options.signal);\n const admitted = new Set<string>();\n try {\n controller.signal.throwIfAborted();\n await this.serializeToolChange(() => this.applyPendingTools());\n const journal = sessionRecoveryJournal(\n noteEffectAdmissions(options.journal, admitted, () => {\n if (this.recoveryState.kind === 'pending') this.recoveryState.liveWaits = false;\n }),\n this.sessionId,\n this.cwd,\n (peerTurn) => this.permissionEnforcer.beginTurn(peerTurn, checkpointedApprovals),\n this.canonicalizePath,\n );\n const response = await executeResume(this.buildRunContext(true), {\n ...options,\n journal,\n signal: controller.signal,\n });\n this.setPending(undefined);\n this.commitConversation(options.executionId);\n return response;\n } catch (error) {\n if (error instanceof ExecutionSuspendedError) {\n this.setPending(pendingExecution(error), true);\n controller.signal.throwIfAborted();\n } else {\n // Settled rounds end the execution like an ordinary turn; a round left open keeps it pending.\n this.setPending(\n unfinishedExecution(this.agent.getHistory(), this.pendingExecution, admitted),\n );\n }\n this.saveAfterFailure(error);\n throw error;\n } finally {\n this.permissionEnforcer.endTurn();\n unlink();\n this.turnClaim.release(controller);\n }\n }\n\n /** The execution blocking new input, if any: parked on saved waits, or left open by a failure. */\n getPendingExecution(): ISessionPendingExecution | undefined {\n return structuredClone(this.pendingExecution);\n }\n\n /**\n * Give up a pending execution so the Session accepts new input. Nothing runs and nothing is\n * journaled: calls its parked round left open are closed in history as failed, and the journal's\n * records stay with the host. Resuming the execution afterwards is refused as a history conflict.\n */\n abandonPendingExecution(executionId: string): void {\n if (this.shuttingDown) throw new Error('[LIFECYCLE] Session is shutting down');\n if (this.recoveryState.kind === 'save-required') {\n if (this.recoveryState.executionId !== executionId || this.pendingExecution)\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_CONFLICT',\n 'Checkpoint the unresolved Session transition first',\n );\n this.checkpoint();\n return;\n }\n const pending = this.pendingExecution;\n if (!pending) return;\n if (pending.executionId !== executionId)\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_CONFLICT',\n 'A different Session execution is pending',\n );\n const controller = this.turnClaim.claim();\n try {\n const liveWaits = this.recoveryState.kind === 'pending' && this.recoveryState.liveWaits;\n for (const message of abandonedRoundResults(this.agent.getHistory(), pending, liveWaits))\n this.agent.injectRawMessage(message);\n this.setPending(undefined);\n this.commitConversation(executionId);\n } finally {\n this.turnClaim.release(controller);\n }\n }\n\n /** Refuse admission before callers resolve input or change scheduling state. */\n assertInputAllowed(): void {\n if (this.shuttingDown) throw new Error('[LIFECYCLE] Session is shutting down');\n this.assertCheckpointSettled();\n if (this.pendingExecution)\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_REQUIRED',\n 'Resume or abandon the pending Session execution before submitting new input',\n );\n }\n private assertCheckpointSettled(): void {\n if (this.checkpointing || this.recoveryState.kind === 'save-required')\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_REQUIRED',\n 'Call Session.checkpoint() to save the unresolved conversation transition',\n );\n }\n getConversationSnapshot(): ISessionConversationSnapshot {\n return structuredClone({\n id: this.sessionId,\n cwd: this.canonicalizePath(this.cwd),\n messages: this.agent.getHistory(),\n ...(this.pendingExecution ? { pendingExecution: this.pendingExecution } : {}),\n });\n }\n /** Install a complete same-owner snapshot on a pristine, idle Session. */\n restoreConversation(snapshot: ISessionConversationSnapshot): void {\n this.assertInputAllowed();\n if (\n this.turnClaim.isRunning() ||\n this.agent.getHistory().some((message) => message.role !== 'system')\n )\n throw new Error('Conversation restore requires a pristine idle Session');\n if (\n snapshot.id !== this.sessionId ||\n this.canonicalizePath(snapshot.cwd) !== this.canonicalizePath(this.cwd)\n )\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_CONFLICT',\n 'Conversation owner or workspace differs',\n );\n const decoded = decodeInteractiveSessionRecord({\n id: snapshot.id,\n cwd: snapshot.cwd,\n messages: snapshot.messages,\n ...(snapshot.pendingExecution === undefined\n ? {}\n : { pendingExecution: snapshot.pendingExecution }),\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n });\n if (decoded.status !== 'valid')\n throw new ExecutionRecoveryError(\n 'EXECUTION_RECOVERY_INVALID',\n 'Invalid conversation recovery snapshot',\n );\n const conversation = structuredClone(decoded.record);\n this.agent.clearHistory();\n for (const message of conversation.messages) this.agent.injectRawMessage(message);\n this.setPending(conversation.pendingExecution);\n this.syncContextFromHistory();\n }\n /** Retry only persistence, never model or tool execution. A sink must acknowledge synchronously. */\n checkpoint(): void {\n if (this.turnClaim.isRunning() || this.checkpointing)\n throw new Error('Cannot checkpoint during a Session transition');\n if (!this.checkpointHistoryCallback && !this.sessionStore)\n throw new Error('Session checkpoint requires a persistence sink');\n this.commitConversation(\n this.recoveryState.kind === 'save-required' ? this.recoveryState.executionId : undefined,\n );\n }\n private commitConversation(executionId?: string): void {\n if (!this.checkpointHistoryCallback && !this.sessionStore) return;\n if (this.checkpointing) throw new Error('Reentrant Session checkpoint');\n const pending = this.pendingExecution;\n const liveWaits = this.recoveryState.kind !== 'ready' && this.recoveryState.liveWaits;\n this.checkpointing = true;\n try {\n if (this.checkpointHistoryCallback)\n assertSynchronousCheckpoint(this.checkpointHistoryCallback());\n else this.persistSessionInternal(true);\n this.recoveryState = pending ? { kind: 'pending', pending, liveWaits } : { kind: 'ready' };\n } catch (error) {\n this.recoveryState = {\n kind: 'save-required',\n pending,\n liveWaits,\n executionId: executionId ?? pending?.executionId,\n };\n throw error;\n } finally {\n this.checkpointing = false;\n }\n }\n private saveAfterFailure(error: unknown): void {\n if (this.recoveryState.kind === 'save-required') return;\n try {\n this.commitConversation();\n } catch (saveError) {\n if (error instanceof ExecutionSuspendedError) throw saveError;\n try {\n this.log('error', {\n message: `Session checkpoint failed while preserving the execution error: ${saveError instanceof Error ? saveError.message : String(saveError)}`,\n stack: saveError instanceof Error ? (saveError.stack ?? '') : '',\n historyLength: this.agent.getHistory().length,\n });\n } catch {\n // allow-fallback: a secondary diagnostic must never replace the original execution failure.\n }\n }\n }\n\n /**\n * Make tools available from the next turn on — for a capability that became usable mid-session,\n * such as an MCP server connected after its sign-in. Each goes through the same wrappers and\n * permission gate as a tool present from the start. A tool whose name the session already has, or\n * has queued, is left out rather than replacing the one the conversation has been using.\n *\n * The tool list is part of what a provider caches a prompt by, and a turn's rounds must all see\n * the same list: while a turn runs, the tools wait and are applied when the next turn starts;\n * otherwise they are applied now. Calls are serialized, so two concurrent ones both land.\n * Resolves to the names that will be offered.\n */\n addTools(tools: readonly IToolWithEventService[]): Promise<readonly string[]> {\n this.assertInputAllowed();\n if (this.shuttingDown) {\n return Promise.reject(new Error('[LIFECYCLE] Session is shutting down'));\n }\n return this.serializeToolChange(async () => {\n const known = new Set([\n ...this.toolSchemas.map((schema) => schema.name),\n ...this.pendingTools.map((tool) => tool.schema.name),\n ]);\n const fresh: IToolWithEventService[] = [];\n for (const tool of tools) {\n if (known.has(tool.schema.name)) continue;\n known.add(tool.schema.name);\n fresh.push(tool);\n }\n if (fresh.length === 0) return [];\n this.pendingTools.push(...fresh);\n if (!this.turnClaim.isRunning()) await this.applyPendingTools();\n return fresh.map((tool) => tool.schema.name);\n });\n }\n\n /** Runs `change` after every tool change before it, so each reads the list the last one wrote. */\n private serializeToolChange<T>(change: () => Promise<T>): Promise<T> {\n const result = this.toolChange.then(change);\n this.toolChange = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n }\n\n /** Registers the queued tools with the agent. Only ever called inside `serializeToolChange`. */\n private async applyPendingTools(): Promise<void> {\n if (this.pendingTools.length === 0) return;\n const fresh = this.pendingTools.splice(0);\n await this.agent.ensureReady();\n const wrapped = this.permissionEnforcer.wrapTools(this.wrapAddedTools?.(fresh) ?? fresh);\n await this.agent.updateTools([...(this.agent.getConfig().tools ?? []), ...wrapped]);\n this.toolSchemas.push(...wrapped.map((tool) => tool.schema));\n }\n\n async listRuntimeTools(): Promise<IToolSchema[]> {\n if (this.shuttingDown) throw new Error('[LIFECYCLE] Session is shutting down');\n return this.agent.listRuntimeTools();\n }\n\n async invokeRuntimeTool(\n name: string,\n parameters: TToolParameters,\n options?: IRuntimeToolInvokeOptions,\n ): Promise<IToolExecutionResult> {\n if (this.shuttingDown) throw new Error('[LIFECYCLE] Session is shutting down');\n this.assertInputAllowed();\n return this.runtimeTools.invoke(name, parameters, options);\n }\n\n /**\n * The session-owned observable event bus the agent's tools publish to. The interactive\n * turn subscribes to it to collect span-completion events and project them onto session history.\n */\n getEventService(): IEventService {\n return this.eventService;\n }\n\n private log(event: string, data: TSessionLogData): void {\n this.sessionLogger?.log(this.sessionId, event, data);\n }\n\n private persistSessionInternal(strict = false): void {\n if (!this.sessionStore) return;\n const outcome = persistSession({\n sessionId: this.sessionId,\n cwd: this.cwd,\n systemPrompt: this.systemMessage,\n toolSchemas: this.toolSchemas,\n sessionStore: this.sessionStore,\n conversation: this.getConversationSnapshot(),\n getFullHistory: () => this.getFullHistory(),\n });\n if (strict && outcome.status !== 'valid') {\n throw new Error(`Session history could not be durably checkpointed: ${outcome.status}`);\n }\n }\n\n /**\n * Gracefully end the session and fire SessionEnd hooks once — **best-effort** (disposal\n * convention): never rejects, so `void session.shutdown()` cannot become an\n * unhandled rejection. Step failures are recorded to the session log and remaining steps\n * still run.\n */\n shutdown(options: ISessionShutdownOptions = {}): Promise<void> {\n if (this.shutdownPromise) return this.shutdownPromise;\n this.shuttingDown = true;\n const reason = options.reason ?? 'other';\n const step = async (label: string, run: () => Promise<void> | void): Promise<void> => {\n try {\n await run();\n } catch (error) {\n // allow-fallback: best-effort disposal IS the contract — the failure is logged and remaining shutdown steps still run (disposal convention)\n this.log('session_shutdown_step_error', {\n step: label,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n };\n this.shutdownPromise = (async () => {\n await step('abort', () => this.abort());\n await step('drain-direct-tool', () => this.runtimeTools.drain());\n await step('drain-turn', () => this.turnClaim.drained);\n this.log('session_shutdown', { reason });\n await step('persist', () => this.persistSessionInternal());\n await step('session-end-hook', () =>\n fireSessionEndHook(\n this.sessionId,\n this.cwd,\n reason,\n this.hooks,\n this.hookTypeExecutors,\n this.permissionMode,\n this.transcriptPath,\n ),\n );\n // (SPEC § Disposal Chain Contract): shutdown drives agent destruction —\n // plugins are disposed so no timers/listeners survive and the process can exit.\n await step('destroy-agent', async () => {\n await this.agent.destroy();\n });\n })();\n return this.shutdownPromise;\n }\n\n swapProvider(\n newProvider: IAIProvider,\n model: string,\n options?: IProviderRegistrationOptions,\n ): void {\n this.assertInputAllowed();\n this.agent.swapDefaultProvider(newProvider, model, options);\n if ('onServerToolUse' in newProvider) {\n (\n newProvider as { onServerToolUse?: (name: string, input: Record<string, string>) => void }\n ).onServerToolUse = (name: string, input: Record<string, string>) =>\n this.log('server_tool', { tool: name, ...input });\n }\n this.aiProvider = newProvider;\n this.setActiveModel(model);\n }\n\n async compact(\n instructions?: string,\n trigger: TCompactTrigger = 'manual',\n signal?: AbortSignal,\n executionJournal?: IExecutionJournal,\n ): Promise<void> {\n this.assertInputAllowed();\n const controller = this.turnClaim.claim();\n const unlink = linkCancellation(controller, signal);\n try {\n await this.compactWith(instructions, trigger, controller.signal, undefined, executionJournal);\n } finally {\n unlink();\n this.turnClaim.release(controller);\n }\n }\n\n /** `hookTraceEnv` reaches PreCompact only for a compaction inside a prompt (see `executeRun`). */\n private async compactWith(\n instructions: string | undefined,\n trigger: TCompactTrigger,\n signal?: AbortSignal,\n hookTraceEnv?: ISubprocessTraceEnv,\n executionJournal?: IExecutionJournal,\n ): Promise<void> {\n const extras = {\n systemMessage: this.systemMessage,\n compactionOrchestrator: this.compactionOrchestrator,\n onCompactCallback: this.onCompactCallback,\n onCompactEventCallback: this.onCompactEventCallback,\n trigger,\n ...(hookTraceEnv ? { hookTraceEnv } : {}),\n ...(executionJournal ? { executionJournal } : {}),\n };\n await compact(instructions, buildCompactContext(this.buildRunContext(), extras), signal);\n }\n\n private buildRunContext(journaled = false): IRunContext {\n return {\n sessionId: this.sessionId,\n cwd: this.cwd,\n model: this.model,\n effort: this.getModelEffort(),\n agent: this.agent,\n aiProvider: this.aiProvider,\n contextTracker: this.contextTracker,\n hooks: this.hooks,\n hookTypeExecutors: this.hookTypeExecutors,\n sessionStartStdout: this.sessionStartStdout,\n log: (event: string, data: TSessionLogData) => this.log(event, data),\n compact: (signal, hookTraceEnv, executionJournal) =>\n this.compactWith(undefined, 'auto', signal, hookTraceEnv, executionJournal),\n persistSession: () => this.persistSessionInternal(),\n checkpointHistory:\n this.checkpointHistoryCallback || this.sessionStore\n ? () => {\n if (journaled)\n this.setPending(\n unfinishedExecution(this.agent.getHistory(), this.pendingExecution, new Set()),\n );\n this.commitConversation();\n }\n : undefined,\n\n getSessionStore: () => !!this.sessionStore,\n clearSessionStartStdout: () => void (this.sessionStartStdout = ''),\n permissionMode: this.permissionMode,\n transcriptPath: this.transcriptPath,\n maxTurns: this.maxTurns,\n onTextDelta: this.onTextDeltaCallback,\n onThinkingDelta: this.onThinkingDeltaCallback,\n onContextUpdate: this.onContextUpdateCallback,\n onToolExecution: this.onToolExecutionCallback,\n emitProviderCallCompleted: (observation) =>\n this.eventService.emit(\n PROVIDER_CALL_EVENTS.COMPLETED,\n { timestamp: new Date(), ...observation },\n {\n ownerType: 'session',\n ownerId: this.sessionId,\n ownerPath: [{ type: 'session', id: this.sessionId }],\n },\n ),\n emitProviderFallback: (notice) =>\n this.eventService.emit(\n PROVIDER_FALLBACK_EVENTS.SWITCHED,\n {\n timestamp: new Date(),\n fromProvider: notice.from.provider,\n fromModel: notice.from.model,\n toProvider: notice.to.provider,\n toModel: notice.to.model,\n reason: notice.reason,\n },\n {\n ownerType: 'session',\n ownerId: this.sessionId,\n ownerPath: [{ type: 'session', id: this.sessionId }],\n },\n ),\n knownToolNames: this.toolSchemas.map((tool) => tool.name),\n };\n }\n}\n","/**\n * The neutral export/import envelope for a session, over `IInteractiveSessionRecord`.\n *\n * This is a RECORD-TRANSPORT sibling of the file-backed `session-store.ts` (SRP: transport vs file persistence).\n * It is the async, durable COMPLEMENT to the live P2P channel — export a thread to a portable artifact,\n * hand it off, import + resume it later on a second surface, with both peers possibly offline. No transport, no\n * pairing, no wire protocol.\n *\n * Two operations, specified separately and never conflated:\n * 1. Round-trip serialize (fidelity, local): `serializeSessionArtifact(record)` with no transform is\n * full-fidelity — `deserializeSessionArtifact(serializeSessionArtifact(record))` deep-equals the record.\n * (Precondition: `IInteractiveSessionRecord` is JSON-safe, as it is by construction — it is the on-disk file-store\n * shape; a non-JSON value like a `Date`/`Map`/class instance nested in a payload would not round-trip.)\n * 2. Export-for-share: `serializeSessionArtifact(record, { redact })` applies a caller-supplied, policy-free\n * `redact` transform BEFORE writing bytes. The envelope selects NO fields and owns NO field policy — the app\n * builds `redact` (composing the opt-in `scrubSensitiveKeys`). The no-transform form stays full-fidelity.\n *\n * Neutrality (mechanically fenced): this module is pure serialize/deserialize + a schema-version header +\n * the app-supplied `redact` seam. It carries NO link/cloud/upload/access-control and NO redaction FIELD policy.\n */\n\nimport {\n SESSION_RECORD_ENVELOPE_VERSION,\n decodeVersionedInteractiveSessionRecord,\n} from './session-record-codec/index.js';\n\nimport type { IVersionedInteractiveSessionRecord } from './session-record-codec/index.js';\nimport type { IInteractiveSessionRecord } from '@robota-sdk/agent-interface/session';\n\n/**\n * The envelope type and its version constant are the codec's (`session-record-codec/`).\n * They used to be declared here as `ISessionArtifact` and a local constant, which was a second name\n * and a second number for one shape — the envelope the codec decodes and the envelope this module\n * writes were always the same `{ schemaVersion, record }`. Nothing on disk changed when they were\n * unified, because there was nothing to change.\n */\n\nexport interface ISerializeSessionArtifactOptions {\n /**\n * SHARE-PATH ONLY. An app-supplied, policy-free transform applied to the record before serialization — the app\n * decides which trust-boundary fields to strip (composing the opt-in `scrubSensitiveKeys`). Omit for the\n * full-fidelity local round-trip.\n */\n redact?: (record: IInteractiveSessionRecord) => IInteractiveSessionRecord;\n}\n\n/** How many decode issues an error message carries before it elides the rest. */\nconst MAX_REPORTED_ISSUES = 5;\n\n/**\n * Parse the bytes, reporting a non-JSON body the same way a non-record body is reported.\n *\n * `JSON.parse` throwing and the decoder refusing are the same failure for a caller — the bytes are\n * not an artifact — so they are not two different exceptions to catch.\n */\nfunction parseArtifactBytes(bytes: string): unknown {\n try {\n return JSON.parse(bytes) as unknown;\n } catch {\n throw new Error('Invalid session artifact: the bytes are not JSON.');\n }\n}\n\n/**\n * Serialize a session record into a portable, versioned artifact. With no `redact`, this is the full-fidelity\n * round-trip form; with `redact`, the caller's transform is applied first (the share path).\n */\nexport function serializeSessionArtifact(\n record: IInteractiveSessionRecord,\n options: ISerializeSessionArtifactOptions = {},\n): string {\n const payload = options.redact ? options.redact(record) : record;\n const artifact: IVersionedInteractiveSessionRecord = {\n schemaVersion: SESSION_RECORD_ENVELOPE_VERSION,\n record: payload,\n };\n return JSON.stringify(artifact, null, 2);\n}\n\n/**\n * Parse a session artifact back into an `IInteractiveSessionRecord`, rejecting an artifact whose schema version this build\n * does not understand (so an incompatible artifact is never silently mis-imported).\n */\nexport function deserializeSessionArtifact(bytes: string): IInteractiveSessionRecord {\n const outcome = decodeVersionedInteractiveSessionRecord(parseArtifactBytes(bytes));\n if (outcome.status === 'unsupported') {\n throw new Error(\n `Unsupported session artifact schema version ${outcome.schemaVersion ?? '(absent or not a number)'} ` +\n `(this build reads ${SESSION_RECORD_ENVELOPE_VERSION}).`,\n );\n }\n if (outcome.status === 'corrupt') {\n // The paths are the point: an artifact that cannot be imported should tell its holder WHERE it\n // is wrong, not that it is wrong. Bounded, because a wholly unrelated payload produces an issue\n // per member and a thousand-line error informs nobody.\n const shown = outcome.issues.slice(0, MAX_REPORTED_ISSUES);\n const detail = shown\n .map((issue) => `${issue.path === '' ? '(root)' : issue.path}: ${issue.message}`)\n .join('; ');\n const elided =\n outcome.issues.length > shown.length\n ? ` (+${outcome.issues.length - shown.length} more)`\n : '';\n throw new Error(`Invalid session artifact: ${detail}${elided}`);\n }\n return outcome.record;\n}\n","/**\n * The single source of the sensitive-key redaction (SSOT).\n *\n * The recursive secret-key scrub used to live privately in `session-logger.ts`, logging-coupled and not exported.\n * It is extracted here so exactly ONE definition of \"which keys are sensitive\" exists, consumed by BOTH the file\n * session logger (persistence-time redaction) and the share-artifact `redact` transform (an opt-in\n * the app composes). This utility is a pure, mechanism-level key scrub only: it carries NO field/trust-boundary\n * policy (which of `cwd`/`sandboxSnapshotId`/… to strip is an app decision) and is NEVER forced into the\n * full-fidelity local round-trip.\n */\n\n/** Values a scrub can walk (mirrors the logger's log-value shape; avoids `any`/`unknown`). */\nexport type TScrubbableValue = string | number | boolean | object | null | undefined;\n\nconst DEFAULT_REDACTED_VALUE = '[REDACTED]';\n\n/**\n * Keys whose VALUE is a secret and must be redacted before persistence or sharing:\n * `apiKey`/`authorization`/`authToken`/`accessToken`/`refreshToken`/`secret`/`password`/`xApiKey`\n * (case/`-`/`_`-insensitive). `authToken` is how a provider's bearer credential is stored in\n * settings, so it must be redacted alongside `apiKey`; bare `token` stays unmatched (too broad).\n */\nexport const SENSITIVE_KEY_PATTERN =\n /^(api[-_]?key|authorization|auth[-_]?token|access[-_]?token|refresh[-_]?token|secret|password|x[-_]?api[-_]?key)$/i;\n\n/** True when a key's value should be redacted. The one predicate both the logger and the artifact scrub use. */\nexport function isSensitiveKey(key: string): boolean {\n return SENSITIVE_KEY_PATTERN.test(key);\n}\n\nfunction scrubValue(\n key: string | undefined,\n value: TScrubbableValue,\n redactedValue: string,\n): TScrubbableValue {\n if (key !== undefined && isSensitiveKey(key)) {\n return redactedValue;\n }\n if (Array.isArray(value)) {\n return value.map((item) => scrubValue(undefined, item as TScrubbableValue, redactedValue));\n }\n // A Date (or any non-plain object without own enumerable keys) is returned as-is by the Object.entries walk.\n if (value !== null && typeof value === 'object' && !(value instanceof Date)) {\n const record = value as Record<string, TScrubbableValue>;\n const out: Record<string, TScrubbableValue> = {};\n for (const [childKey, childValue] of Object.entries(record)) {\n out[childKey] = scrubValue(childKey, childValue, redactedValue);\n }\n return out;\n }\n return value;\n}\n\n/**\n * Deep-copy `value`, replacing any value whose KEY is sensitive with `redactedValue` (default `[REDACTED]`).\n * Pure — does not mutate the input. Returns the same shape (`T`).\n */\nexport function scrubSensitiveKeys<T>(value: T, redactedValue: string = DEFAULT_REDACTED_VALUE): T {\n return scrubValue(undefined, value as TScrubbableValue, redactedValue) as T;\n}\n","/**\n * Session log payload normalization — scrubbing and size-bounding what goes into a log line.\n *\n * Split out of `session-logger.ts` to keep each file under 300 lines. The split is by\n * responsibility, not by size alone: this module decides WHAT a line may contain (sensitive keys\n * redacted, oversized payloads written beside the log and referenced), while `session-logger.ts`\n * decides WHERE and WHEN the bytes are written.\n */\n\nimport { sha256Hex, utf8ByteLength } from '@robota-sdk/agent-core';\n\nimport { isSensitiveKey } from './scrub-sensitive.js';\n\nimport type { IExternalPayloadSink } from './session-log-sinks.js';\nimport type {\n IFileSessionLoggerOptions,\n TSessionLogData,\n TSessionLogValue,\n} from './session-log-reference-types.js';\n\n/** What decides a log line's content; where and when it is written is the logger's business. */\nexport type TLogNormalizationOptions = Required<\n Pick<IFileSessionLoggerOptions, 'externalPayloadThresholdBytes' | 'redactedValue'>\n>;\n\nexport function normalizeLogData(\n sessionId: string,\n data: TSessionLogData,\n options: TLogNormalizationOptions,\n externalPayloadSink: IExternalPayloadSink | undefined,\n): TSessionLogData {\n const normalized: TSessionLogData = {};\n for (const [key, value] of Object.entries(data)) {\n normalized[key] = normalizeLogValue(sessionId, key, value, options, externalPayloadSink);\n }\n return normalized;\n}\n\nfunction normalizeLogValue(\n sessionId: string,\n key: string,\n value: TSessionLogValue,\n options: TLogNormalizationOptions,\n externalPayloadSink: IExternalPayloadSink | undefined,\n): TSessionLogValue {\n if (isSensitiveKey(key)) {\n return options.redactedValue;\n }\n if (\n value === null ||\n value === undefined ||\n typeof value === 'string' ||\n typeof value === 'number'\n ) {\n return maybeExternalizePayload(sessionId, value, options, externalPayloadSink);\n }\n if (typeof value === 'boolean') {\n return value;\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (Array.isArray(value)) {\n const normalizedArray = value.map((item) =>\n normalizeLogValue(sessionId, key, item as TSessionLogValue, options, externalPayloadSink),\n );\n return maybeExternalizePayload(sessionId, normalizedArray, options, externalPayloadSink);\n }\n if (typeof value === 'object') {\n const record = value as Record<string, TSessionLogValue>;\n const normalizedRecord: Record<string, TSessionLogValue> = {};\n for (const [childKey, childValue] of Object.entries(record)) {\n normalizedRecord[childKey] = normalizeLogValue(\n sessionId,\n childKey,\n childValue,\n options,\n externalPayloadSink,\n );\n }\n return maybeExternalizePayload(sessionId, normalizedRecord, options, externalPayloadSink);\n }\n return String(value);\n}\n\nfunction maybeExternalizePayload(\n sessionId: string,\n value: TSessionLogValue,\n options: TLogNormalizationOptions,\n externalPayloadSink: IExternalPayloadSink | undefined,\n): TSessionLogValue {\n const serialized = JSON.stringify(value);\n if (serialized === undefined) {\n return value;\n }\n const byteLength = utf8ByteLength(serialized);\n if (byteLength <= options.externalPayloadThresholdBytes || externalPayloadSink === undefined) {\n return value;\n }\n\n const sha256 = sha256Hex(serialized);\n return externalPayloadSink.writeJson(sessionId, sha256, serialized);\n}\n\n/** No-op logger — used when logging is disabled. */\n","/**\n * Session Logger — pluggable logging interface for session events.\n *\n * ISessionLogger defines the contract. FileSessionLogger serializes JSONL through an\n * injected sink and never opens a path itself. Consumers can implement their\n * own (e.g., remote, database, silent) and inject via Session constructor.\n */\n\nimport { createLogger } from '@robota-sdk/agent-core';\n\nimport { isSafeSessionId } from './session-id.js';\nimport { SESSION_LOG_SCHEMA_VERSION } from './session-log-events.js';\nimport { normalizeLogData } from './session-log-payload.js';\n\nimport type { TLogNormalizationOptions } from './session-log-payload.js';\nimport type { ISessionLogSink } from './session-log-sinks.js';\nimport type {\n IFileSessionLoggerOptions,\n TRegisterExitFlush,\n TSessionLogData,\n} from './session-log-reference-types.js';\n\nexport type {\n IExternalPayloadReference,\n IFileSessionLoggerOptions,\n TRegisterExitFlush,\n TSessionLogData,\n TSessionLogValue,\n} from './session-log-reference-types.js';\n\nconst logger = createLogger('FileSessionLogger');\n\n/**\n * Events that arrive once per streamed token.\n *\n * `session-run.ts` logs one of these per text delta and this class answered each with a\n * blocking `appendFileSync` — a synchronous disk write per token on the streaming hot path. They are\n * buffered and written in one call; every OTHER event flushes the buffer before writing itself, so\n * ordering in the file is unchanged and no semantic event is ever delayed behind a stream.\n */\nconst HOT_PATH_EVENTS: ReadonlySet<string> = new Set(['text_delta']);\n\n/** Flush the delta buffer once it reaches this size, so a long stream cannot grow without bound. */\nconst HOT_PATH_FLUSH_BYTES = 64 * 1024;\n\n/**\n * Every logger with something buffered, flushed if the process exits mid-stream.\n *\n * Buffering trades a write per token for a write per batch, and the price is a window in which the\n * tail of a stream exists only in memory. A normal shutdown closes that window by itself — the\n * `session_shutdown` event is not a hot-path event, so it flushes before writing — but an abnormal\n * exit would not, and a replay log missing its last exchange is a worse defect than the one being\n * fixed. `exit` handlers may only do synchronous work, which is exactly what `flush` does. Whether\n * and how the process's exit is observed is the host's to say (`registerExitFlush`): a neutral\n * logger has no process to watch.\n */\nconst liveLoggers = new Set<FileSessionLogger>();\n/** Registrars already handed {@link flushLiveLoggers}: one exit listener per host, not per logger. */\nconst registrars = new WeakSet<TRegisterExitFlush>();\n\nfunction flushLiveLoggers(): void {\n for (const live of liveLoggers) {\n live.flush();\n }\n}\n\nfunction ensureExitFlush(\n loggerInstance: FileSessionLogger,\n registerExitFlush: TRegisterExitFlush | undefined,\n): void {\n liveLoggers.add(loggerInstance);\n if (registerExitFlush === undefined || registrars.has(registerExitFlush)) return;\n registrars.add(registerExitFlush);\n registerExitFlush(flushLiveLoggers);\n}\n\nconst BYTES_PER_KIB = 1024;\nconst DEFAULT_EXTERNAL_PAYLOAD_THRESHOLD_KIB = 32;\nconst DEFAULT_EXTERNAL_PAYLOAD_THRESHOLD_BYTES =\n DEFAULT_EXTERNAL_PAYLOAD_THRESHOLD_KIB * BYTES_PER_KIB;\nconst DEFAULT_REDACTED_VALUE = '[REDACTED]';\n\n/**\n * Session logger interface — injected into Session for pluggable logging.\n *\n * Implementations decide where and how to persist session events.\n * The Session class calls log() for every significant action.\n */\nexport interface ISessionLogger {\n /** Log a session event with structured data. */\n log(sessionId: string, event: string, data: TSessionLogData): void;\n /**\n * Write out anything buffered.\n *\n * Optional because an implementation that never buffers has nothing to do. A caller that needs\n * the log to be complete on disk — session end, or a reader about to parse it — calls this.\n */\n flush?(): void;\n}\n\n/**\n * Sink-driven session logger — writes JSONL through `ISessionLogSink`.\n *\n * This is the default implementation used by the CLI.\n * Each line is a self-contained JSON object with timestamp, sessionId, event, and data.\n */\nexport class FileSessionLogger implements ISessionLogger {\n private readonly options: TLogNormalizationOptions;\n private readonly registerExitFlush: TRegisterExitFlush | undefined;\n /** Buffered hot-path lines, per session file. Keyed by session id. */\n private readonly pending = new Map<string, string[]>();\n private pendingBytes = 0;\n\n constructor(\n private readonly sink: ISessionLogSink,\n options: IFileSessionLoggerOptions = {},\n ) {\n this.options = {\n externalPayloadThresholdBytes:\n options.externalPayloadThresholdBytes ?? DEFAULT_EXTERNAL_PAYLOAD_THRESHOLD_BYTES,\n redactedValue: options.redactedValue ?? DEFAULT_REDACTED_VALUE,\n };\n this.registerExitFlush = options.registerExitFlush;\n }\n\n log(sessionId: string, event: string, data: TSessionLogData): void {\n // `sessionId` becomes a path component below. This is a second sink on the same value the\n // session store guards, and a host can pass a remote-supplied id, so it must not rely on the\n // store having been called first. Logging must never break a\n // session, so a rejected id drops the line rather than throwing — the store raises the loud error.\n if (!isSafeSessionId(sessionId)) return;\n try {\n const normalizedData = normalizeLogData(\n sessionId,\n data,\n this.options,\n this.sink.externalPayloadSink,\n );\n const entry =\n JSON.stringify({\n ...normalizedData,\n schemaVersion: SESSION_LOG_SCHEMA_VERSION,\n timestamp: new Date().toISOString(),\n sessionId,\n event,\n }) + '\\n';\n\n if (HOT_PATH_EVENTS.has(event)) {\n this.buffer(sessionId, entry);\n return;\n }\n\n // Any other event flushes first, so the file's order is the order the events happened in.\n this.flush();\n this.write(sessionId, entry);\n } catch (error) {\n // allow-fallback: logging must never break a session (the same rule holds for a\n // rejected id). The failure must not be INVISIBLE — a log\n // that silently stops writing is indistinguishable from a session that produced no events.\n this.report(sessionId, event, error);\n }\n }\n\n /** Write out every buffered hot-path line. Safe to call when nothing is pending. */\n flush(): void {\n if (this.pending.size === 0) {\n liveLoggers.delete(this);\n return;\n }\n const batches = [...this.pending.entries()];\n this.pending.clear();\n this.pendingBytes = 0;\n liveLoggers.delete(this);\n for (const [sessionId, lines] of batches) {\n try {\n this.write(sessionId, lines.join(''));\n } catch (error) {\n this.report(sessionId, 'flush', error);\n }\n }\n }\n\n private buffer(sessionId: string, entry: string): void {\n ensureExitFlush(this, this.registerExitFlush);\n const lines = this.pending.get(sessionId) ?? [];\n lines.push(entry);\n this.pending.set(sessionId, lines);\n this.pendingBytes += entry.length;\n // A stream that never ends must not accumulate without bound; flushing on size keeps the\n // write count proportional to bytes rather than to tokens, which is the whole point.\n if (this.pendingBytes >= HOT_PATH_FLUSH_BYTES) {\n this.flush();\n }\n }\n\n private write(sessionId: string, text: string): void {\n this.sink.append(sessionId, text);\n }\n\n private report(sessionId: string, event: string, error: unknown): void {\n logger.warn('session log write failed', {\n sessionId,\n event,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\nexport class SilentSessionLogger implements ISessionLogger {\n log(): void {\n // intentionally empty\n }\n}\n","import { SessionLogPayloadResolutionError } from './external-payload-resolution-contracts.js';\n\nimport type { IExternalPayloadReference } from './session-logger.js';\n\nconst SHA256_PATTERN = /^[0-9a-f]{64}$/i;\n\nexport function validateExternalPayloadReference(\n value: Record<string, unknown>,\n): IExternalPayloadReference {\n if (!isValidSessionLogExternalPayloadReference(value)) {\n throw new SessionLogPayloadResolutionError(\n 'INVALID_REFERENCE',\n 'External payload reference has an invalid shape.',\n );\n }\n return {\n kind: 'external-payload',\n encoding: 'json',\n sha256: String(value.sha256).toLowerCase(),\n byteLength: Number(value.byteLength),\n relativePath: String(value.relativePath),\n };\n}\n\n/** Internal SSOT shared by the resolver and raw-log validator; not part of the package barrel. */\nexport function isValidSessionLogExternalPayloadReference(value: Record<string, unknown>): boolean {\n const keys = Object.keys(value).sort();\n const expectedKeys = ['byteLength', 'encoding', 'kind', 'relativePath', 'sha256'];\n return (\n keys.length === expectedKeys.length &&\n keys.every((key, index) => key === expectedKeys[index]) &&\n value.kind === 'external-payload' &&\n value.encoding === 'json' &&\n typeof value.sha256 === 'string' &&\n SHA256_PATTERN.test(value.sha256) &&\n typeof value.byteLength === 'number' &&\n Number.isSafeInteger(value.byteLength) &&\n value.byteLength >= 0 &&\n typeof value.relativePath === 'string' &&\n value.relativePath.trim().length > 0\n );\n}\n","import { sha256Hex } from '@robota-sdk/agent-core';\n\nimport { validateExternalPayloadReference } from './external-payload-file-reader.js';\nimport { SessionLogPayloadResolutionError } from './external-payload-resolution-contracts.js';\n\nimport type { ISessionLogPayloadResolutionOptions } from './external-payload-resolution-contracts.js';\nimport type { IExternalPayloadSource } from './external-payload-source-types.js';\nimport type { IExternalPayloadReference } from './session-logger.js';\n\nexport { SessionLogPayloadResolutionError } from './external-payload-resolution-contracts.js';\nexport type {\n ISessionLogPayloadResolutionErrorMetadata,\n ISessionLogPayloadResolutionOptions,\n TSessionLogPayloadResolutionErrorCode,\n} from './external-payload-resolution-contracts.js';\n\n// A leading byte-order mark is content, not framing: it must still make the JSON invalid, as it did\n// when the bytes were decoded with Node's `Buffer`.\nconst PAYLOAD_DECODER = new TextDecoder('utf-8', { ignoreBOM: true });\nconst DEFAULT_MAX_DEPTH = 32;\nconst DEFAULT_MAX_TOTAL_MIB = 64;\nconst BYTES_PER_KIB = 1024;\nconst KIB_PER_MIB = 1024;\nconst DEFAULT_MAX_TOTAL_BYTES = DEFAULT_MAX_TOTAL_MIB * KIB_PER_MIB * BYTES_PER_KIB;\n\ninterface IResolutionState {\n readonly maxDepth: number;\n readonly maxTotalBytes: number;\n readonly source: IExternalPayloadSource | undefined;\n totalBytes: number;\n readonly activePayloadPaths: Set<string>;\n readonly activeObjects: WeakSet<object>;\n}\n\n/**\n * Hydrate every external JSON payload reference in one value using one aggregate budget.\n * The input is treated as untrusted and the returned graph contains only JSON-compatible values.\n */\nexport function resolveSessionLogExternalPayloads(\n value: unknown,\n options: ISessionLogPayloadResolutionOptions,\n): unknown {\n const state = createResolutionState(options);\n return resolveValue(value, state, 0);\n}\n\nfunction createResolutionState(options: ISessionLogPayloadResolutionOptions): IResolutionState {\n const maxDepth = validateLimit('maxDepth', options.maxDepth ?? DEFAULT_MAX_DEPTH);\n const maxTotalBytes = validateLimit(\n 'maxTotalBytes',\n options.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES,\n );\n return {\n source: options.source,\n maxDepth,\n maxTotalBytes,\n totalBytes: 0,\n activePayloadPaths: new Set<string>(),\n activeObjects: new WeakSet<object>(),\n };\n}\n\nfunction validateLimit(name: string, value: number): number {\n if (!Number.isFinite(value) || !Number.isSafeInteger(value) || value < 0) {\n throw new SessionLogPayloadResolutionError(\n 'INVALID_LIMIT',\n `${name} must be a finite, non-negative safe integer.`,\n { actual: String(value) },\n );\n }\n return value;\n}\n\nfunction resolveValue(value: unknown, state: IResolutionState, referenceDepth: number): unknown {\n if (\n value === null ||\n typeof value === 'string' ||\n typeof value === 'boolean' ||\n (typeof value === 'number' && Number.isFinite(value))\n ) {\n return value;\n }\n if (typeof value === 'number') {\n throw invalidJsonValue('Non-finite numbers are not valid session-log JSON values.');\n }\n if (typeof value !== 'object') {\n throw invalidJsonValue(`Unsupported session-log JSON value type: ${typeof value}.`);\n }\n if (state.activeObjects.has(value)) {\n throw new SessionLogPayloadResolutionError(\n 'CIRCULAR_REFERENCE',\n 'Circular in-memory value encountered while resolving external payloads.',\n { depth: referenceDepth },\n );\n }\n if (isPotentialExternalPayloadReference(value)) {\n return resolveReference(value, state, referenceDepth);\n }\n if (Array.isArray(value)) {\n state.activeObjects.add(value);\n try {\n return value.map((item) => resolveValue(item, state, referenceDepth));\n } finally {\n state.activeObjects.delete(value);\n }\n }\n if (!isPlainRecord(value)) {\n throw invalidJsonValue('Session-log payload objects must be plain JSON records.');\n }\n state.activeObjects.add(value);\n try {\n return Object.fromEntries(\n Object.entries(value).map(([key, child]) => [\n key,\n resolveValue(child, state, referenceDepth),\n ]),\n );\n } finally {\n state.activeObjects.delete(value);\n }\n}\n\nfunction resolveReference(\n value: Record<string, unknown>,\n state: IResolutionState,\n referenceDepth: number,\n): unknown {\n const reference = validateExternalPayloadReference(value);\n if (referenceDepth >= state.maxDepth) {\n throw new SessionLogPayloadResolutionError(\n 'MAX_DEPTH_EXCEEDED',\n `External-payload reference depth exceeds the configured maximum of ${state.maxDepth}.`,\n { relativePath: reference.relativePath, depth: referenceDepth },\n );\n }\n if (state.source === undefined) {\n throw new SessionLogPayloadResolutionError(\n 'UNRESOLVED_REFERENCE',\n 'An external session-log payload requires an explicit payload source.',\n { relativePath: reference.relativePath, depth: referenceDepth },\n );\n }\n if (state.activePayloadPaths.has(reference.relativePath)) {\n throw new SessionLogPayloadResolutionError(\n 'CIRCULAR_REFERENCE',\n `External payload ${reference.relativePath} recursively references an active payload.`,\n { relativePath: reference.relativePath, depth: referenceDepth },\n );\n }\n\n const parsed = readExternalPayloadJson(reference, state);\n state.activePayloadPaths.add(reference.relativePath);\n try {\n return resolveValue(parsed, state, referenceDepth + 1);\n } finally {\n state.activePayloadPaths.delete(reference.relativePath);\n }\n}\n\nfunction readExternalPayloadJson(\n reference: IExternalPayloadReference,\n state: IResolutionState,\n): unknown {\n const remainingBytes = state.maxTotalBytes - state.totalBytes;\n const bytes = state.source?.readBytes(reference.relativePath, remainingBytes);\n if (bytes === undefined) {\n throw new SessionLogPayloadResolutionError(\n 'PAYLOAD_NOT_FOUND',\n `External payload was not found: ${reference.relativePath}.`,\n { relativePath: reference.relativePath },\n );\n }\n const nextTotalBytes = state.totalBytes + bytes.byteLength;\n if (!Number.isSafeInteger(nextTotalBytes) || nextTotalBytes > state.maxTotalBytes) {\n throw new SessionLogPayloadResolutionError(\n 'MAX_TOTAL_BYTES_EXCEEDED',\n `External-payload bytes exceed the configured maximum of ${state.maxTotalBytes}.`,\n { expected: state.maxTotalBytes, actual: nextTotalBytes },\n );\n }\n state.totalBytes = nextTotalBytes;\n if (bytes.byteLength !== reference.byteLength) {\n throw new SessionLogPayloadResolutionError(\n 'BYTE_LENGTH_MISMATCH',\n `External payload byte length does not match its reference: ${reference.relativePath}.`,\n {\n relativePath: reference.relativePath,\n expected: reference.byteLength,\n actual: bytes.byteLength,\n },\n );\n }\n const actualSha256 = sha256Hex(bytes);\n if (actualSha256 !== reference.sha256) {\n throw new SessionLogPayloadResolutionError(\n 'SHA256_MISMATCH',\n `External payload sha256 does not match its reference: ${reference.relativePath}.`,\n { relativePath: reference.relativePath, expected: reference.sha256, actual: actualSha256 },\n );\n }\n try {\n return JSON.parse(PAYLOAD_DECODER.decode(bytes)) as unknown;\n } catch (error) {\n throw new SessionLogPayloadResolutionError(\n 'INVALID_JSON',\n `External payload is not valid JSON: ${reference.relativePath}.`,\n { relativePath: reference.relativePath },\n error,\n );\n }\n}\n\nfunction invalidJsonValue(message: string): SessionLogPayloadResolutionError {\n return new SessionLogPayloadResolutionError('INVALID_JSON', message);\n}\n\nfunction isPotentialExternalPayloadReference(value: object): value is Record<string, unknown> {\n return !Array.isArray(value) && 'kind' in value && value.kind === 'external-payload';\n}\n\nfunction isPlainRecord(value: object): value is Record<string, unknown> {\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n","import { isValidSessionLogExternalPayloadReference } from './external-payload-file-reader.js';\nimport { decodeSessionLogEntries } from './session-log-codec/index.js';\n\nimport type { ISessionLogEntry } from './session-log-entry-types.js';\nimport type { TUniversalValue } from '@robota-sdk/agent-core';\n\nexport interface ISessionReplayValidationIssue {\n code:\n | 'PROVIDER_RESPONSE_RAW_MISSING'\n | 'PROVIDER_NATIVE_RAW_PAYLOAD_MISSING'\n | 'PROVIDER_RESPONSE_NORMALIZED_MISSING'\n | 'TOOL_RESULT_MISSING'\n | 'PAYLOAD_REFERENCE_INVALID'\n | 'UNRESOLVED_REPLAY_PAYLOAD';\n message: string;\n eventIndex?: number;\n executionId?: string;\n round?: number;\n toolCallId?: string;\n}\n\nexport interface ISessionReplayValidationResult {\n ok: boolean;\n issues: ISessionReplayValidationIssue[];\n}\n\nexport function validateSessionReplayLogEntries(\n entries: readonly ISessionLogEntry[],\n): ISessionReplayValidationResult {\n const issues: ISessionReplayValidationIssue[] = [];\n const providerEvents = createProviderReplayEventIndex();\n const toolEvents = createToolReplayEventIndex();\n\n decodeSessionLogEntries(entries).forEach((entry, index) => {\n collectPayloadReferenceIssues(entry, index, issues);\n collectUnresolvedReplayPayloadIssue(entry, index, issues);\n collectProviderReplayEvent(providerEvents, entry, index);\n collectToolReplayEvent(toolEvents, entry, index);\n });\n\n appendProviderReplayIssues(providerEvents, issues);\n appendToolReplayIssues(toolEvents, issues);\n\n return { ok: issues.length === 0, issues };\n}\n\ninterface IProviderReplayRequest {\n executionId: string;\n round: number;\n index: number;\n}\n\ninterface IProviderReplayEventIndex {\n requests: Map<string, IProviderReplayRequest>;\n nativeRawPayloads: Set<string>;\n rawResponses: Set<string>;\n normalizedResponses: Set<string>;\n}\n\ninterface IToolReplayRequest {\n executionId: string;\n toolCallId: string;\n index: number;\n}\n\ninterface IToolReplayEventIndex {\n requests: Map<string, IToolReplayRequest>;\n results: Set<string>;\n}\n\nfunction createProviderReplayEventIndex(): IProviderReplayEventIndex {\n return {\n requests: new Map<string, IProviderReplayRequest>(),\n nativeRawPayloads: new Set<string>(),\n rawResponses: new Set<string>(),\n normalizedResponses: new Set<string>(),\n };\n}\n\nfunction createToolReplayEventIndex(): IToolReplayEventIndex {\n return {\n requests: new Map<string, IToolReplayRequest>(),\n results: new Set<string>(),\n };\n}\n\nfunction collectProviderReplayEvent(\n events: IProviderReplayEventIndex,\n entry: ISessionLogEntry,\n index: number,\n): void {\n const key = providerKey(entry);\n if (!key) return;\n if (entry.event === 'provider_request') {\n events.requests.set(key.key, {\n executionId: key.executionId,\n round: key.round,\n index,\n });\n }\n if (entry.event === 'provider_response_raw') {\n events.rawResponses.add(key.key);\n }\n if (\n entry.event === 'provider_native_raw_payload' &&\n (entry.payloadKind === 'response' || entry.payloadKind === 'stream_event')\n ) {\n events.nativeRawPayloads.add(key.key);\n }\n if (\n entry.event === 'provider_response_normalized' &&\n !containsExternalPayloadReference(entry.response)\n ) {\n events.normalizedResponses.add(key.key);\n }\n}\n\nfunction collectUnresolvedReplayPayloadIssue(\n entry: ISessionLogEntry,\n eventIndex: number,\n issues: ISessionReplayValidationIssue[],\n): void {\n const replayValue =\n entry.event === 'history_mutation'\n ? entry.message\n : entry.event === 'provider_response_normalized'\n ? entry.response\n : undefined;\n if (!containsExternalPayloadReference(replayValue)) return;\n issues.push({\n code: 'UNRESOLVED_REPLAY_PAYLOAD',\n message: `Replay substrate ${entry.event} contains an unresolved external payload.`,\n eventIndex,\n executionId: typeof entry.executionId === 'string' ? entry.executionId : undefined,\n round: typeof entry.round === 'number' ? entry.round : undefined,\n });\n}\n\nfunction collectToolReplayEvent(\n events: IToolReplayEventIndex,\n entry: ISessionLogEntry,\n index: number,\n): void {\n const key = toolKey(entry);\n if (!key) return;\n if (entry.event === 'tool_execution_request') {\n events.requests.set(key.key, {\n executionId: key.executionId,\n toolCallId: key.toolCallId,\n index,\n });\n }\n if (entry.event === 'tool_execution_result') {\n events.results.add(key.key);\n }\n}\n\nfunction appendProviderReplayIssues(\n events: IProviderReplayEventIndex,\n issues: ISessionReplayValidationIssue[],\n): void {\n for (const [key, request] of events.requests) {\n if (!events.nativeRawPayloads.has(key)) {\n issues.push({\n code: 'PROVIDER_NATIVE_RAW_PAYLOAD_MISSING',\n message: `Provider request ${key} has no provider-native raw response or stream payload event.`,\n eventIndex: request.index,\n executionId: request.executionId,\n round: request.round,\n });\n }\n if (!events.rawResponses.has(key)) {\n issues.push({\n code: 'PROVIDER_RESPONSE_RAW_MISSING',\n message: `Provider request ${key} has no raw response event.`,\n eventIndex: request.index,\n executionId: request.executionId,\n round: request.round,\n });\n }\n if (!events.normalizedResponses.has(key)) {\n issues.push({\n code: 'PROVIDER_RESPONSE_NORMALIZED_MISSING',\n message: `Provider request ${key} has no normalized response event.`,\n eventIndex: request.index,\n executionId: request.executionId,\n round: request.round,\n });\n }\n }\n}\n\nfunction appendToolReplayIssues(\n events: IToolReplayEventIndex,\n issues: ISessionReplayValidationIssue[],\n): void {\n for (const [key, request] of events.requests) {\n if (!events.results.has(key)) {\n issues.push({\n code: 'TOOL_RESULT_MISSING',\n message: `Tool request ${key} has no terminal result event.`,\n eventIndex: request.index,\n executionId: request.executionId,\n toolCallId: request.toolCallId,\n });\n }\n }\n}\n\nfunction providerKey(\n entry: ISessionLogEntry,\n): { key: string; executionId: string; round: number } | undefined {\n if (typeof entry.executionId !== 'string') return undefined;\n const round = typeof entry.round === 'number' ? entry.round : Number(entry.round);\n if (!Number.isFinite(round)) return undefined;\n return { key: `${entry.executionId}:${round}`, executionId: entry.executionId, round };\n}\n\nfunction toolKey(\n entry: ISessionLogEntry,\n): { key: string; executionId: string; toolCallId: string } | undefined {\n if (typeof entry.executionId !== 'string') return undefined;\n const toolCallId =\n typeof entry.toolCallId === 'string'\n ? entry.toolCallId\n : typeof entry.toolExecutionId === 'string'\n ? entry.toolExecutionId\n : undefined;\n if (!toolCallId) return undefined;\n return { key: `${entry.executionId}:${toolCallId}`, executionId: entry.executionId, toolCallId };\n}\n\nfunction collectPayloadReferenceIssues(\n value: TUniversalValue,\n eventIndex: number,\n issues: ISessionReplayValidationIssue[],\n): void {\n if (Array.isArray(value)) {\n value.forEach((item) => collectPayloadReferenceIssues(item, eventIndex, issues));\n return;\n }\n if (!isRecord(value)) return;\n if (value.kind === 'external-payload') {\n if (!isValidSessionLogExternalPayloadReference(value)) {\n issues.push({\n code: 'PAYLOAD_REFERENCE_INVALID',\n message: 'External payload reference is missing required replay fields.',\n eventIndex,\n });\n }\n return;\n }\n Object.values(value).forEach((child) => collectPayloadReferenceIssues(child, eventIndex, issues));\n}\n\nfunction containsExternalPayloadReference(value: TUniversalValue): boolean {\n if (Array.isArray(value)) {\n return value.some((item) => containsExternalPayloadReference(item));\n }\n if (!isRecord(value)) return false;\n if (value.kind === 'external-payload') return true;\n return Object.values(value).some((child) => containsExternalPayloadReference(child));\n}\n\nfunction isRecord(value: TUniversalValue): value is Record<string, TUniversalValue> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","import { messageToHistoryEntry } from '@robota-sdk/agent-core';\n\nimport { resolveSessionLogExternalPayloads } from './external-payload-resolver.js';\nimport { decodeSessionLogEntries, SessionLogDecodeError } from './session-log-codec/index.js';\n\nimport type { ISessionLogPayloadResolutionOptions } from './external-payload-resolver.js';\nimport type {\n IExternalPayloadSource,\n ISessionLogSource,\n} from './external-payload-source-types.js';\nimport type { IHistoryEntry, TUniversalMessage } from '@robota-sdk/agent-core';\nimport type { ISessionLogEntry } from './session-log-entry-types.js';\n\nexport type { ISessionLogEntry } from './session-log-entry-types.js';\n\nexport interface ISessionReplayRecord {\n sessionId: string | undefined;\n cwd: string | undefined;\n createdAt: string | undefined;\n updatedAt: string | undefined;\n messages: TUniversalMessage[];\n history: IHistoryEntry[];\n backgroundTaskEvents: object[];\n backgroundJobGroupEvents: object[];\n memoryEvents: object[];\n}\n\nexport type ISessionLogLoadOptions = Omit<ISessionLogPayloadResolutionOptions, 'source'> & {\n readonly externalPayloadSource?: IExternalPayloadSource;\n};\n\nexport { validateSessionReplayLogEntries } from './session-log-validation.js';\nexport type {\n ISessionReplayValidationIssue,\n ISessionReplayValidationResult,\n} from './session-log-validation.js';\n\nexport function loadSessionLogEntries(\n source: ISessionLogSource,\n options: ISessionLogLoadOptions = {},\n): ISessionLogEntry[] {\n const text = source.readText();\n const parsedLines: { value: unknown; lineNumber: number }[] = [];\n if (text !== undefined) {\n text.split('\\n').forEach((line, index) => {\n if (line.trim().length === 0) return;\n try {\n parsedLines.push({ value: JSON.parse(line) as unknown, lineNumber: index + 1 });\n } catch (cause) {\n throw new SessionLogDecodeError(\n 'INVALID_JSON',\n [{ path: `line ${index + 1}`, message: 'expected valid JSON' }],\n { cause },\n );\n }\n });\n }\n const hydrated = resolveSessionLogExternalPayloads(\n parsedLines.map(({ value }) => value),\n {\n source: options.externalPayloadSource ?? source.externalPayloadSource,\n ...options,\n },\n );\n return decodeSessionLogEntries(hydrated, {\n lineNumbers: parsedLines.map(({ lineNumber }) => lineNumber),\n });\n}\n\nexport function replaySessionLogEntries(\n entries: readonly ISessionLogEntry[],\n): ISessionReplayRecord {\n const messages: TUniversalMessage[] = [];\n const history: IHistoryEntry[] = [];\n const auxiliaryEvents: IAuxiliaryReplayEvents = {\n backgroundTaskEvents: [],\n backgroundJobGroupEvents: [],\n memoryEvents: [],\n };\n let sessionId: string | undefined;\n let cwd: string | undefined;\n let createdAt: string | undefined;\n let updatedAt: string | undefined;\n\n for (const entry of decodeSessionLogEntries(entries)) {\n sessionId = sessionId ?? entry.sessionId;\n createdAt = createdAt ?? entry.timestamp;\n updatedAt = entry.timestamp;\n\n if (entry.event === 'session_init') {\n cwd = typeof entry.cwd === 'string' ? entry.cwd : cwd;\n }\n\n if (entry.event === 'history_mutation' && entry.mutation === 'append_message') {\n messages.push(entry.message);\n history.push(messageToHistoryEntry(entry.message));\n }\n\n collectAuxiliaryReplayEvent(entry, auxiliaryEvents);\n }\n\n return {\n sessionId,\n cwd,\n createdAt,\n updatedAt,\n messages,\n history,\n backgroundTaskEvents: auxiliaryEvents.backgroundTaskEvents,\n backgroundJobGroupEvents: auxiliaryEvents.backgroundJobGroupEvents,\n memoryEvents: auxiliaryEvents.memoryEvents,\n };\n}\n\ninterface IAuxiliaryReplayEvents {\n backgroundTaskEvents: object[];\n backgroundJobGroupEvents: object[];\n memoryEvents: object[];\n}\n\nfunction collectAuxiliaryReplayEvent(\n entry: ISessionLogEntry,\n auxiliaryEvents: IAuxiliaryReplayEvents,\n): void {\n if (entry.event === 'background_task_event') {\n pushObjectPayload(auxiliaryEvents.backgroundTaskEvents, entry, 'backgroundEvent', 'data');\n return;\n }\n if (entry.event === 'background_job_group_event') {\n pushObjectPayload(\n auxiliaryEvents.backgroundJobGroupEvents,\n entry,\n 'backgroundJobGroupEvent',\n 'data',\n );\n return;\n }\n if (entry.event === 'memory_event') {\n pushObjectPayload(auxiliaryEvents.memoryEvents, entry, 'memoryEvent', 'data');\n }\n}\n\nfunction getObjectPayload(entry: ISessionLogEntry, key: string): object | undefined {\n const value = entry[key];\n if (\n typeof value !== 'object' ||\n value === null ||\n Array.isArray(value) ||\n value instanceof Date\n ) {\n return undefined;\n }\n return value;\n}\n\nfunction pushObjectPayload(\n target: object[],\n entry: ISessionLogEntry,\n primaryKey: string,\n fallbackKey: string,\n): void {\n const payload = getObjectPayload(entry, primaryKey) ?? getObjectPayload(entry, fallbackKey);\n if (payload) target.push(payload);\n}\n","/**\n * Neutral checkpoint tree (branching time-travel).\n *\n * A pure, I/O-free branch-tree over opaque checkpoint-node ids — git-for-a-session with no file I/O,\n * no persistence, and no retention/prune policy. It lives beside the storage-neutral persistence\n * primitive (`SessionStore`/`IInteractiveSessionRecord`): same neutral-mechanism class (opaque payloads, no\n * product policy). The agent-framework checkpoint store consumes it over the existing one-way\n * `agent-framework → agent-session` edge; the reverse edge would be a cycle and is forbidden.\n *\n * Model: each node is `{ id, parentId }` (root has no `parentId`). Adding a checkpoint appends a child\n * of the active head. Forking moves the active head to a PAST node so the next append DIVERGES — the\n * original descendants stay reachable (a sibling branch). A \"branch\" is a leaf (a node with no\n * children); `switch` moves the active head to any existing node.\n */\n\nexport interface ICheckpointNode {\n id: string;\n /** Parent checkpoint id; absent for the root. */\n parentId?: string;\n}\n\nexport class CheckpointTree {\n private readonly nodes = new Map<string, ICheckpointNode>();\n /** Child adjacency (parentId → child ids in insertion order) for O(1) leaf/branch queries. */\n private readonly children = new Map<string, string[]>();\n private activeId: string | undefined;\n\n /**\n * Build a tree from explicit `{ id, parentId }` edges (e.g. reconstructed from persisted checkpoint\n * manifests). Nodes may arrive in any order — parents are linked by id. The active head is left at\n * the given `activeId` (or undefined). This is the delegation entry point a consumer store uses to\n * answer navigation queries (`listBranches`/`ancestors`) without the tree owning any persistence.\n */\n static fromNodes(nodes: ICheckpointNode[], activeId?: string): CheckpointTree {\n const tree = new CheckpointTree();\n // First pass: register every node so parent links resolve regardless of arrival order.\n for (const node of nodes) {\n if (tree.nodes.has(node.id)) throw new Error(`CheckpointTree: duplicate node \"${node.id}\"`);\n tree.nodes.set(node.id, node.parentId === undefined ? { id: node.id } : { ...node });\n }\n // Second pass: build child adjacency in the provided order.\n for (const node of nodes) {\n if (node.parentId !== undefined) {\n const siblings = tree.children.get(node.parentId) ?? [];\n siblings.push(node.id);\n tree.children.set(node.parentId, siblings);\n }\n }\n tree.activeId = activeId;\n return tree;\n }\n\n /**\n * Append a checkpoint as a child of the current active head and make it the new active head.\n * The first append (no active head) becomes the root. Ids must be unique.\n */\n addCheckpoint(id: string): void {\n if (this.nodes.has(id)) throw new Error(`CheckpointTree: duplicate checkpoint id \"${id}\"`);\n const parentId = this.activeId;\n this.nodes.set(id, parentId === undefined ? { id } : { id, parentId });\n if (parentId !== undefined) {\n const siblings = this.children.get(parentId) ?? [];\n siblings.push(id);\n this.children.set(parentId, siblings);\n }\n this.activeId = id;\n }\n\n /**\n * Fork from a PAST checkpoint: move the active head to `fromId` so the next `addCheckpoint` diverges\n * into a sibling branch, leaving `fromId`'s original descendants reachable. Returns `fromId`.\n */\n fork(fromId: string): string {\n if (!this.nodes.has(fromId)) throw new Error(`CheckpointTree: unknown checkpoint \"${fromId}\"`);\n this.activeId = fromId;\n return fromId;\n }\n\n /** Move the active head to an existing node (typically a branch leaf). */\n switch(nodeId: string): void {\n if (!this.nodes.has(nodeId)) throw new Error(`CheckpointTree: unknown checkpoint \"${nodeId}\"`);\n this.activeId = nodeId;\n }\n\n /** The current active head id (undefined for an empty tree). */\n activeLeaf(): string | undefined {\n return this.activeId;\n }\n\n /** All branch tips (leaf nodes with no children), in insertion order. */\n listBranches(): string[] {\n const leaves: string[] = [];\n for (const id of this.nodes.keys()) {\n if ((this.children.get(id)?.length ?? 0) === 0) leaves.push(id);\n }\n return leaves;\n }\n\n /**\n * The chain from `id` up to (and including) the root — nearest first. Empty if `id` is unknown. Only\n * REGISTERED nodes are included: a dangling `parentId` (edge to a node not in the tree — possible on\n * manifest drift/corruption) terminates the walk rather than emitting a phantom id, and so does an\n * edge back to a node already in the chain, which only damaged edges can form.\n */\n ancestors(id: string): string[] {\n const chain: string[] = [];\n const seen = new Set<string>();\n let cursor: string | undefined = id;\n while (cursor !== undefined && this.nodes.has(cursor) && !seen.has(cursor)) {\n chain.push(cursor);\n seen.add(cursor);\n cursor = this.nodes.get(cursor)!.parentId;\n }\n return chain;\n }\n\n /** Whether a checkpoint id exists in the tree. */\n has(id: string): boolean {\n return this.nodes.has(id);\n }\n\n /** The number of checkpoints in the tree. */\n get size(): number {\n return this.nodes.size;\n }\n}\n"],"mappings":"s+CACA,SAAgB,GAA4B,EAAuB,CACjE,IACI,OAAO,GAAW,UAAY,GAAoB,OAAO,GAAW,aACtE,SAAU,GACV,OAAO,EAAO,MAAS,WAGvB,MADA,QAAa,QAAQ,CAAM,CAAC,CAAC,UAAY,IAAA,EAAS,EACxC,MACR,4FACF,CAEJ,CCIA,SAAgB,GAAqB,EAAsB,CACzD,GAAI,OAAO,GAAQ,UAAY,EAAI,SAAW,EAC5C,MAAU,MACR,sOAGF,EAKF,GAAI,CAAC,EAAe,CAAG,EACrB,MAAU,MACR,6CAA6C,KAAK,UAAU,CAAG,EAAE,sKAGnE,EAEF,OAAO,CACT,CC3BA,IAAa,GAAb,cAAsC,KAAM,CAE1C,YAAuB,GAEvB,YAAY,EAAiB,CAC3B,MAAM,CAAO,EACb,KAAK,KAAO,kBACd,CACF,EAqBa,GAAb,KAAuB,CACrB,WAA6C,KAC7C,WAAoC,QAAQ,QAAQ,EACpD,OAGA,IAAI,SAAyB,CAC3B,OAAO,KAAK,UACd,CAWA,OAAyB,CACvB,GAAI,KAAK,aAAe,KACtB,MAAM,IAAI,GACR,+KAEF,EAMF,MAJA,MAAK,WAAa,IAAI,QAAe,GAAY,CAC/C,KAAK,OAAS,CAChB,CAAC,EACD,KAAK,WAAa,IAAI,gBACf,KAAK,UACd,CASA,QAAQ,EAAmC,CACrC,KAAK,aAAe,IACtB,KAAK,WAAa,KAClB,KAAK,SAAS,EACd,KAAK,OAAS,IAAA,GAElB,CAgBA,OAAc,CACZ,KAAK,YAAY,MAAM,CACzB,CAEA,WAAqB,CACnB,OAAO,KAAK,aAAe,IAC7B,CACF,ECtFsB,GAAtB,KAAkC,CAgBhC,IAEA,YAAsB,EAAa,CACjC,KAAK,IAAM,GAAqB,CAAG,CACrC,CAMA,UAA+B,IAAI,GACnC,qBAAwC,IAAI,IAE5C,mBAAqC,CACnC,OAAO,KAAK,cACd,CAGA,kBAAkB,EAA6B,CAC7C,IAAK,IAAM,KAAS,KAAK,qBAAsB,EAAM,CAAI,EACzD,KAAK,eAAiB,CACxB,CAGA,uBAAuB,EAAoD,CAEzE,OADA,KAAK,qBAAqB,IAAI,CAAK,MACtB,KAAK,qBAAqB,OAAO,CAAK,CACrD,CAGA,mBAA4B,CAC1B,OAAO,KAAK,cACd,CAOA,kBAAkB,EAAkB,CAClC,KAAK,eAAiB,CACxB,CAGA,6BAAuC,CACrC,OAAO,KAAK,wBACd,CAGA,4BAA4B,EAAwB,CAClD,KAAK,yBAA2B,CAClC,CAEA,cAAuB,CACrB,OAAO,KAAK,SACd,CASA,QAAiB,CACf,OAAO,KAAK,GACd,CAEA,kBAA2B,CACzB,OAAO,KAAK,aACd,CAUA,oBAAoB,EAA0B,CAC5C,KAAK,mBAAmB,EACxB,KAAK,cAAgB,EACrB,KAAK,MAAM,mBAAmB,CAAU,CAC1C,CASA,MAAM,kBAAkB,EAKN,CAKhB,MAAM,KAAK,MAAM,YAAY,EAC7B,IAAM,EAAY,EAAQ,OAAS,KAAK,MAExC,KAAK,MAAM,SAAS,CAClB,SAAU,KAAK,WAAW,KAC1B,MAAO,EACP,GAAI,EAAQ,SAAW,IAAA,IAAa,CAAE,OAAQ,EAAQ,MAAO,EAC7D,GAAI,EAAQ,cAAgB,IAAA,IAAa,CAAE,YAAa,EAAQ,WAAY,EAC5E,GAAI,EAAQ,kBAAoB,IAAA,IAAa,CAAE,UAAW,EAAQ,eAAgB,CACpF,CAAC,EACD,KAAK,eAAe,CAAS,CAC/B,CAEA,eAAyB,EAAqB,CAC5C,KAAK,MAAQ,EACb,KAAK,eAAe,SAAS,CAAK,EAClC,KAAK,uBAAuB,SAAS,CAAK,CAC5C,CAGA,gBAAwC,CAGtC,IAAM,EACJ,KAAK,MACL,SACF,GAAI,IAAa,IAAA,GAAW,MAAO,OACnC,GAAI,CACF,OAAO,EAAS,KAAK,KAAK,KAAK,CAAC,CAAC,QAAU,MAC7C,OAAS,EAAO,CAEd,GAAI,aAAiB,OAAS,YAAY,KAAK,EAAM,OAAO,EAAG,MAAO,OACtE,MAAM,CACR,CACF,CAGA,MAAM,sBAAyB,EAAsB,EAAyC,CAC5F,IAAM,EAAW,KAAK,eAAe,EACrC,MAAM,KAAK,kBAAkB,CAAE,QAAO,CAAC,EACvC,GAAI,CACF,OAAO,MAAM,EAAU,CACzB,QAAU,CACR,MAAM,KAAK,kBAAkB,CAAE,OAAQ,CAAS,CAAC,CACnD,CACF,CAYA,MAAM,eAAe,EAA6B,CAChD,MAAM,KAAK,MAAM,oBAAoB,CAAE,MAAK,CAAC,CAC/C,CAEA,gBAAgC,CAC9B,OAAO,KAAK,WACd,CAEA,iBAA0B,CACxB,OAAO,KAAK,YACd,CAWA,qBAAqB,EAGZ,CACP,KAAK,mBAAmB,qBAAqB,CAAM,CACrD,CAOA,oBAAyE,CACvE,IAAM,EAAQ,KAAK,mBAAmB,uBAAuB,EAC7D,MAAO,CAAE,MAAO,CAAC,GAAG,EAAM,KAAK,EAAG,KAAM,CAAC,GAAG,EAAM,IAAI,EAAG,IAAK,CAAC,GAAG,EAAM,GAAG,CAAE,CAC/E,CAEA,wBAAmC,CACjC,OAAO,KAAK,mBAAmB,uBAAuB,CACxD,CAQA,oBACE,EACA,EACA,EACkB,CAClB,OAAO,KAAK,mBAAmB,uBAAuB,EAAU,EAAgB,CAAM,CACxF,CAGA,qBAA+B,EAA6B,CAC1D,GAAI,IAAS,QAAU,CAAC,KAAK,mBAAmB,wBAAwB,EACtE,MAAU,MAAM,sEAAsE,CAE1F,CAOA,sBAAsB,EAA8C,CAClE,OAAO,KAAK,mBAAmB,mBAAmB,CAAK,CACzD,CAGA,4BAA2D,CACzD,OAAO,KAAK,mBAAmB,iBAAiB,CAClD,CAEA,0BAAiC,CAC/B,KAAK,mBAAmB,yBAAyB,CACnD,CAGA,OAAc,CACZ,KAAK,UAAU,MAAM,CACvB,CAEA,WAAqB,CACnB,OAAO,KAAK,UAAU,UAAU,CAClC,CAEA,iBAAuC,CACrC,OAAO,KAAK,eAAe,gBAAgB,CAC7C,CAGA,wBAA+B,CAC7B,KAAK,eAAe,kBAAkB,KAAK,MAAM,WAAW,CAAC,CAC/D,CAEA,yBAAiD,CAC/C,OAAO,KAAK,eAAe,wBAAwB,CACrD,CAEA,wBAAwB,EAAiC,CACvD,KAAK,eAAe,wBAAwB,CAAS,CACvD,CAEA,YAAkC,CAChC,OAAO,gBAAgB,KAAK,MAAM,WAAW,CAAC,CAChD,CAEA,gBAAkC,CAChC,OAAO,gBAAgB,KAAK,MAAM,eAAe,CAAC,CACpD,CAEA,sBAAkF,CAChF,IAAI,EAAc,EACd,EAAe,EACf,EAAQ,GACZ,IAAK,IAAM,KAAS,KAAK,eAAe,EAAG,CACzC,GAAI,EAAM,WAAa,SAAW,EAAM,OAAS,gBAAiB,SAClE,IAAM,EAAO,EAAM,KACnB,GAAe,GAAM,cAAgB,EACrC,GAAgB,GAAM,kBAAoB,EAC1C,EAAQ,EACV,CACA,OAAO,EAAQ,CAAE,cAAa,cAAa,EAAI,IAAA,EACjD,CAEA,YAAqB,CACnB,OAAO,KAAK,KACd,CASA,uBAAuC,CACrC,OAAO,KAAK,MAAM,sBAAsB,CAC1C,CAGA,aAA2B,CACzB,OAAO,KAAK,UACd,CAEA,eAAwB,CACtB,OAAO,KAAK,WAAW,IACzB,CAGA,gBAAgB,EAA4B,CAC1C,KAAK,mBAAmB,EACxB,KAAK,MAAM,gBAAgB,CAAK,CAClC,CAGA,cACE,EACA,EACA,EACM,CACN,KAAK,mBAAmB,EACxB,KAAK,MAAM,cAAc,EAAM,EAAS,CAAO,CACjD,CAMA,iBAAiB,EAA8B,CAC7C,KAAK,mBAAmB,EACxB,KAAK,MAAM,iBAAiB,CAAG,CACjC,CAEA,cAAqB,CACnB,KAAK,mBAAmB,EACxB,KAAK,MAAM,aAAa,EACxB,KAAK,eAAe,MAAM,CAC5B,CACF,ECtWA,SAAS,GAAO,EAA0B,CACxC,OAAO,KAAK,UAAU,OAAO,GAAY,SAAW,EAAW,GAAW,EAAG,CAC/E,CAEA,SAAS,EAAc,EAAoC,CACzD,IAAM,GAAgB,EAAQ,OAAS,CAAC,EAAA,CAAG,QAAS,GAAS,CAC3D,IAAM,EAAc,GAAyB,CAAI,EACjD,OAAO,IAAgB,IAAA,GAAY,CAAC,EAAI,CAAC,CAAW,CACtD,CAAC,EACD,OAAO,EAAa,OAAS,EACzB,KAAK,UAAU,CAAE,QAAS,EAAQ,QAAS,cAAa,CAAC,EACzD,GAAO,EAAQ,OAAO,CAC5B,CAMA,SAAS,GAAU,EAAoC,CACrD,IAAM,EAAO,GAAa,CAAO,EACjC,OAAO,EAAO,cAAc,KAAK,UAAU,GAAoB,CAAI,CAAC,EAAE,GAAK,MAC7E,CAEA,SAAS,GAAc,EAAsC,CAC3D,OAAQ,EAAQ,KAAhB,CACE,IAAK,OACH,MAAO,CAAC,GAAG,GAAU,CAAO,EAAE,IAAI,EAAc,CAAO,GAAG,EAC5D,IAAK,YAAa,CAChB,IAAM,EAAkB,CAAC,EACrB,EAAQ,UAAY,MAAQ,EAAQ,UAAY,IAClD,EAAM,KAAK,cAAc,EAAc,CAAO,GAAG,EAEnD,IAAK,IAAM,KAAQ,EAAQ,WAAa,CAAC,EACvC,EAAM,KACJ,uBAAuB,KAAK,UAAU,EAAK,SAAS,IAAI,EAAE,IAAI,KAAK,UAAU,EAAK,EAAE,EAAE,KAAK,GAAO,EAAK,SAAS,SAAS,GAC3H,EAEF,OAAO,EAAM,OAAS,EAAI,EAAQ,CAAC,eAAe,CACpD,CACA,IAAK,OACH,MAAO,CACL,cAAc,EAAQ,KAAO,IAAI,KAAK,UAAU,EAAQ,IAAI,IAAM,GAAG,IAAI,KAAK,UAAU,EAAQ,UAAU,EAAE,KAAK,EAAc,CAAO,GACxI,EACF,IAAK,SACH,MAAO,CAAC,WAAW,EAAc,CAAO,GAAG,CAC/C,CACF,CAOA,SAAgB,GAA0B,EAAiD,CACzF,OAAO,EAAQ,IAAK,GAAY,GAAc,CAAO,CAAC,CAAC,KAAK;CAAI,CAAC,CACnE,CCjEA,SAAgB,EACd,EACA,EAC8B,CAC9B,GAAI,CAAC,MAAM,QAAQ,CAAS,EAC1B,MAAU,MACR,GAAG,EAAM,mNAGX,EAEF,OAAO,CACT,CCUA,IAAa,EAAb,cAAqC,KAAM,CACzC,YAAY,EAAiB,CAC3B,MAAM,CAAO,EACb,KAAK,KAAO,iBACd,CACF,EAQA,MAAa,GAA4B,CACvC,8DACA,uCACA,oDACA,mEACA,uCACA,qFACF,CAAC,CAAC,KAAK;CAAI,EAkBX,IAAa,GAAb,KAAoC,CAClC,UACA,IACA,MACA,MACA,oBACA,WACA,kBAEA,YAAY,EAA6B,CACvC,KAAK,UAAY,EAAQ,UACzB,KAAK,IAAM,EAAQ,IACnB,KAAK,MAAQ,EAAQ,MACrB,KAAK,MAAQ,EAAQ,MACrB,KAAK,oBAAsB,EAAQ,oBACnC,KAAK,WAAa,EAAQ,WAC1B,KAAK,kBAAoB,EACvB,EAAQ,kBACR,wBACF,CACF,CAEA,SAAS,EAAqB,CAC5B,KAAK,MAAQ,CACf,CAgBA,MAAM,QACJ,EACA,EACA,EACA,EACA,EAA2B,SAC3B,EACA,EACiB,CAUjB,GANA,GAAQ,eAAe,EAMnB,EAAQ,SAAW,EACrB,MAAM,IAAI,EACR,8FACF,EAIF,IAAM,EAA2B,CAC/B,WAAY,KAAK,UACjB,IAAK,KAAK,IACV,gBAAiB,aACjB,SACF,EACA,MAAM,EACJ,KAAK,MACL,aACA,EACA,KAAK,kBACL,CACF,EAGA,IAAM,EAAgB,KAAK,sBAAsB,EAAS,CAAY,EAGhE,EAAS,EAAS,EAClB,EAAc,EAAS,EACzB,EAAQ,EAAS,oBAAoB,KAAK,MAAO,CAAW,GAAK,CACnE,SAAU,EAAS,KACnB,MAAO,KAAK,KACd,EACM,EAAiB,MAAM,GAC3B,MAAO,EAAU,IAAY,CAC3B,GAAI,CACF,OAAO,MAAM,EAAS,KAAK,EAAU,CAAO,CAC9C,OAAS,EAAO,CAEd,MADI,GAAQ,QAAe,GAAiB,uBAAwB,CAAK,EACnE,CACR,CACF,EACA,CACE,CACE,GAAI,EAAS,EACb,KAAM,OACN,QAAS,EACT,MAAO,WACP,UAAW,IAAI,IACjB,CACF,EACA,CACE,MAAO,KAAK,MACZ,cAEA,gBAAmB,CAAC,EACpB,gBAAkB,GAAW,CAC3B,EAAQ,CAAE,GAAG,EAAO,EAAG,CACzB,EACA,WAAY,OAEZ,sBAAuB,GACvB,GAAI,IAAW,IAAA,GAAyB,CAAC,EAAd,CAAE,QAAO,CACtC,EACA,EACI,CACE,QAAS,EACT,cACA,SACA,WAAc,CAAE,WAAY,EAAM,SAAU,QAAS,EAAM,KAAM,EACnE,EACA,IAAA,EACN,EAKA,GADA,GAAQ,eAAe,EACnB,OAAO,EAAe,SAAY,UAAY,EAAe,QAAQ,KAAK,IAAM,GAClF,MAAM,IAAI,EACR,oDAAoD,EAAS,KAAK,iBAAiB,OAAO,EAAe,QAAQ,4CACnH,EAGF,OAAO,EAAe,OACxB,CAGA,sBAA8B,EAA8B,EAA+B,CACzF,IAAM,EAAmB,GAAgB,KAAK,qBAAuB,GAC/D,EAAqB,EAAmB,wBAAwB,EAAiB,IAAM,GAEvF,EAAmB,GAA0B,CAAO,CAAC,CAAC,KAAK;CAAI,EAErE,MAAO,CACL,KAAK,YAAc,GACnB,EACA,GACA,gBACA,CACF,CAAC,CAAC,KAAK;CAAI,CACb,CACF,ECzNA,MAGa,GAAyB,KAItC,IAAa,GAAb,KAAkC,CAChC,kBAA4B,EAC5B,iBACA,yBACA,qBAEA,YACE,EACA,EACA,EACA,CACA,KAAK,yBAA2B,EAChC,KAAK,iBAAmB,GAAoB,GAAsB,CAAK,EACvE,KAAK,qBAAuB,GAA8B,CAAoB,CAChF,CAGA,iBAAuC,CACrC,IAAM,EAAiB,KAAK,IAC1B,IACC,KAAK,kBAAoB,KAAK,iBAAoB,GACrD,EACA,MAAO,CACL,UAAW,KAAK,iBAChB,WAAY,KAAK,kBACjB,eAAgB,KAAK,MAAM,EAAiB,GAAO,EAAI,IACvD,oBAAqB,KAAK,OAAO,IAAU,GAAkB,GAAO,EAAI,GAC1E,CACF,CAGA,kBAAkB,EAAgD,CAOhE,OANI,KAAK,uBAAyB,KAGf,EACf,GAAkC,CAAe,CAAC,CAAC,WACnD,KAAK,oBACY,KAAK,iBAAmB,KAAK,oBACpD,CAGA,SAAS,EAAqB,CAC5B,KAAK,iBAAmB,KAAK,0BAA4B,GAAsB,CAAK,CACtF,CAGA,yBAAiD,CAC/C,OAAO,KAAK,oBACd,CAGA,wBAAwB,EAAmD,CACzE,KAAK,qBAAuB,GAA8B,CAAoB,CAChF,CAWA,kBAAkB,EAAoC,CACpD,KAAK,kBAAoB,GAAkC,CAAO,CAAC,CAAC,UACtE,CAGA,OAAc,CACZ,KAAK,kBAAoB,CAC3B,CACF,EAEA,SAAS,GACP,EACuB,CACvB,GAAI,IAAyB,IAAA,GAC3B,OAAO,GAET,GAAI,IAAyB,GAC3B,MAAO,GAET,GACE,CAAC,OAAO,SAAS,CAAoB,GACrC,GAAwB,GACxB,EAAuB,EAEvB,MAAU,WAAW,qEAAqE,EAE5F,OAAO,CACT,CCnFA,eAAe,GACb,EACA,EAC4B,CAC5B,GAAI,IAAW,IAAA,GAAW,OAAO,EACjC,GAAI,EAAO,QAAS,MAAO,GAE3B,IAAI,EACJ,GAAI,CACF,OAAO,MAAM,QAAQ,KAAK,CACxB,EACA,IAAI,QAA4B,GAAY,CAC1C,MAAsB,EAAQ,EAAK,EACnC,EAAO,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,CAC1D,CAAC,CACH,CAAC,CACH,QAAU,CACJ,IAAY,IAAA,IAAW,EAAO,oBAAoB,QAAS,CAAO,CACxE,CACF,CAkBA,SAAS,GAAkB,EAA6C,CAOtE,OANI,IAAW,gBACN,CAAE,QAAS,GAAM,mBAAoB,GAAM,mBAAoB,EAAM,EAE1E,IAAW,gBACN,CAAE,QAAS,GAAM,mBAAoB,GAAM,mBAAoB,EAAK,EAEtE,CAAE,QAAS,IAAW,GAAM,mBAAoB,GAAO,mBAAoB,EAAM,CAC1F,CAgCA,eAAsB,GAAe,EAAsD,CACzF,IAAM,EAA2B,CAC/B,QAAS,GACT,mBAAoB,GACpB,mBAAoB,EACtB,EACA,GAAI,EAAQ,eACV,MAAO,CAAE,QAAS,GAAM,mBAAoB,GAAO,mBAAoB,EAAM,EAI/E,GAAI,EAAQ,QAAQ,UAAY,GAAM,OAAO,EAO7C,IAAM,EAAiC,CACrC,GAAI,EAAQ,OAAS,CAAE,OAAQ,EAAQ,MAAO,EAAI,CAAC,EACnD,GAAI,EAAQ,cAAgB,IAAA,GAAmD,CAAC,EAAxC,CAAE,YAAa,EAAQ,WAAY,CAC7E,EACM,EAAa,EAAQ,SAAW,IAAA,IAAa,EAAQ,cAAgB,IAAA,GACrE,EAAS,EAAQ,YAEjB,EACI,EAAQ,QAAS,EAAQ,SAAU,EAAQ,SAAU,CAAO,EAC5D,EAAQ,QAAS,EAAQ,SAAU,EAAQ,QAAQ,EACzD,EAAQ,gBAAkB,EAAQ,aAE9B,EAAQ,eAAgB,EAAQ,SAAW,EAAQ,SAAU,EAAQ,QAAQ,EAC/E,IAAA,GAGN,OADI,IAAW,IAAA,GAAkB,EAC1B,GAAkB,MAAM,GAAU,EAAO,EAAG,EAAQ,MAAM,CAAC,CACpE,CCxIA,MAAM,EAAO,0BAEb,SAAgB,GAAwB,EAAsD,CAC5F,OAAO,GAAc,MAAM,KAAM,GAAS,EAAK,QAAQ,OAAS,CAAI,GAAK,EAC3E,CAGA,eAAsB,GACpB,EAQA,EACA,EAC8B,CAC9B,IAAM,EAAQ,GAAc,MAAM,OAAQ,GAAS,EAAK,QAAQ,OAAS,CAAI,GAAK,CAAC,EACnF,GAAI,CAAC,GAAmB,CAAC,EAAM,OAAQ,OACvC,GAAI,CAAC,GAAgB,EAAa,OAAO,WAAa,EAAQ,SAC5D,MAAM,IAAI,EACR,6BACA,6DACF,EACF,IAAM,EAAM,EAAQ,iBAAiB,EAAQ,GAAG,EAChD,IAAK,GAAM,CAAE,aAAa,EACxB,GACE,EAAQ,KAAK,UAAY,GACzB,EAAQ,KAAK,YAAc,EAAQ,WACnC,EAAQ,KAAK,MAAQ,GACrB,EAAQ,KAAK,WAAa,EAAQ,SAElC,MAAM,IAAI,EACR,6BACA,uDACF,EAEJ,IAAM,EAAS,MAAM,EAAa,QAAQ,CACxC,OACA,KAAM,CACJ,QAAS,EACT,UAAW,EAAQ,UACnB,MACA,SAAU,EAAQ,SAElB,UAAW,gBAAgB,EAAQ,QAAQ,CAC7C,CACF,CAAC,EACD,GAAI,OAAO,KAAK,CAAM,CAAC,CAAC,SAAW,GAAK,OAAO,EAAO,UAAa,UACjE,MAAM,IAAI,EACR,6BACA,iEACF,EACF,OAAO,EAAO,QAChB,CCvBA,MAAa,GAA0B,EAC1B,GAAoB,GAQjC,SAAS,GAAQ,EAAkB,EAA6B,CAC9D,MAAO,GAAG,EAAS,QAAQ,KAAK,UAAU,CAAQ,GACpD,CAEA,IAAa,GAAb,KAA0B,CAOK,WAN7B,YAAsB,EACtB,MAAgB,EAChB,OAAiB,GAEjB,QAA2B,IAAI,IAE/B,YAAY,EAAoD,CAAnC,KAAA,WAAA,CAAoC,CAGjE,UAAoB,CAClB,OAAO,KAAK,MACd,CAGA,QAAe,CACb,KAAK,OAAS,GACd,KAAK,YAAc,CACrB,CAGA,WAAW,EAAkB,EAA2B,CACtD,KAAK,QAAQ,IAAI,GAAQ,EAAU,CAAQ,CAAC,CAC9C,CAGA,UAAU,EAAkB,EAA8B,CACxD,OAAO,KAAK,QAAQ,OAAO,GAAQ,EAAU,CAAQ,CAAC,CACxD,CAEA,MAAM,MAAM,EAAuB,EAAmD,CACpF,IAAI,EACJ,GAAI,CACF,EAAU,MAAM,KAAK,WAAW,SAAS,EAAM,CAAM,CACvD,MAAQ,CAEN,EAAU,IAAA,EACZ,CACA,GAAI,IAAY,IAAA,GAAW,CAErB,GAAQ,UAAY,KAAM,KAAK,aAAe,GAClD,IAAM,EAAQ,KAAK,aAAA,EAEnB,OADI,IAAO,KAAK,OAAS,IAClB,CACL,KAAM,WACN,OAAQ,oBACR,QACE,uHAEC,EAAQ,qDAAuD,GACpE,CACF,CACA,GAAI,EAAQ,WAAa,QAEvB,MADA,MAAK,YAAc,EACZ,CAAE,KAAM,OAAQ,EAEzB,KAAK,aAAe,EACpB,KAAK,OAAS,EACd,IAAM,EAAQ,KAAK,aAAA,GAA0C,KAAK,OAAA,GAGlE,OAFI,KAAK,OAAA,KAA4B,KAAK,MAAQ,GAC9C,IAAO,KAAK,OAAS,IAClB,CACL,KAAM,QACN,OAAQ,EAAQ,OAChB,QACE,wCAAwC,EAAQ,OAAO,yEAEtD,EAAQ,2EAA6E,GAC1F,CACF,CACF,EC3FA,SAAS,GAAY,EAAsB,CACzC,IAAM,EAAU,EAAK,QAAQ,MAAO,GAAG,EACjC,EAAM,EAAQ,YAAY,GAAG,EAGnC,OAFI,EAAM,EAAU,IAChB,IAAQ,EAAU,IACf,EAAQ,MAAM,EAAG,CAAG,CAC7B,CAGA,SAAS,GAAc,EAAc,EAAmC,CACtE,OAAQ,EAAR,CACE,IAAK,OAAQ,CACX,IAAM,EAAM,GAAY,CAAK,EAC7B,OAAO,IAAQ,IAAM,MAAQ,GAAG,EAAI,IACtC,CACA,IAAK,MACH,GAAI,CACF,IAAM,EAAM,IAAI,IAAI,CAAK,EACzB,MAAO,GAAG,EAAI,SAAS,IAAI,EAAI,KAAK,IACtC,MAAQ,CAGN,OAAO,CACT,CAEF,IAAK,UAAW,CACd,IAAM,EAAQ,EAAM,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,GACxC,OAAO,EAAQ,GAAG,EAAM,IAAM,IAAA,EAChC,CACA,QACE,MACJ,CACF,CAGA,SAAgB,GAAgB,EAAkB,EAA6B,CAC7E,IAAM,EAAW,EAAyB,CAAQ,CAAC,CAAC,SACpD,GAAI,IAAa,IAAA,GAAW,OAAO,EACnC,IAAM,EAAQ,EAAS,EAAS,KAChC,GAAI,OAAO,GAAU,UAAY,IAAU,GAAI,OAAO,EACtD,IAAM,EAAS,GAAc,EAAS,KAAM,CAAK,EACjD,OAAO,IAAW,IAAA,GAAY,EAAW,GAAG,EAAS,GAAG,EAAO,EACjE,CClDA,SAAgB,EACd,EACA,EACA,EACiB,CACjB,IAAM,EAAW,EAAyB,CAAQ,CAAC,CAAC,SACpD,GAAI,IAAa,IAAA,IAAa,EAAS,OAAS,OAAQ,OAAO,EAC/D,IAAM,EAAQ,EAAW,EAAS,KAClC,GAAI,OAAO,GAAU,UAAY,IAAU,IAAM,EAAe,CAAK,EAAG,OAAO,EAC/E,IAAM,EAAW,GAAY,EAAK,CAAK,EAGvC,OADK,EAAe,CAAQ,EACrB,CAAE,GAAG,GAAa,EAAS,KAAM,CAAS,EADX,CAExC,CCwJA,SAAgB,EACd,EACA,EAgBA,EACoB,CACpB,MAAO,CACL,QAAS,GACT,UACA,QACA,KAAM,KAAK,UAAU,GAAQ,CAAE,QAAS,GAAO,OAAQ,GAAI,OAAM,CAAC,EAClE,SAAU,CAAC,CACb,CACF,CA0BA,SAAgB,GACd,EACA,EACA,EACoB,CACpB,IAAM,EAAU,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EAQrE,OAPA,IAAW,CACT,KAAM,MACN,SAAU,EAAM,SAChB,SAAU,EAAM,SAChB,QAAS,GACT,YAAa,EAAM,WACrB,CAAC,EACM,EAAY,QAAS,CAAO,CACrC,CAQA,MAAa,GAA2B,EACtC,SACA,0DACF,ECnPMA,GAAS,EAAa,iBAAiB,EAQ7C,SAAgB,GAAmB,EAAkC,CAGnE,GAFI,GAAsB,CAAM,GAC5B,OAAO,EAAO,MAAS,UACvB,EAAO,KAAK,QAAA,IAAiC,OAAO,EAExD,IAAM,EAAY,KACZ,EAAO,EAAO,KAAK,UAAU,EAAG,CAAS,EACzC,EAAO,EAAO,KAAK,UAAU,EAAO,KAAK,OAAS,CAAS,EAE3D,EAAgB,GAAG,EAAK,6BADT,EAAO,KAAK,OACuC,eAAe,EAAE,uCAAuC,EAAU,eAAe,EAAE,iBAAiB,IAE5K,MAAO,CAAE,GAAG,EAAQ,KAAM,CAAc,CAC1C,CAGA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACA,EACY,CACZ,MAAO,CACL,WAAY,EACZ,MACA,gBAAiB,aACjB,UAAW,EACX,WAAY,EACZ,GAAI,IAAmB,IAAA,IAAa,CAAE,gBAAiB,CAAe,EACtE,GAAI,IAAmB,IAAA,IAAa,CAAE,gBAAiB,CAAe,CACxE,CACF,CA2BA,eAAsB,EACpB,EACA,EACA,EACA,EAC8B,CAC9B,IAAI,EAAQ,EACR,EACE,EAAO,IAAI,IAAI,CAAC,KAAK,UAAU,EAAM,UAAU,CAAC,CAAC,EACvD,IAAK,IAAI,EAAO,EAAG,EAAO,EAAG,IAAQ,CACnC,IAAM,EAAY,MAAM,GAAqB,EAAO,EAAO,EAAmB,CAAY,EAC1F,GAAI,EAAU,SAAW,KAAM,MAAO,CAAE,QAAS,EAAU,MAAO,EAClE,GAAM,CAAE,qBAAoB,gBAAiB,EAAU,WACvD,GAAI,IAAiB,IAAA,GAAW,CAC9B,GAA6B,OAAO,GAAiB,WAAjD,GAA6D,MAAM,QAAQ,CAAY,EACzF,MAAO,CACL,QAAS,EAAY,eAAgB,4CAA4C,CACnF,EACF,IAAM,EAAa,EACjB,EAAM,WAAa,GACnB,EACA,EAAM,GACR,EACM,EAAU,KAAK,UAAU,CAAU,EACzC,GAAI,IAAY,KAAK,UAAU,EAAM,UAAU,EAAG,CAChD,GAAI,EAAK,IAAI,CAAO,EAAG,MACvB,EAAK,IAAI,CAAO,EAChB,EAAY,EACZ,EAAQ,CAAE,GAAG,EAAO,WAAY,CAAuC,EACvE,QACF,CACF,CACA,MAAO,CACL,QAAS,KACT,GAAI,IAAc,IAAA,GAAY,CAAC,EAAI,CAAE,WAAY,CAAU,EAC3D,GAAI,IAAuB,SAAW,IAAuB,MACzD,CAAE,SAAU,CAAmB,EAC/B,CAAC,CACP,CACF,CACA,IAAM,EAAS,2EACf,MAAO,CAAE,QAAS,EAAY,eAAgB,EAAQ,CAAE,QAAS,GAAM,QAAO,CAAC,CAAE,CACnF,CAEA,eAAe,GACb,EACA,EACA,EACA,EACsE,CACtE,IAAM,EAAa,MAAM,EACvB,EACA,aACA,EACA,EACA,CACF,EACA,GAAI,EAAW,QAAS,CAItB,IAAM,EAAS,EAAW,QAAU,kBACpC,MAAO,CAAE,OAAQ,EAAY,eAAgB,EAAQ,CAAE,QAAS,GAAM,QAAO,CAAC,EAAG,YAAW,CAC9F,CAiBA,GAAI,GAAY,YAAY,EAAG,CAI7B,IAAM,EAAW,EAAW,OACtB,EAAU,IAAW,GAGrB,EAAe,EAAW,kBAAoB,CAAC,EAiB/C,EACJ,EAAa,OAAS,EAClB,6CAA6C,EAAa,KAAK,IAAI,EAAE,gLAGrE,GAEN,GAAI,IAAa,IAAA,IAAa,IAAY,IAAA,GAAW,CAGnD,IAAM,EAAS,EAAS,OAAS,EAC3B,EACJ,4BAA4B,EAAQ,KAAK,YAAY,EAAQ,OAAO,KAAK,EAAQ,OAAO,IAGvF,EAAS,EAAI,MAAM,EAAO,wBAA0B,KAKpD,IAAuB,GAAmD,GAA9C,uBAAuB,KACtD,MAAO,CAAE,OAAQ,EAAY,eAAgB,EAAQ,CAAE,QAAS,GAAM,QAAO,CAAC,EAAG,YAAW,CAC9F,CAEA,GAAI,IAAuB,GACzB,MAAO,CACL,OAAQ,EAAY,eAAgB,EAAoB,CACtD,QAAS,GACT,OAAQ,CACV,CAAC,EACD,YACF,CAEJ,CAEA,MAAO,CAAE,OAAQ,KAAM,YAAW,CACpC,CAGA,SAAgB,GACd,EACA,EACA,EACA,EACA,EACM,CAMN,EACE,EACA,cACA,CAPA,GAAG,EACH,gBAAiB,cACjB,YAAa,OAAO,EAAO,MAAS,SAAW,EAAO,KAAO,KAAK,UAAU,EAAO,IAAI,CAK3E,EACZ,EACA,CACF,CAAC,CAAC,MAAO,GAAUA,GAAO,KAAK,cAAe,CAAE,OAAM,CAAC,CAAC,CAC1D,CCvNA,SAAS,GAAW,EAAkB,EAAyC,CAC7E,IAAM,EAAM,EAAyB,CAAQ,CAAC,CAAC,UAAU,IACzD,GAAI,IAAQ,IAAA,GAAW,OACvB,IAAM,EAAQ,EAAS,GACnB,UAAO,GAAU,SACrB,OAAO,EAAM,OAAS,IAClB,GAAG,EAAM,MAAM,EAAG,GAAsB,EAAE,GAC1C,CACN,CAEA,IAAa,GAAb,KAAiC,CAMZ,SACA,IANnB,QAAgD,CAAC,EAEjD,MAAsE,CAAC,EAEvE,YACE,EAAoC,GACpC,EAAqC,KAAK,IAC1C,CAFiB,KAAA,SAAA,EACA,KAAA,IAAA,CAChB,CAEH,OACE,EACA,EACA,EACA,EACM,CACN,IAAM,EAAW,GAAW,EAAU,CAAQ,EAC9C,KAAK,QAAQ,QAAQ,CACnB,WACA,GAAI,IAAa,IAAA,GAA2B,CAAC,EAAhB,CAAE,UAAS,EACxC,SACA,GAAI,IAAW,IAAA,GAAyB,CAAC,EAAd,CAAE,QAAO,EACpC,GAAI,KAAK,IAAI,CACf,CAAC,EACD,KAAK,MAAM,QAAQ,CAAE,WAAU,UAAS,CAAC,EACrC,KAAK,MAAM,OAAS,KAAK,WAAU,KAAK,MAAM,OAAS,KAAK,UAC5D,KAAK,QAAQ,OAAS,KAAK,WAAU,KAAK,QAAQ,OAAS,KAAK,SACtE,CAEA,MAAqC,CACnC,MAAO,CAAC,GAAG,KAAK,OAAO,CACzB,CAGA,OAAO,EAAsE,CAC3E,OAAO,KAAK,MAAM,EACpB,CACF,EC/CA,MAAMC,GAAS,EAAa,eAAe,EAE3C,IAAM,EAAN,cAAwC,KAAM,CACvB,OAArB,YAAY,EAA8B,CACxC,MAAM,8CAA8C,EADjC,KAAA,OAAA,CAErB,CACF,EAGA,SAAS,EACP,EACA,EACM,CACN,GAAI,CACF,GAAS,cAAc,KAAK,GAAuB,QAAS,CAC1D,UAAW,IAAI,KACf,YAAa,EAAQ,YACrB,UAAW,IAAI,KAAK,CAAA,CAAE,YAAY,EAClC,UACF,CAAC,CACH,OAAS,EAAO,CACd,GAAO,KACL,qCACA,aAAiB,MAAQ,EAAY,MAAM,OAAO,CAAK,CAAC,CAC1D,CACF,CACF,CAkCA,SAAgB,GACd,EACA,EACuB,CACvB,IAAM,EAAkB,EAAK,QAAQ,KAAK,CAAI,EACxC,EAA+B,EAAK,sBAAsB,KAAK,CAAI,EACnE,EAAa,GAAuB,EAAK,UAAU,EACnD,EAAwD,GAAU,CACtE,GAAI,EAAM,OAAS,OAAS,CAAC,EAAY,OAAO,EAAS,kBAAkB,CAAK,EAChF,IAAM,EAAc,GAClB,CAAE,QAAS,EAAM,SAAW,GAAO,MAAO,EAAM,eAAgB,EAChE,CACF,EACA,EAAS,kBAAkB,CAAE,GAAG,EAAO,gBAAiB,EAAY,KAAM,CAAC,CAC7E,EAKI,EAAqC,EAEnC,EAAc,OAAO,OAAO,CAAI,EACtC,OAAO,eAAe,EAAa,aAAc,CAAE,MAAO,EAAY,WAAY,EAAK,CAAC,EACxF,IAAM,EAAU,MACd,EACA,EACA,IACyB,CAKzB,IAAI,EAA8B,EAI9B,EAAW,YAEf,GAAI,CACF,EAAW,EAAK,QAAQ,EACxB,EAAa,EAA0B,EAAU,EAAe,EAAS,GAAG,EAC5E,EAAS,IAAI,YAAa,CACxB,KAAM,EACN,KAAM,CACR,CAAC,EAED,IAAI,EAAY,EACd,EAAS,UACT,EAAS,IACT,EACA,EACA,EAAS,kBAAkB,EAC3B,EAAS,cACX,EAEM,EAAO,MAAM,EACjB,EAAS,OAAO,MAChB,EACA,EAAS,kBACT,GAAS,YACX,EACA,GAAI,EAAK,QAGP,OAFA,EAAS,IAAI,eAAgB,CAAE,KAAM,EAAU,OAAQ,MAAO,CAAC,EAC/D,EAAuB,EAAS,cAAc,EACvC,EAAK,QAGV,EAAK,aAAe,IAAA,KACtB,EAAa,EAAK,WAClB,EAAY,CAAE,GAAG,EAAW,WAAY,CAAuC,GAIjF,IAAM,EAAU,MAAM,EAAS,gBAC7B,EACA,EACA,GAAS,OACT,GAAS,sBACT,GAAS,aACT,GAAS,aACT,EAAK,SACL,GAAS,WACX,EACA,GAAI,IAAY,GAYd,OAXA,EAAS,IAAI,cAAe,CAAE,KAAM,EAAU,OAAQ,YAAa,CAAC,EACpE,EAAuB,EAAS,QAAQ,EACxC,EAAgB,CACd,KAAM,MACN,WACA,SAAU,EACV,QAAS,GACT,OAAQ,GACR,YAAa,GAAS,WACxB,CAAC,EAEM,OAAO,GAAY,SACtB,EAAY,SAAU,EAAQ,OAAO,EACrC,GAGN,EAAuB,EAAS,SAAS,EACzC,GAAS,QAAQ,eAAe,EAChC,EAAgB,CACd,KAAM,QACN,WACA,SAAU,gBAAgB,CAAU,EACpC,YAAa,GAAS,WACxB,CAAC,EACD,GAAS,QAAQ,eAAe,EAIhC,IAAI,EACE,EAA8B,KAAO,IAAc,CACvD,GAAS,QAAQ,eAAe,EAChC,IAAM,EAAoB,EAA0B,EAAU,EAAW,EAAS,GAAG,EACrF,GAAI,KAAK,UAAU,CAAiB,IAAM,KAAK,UAAU,CAAS,EAChE,MAAM,IAAI,EACR,EACE,SACA,sGACF,CACF,EAEF,GAAI,KAAK,UAAU,CAAiB,IAAM,KAAK,UAAU,CAAU,EAAG,CACpE,IAAM,EAAgB,EACpB,EAAS,UACT,EAAS,IACT,EACA,EACA,EAAS,kBAAkB,EAC3B,EAAS,cACX,EACM,EAAW,MAAM,EACrB,EAAS,OAAO,MAChB,EACA,EAAS,kBACT,GAAS,YACX,EACA,GAAI,EAAS,QAAS,MAAM,IAAI,EAA0B,EAAS,OAAO,EAC1E,GACE,EAAS,aAAe,IAAA,IACxB,KAAK,UAAU,EAAS,UAAU,IAAM,KAAK,UAAU,CAAiB,EAExE,MAAM,IAAI,EACR,EACE,eACA,mFACF,CACF,EACF,IAAM,EAAmB,MAAM,EAAS,gBACtC,EACA,EACA,GAAS,OACT,GAAS,sBACT,GAAS,aACT,GAAS,aACT,EAAS,SACT,GAAS,WACX,EACA,GAAI,IAAqB,GACvB,MAAM,IAAI,EACR,OAAO,GAAqB,SACxB,EAAY,SAAU,EAAiB,OAAO,EAC9C,EACN,EACF,EAAY,CACd,CACA,EAAa,gBAAgB,CAAS,EACtC,GAAS,QAAQ,eAAe,EAChC,MAAM,IAAe,CAAU,EAC/B,GAAS,QAAQ,eAAe,EAChC,EAAc,KAAK,IAAI,CACzB,EACI,EAAiD,UACjD,EACJ,GAAI,CAEF,IAAM,EACJ,IAAY,IAAA,GACR,IAAA,GACA,CAAE,GAAG,EAAS,aAAY,qBAAsB,CAAoB,EACtE,GAAgB,EAClB,EAAS,MAAM,EACb,EACA,EACA,CACF,GAEI,EAAc,MAAM,EAAM,CAAU,EACnC,EAAc,KAAK,IAAI,EAC5B,GAAS,QAAQ,eAAe,EAChC,EAAS,MAAM,EAAgB,EAAY,CAAoC,GAEjF,EAAU,GAAS,QAAQ,QAAU,cAAgB,EAAO,QAAU,UAAY,SACpF,OAAS,EAAO,CAEd,KADA,GAAU,GAAS,QAAQ,SAAW,GAAe,CAAK,EAAI,cAAgB,UACxE,CACR,QAAU,CACR,GAAI,CACE,IAAgB,IAAA,IAClB,GAAS,cAAc,KAAK,GAAiB,UAAW,CACtD,UAAW,IAAI,KACf,YAAa,EAAQ,YACrB,UAAW,IAAI,KAAK,CAAW,CAAC,CAAC,YAAY,EAC7C,QAAS,IAAI,KAAK,KAAK,IAAI,KAAK,IAAI,EAAG,CAAW,CAAC,CAAC,CAAC,YAAY,EACjE,UACA,WACA,GAAI,OAAO,EAAQ,YAAe,SAAW,CAAE,WAAY,EAAQ,UAAW,EAAI,CAAC,CACrF,CAAC,CACL,OAAS,EAAO,CAEd,GAAO,KACL,+BACA,aAAiB,MAAQ,EAAY,MAAM,OAAO,CAAK,CAAC,CAC1D,CACF,CACF,CAGA,IAAM,EAAkB,GAAmB,CAAM,EAE7C,IAAoB,GAAU,OAAO,EAAO,MAAS,UACvD,EAAS,SAAS,UAChB,0BAA0B,EAAO,KAAK,OAAO,eAAe,EAAE,sDAChE,EAGF,EAAgB,CACd,KAAM,MACN,WACA,SAAU,EACV,QAAS,EAAgB,QACzB,eACE,OAAO,EAAgB,MAAS,SAC5B,EAAgB,KAChB,KAAK,UAAU,EAAgB,IAAI,EACzC,gBAAiB,EAAgB,MACjC,YAAa,GAAS,YAEtB,GAAI,EAAgB,KAAO,CAAE,KAAM,EAAgB,IAAK,EAAI,CAAC,CAC/D,CAAC,EAED,IAAM,EACJ,OAAO,EAAgB,MAAS,SAC5B,EAAgB,KAAK,OACpB,KAAK,UAAU,EAAgB,IAAI,CAAC,EAAE,QAAU,EAcvD,OAbA,EAAS,IAAI,cAAe,CAC1B,KAAM,EACN,QAAS,EAAgB,QACzB,UAAW,EACX,UAAW,IAAoB,CACjC,CAAC,EACD,GACE,EAAS,OAAO,MAChB,EACA,EACA,EAAS,kBACT,GAAS,YACX,EACO,CACT,OAAS,EAAK,CACZ,GAAI,GAAwB,CAAG,EAAG,MAAM,EAGxC,OAFI,aAAe,EAAkC,EAAI,OAElD,GAAgB,EAAK,EAAiB,CAC3C,WACA,SAAU,EACV,YAAa,GAAS,WACxB,CAAC,CACH,CACF,EAEM,EAAoC,MAAO,GAAG,IAClD,GAAmB,MAAM,EAAQ,GAAG,CAAI,EAAG,CAAU,EAYvD,MAXA,GAAY,QAAU,EACtB,EAAY,qBAAuB,EAKnC,EAAY,gBAAmB,GAAiB,CAC9C,EAAsB,GAAgB,EACtC,EAAK,gBAAgB,CAAY,CACnC,EAEO,CACT,CCjUA,SAAS,GAAkC,EAGlC,CACP,IAAM,EAAW,CACf,GAAG,GAA8B,EAAM,MAAO,OAAO,EACrD,GAAG,GAA8B,EAAM,YAAa,MAAM,CAC5D,EACA,GAAI,EAAS,SAAW,EAAG,OAC3B,IAAM,EAAS,EAAS,KAAK,CAAE,UAAS,YAAa,IAAI,EAAQ,IAAI,GAAQ,CAAC,CAAC,KAAK,IAAI,EACxF,MAAU,MACR,gEAAgE,EAAO,0CAEzE,CACF,CAaA,IAAa,GAAb,KAAgC,CAC9B,UACA,IACA,kBACA,OACA,SACA,kBACA,oBACA,cACA,gBACA,kBACA,eAKA,oBAAuC,IAAI,IAE3C,gBACA,mBACA,iBACA,gBACA,eACA,cACA,mBACA,iBACA,eACA,QAA2B,IAAI,GAE/B,SAAmB,GACnB,sBAAgC,GAChC,SAEA,YAAY,EAAqC,CAC/C,KAAK,UAAY,EAAQ,UACzB,KAAK,IAAM,EAAQ,IACnB,KAAK,kBAAoB,EAAQ,kBACjC,KAAK,OAAS,EAAQ,OAItB,KAAK,gBAAkB,EAAQ,uBAAyB,CACtD,MAAO,CAAC,GAAG,EAAQ,OAAO,YAAY,KAAK,EAC3C,KAAM,CAAC,GAAG,EAAQ,OAAO,YAAY,IAAI,CAC3C,EAGA,GAAkC,KAAK,gBAAgB,CAAO,CAAC,EAC/D,KAAK,SAAW,EAAQ,SACxB,KAAK,kBAAoB,EAAQ,kBACjC,KAAK,oBAAsB,EAAQ,oBACnC,KAAK,cAAgB,EAAQ,cAC7B,KAAK,gBAAkB,EAAQ,gBAC/B,KAAK,kBAAoB,EACvB,EAAQ,kBACR,oBACF,EACA,KAAK,eAAiB,EAAQ,eAC9B,KAAK,mBAAqB,EAAQ,mBAClC,KAAK,iBAAmB,EAAQ,iBAChC,KAAK,gBAAkB,EAAQ,gBAC/B,KAAK,cAAgB,EAAQ,cAC7B,KAAK,eAAiB,EAAQ,eAC9B,KAAK,mBAAqB,EAAQ,mBAClC,KAAK,iBAAmB,EAAQ,mBAAsB,GAAS,GAC/D,KAAK,eAAiB,EAAQ,eAC1B,EAAQ,uBAAyB,IAAA,KACnC,KAAK,SAAW,IAAI,GAAa,EAAQ,oBAAoB,EAEjE,CAMA,UAAU,EAAmB,EAAwB,GAAa,CAChE,KAAK,SAAW,EAChB,KAAK,sBAAwB,CAC/B,CAGA,SAAgB,CACd,KAAK,SAAW,GAChB,KAAK,sBAAwB,EAC/B,CAGA,yBAAmC,CACjC,OAAO,KAAK,WAAa,IAAA,EAC3B,CAMA,mBAAmB,EAA8C,CAC/D,IAAM,EAAS,KAAK,QAAQ,KAAK,CAAC,CAAC,GAC7B,EAAO,KAAK,QAAQ,OAAO,CAAK,EAClC,MAAQ,SAAW,cAAgB,IAAS,IAAA,IAAa,KAAK,WAAa,IAAA,GAI/E,OADA,KAAK,SAAS,WAAW,EAAK,SAAU,EAAK,QAAQ,EAC9C,CACT,CAGA,gBACE,EAA0E,CACxE,OAAQ,KAAK,OACb,GAAI,KAAK,kBAAoB,IAAA,GAAwD,CAAC,EAA7C,CAAE,gBAAiB,KAAK,eAAgB,CACnF,EAC4C,CAC5C,MAAO,CACL,MAAO,CAAC,GAAG,EAAQ,OAAO,YAAY,MAAO,GAAI,EAAQ,iBAAiB,OAAS,CAAC,CAAE,EACtF,YAAa,CACX,GAAG,EAAQ,OAAO,YAAY,KAC9B,GAAI,EAAQ,OAAO,YAAY,KAAO,CAAC,EACvC,GAAI,EAAQ,iBAAiB,MAAQ,CAAC,CACxC,CACF,CACF,CAOA,cAAc,EAA2B,CAEvC,IAAM,EAAU,EAAyB,CAAQ,EAGjD,MAFI,CAAC,KAAK,UAAY,EAAQ,gBAAkB,IAC5C,KAAK,UAAY,EAAQ,gBAAkB,GAAa,GACrD,CAAC,GAAqB,EAAU,CACrC,GAAG,KAAK,OAAO,YAAY,KAC3B,GAAI,KAAK,iBAAiB,MAAQ,CAAC,CACrC,CAAC,CACH,CAMA,uBAA+B,EAA+C,CAC5E,IAAK,IAAM,KAAQ,EAAO,CAExB,IAAM,EAAU,EAAwD,OACxE,GAAI,IAAW,IAAA,GAAW,SAC1B,IAAM,EAAa,EAAO,YAAY,YAAc,CAAC,EACrD,GAA8B,EAAO,KAAM,CAAE,WAAY,OAAO,KAAK,CAAU,CAAE,CAAC,CACpF,CACA,IAAM,EAAQ,KAAK,gBAAgB,EACnC,GAAkC,CAAK,EACvC,IAAK,GAAM,CAAE,UAAS,YAAY,GAA8B,EAAM,WAAW,EAC/E,KAAK,SAAS,UAAU,yBAAyB,EAAQ,IAAI,EAAO,EAAE,CAE1E,CAGA,UAAU,EAAyD,CACjE,KAAK,uBAAuB,CAAK,EAKjC,IAAM,EAAyB,CAC7B,UAAW,KAAK,UAChB,IAAK,KAAK,IACV,OAAQ,KAAK,OACb,SAAU,KAAK,SACf,eAAgB,KAAK,eACrB,gBAAiB,KAAK,gBACtB,kBAAmB,KAAK,kBACxB,kBAAmB,KAAK,kBACxB,KAAM,EAAO,IAAW,KAAK,IAAI,EAAO,CAAM,EAC9C,iBACE,EACA,EACA,EACA,EACA,EACA,EACA,EACA,IAEA,KAAK,iBAAiB,EAAU,EAAU,EAAQ,EAAa,EAAc,CAC3E,eACA,GAAI,IAAiB,IAAA,GAA+B,CAAC,EAApB,CAAE,cAAa,EAChD,GAAI,IAAgB,IAAA,GAA8B,CAAC,EAAnB,CAAE,aAAY,CAChD,CAAC,CACL,EAEA,OAAO,EAAM,IAAK,GAAS,GAAuB,EAAM,CAAI,CAAC,CAC/D,CAGA,wBAAmC,CACjC,MAAO,CAAC,GAAG,KAAK,mBAAmB,CACrC,CAGA,kBAAiD,CAC/C,OAAO,KAAK,QAAQ,KAAK,CAC3B,CAGA,0BAAiC,CAC/B,KAAK,oBAAoB,MAAM,CACjC,CA6BA,wBAIE,CACA,MAAO,CACL,MAAO,CAAC,GAAG,KAAK,OAAO,YAAY,KAAK,EACxC,KAAM,CAAC,GAAG,KAAK,OAAO,YAAY,IAAI,EACtC,IAAK,CAAC,GAAI,KAAK,OAAO,YAAY,KAAO,CAAC,CAAE,CAC9C,CACF,CAEA,qBAAqB,EAGZ,CAOP,IAAM,EAAO,GAAqB,KAAK,gBAAiB,CAAM,EAC9D,KAAK,OAAO,YAAY,MAAQ,EAAK,MACrC,KAAK,OAAO,YAAY,KAAO,EAAK,IACtC,CAIA,MAAM,gBACJ,EACA,EACA,EACA,EAA8D,cAC9D,EACkB,CAClB,OACG,MAAM,KAAK,iBAAiB,EAAU,EAAU,EAAQ,EAAa,CAAY,IAAO,EAE7F,CAQA,MAAM,uBACJ,EACA,EACA,EACkB,CAClB,IAAM,EAAY,EAChB,KAAK,UACL,KAAK,IACL,EACA,EACA,KAAK,kBAAkB,EACvB,KAAK,cACP,EACM,EAAO,MAAM,EAAe,KAAK,OAAO,MAAO,EAAW,KAAK,iBAAiB,EAatF,OAZI,EAAK,SAAW,EAAK,aAAe,IAAA,IACtC,KAAK,IAAI,eAAgB,CAAE,KAAM,EAAU,OAAQ,OAAQ,UAAW,EAAK,CAAC,EACrE,IAUF,MARgB,KAAK,iBAC1B,EACA,EACA,EACA,cACA,IAAA,GACA,CAAE,UAAW,GAAO,GAAI,EAAK,WAAa,IAAA,GAA8C,CAAC,EAAnC,CAAE,aAAc,EAAK,QAAS,CAAQ,CAC9F,IACoB,EACtB,CAGA,MAAc,iBACZ,EACA,EACA,EACA,EAA8D,cAC9D,EACA,EAAwB,CAAC,EACc,CACvC,IAAM,EAAU,MAAM,KAAK,uBACzB,EACA,EACA,EACA,EACA,EACA,CACF,EAIA,OAHI,IAAY,IAChB,GAAQ,eAAe,EAGpB,MAAM,GACL,CACE,UAAW,KAAK,UAChB,IAAK,KAAK,IACV,WACA,WACA,iBAAkB,KAAK,gBACzB,EACA,EAAM,aACN,EACF,GAAM,IAdqB,CAgB/B,CAEA,MAAc,uBACZ,EACA,EACA,EACA,EAA8D,cAC9D,EACA,EAAwB,CAAC,EACc,CAIvC,IAAM,EACJ,KAAK,mBAAqB,IAAA,GAMtB,IAAA,GALA,GAAwB,KAAK,iBAAkB,CAC7C,UAAW,KAAK,iBAAiB,MACjC,SAAU,KAAK,iBAAiB,KAChC,YAAa,KAAK,OAAO,YAAY,KACvC,CAAC,EAGD,EAAO,KAAK,kBAAkB,EAC9B,EAAQ,CAAC,GAAG,KAAK,OAAO,YAAY,MAAO,GAAI,GAAQ,OAAS,CAAC,CAAE,EACnE,EAAQ,CAEZ,MAAO,IAAS,OAAS,GAAsB,CAAK,EAAI,EACxD,KAAM,CAAC,GAAG,KAAK,OAAO,YAAY,KAAM,GAAI,GAAQ,MAAQ,CAAC,CAAE,EAC/D,IAAK,KAAK,OAAO,YAAY,KAAO,CAAC,CACvC,EACM,EAAQ,CACZ,IAAK,KAAK,IACV,GAAI,KAAK,gBAAkB,IAAA,GAAoD,CAAC,EAAzC,CAAE,cAAe,KAAK,aAAc,EAC3E,eAAgB,KAAK,cACvB,EACM,EAAW,GAAmB,EAAU,EAAU,EAAM,EAAO,CACnE,GAAG,EACH,GAAI,KAAK,qBAAuB,IAAA,GAE5B,CAAC,EADD,CAAE,mBAAoB,KAAK,kBAAmB,EAElD,oBACE,EAAM,YAAc,IAAS,KAAK,oBAAoB,EAAU,CAAQ,EAC1E,GAAI,GAAQ,UAAY,IAAA,GAA0C,CAAC,EAA/B,CAAE,QAAS,EAAO,OAAQ,EAC9D,OAAQ,GAAQ,QAAU,GAC1B,GAAI,KAAK,SAAW,CAAE,SAAU,EAAK,EAAI,CAAC,CAC5C,CAAC,EAMD,GAFA,KAAK,2BAA2B,EAAU,EAAU,EAAU,CAAY,EAEtE,IAAa,OAEf,OADA,KAAK,QAAQ,OAAO,EAAU,EAAU,QAAQ,EACzC,GAIT,GAAI,EAAM,eAAiB,MACzB,OAAO,KAAK,kBAAkB,EAAU,EAAU,EAAQ,EAAa,GAAM,CAAK,EAEpF,GAAI,IAAa,OAAQ,MAAO,GAIhC,IAAM,EAAQ,GAAsB,EAAU,EAAU,EAAO,CAAK,EAiBpE,OAdI,IAAS,QAAU,KAAK,WAAa,IAAA,IAAa,CAAC,GAAS,GAAQ,SAAW,GAC1E,KAAK,iBACV,KAAK,SACL,EACA,EACA,EACA,EACA,EACA,CACF,EAIE,EAAM,eAAiB,SAAW,CAAC,GAAS,GAAQ,SAAW,IAC5D,KAAK,kBAAkB,EAAU,EAAU,EAAQ,EAAa,EAAO,CAAK,CACrF,CAEA,MAAc,iBACZ,EACA,EACA,EACA,EACA,EACA,EACA,EACuC,CAGvC,GAFI,EAAK,UAAU,EAAU,CAAQ,GAGnC,GAAkB,EAAU,EAAU,GAAsB,CAAC,GAAG,KAAK,mBAAmB,CAAC,CAAC,EAE1F,MAAO,GAET,GAAI,EAAK,SAAS,EAAG,CACnB,IAAM,EAAU,MAAM,KAAK,kBACzB,EACA,EACA,EACA,EACA,GACA,CACF,EAGA,OAFI,GAAW,CAAC,KAAK,uBAAyB,CAAC,GAAwB,EAAM,YAAY,GACvF,EAAK,OAAO,EACP,CACT,CACA,IAAM,EAAY,MAAM,EAAK,MAAM,CAAE,WAAU,WAAU,IAAK,KAAK,GAAI,EAAG,CAAM,EAQhF,OAPI,GAAQ,UAAY,GAAa,GAEjC,KAAK,kBAAkB,IAAM,OAG7B,EAAU,OAAS,QAAgB,IACvC,KAAK,QAAQ,OAAO,EAAU,EAAU,aAAc,EAAU,MAAM,EAC/D,CAAE,QAAS,EAAU,OAAQ,GAJ3B,KAAK,iBAAiB,EAAU,EAAU,EAAQ,EAAa,EAAc,CAAK,CAK7F,CAOA,MAAc,kBACZ,EACA,EACA,EACA,EAA8D,cAC9D,EAAQ,GACR,CAAE,eAAc,eAAgC,CAAC,EAC/B,CAClB,IAAM,EACJ,CAAC,GAAS,GAAkB,EAAU,EAAU,CAAC,GAAG,KAAK,mBAAmB,CAAC,EAC/E,GAAK,KAAK,uBAAyB,CAAC,GAAmB,GAAwB,CAAY,EAAG,CAC5F,GAAI,GAAQ,SAAW,IAAgB,cAAe,MAAO,GAC7D,IAAM,EAAU,MAAM,GACpB,CACE,UAAW,KAAK,UAChB,IAAK,KAAK,IACV,WACA,WACA,iBAAkB,KAAK,gBACzB,EACA,EACA,EACF,EAEA,OADI,IAAY,IAAM,KAAK,QAAQ,OAAO,EAAU,EAAU,MAAM,EAC7D,IAAY,EACrB,CACA,IAAM,EAAQ,GAAgB,EAAU,CAAQ,EAC1C,EAAwB,GAAQ,UAAY,GAC5C,EACJ,IAAgB,gBACf,KAAK,oBAAsB,IAAA,IAAa,KAAK,sBAAwB,IAAA,IAClE,EAAU,MAAM,GAAe,CACnC,WACA,iBACA,GAAI,IAAgB,eAAiB,KAAK,kBACtC,CAAE,QAAS,KAAK,iBAAkB,EAClC,CAAC,EACL,GAAI,IAAgB,eAAiB,KAAK,oBACtC,CAAE,eAAgB,KAAK,oBAAqB,SAAU,KAAK,QAAS,EACpE,CAAC,EACL,WACA,GAAI,EAAS,CAAE,QAAO,EAAI,CAAC,EAC3B,GAAI,IAAgB,IAAA,GAA8B,CAAC,EAAnB,CAAE,aAAY,CAChD,CAAC,EAOD,GALI,CAAC,EAAQ,SAAW,CAAC,GACvB,KAAK,QAAQ,OAAO,EAAU,EAAU,EAAc,OAAS,aAAa,EAI1E,EAAO,OAAO,EAAQ,QAC1B,GAAI,EAAQ,mBAAoB,CAC9B,GAAI,KAAK,qBAAuB,IAAA,GAC9B,MAAU,MAAM,sEAAsE,EAExF,KAAK,mBAAmB,CAAK,CAC/B,CAEA,OADI,EAAQ,oBAAoB,KAAK,oBAAoB,IAAI,CAAK,EAC3D,EAAQ,OACjB,CAOA,2BACE,EACA,EACA,EACA,EACM,CACN,IAAM,EAAiB,KAAK,kBAAkB,EAC9C,EACE,KAAK,OAAO,MACZ,qBACA,CACE,WAAY,KAAK,UACjB,IAAK,KAAK,IACV,gBAAiB,qBACjB,UAAW,EACX,WAAY,EACZ,oBAAqB,EACrB,GAAI,IAAmB,IAAA,IAAa,CAAE,gBAAiB,CAAe,EACtE,GAAI,KAAK,iBAAmB,IAAA,IAAa,CAAE,gBAAiB,KAAK,cAAe,EAChF,IAAK,CACH,mBAAoB,KAAK,IACzB,kBAAmB,KAAK,SAC1B,CACF,EACA,KAAK,kBACL,CACF,CAAC,CAAC,UAAY,IAAA,EAAS,CACzB,CAGA,oBAA4B,EAAkB,EAA8B,CAC1E,GAAI,KAAK,iBAAmB,IAAA,GAAW,MAAO,GAC9C,IAAM,EAAW,EAAyB,CAAQ,CAAC,CAAC,SACpD,GAAI,GAAU,OAAS,UAAW,MAAO,GACzC,IAAM,EAAU,EAAS,EAAS,KAClC,OAAO,OAAO,GAAY,UAAY,KAAK,eAAe,aAAa,EAAU,CAAO,CAC1F,CAGA,IAAY,EAAe,EAA6B,CACtD,KAAK,eAAe,IAAI,KAAK,UAAW,EAAO,CAAI,CACrD,CACF,EC/oBA,SAAgB,GACd,EACA,EACA,EACA,EACA,EACoB,CACpB,OAAO,IAAI,GAAmB,CAC5B,eAAgB,EAAQ,eACxB,YACA,MACA,oBACA,OAAQ,CACN,YAAa,EAAQ,aAAe,CAAE,MAAO,CAAC,EAAG,KAAM,CAAC,CAAE,EAC1D,MAAO,EAAQ,KACjB,EAMA,GAAI,EAAQ,wBAA0B,IAAA,GAElC,CAAC,EADD,CAAE,sBAAuB,EAAQ,qBAAsB,EAE3D,SAAU,EAAQ,SAClB,kBAAmB,EAAQ,kBAC3B,GAAI,EAAQ,iBAAmB,IAAA,GAAyD,CAAC,EAA9C,CAAE,eAAgB,EAAQ,cAAe,EACpF,GAAI,EAAQ,uBAAyB,IAAA,GAEjC,CAAC,EADD,CAAE,qBAAsB,EAAQ,oBAAqB,EAEzD,oBAAqB,EAAQ,kBAC7B,cAAe,EAAQ,cACvB,gBAAiB,EAAQ,gBACzB,kBAAmB,EAAQ,kBAC3B,GAAI,EAAQ,gBAAkB,IAAA,GAAuD,CAAC,EAA5C,CAAE,cAAe,EAAQ,aAAc,EACjF,GAAI,EAAQ,qBAAuB,IAAA,GAE/B,CAAC,EADD,CAAE,mBAAoB,EAAQ,kBAAmB,EAErD,GAAI,EAAQ,mBAAqB,IAAA,GAE7B,CAAC,EADD,CAAE,iBAAkB,EAAQ,gBAAiB,EAEjD,iBACA,mBAAoB,EAAQ,mBAE5B,iBAAkB,EAAQ,iBAC1B,gBAAiB,EAAQ,eAC3B,CAAC,CACH,CAEA,SAAgB,GACd,EACA,EACA,EACA,EAC0F,CAe1F,MAAO,CAAE,eAAA,IAdkB,GACzB,EACA,EAAQ,iBACR,EAAQ,oBAWY,EAAG,uBAAA,IATU,GAAuB,CACxD,YACA,MACA,QACA,MAAO,EAAQ,MACf,oBAAqB,EAAQ,oBAC7B,WAAY,EAAQ,qBACpB,kBAAmB,EAAQ,iBAC7B,CAC8C,CAAE,CAClD,CAEA,SAAgB,GACd,EACA,EACA,EACA,EACA,EACA,EACA,EACmB,CACnB,IAAM,EAAe,EAAmB,UAAU,CAAK,EAuCvD,OAAO,IAAI,EAAkB,CArC3B,KAAM,EAAQ,WAAa,QAC3B,YAAa,CAAC,CAAQ,EACtB,GAAI,EAAQ,oBAAsB,IAAA,GAE9B,CAAC,EADD,CAAE,kBAAmB,EAAG,EAAS,MAAO,EAAQ,iBAAkB,CAAE,EAExE,aAAc,CACZ,SAAU,EAAS,KACnB,QACA,GAAI,EAAQ,SAAW,IAAA,IAAa,CAAE,OAAQ,EAAQ,MAAO,EAG7D,GAAI,EAAQ,cAAgB,IAAA,IAAa,CAAE,YAAa,EAAQ,WAAY,EAC5E,GAAI,EAAQ,kBAAoB,IAAA,IAAa,CAAE,UAAW,EAAQ,eAAgB,CACpF,EAEA,gBACA,MAAO,EACP,GAAI,EAAQ,sBAAwB,IAAA,GAEhC,CAAC,EADD,CAAE,oBAAqB,EAAQ,mBAAoB,EAGvD,cAAgB,GAAa,EAAmB,cAAc,CAAQ,EACtE,QAAS,CAAE,QAAS,EAAM,EAK1B,eACA,GAAI,EAAQ,kBAAoB,IAAA,IAAa,CAAE,QAAS,EAAQ,eAAgB,EAChF,GAAI,EAAQ,eAAiB,CAAE,eAAgB,EAAQ,cAAe,EAAI,CAAC,EAE3E,GAAI,EAAQ,IAAM,CAAE,IAAK,EAAQ,GAAI,EAAI,CAAC,EAE1C,GAAI,EAAQ,sBAAwB,IAAA,GAEhC,CAAC,EADD,CAAE,oBAAqB,EAAQ,mBAAoB,CAGlB,CAAC,CAC1C,CCxHA,SAAgB,GACd,EACA,EACqB,CACrB,IAAM,EAAU,EAAM,KAAK,EAAI,CAAE,KAAM,EAAK,CAAC,EAE7C,OADI,EAAQ,SAAW,SAChB,EAAM,WAAW,CAAE,GAAK,EADS,CAE1C,CCTA,SAAgB,GAA8B,EAAiD,CAC7F,IAAM,EAAqB,CAAC,EACtB,EAA0D,CAAC,EACjE,IAAK,IAAM,KAAW,EAAS,CAC7B,GAAI,EAAQ,OAAS,YAAa,CAChC,IAAM,EAAW,EAAQ,UAAU,sBAC/B,MAAM,QAAQ,CAAQ,GAAK,EAAS,MAAO,GAAS,OAAO,GAAS,QAAQ,GAC9E,EAAS,KAAK,GAAG,CAAQ,EAC3B,IAAK,IAAM,KAAQ,EAAQ,WAAa,CAAC,EACvC,EAAQ,KAAK,CACX,QAAS,CACP,OAAQ,EAAK,GACb,KAAM,EAAK,SAAS,KACpB,OAAQ,UACR,SAAU,YACZ,EACA,QAAS,EACX,CAAC,CACL,CACA,GAAI,EAAQ,OAAS,OAAQ,SAC7B,IAAM,EAAoB,CACxB,OAAQ,EAAQ,WAChB,GAAI,EAAQ,KAAO,CAAE,KAAM,EAAQ,IAAK,EAAI,CAAC,EAC7C,OACE,EAAQ,UAAU,UAAY,GAC1B,UACA,EAAQ,UAAU,UAAY,GAC5B,UACA,UACR,GAAI,OAAO,EAAQ,UAAU,gBAAmB,SAC5C,CAAE,OAAQ,EAAQ,SAAS,cAAe,EAC1C,CAAC,EAEL,SAAU,YACZ,EAEM,EAAU,CAAC,GAAG,CAAO,CAAC,CACzB,QAAQ,CAAC,CACT,KAAM,GAAU,CAAC,EAAM,SAAW,EAAM,QAAQ,SAAW,EAAQ,MAAM,EACxE,GACF,EAAQ,QAAU,EAClB,EAAQ,QAAU,IACb,EAAQ,KAAK,CAAE,UAAS,QAAS,EAAK,CAAC,CAChD,CACA,MAAO,CAAC,GAAG,EAAU,GAAG,EAAQ,KAAK,CAAE,aAAc,KAAK,UAAU,CAAO,CAAC,CAAC,CAC/E,CAEA,SAAgB,GAAyB,EAAuC,CAC9E,MAAO,CACL,GAAI,EAAS,EACb,UAAW,IAAI,KACf,MAAO,WACP,KAAM,YACN,QAAS,gNAAgN,EAAS,KAAK;CAAI,IAC3O,SAAU,CAAE,sBAAuB,CAAS,CAC9C,CACF,CCnCA,MAAMC,GAAS,EAAa,mBAAmB,EAuC/C,SAAgB,GACd,EACA,EACiB,CACjB,MAAO,CAAE,GAAG,EAAK,GAAG,CAAO,CAC7B,CAaA,eAAsB,GACpB,EACA,EACA,EACe,CAKf,GAAQ,eAAe,EAEvB,IAAM,EAAU,EAAI,MAAM,WAAW,EAG/B,EAAmB,EAAQ,OAAQ,GAAQ,EAAI,OAAS,QAAQ,EAMtE,GAAI,EAAiB,SAAW,EAAG,OAEnC,EAAI,eAAe,kBAAkB,CAAO,EAC5C,IAAM,EAAS,EAAI,eAAe,gBAAgB,EAC5C,EAAW,GAA8B,CAAO,EAIhD,EAAU,MAAM,EAAI,uBAAuB,QAC/C,EAAI,WACJ,EACA,EACA,EACA,EAAI,QACJ,EAAI,aACJ,EAAI,gBACN,EAKA,EAAI,MAAM,aAAa,EACvB,EAAI,MAAM,cAAc,SAAU,EAAI,aAAa,EACnD,EAAI,MAAM,cAAc,YAAa,sBAAsB,GAAS,EAChE,EAAS,OAAS,GAAG,EAAI,MAAM,iBAAiB,GAAyB,CAAQ,CAAC,EAGtF,EAAI,eAAe,kBAAkB,EAAI,MAAM,WAAW,CAAC,EAG3D,IAAM,EAA4B,CAChC,WAAY,EAAI,UAChB,IAAK,EAAI,IACT,gBAAiB,cACjB,QAAS,EAAI,QACb,gBAAiB,CACnB,EACA,EACE,EAAI,MACJ,cACA,EACA,EAAI,kBACJ,EAAI,YACN,CAAC,CAAC,MAAO,GAAUA,GAAO,KAAK,cAAe,CAAE,OAAM,CAAC,CAAC,EAGxD,IAAM,EAAQ,EAAI,eAAe,gBAAgB,EACjD,EAAI,IAAI,kBAAmB,CACzB,QAAS,EAAI,QACb,SACA,OACF,CAAC,EACD,EAAI,yBAAyB,CAAE,QAAS,EAAI,QAAS,SAAQ,OAAM,CAAC,EAChE,EAAI,mBACN,EAAI,kBAAkB,CAAO,CAEjC,CAqCA,SAAgB,GAAe,EAA2C,CACxE,IAAM,EAAM,IAAI,KAAK,CAAA,CAAE,YAAY,EAE7B,EAAU,GAAqB,EAAI,aAAc,EAAI,SAAS,EACpE,GAAI,EAAQ,SAAW,SAAW,EAAQ,SAAW,UACnD,OAAO,EAET,IAAM,EAAW,EAAQ,SAAW,QAAU,EAAQ,OAAS,IAAA,GAEzD,EAAoC,CACxC,GAAG,EACH,GAAI,EAAI,aAAa,GACrB,KAAM,GAAU,KAChB,IAAK,EAAI,aAAa,IACtB,UAAW,GAAU,WAAa,EAClC,UAAW,EACX,SAAU,EAAI,aAAa,SAC3B,iBAAkB,EAAI,aAAa,iBACnC,QAAS,EAAI,eAAe,EAC5B,aAAc,EAAI,aAClB,YAAa,EAAI,WACnB,EAIM,EAAa,EAAQ,SAAW,QAAU,EAAQ,WAAa,IAAA,GAQrE,OAPA,GACE,IAAe,IAAA,GACX,EAAI,aAAa,KAAK,CAAM,EAC5B,EAAI,aAAa,KAAK,EAAQ,CAC5B,WAAY,IAAe,WAAa,QAAU,YACpD,CAAC,CACP,EACO,CAAE,OAAQ,QAAS,QAAO,CACnC,CC5NA,MAAMC,GAAS,EAAa,kBAAkB,EAM9C,SAAgB,GACd,EACA,EACA,EACM,CAEF,oBAAqB,IACvB,EAEE,iBAAmB,EAAc,IAAkC,CACnE,EAAI,cAAe,CAAE,KAAM,EAAM,GAAG,CAAM,CAAC,CAC7C,EAEJ,CAMA,SAAgB,GACd,EACA,EACA,EACA,EACA,EACA,EACA,EACM,CAYN,EAAS,EAAmC,eAAgB,CAV1D,WAAY,EACZ,MACA,gBAAiB,eACjB,GAAI,IAAmB,IAAA,IAAa,CAAE,gBAAiB,CAAe,EACtE,GAAI,IAAmB,IAAA,IAAa,CAAE,gBAAiB,CAAe,EACtE,IAAK,CACH,mBAAoB,EACpB,kBAAmB,CACrB,CAEkE,EAAG,CAAiB,CAAC,CACtF,KAAM,GAAW,CACZ,EAAO,QACT,EAAS,EAAO,MAAM,CAE1B,CAAC,CAAC,CACD,MAAO,GAAUA,GAAO,KAAK,2BAA4B,CAAE,OAAM,CAAC,CAAC,CACxE,CAGA,eAAsB,GACpB,EACA,EACA,EACA,EACA,EACA,EACA,EACe,CAaf,MAAM,EAAS,EAAmC,aAAc,CAX9D,WAAY,EACZ,MACA,gBAAiB,aACjB,SACA,GAAI,IAAmB,IAAA,IAAa,CAAE,gBAAiB,CAAe,EACtE,GAAI,IAAmB,IAAA,IAAa,CAAE,gBAAiB,CAAe,EACtE,IAAK,CACH,mBAAoB,EACpB,kBAAmB,CACrB,CAEsE,EAAG,CAAiB,CAC9F,CC7FA,MAAM,GAA0B,eAWhC,SAAgB,GAA0B,EAGjB,CACvB,MAAO,CACL,eAAgB,IAAI,IAAI,EAAQ,cAAc,EAC9C,mBAAoB,IAAI,IACxB,YAAa,IAAI,IACjB,GAAI,EAAQ,iBAAmB,CAAE,gBAAiB,EAAQ,eAAgB,CAC5E,CACF,CAEA,SAAgB,GACd,EACA,EACA,EACM,CACD,KAAO,gBACZ,IAAI,IAAU,yBAA0B,CACtC,IAAM,EAAK,EAAU,EAAK,UAAU,EAChC,GAAI,EAAO,YAAY,IAAI,EAAI,GAAW,EAAK,UAAU,CAAC,EAC9D,GAAwB,EAAQ,CAAI,EACpC,MACF,CACI,IAAU,yBACZ,GAAwB,EAAQ,CAAI,CAFtC,CAIF,CAEA,SAAS,GAAwB,EAA8B,EAAiC,CAC9F,IAAM,EAAW,EAAU,EAAK,QAAQ,EAClC,EAAa,EAAU,EAAK,UAAU,EACxC,CAAC,GAAY,CAAC,GAAc,EAAO,eAAe,IAAI,CAAQ,IAElE,EAAO,mBAAmB,IAAI,CAAU,EACxC,EAAO,kBAAkB,CACvB,KAAM,QACN,WACA,SAAU,GAAW,EAAK,UAAU,CACtC,CAAC,EACH,CAEA,SAAS,GAAwB,EAA8B,EAAiC,CAC9F,IAAM,EAAW,EAAU,EAAK,QAAQ,EAClC,EAAa,EAAU,EAAK,UAAU,EAC5C,GAAI,CAAC,GAAY,CAAC,EAAY,OAE9B,IAAM,EAAW,GAAU,EAAK,QAAQ,EAClC,EAAO,EAAO,YAAY,IAAI,CAAU,EAE9C,GADA,EAAO,YAAY,OAAO,CAAU,EAChC,EAAK,UAAY,IAAS,GAAU,iBAAmB,iBAAkB,CAC3E,IAAM,EAAS,EAAU,EAAS,cAAc,EAChD,EAAO,kBAAkB,CACvB,KAAM,MAAO,WAAU,YAAa,EAAY,SAAU,EAAM,QAAS,GACzE,eAAgB,KAAK,UAAU,CAAE,QAAS,GAAO,MAAO,EAAK,MAAO,UAAS,CAAC,EAC9E,GAAI,EAAS,CAAE,gBAAiB,CAAC,CAAE,KAAM,OAAQ,KAAM,kDAAkD,GAAS,CAAC,CAAE,EAAI,CAAC,CAC5H,CAAC,EACD,EAAO,mBAAmB,OAAO,CAAU,EAC3C,MACF,CAGA,GAAI,EADF,EAAO,mBAAmB,IAAI,CAAU,GAAK,GAAU,YAAc,IACvD,OAEhB,EAAO,mBAAmB,OAAO,CAAU,EAC3C,IAAM,EAAQ,EAAU,EAAK,KAAK,GAAK,SAAS,EAAS,sBACzD,EAAO,kBAAkB,CACvB,KAAM,MACN,WACA,QAAS,GACT,eAAgB,KAAK,UAAU,CAC7B,QAAS,GACT,QACA,UAAW,GACX,cAAe,EAAU,GAAU,aAAa,GAAK,EACrD,eAAgB,GAAe,GAAU,cAAc,CACzD,CAAC,CACH,CAAC,CACH,CAEA,SAAS,GAAW,EAAuC,CACzD,IAAM,EAAS,GAAU,CAAK,EAC9B,GAAI,CAAC,EAAQ,OAEb,IAAM,EAAkB,CAAC,EACzB,IAAK,GAAM,CAAC,EAAK,KAAS,OAAO,QAAQ,CAAM,GAE3C,OAAO,GAAS,UAChB,OAAO,GAAS,UAChB,OAAO,GAAS,WACf,OAAO,GAAS,UAAY,KAE7B,EAAK,GAAO,GAGhB,OAAO,CACT,CAEA,SAAS,EAAU,EAAoC,CACrD,OAAO,OAAO,GAAU,UAAY,EAAM,OAAS,EAAI,EAAQ,IAAA,EACjE,CAEA,SAAS,GAAU,EAAqD,CACtE,OAAO,OAAO,GAAU,UAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,EACrE,EACD,IAAA,EACN,CAEA,SAAS,GAAe,EAA0B,CAEhD,OADK,MAAM,QAAQ,CAAK,EACjB,EAAM,OAAQ,GAAyB,OAAO,GAAS,QAAQ,EADpC,CAAC,CAErC,CC7GA,MAAMC,GAAS,EAAa,uBAAuB,EAQnD,SAAS,EACP,EACA,EACA,EACA,EACM,CACN,IAAM,EAAQ,OAAO,EAAK,OAAa,SAAY,EAAK,MAAsB,EAAI,MAC5E,EACJ,OAAO,EAAK,UAAgB,SAAY,EAAK,SAAyB,EAAI,WAAW,KACjF,EAAY,EAAK,OACjB,EACJ,OAAO,GAAc,UAAY,GAAc,CAAS,EACpD,EACC,EAAI,SACJ,OAAO,EAAI,MAAM,UAAa,WAAa,EAAI,MAAM,SAAS,CAAC,CAAC,OAAS,IAAA,KAC1E,OACA,EAAQ,OAAO,EAAK,OAAa,SAAY,EAAK,MAAsB,IAAA,GAC9E,EACE,EAAI,MACJ,EACA,CACE,WAAY,EAAI,UAChB,IAAK,EAAI,IACT,gBAAiB,EACjB,QACA,WACA,SACA,GAAI,IAAU,IAAA,IAAa,CAAE,OAAM,EACnC,GAAI,EAAI,iBAAmB,IAAA,IAAa,CAAE,gBAAiB,EAAI,cAAe,EAC9E,GAAI,EAAI,iBAAmB,IAAA,IAAa,CAAE,gBAAiB,EAAI,cAAe,EAC9E,IAAK,CACH,mBAAoB,EAAI,IACxB,kBAAmB,EAAI,SACzB,CACF,EACA,EAAI,kBACJ,CACF,CAAC,CAAC,MAAO,GAAUA,GAAO,KAAK,cAAe,CAAE,OAAM,CAAC,CAAC,CAC1D,CAGA,SAAgB,GACd,EACA,EAC2E,CAC3E,IAAM,EAAe,EACjB,GAAY,QAAS,EAAc,EAAa,YAAY,EAC5D,IAAA,GACE,EAAsB,GAA0B,CACpD,eAAgB,EAAI,gBAAkB,CAAC,EACvC,GAAI,EAAI,iBAAmB,CAAE,gBAAiB,EAAI,eAAgB,CACpE,CAAC,EACK,EAAc,EAAI,YACnB,GAAwB,CACvB,EAAI,IAAI,aAAc,CAAE,OAAM,CAAC,EAC/B,EAAI,cAAc,CAAK,CACzB,EACA,IAAA,GAEA,EAAuC,CAAC,EAC5C,MAAO,CACL,kBAAmB,EAAO,IAAS,CAIjC,GAAI,IAAU,oBAAsB,EAAK,WAAgB,iBAAkB,CACzE,IAAM,EAAU,EAAK,SACjB,GAAU,OAAY,QACrB,GAAU,OAAY,aAAe,MAAM,QAAQ,EAAQ,SAAY,GAAK,EAAQ,UAAa,OAAS,IAC7G,EAAI,oBAAoB,CAE5B,CAUA,GAPI,IAAU,EAAqB,WAAW,EAAI,IAAI,EAAO,CAAuB,EACpF,GAA0B,EAAqB,EAAO,CAAI,EAMtD,IAAU,mBAAoB,CAChC,IAAM,EAAU,EAChB,EAAc,CAAE,MAAO,EAAQ,MAAU,SAAU,EAAQ,QAAY,EACvE,EAAkB,EAAK,eAAgB,EAAS,CAAY,CAC9D,MAAO,GAAI,IAAU,+BAEnB,EACE,EACA,gBACA,CAAE,GAAI,EAAkC,GAAG,CAAY,EACvD,CACF,OACK,GAAI,IAAU,GAAyB,SAAU,CACtD,IAAM,EAAS,GAAwB,CAA+B,EAClE,IAAW,IAAA,KAGb,EACE,EACA,gBACA,CAAE,MAAO,EAAK,MAAU,MAAO,EAAO,KAAK,MAAO,SAAU,EAAO,KAAK,QAAS,EACjF,CACF,EACA,EAAI,uBAAuB,CAAM,EAErC,MAAO,GAAI,IAAU,EAAqB,WAAa,EAAI,0BAA2B,CAEpF,IAAM,EAAc,EAElB,OAAO,cAAc,EAAY,KAAQ,GACxC,EAAY,MAAsB,GACnC,OAAO,EAAY,WAAiB,UACpC,OAAO,EAAY,SAAe,WACjC,EAAY,UAAe,WAC1B,EAAY,UAAe,WAC3B,EAAY,UAAe,gBAE7B,EAAI,0BAA0B,CAC5B,MAAO,EAAY,MACnB,UAAW,EAAY,UACvB,QAAS,EAAY,QACrB,QAAS,EAAY,QACrB,GAAI,OAAO,EAAY,QAAc,UACnC,6EAA6E,KAC3E,EAAY,MACd,GAAK,CAAE,OAAQ,EAAY,MAAU,EACvC,IAAK,EAAY,cAAmB,WAClC,EAAY,cAAmB,aAC/B,EAAY,cAAmB,sBAAwB,CACvD,YAAa,EAAY,WAC3B,EACA,GAAI,OAAO,EAAY,YAAkB,UACvC,EAAY,WAAc,OAAS,GACnC,EAAY,WAAc,QAAU,KACpC,CAAC,GAAG,EAAY,UAAa,CAAC,CAAC,MAAO,GAAS,EAAK,WAAW,CAAC,GAAK,EAAE,GAAK,CAC1E,WAAY,EAAY,UAC1B,EACF,GAAI,OAAO,EAAY,SAAe,UACpC,EAAY,QAAW,OAAS,GAChC,EAAY,QAAW,QAAU,KACjC,CAAC,GAAG,EAAY,OAAU,CAAC,CAAC,MAAO,GAAS,EAAK,WAAW,CAAC,GAAK,EAAE,GAAK,CACvE,QAAS,EAAY,OACvB,EACF,IAAK,EAAY,kBAAuB,YACtC,EAAY,kBAAuB,WACnC,EAAY,kBAAuB,WAAa,CAChD,gBAAiB,EAAY,eAC/B,EACA,GAAI,EAAY,kBAAuB,YACrC,OAAO,EAAY,cAAoB,UACvC,OAAO,cAAc,EAAY,YAAe,GAChD,EAAY,cAAmB,GAC/B,OAAO,EAAY,kBAAwB,UAC3C,OAAO,cAAc,EAAY,gBAAmB,GACpD,EAAY,kBAAuB,GACnC,OAAO,EAAY,aAAmB,UACtC,OAAO,cAAc,EAAY,WAAc,GAC/C,EAAY,cACV,EAAY,aAAkB,EAAY,kBAAuB,CACjE,aAAc,EAAY,aAC1B,iBAAkB,EAAY,iBAC9B,YAAa,EAAY,WAC3B,EACF,GAAI,EAAY,cAAmB,WACjC,OAAO,EAAY,mBAAyB,UAAY,CACtD,kBAAmB,EAAY,iBACjC,CACJ,CAAC,CAEL,CAMI,IAAU,gCACZ,EAAI,eAAe,kBAAkB,EAAI,MAAM,WAAW,CAAC,EAC3D,EAAI,kBAAkB,EAAI,eAAe,gBAAgB,CAAC,EAE9D,EACA,GAAI,GAAe,CAAE,aAAY,EAEjC,GAAI,EAAI,iBAAmB,CAAE,gBAAiB,EAAI,eAAgB,CACpE,CACF,CCvMA,eAAsB,GACpB,EACA,EACiB,CACjB,IAAM,EAAW,MAAM,EAAI,MAAM,OAAO,CACtC,GAAG,EACH,GAAG,GAAmB,EAAK,EAAQ,YAAY,CACjD,CAAC,EAGD,OAFA,EAAI,eAAe,kBAAkB,EAAI,MAAM,WAAW,CAAC,EAC3D,EAAI,kBAAkB,EAAI,eAAe,gBAAgB,CAAC,EACnD,CACT,CCLA,SAAgB,EACd,EACA,EACmB,CACnB,IAAM,EAAM,EAAM,iBAAiB,EAAM,GAAG,EAC5C,MAAO,CACL,OAAQ,KAAO,IAAW,CACxB,IACG,EAAO,OAAS,iBAAmB,EAAO,OAAS,oBACpD,EAAO,WACP,CACA,IAAM,EAAQ,gBAAgB,CAAM,EACpC,EAAM,WAAY,MAAQ,CACxB,KAAM,gBACN,QAAS,EACT,MAAO,CACL,UAAW,EAAM,UACjB,MACA,SAAU,EAAM,SAChB,YAAa,EAAO,WACtB,CACF,EACA,MAAM,EAAQ,OAAO,CAAK,CAC5B,MAAO,MAAM,EAAQ,OAAO,CAAM,CACpC,CACF,CACF,CAGA,SAAgB,GACd,EACA,EACA,EACA,EACA,EAC8B,CAC9B,IAAI,EACE,EAAQ,EAAiB,CAAG,EAClC,MAAO,CACL,KAAM,KAAO,IAAgB,CAC3B,IAAM,EAAU,gBAAgB,MAAM,EAAQ,KAAK,CAAW,CAAC,EAC3D,EACJ,IAAK,IAAM,KAAU,EAAS,CAC5B,GAAI,EAAO,OAAS,iBAAmB,EAAO,OAAS,kBAAmB,SAC1E,IAAM,EAAQ,EAAO,YAAY,MAC3B,EAAQ,GAAO,MACrB,GACE,CAAC,GACD,EAAM,OAAS,iBACf,EAAM,UAAY,GAClB,CAAC,GACD,EAAM,YAAc,GACpB,EAAM,MAAQ,GACd,EAAM,cAAgB,GACtB,OAAO,EAAM,UAAa,WACzB,IAAa,IAAA,IAAa,IAAa,EAAM,UAC7C,EAAM,UAAY,EAAO,YAAY,cAAc,QAAQ,sBAAwB,GAEpF,MAAM,IAAI,EACR,6BACA,qDACF,EAEF,EAAW,EAAM,QACnB,CACA,GAAI,IAAa,IAAA,GACf,MAAM,IAAI,EACR,6BACA,oCACF,EAGF,MAFA,GAAS,EAAwB,EAAS,CAAE,YAAW,MAAK,WAAU,kBAAiB,CAAC,EACxF,EAAU,CAAQ,EACX,CACT,EACA,OAAQ,KAAO,IAAoC,CACjD,GAAI,CAAC,EACH,MAAM,IAAI,EACR,6BACA,qCACF,EACF,MAAM,EAAO,OAAO,CAAM,CAC5B,CACF,CACF,CC/FA,SAAgB,GACd,EAUA,CACA,MAAO,CACL,GAAI,GAAS,mBAAqB,IAAA,IAAa,CAAE,iBAAkB,EAAQ,gBAAiB,EAC5F,GAAI,GAAS,yBAA2B,IAAA,IAAa,CACnD,uBAAwB,EAAQ,sBAClC,EACA,GAAI,GAAS,WAAa,IAAA,IAAa,CAAE,SAAU,EAAQ,QAAS,EACpE,GAAI,GAAS,aAAe,IAAA,IAAa,CAAE,WAAY,EAAQ,UAAW,EAC1E,GAAI,GAAS,aAAe,IAAA,IAAa,CAAE,WAAY,EAAQ,UAAW,EAC1E,GAAI,GAAS,eAAiB,IAAA,IAAa,CAAE,aAAc,EAAQ,YAAa,EAGhF,GAAI,GAAS,WAAa,IAAQ,CAAE,oBAAqB,EAAK,CAChE,CACF,CCHA,MAAMC,GAAS,EAAa,YAAY,EAUxC,eAAsB,GACpB,EACA,EACA,EACA,EACA,EACiB,CAEjB,IAAM,EAAe,GAAY,aAC3B,EAAe,EACjB,GAAY,QAAS,EAAc,EAAa,YAAY,EAC5D,IAAA,GACJ,EAAI,IAAI,OAAQ,CAAE,QAAS,CAAQ,CAAC,EAGpC,IAAM,EAAa,MAAM,EACvB,EAAI,MACJ,mBACA,CACE,WAAY,EAAI,UAChB,IAAK,EAAI,IACT,gBAAiB,mBACjB,aAAc,GAAY,EAC1B,OAAQ,GAAY,EACpB,GAAI,EAAI,iBAAmB,IAAA,IAAa,CAAE,gBAAiB,EAAI,cAAe,EAC9E,GAAI,EAAI,iBAAmB,IAAA,IAAa,CAAE,gBAAiB,EAAI,cAAe,EAC9E,IAAK,CACH,mBAAoB,EAAI,IACxB,kBAAmB,EAAI,SACzB,CACF,EACA,EAAI,kBACJ,CACF,EAGM,EAAa,CAAC,EAAI,mBAAoB,EAAW,MAAM,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK;CAAI,EAClF,EAAkB,EACpB,sBAAsB,EAAW,wBAAwB,IACzD,EAGE,EAAe,GAAkB,CAAe,EAChD,EAAyB,GAAY,uBACrC,EAAkB,CACtB,EACA,GAAI,GAAwB,KAAK,EAAI,CAAC,GAAoB,CAAsB,CAAC,EAAI,CAAC,CACxF,EACA,EAAI,eAAe,kBAAkB,EAAI,MAAM,WAAW,CAAC,EACvD,EAAI,eAAe,kBAAkB,CAAC,GAAG,EAAI,MAAM,WAAW,EAAG,GAAG,CAAe,CAAC,IAClF,GAAY,iBACd,MAAM,EAAI,QAAQ,EAAa,EAAc,EAAW,gBAAgB,EAExE,MAAO,EAAe,EAAI,QAAQ,EAAa,CAAY,EAAI,EAAI,QAAQ,CAAW,IAK1F,EAAI,wBAAwB,EAE5B,IAAM,EAAU,EAAI,MAAM,WAAW,EAC/B,EAAc,KAAK,UAAU,CAAO,EACpC,EAAuB,GAAwB,EAAI,UAAU,EACnE,EAAI,IAAI,UAAW,CACjB,cAAe,EAAQ,OACvB,aAAc,EAAY,OAC1B,iBAAkB,KAAK,KAAK,EAAY,OAAS,CAAgC,EACjF,MAAO,EACP,UACA,MAAO,EAAI,MACX,SAAU,EAAI,WAAW,KACzB,UAAW,EAAI,eAAe,gBAAgB,CAAC,CAAC,UAChD,yBAA0B,EAAqB,eAAe,UAAU,UACxE,uBAAwB,EAAqB,eAAe,UAAU,QACtE,wBAAyB,EAAqB,eAAe,SAAS,UACtE,sBAAuB,EAAqB,eAAe,SAAS,OACtE,CAAC,EACD,EAAI,eAAe,kBAAkB,CAAC,GAAG,EAAS,GAAG,CAAe,CAAC,EACrE,EAAI,kBAAkB,EAAI,eAAe,gBAAgB,CAAC,EAE1D,IAAI,EACJ,GAAI,CAWF,GAVA,EAAW,MAAM,EAAI,MAAM,IAAI,EAAiB,CAC9C,OAAQ,EACR,mBAAoB,EAAI,UAAY,EAEpC,GAAG,GAAkB,CAAU,EAC/B,GAAG,GAAmB,EAAK,CAAY,CACzC,CAAC,EAIG,EAAY,QACd,MAAM,IAAI,aAAa,UAAW,YAAY,CAElD,OAAS,EAAO,CACd,GAAI,aAAiB,EAAyB,MAAM,EACpD,GAAI,CACF,EAAI,IAAI,QAAS,CACf,QAAS,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EAC9D,MAAO,aAAiB,MAAS,EAAM,OAAS,GAAM,GACtD,cAAe,EAAI,MAAM,WAAW,CAAC,CAAC,MACxC,CAAC,CACH,MAAQ,CAER,CAoBA,MAnBA,EACE,EAAI,MACJ,cACA,CACE,WAAY,EAAI,UAChB,IAAK,EAAI,IACT,gBAAiB,cACjB,OAAQ,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EAC7D,iBAAkB,GAClB,GAAI,EAAI,iBAAmB,IAAA,IAAa,CAAE,gBAAiB,EAAI,cAAe,EAC9E,GAAI,EAAI,iBAAmB,IAAA,IAAa,CAAE,gBAAiB,EAAI,cAAe,EAC9E,IAAK,CACH,mBAAoB,EAAI,IACxB,kBAAmB,EAAI,SACzB,CACF,EACA,EAAI,kBACJ,CACF,CAAC,CAAC,MAAO,GAAUA,GAAO,KAAK,cAAe,CAAE,OAAM,CAAC,CAAC,EAClD,CACR,CAGA,IAAM,EAAc,EAAI,MAAM,WAAW,EACnC,EAAmB,EAAY,IAAK,GAAQ,CAChD,IAAM,EACJ,cAAe,GAAO,MAAM,QAAQ,EAAI,SAAS,GAAK,EAAI,UAAU,OAAS,EACzE,EAAgB,EACjB,EAAI,UAAoD,IAAK,GAAO,EAAG,SAAS,IAAI,EACrF,CAAC,EACL,MAAO,CACL,KAAM,EAAI,KACV,cAAe,OAAO,EAAI,SAAY,SAAW,EAAI,QAAQ,OAAS,EACtE,eACA,gBACA,GAAI,EAAI,SAAW,CAAE,SAAU,EAAI,QAAS,EAAI,CAAC,CACnD,CACF,CAAC,EACD,EAAI,IAAI,YAAa,CACnB,QAAS,EACT,cAAe,EAAY,OAC3B,eAAgB,KAAK,UAAU,CAAW,CAAC,CAAC,OAC5C,QAAS,EACT,kBACF,CAAC,EAGD,EAAI,eAAe,kBAAkB,CAAW,EAEhD,IAAM,EAAW,EAAI,eAAe,gBAAgB,EA+BpD,OA9BA,EAAI,kBAAkB,CAAQ,EAC9B,EAAI,IAAI,UAAW,CACjB,UAAW,EAAS,UACpB,WAAY,EAAS,WACrB,eAAgB,EAAS,eACzB,oBAAqB,EAAS,mBAChC,CAAC,EAGD,EACE,EAAI,MACJ,OACA,CACE,WAAY,EAAI,UAChB,IAAK,EAAI,IACT,gBAAiB,OACjB,SAAU,EAAS,UAAU,EAAG,GAAG,EACnC,uBAAwB,EACxB,iBAAkB,GAClB,GAAI,EAAI,iBAAmB,IAAA,IAAa,CAAE,gBAAiB,EAAI,cAAe,EAC9E,GAAI,EAAI,iBAAmB,IAAA,IAAa,CAAE,gBAAiB,EAAI,cAAe,EAC9E,IAAK,CACH,mBAAoB,EAAI,IACxB,kBAAmB,EAAI,SACzB,CACF,EACA,EAAI,kBACJ,CACF,CAAC,CAAC,MAAO,GAAUA,GAAO,KAAK,cAAe,CAAE,OAAM,CAAC,CAAC,EAEjD,CACT,CCtMA,SAAgB,GACd,EACsC,CACtC,IAAM,EAAc,EAAM,SAAS,EAAE,EAAE,YACvC,OAAO,EAAc,CAAE,cAAa,SAAU,gBAAgB,EAAM,QAAQ,CAAE,EAAI,IAAA,EACpF,CAEA,SAAS,GAAU,EAAuC,CACxD,IAAM,EAAQ,EAAQ,cAAe,GAAY,EAAQ,OAAS,WAAW,EACvE,EAAY,EAAQ,GAC1B,GAAI,GAAW,OAAS,YAAa,OACrC,IAAM,EAAW,IAAI,IACnB,EACG,MAAM,EAAQ,CAAC,CAAC,CAChB,QAAS,GAAa,EAAQ,OAAS,OAAS,CAAC,EAAQ,UAAU,EAAI,CAAC,CAAE,CAC/E,EACA,MAAO,CACL,YACA,MAAO,EAAU,WAAa,CAAC,EAAA,CAAG,OAAQ,GAAS,CAAC,EAAS,IAAI,EAAK,EAAE,CAAC,EACzE,OAAQ,EAAQ,MAAM,EAAQ,CAAC,CAAC,CAAC,MAAO,GAAY,EAAQ,OAAS,MAAM,CAC7E,CACF,CAOA,SAAgB,EACd,EACA,EACA,EACsC,CACtC,IAAM,EAAQ,GAAU,CAAO,EACzB,EAAc,GAAO,UAAU,UAAU,YAC/C,GAAI,CAAC,GAAO,QAAU,CAAC,EAAM,KAAK,QAAU,OAAO,GAAgB,SAAU,OAC7E,IAAM,EAAO,IAAI,IAAI,EAAM,KAAK,IAAK,GAAS,EAAK,EAAE,CAAC,EAChD,EACJ,GAAU,cAAgB,EACtB,EAAS,SAAS,OACf,GAAY,EAAK,IAAI,EAAQ,UAAU,GAAK,CAAC,EAAS,IAAI,EAAQ,QAAQ,CAC7E,EACA,CAAC,EACP,MAAO,CAAE,cAAa,SAAU,gBAAgB,CAAQ,CAAE,CAC5D,CAGA,SAAgB,GACd,EACA,EACA,EAC8B,CAC9B,MAAO,CACL,KAAO,GAAgB,EAAQ,KAAK,CAAW,EAC/C,OAAS,IACH,EAAO,OAAS,sBAClB,EAAS,IAAI,EAAO,QAAQ,EAC5B,IAAc,GAET,EAAQ,OAAO,CAAM,EAEhC,CACF,CAMA,SAAgB,GACd,EACA,EACA,EACqB,CACrB,IAAM,EAAQ,GAAU,CAAO,EAC/B,GAAI,CAAC,GAAS,EAAM,UAAU,UAAU,cAAgB,EAAQ,YAAa,MAAO,CAAC,EACrF,IAAM,EAAU,IAAI,KAAK,EAAY,EAAQ,SAAW,CAAC,EAAA,CAAG,IAAK,GAAY,EAAQ,UAAU,CAAC,EAChG,OAAO,EAAM,KAAK,IAAK,GAAS,CAC9B,IAAM,EAAQ,EAAQ,IAAI,EAAK,EAAE,EAC7B,2EACA,kFACJ,OAAO,GAAkB,UAAU,IAAS,CAC1C,WAAY,EAAK,GACjB,KAAM,EAAK,SAAS,KACpB,SAAU,CAAE,QAAS,GAAO,QAAO,SAAU,EAAK,SAAS,IAAK,CAClE,CAAC,CACH,CAAC,CACH,CAGA,eAAsB,GACpB,EACA,EACkC,CAClC,GAAI,CAAC,GAAW,OAAO,EAAQ,QAAW,YAAc,OAAO,EAAQ,MAAS,WAC9E,MAAM,IAAI,EACR,6BACA,2DACF,EACF,GAAI,CACF,MAAO,CAAE,OAAQ,YAAa,SAAU,MAAM,EAAI,CAAE,CACtD,OAAS,EAAO,CACd,GAAI,aAAiB,EACnB,MAAO,CAAE,OAAQ,UAAW,SAAU,gBAAgB,EAAM,QAAQ,CAAE,EACxE,MAAM,CACR,CACF,CCtHA,IAAa,GAAb,KAAiC,CAIZ,MACA,MACA,UACA,IANnB,UAA0D,KAE1D,YACE,EACA,EACA,EACA,EACA,CAJiB,KAAA,MAAA,EACA,KAAA,MAAA,EACA,KAAA,UAAA,EACA,KAAA,IAAA,CAChB,CAMH,MAAM,OACJ,EACA,EACA,EAC+B,CAC/B,IAAM,EAAa,KAAK,MAAM,MAAM,EAC9B,EAAS,EAAiB,EAAY,GAAS,MAAM,EAC3D,GAAI,CAeF,OAdA,EAAW,OAAO,eAAe,EACjC,KAAK,UAAY,KAAK,MAAM,kBAAkB,EAAM,EAAY,CAC9D,SAAU,EACV,aACA,YAAa,GAAS,aAAe,EAAS,EAC9C,UAAW,KAAK,UAChB,OAAQ,EAAW,OAGnB,sBAAuB,GAAS,uBAAyB,OACzD,GAAI,GAAS,wBAA0B,eAAiB,KAAK,MAAQ,IAAA,GACjE,CAAE,IAAK,KAAK,SAAS,EAAW,MAAM,CAAE,EACxC,CAAC,CACP,CAAC,EACM,MAAM,KAAK,SACpB,QAAU,CACR,KAAK,UAAY,KACjB,EAAO,EACP,KAAK,MAAM,QAAQ,CAAU,CAC/B,CACF,CAGA,SAAiB,EAA8C,CAC7D,IAAM,EAAM,KAAK,IACjB,GAAI,IAAQ,IAAA,GAAW,MAAU,MAAM,aAAa,EACpD,OAAQ,EAAS,IAAY,EAAI,EAAS,CAAE,OAAQ,GAAS,QAAU,CAAO,CAAC,CACjF,CAEA,MAAM,OAAuB,CAC3B,MAAM,KAAK,SACb,CACF,EAGA,SAAgB,EAAiB,EAA6B,EAAkC,CAC9F,IAAM,MAAoB,EAAW,MAAM,GAAQ,MAAM,EAGzD,OAFI,GAAQ,QAAS,EAAM,EACtB,GAAQ,iBAAiB,QAAS,EAAO,CAAE,KAAM,EAAK,CAAC,MAC/C,GAAQ,oBAAoB,QAAS,CAAK,CACzD,CCuBA,IAAa,GAAb,cAA6B,EAAY,CACvC,MAMA,aAAiD,IAAI,GACrD,mBACA,eACA,eACA,eACA,yBACA,UACA,WACA,YACA,MACA,cACA,aAAyB,EACzB,SACA,aACA,0BACA,MACA,kBACA,iBACA,oBACA,wBACA,wBACA,wBACA,kBACA,uBACA,cACA,SACA,uBACA,aACA,eAEA,aAAyD,CAAC,EAE1D,WAAoC,QAAQ,QAAQ,EACpD,aAAuB,GAEvB,cAQQ,CAAE,KAAM,OAAQ,EACxB,cAAwB,GAExB,IAAY,kBAAyD,CACnE,OAAO,KAAK,cAAc,OAAS,QAAU,IAAA,GAAY,KAAK,cAAc,OAC9E,CACA,WAAmB,EAA+C,EAAY,GAAa,CACrF,KAAK,cAAc,OAAS,kBAChC,KAAK,cAAgB,EAAU,CAAE,KAAM,UAAW,UAAS,WAAU,EAAI,CAAE,KAAM,OAAQ,EAC3F,CACA,gBAAgD,KAEhD,mBAA6B,GAE7B,eAEA,YAAY,EAA0B,CACpC,MAAM,EAAQ,GAAG,EACjB,GAAM,CAAE,QAAO,WAAU,iBAAkB,EAE3C,KAAK,SAAW,EAAQ,SACxB,KAAK,aAAe,EAAQ,aAC5B,KAAK,0BAA4B,EAAQ,kBACzC,KAAK,cAAgB,EACrB,KAAK,YAAc,EAAM,IAAK,GAAS,EAAK,MAAM,EAClD,KAAK,eAAiB,EAAQ,eAC9B,KAAK,cAAgB,EAAQ,cAC7B,KAAK,MAAQ,EAAQ,MACrB,KAAK,kBAAoB,EAAyB,EAAQ,kBAAmB,SAAS,EACtF,KAAK,iBAAmB,EAAQ,mBAAsB,GAAS,GAC/D,KAAK,oBAAsB,EAAQ,YACnC,KAAK,wBAA0B,EAAQ,gBACvC,KAAK,wBAA0B,EAAQ,gBACvC,KAAK,wBAA0B,EAAQ,gBACvC,KAAK,kBAAoB,EAAQ,UACjC,KAAK,uBAAyB,EAAQ,eACtC,KAAK,SAAW,EAAQ,SACxB,KAAK,MAAQ,EAAQ,OAAS,oBAC9B,KAAK,UAAY,EAAQ,WAAa,EAAgB,EACtD,KAAK,eACH,EAAQ,iBACP,EAAQ,kBAAoB,GAAc,EAAQ,mBAAqB,IAAA,KACxE,UACF,KAAK,eAAiB,EAAQ,gBAAkB,UAGhD,KAAK,yBAA2B,EAAQ,yBAA2B,GACnE,KAAK,eAAiB,EAAQ,eAC9B,KAAK,IAAI,eAAgB,CACvB,IAAK,KAAK,IACV,mBAAoB,EAAc,OAClC,aAAc,EACd,YAAa,KAAK,YAClB,MAAO,KAAK,MACZ,SAAU,EAAS,IACrB,CAAC,EACD,KAAK,WAAa,EAClB,GAAkB,EAAU,GAAU,EAAO,IAAS,KAAK,IAAI,EAAO,CAAI,CAAC,EAC3E,KAAK,mBAAqB,GACxB,EACA,KAAK,UACL,KAAK,QACC,KAAK,eACX,KAAK,cACP,EACA,KAAK,qBAAqB,KAAK,cAAc,EAC7C,KAAK,uBAAwB,GAAS,KAAK,qBAAqB,CAAI,CAAC,EACrE,GAAM,CAAE,iBAAgB,0BAA2B,GACjD,EACA,KAAK,MACL,KAAK,UACL,KAAK,GACP,EACA,KAAK,eAAiB,EACtB,KAAK,uBAAyB,EAC9B,KAAK,MAAQ,GACX,EACA,KAAK,mBACL,EACA,EACA,KAAK,MACL,EACA,KAAK,YACP,EACA,KAAK,aAAe,IAAI,GACtB,KAAK,MACL,KAAK,UACL,KAAK,UACL,EAAQ,GACV,EACA,GACE,KAAK,UACL,KAAK,IACL,KAAK,MACL,KAAK,kBACJ,GAAW,KAAM,KAAK,mBAAqB,GAC5C,KAAK,eACL,KAAK,cACP,CACF,CAOA,MAAM,IAAI,EAAiB,EAAmB,EAA+C,CAC3F,OAAO,KAAK,QAAQ,EAAS,EAAU,EAAS,EAAK,CACvD,CAGA,eACE,EACA,EACA,EACkC,CAClC,OAAO,OACC,KAAK,QAAQ,EAAS,EAAU,EAAS,EAAI,EACnD,EAAQ,gBACV,CACF,CAEA,MAAc,QACZ,EACA,EACA,EACA,EACiB,CACjB,IAAM,EAAU,EAAQ,CAAE,GAAG,CAAM,EAAI,IAAA,GACvC,GAAI,KAAK,aAAc,MAAU,MAAM,sCAAsC,EAC7E,KAAK,mBAAmB,EACxB,IAAM,EAAa,KAAK,UAAU,MAAM,EAClC,EAAS,EAAiB,EAAY,GAAS,MAAM,EACrD,CAAE,UAAW,EAEnB,KAAK,mBAAmB,UAAU,GAAS,WAAa,GAAM,CAAqB,EACnF,GAAI,CACF,EAAO,eAAe,EAGtB,MAAM,KAAK,wBAA0B,KAAK,kBAAkB,CAAC,EAC7D,IAAM,EAAa,GAAS,iBACxB,CACE,GAAG,EACH,iBAAkB,EAAwB,EAAQ,iBAAkB,CAClE,UAAW,KAAK,UAChB,IAAK,KAAK,IACV,SAAU,EAAQ,WAAa,GAC/B,iBAAkB,KAAK,gBACzB,CAAC,CACH,EACA,EACE,EAAW,MAAM,GACrB,EACA,EACA,KAAK,gBAAgB,GAAS,mBAAqB,IAAA,EAAS,EAC5D,EACA,CACF,EAIA,OAHA,KAAK,WAAW,IAAA,EAAS,EACzB,KAAK,mBAAmB,EACxB,KAAK,cAAgB,EACd,CACT,OAAS,EAAO,CAQd,MAPI,aAAiB,GACnB,KAAK,WAAW,GAAiB,CAAK,EAAG,EAAI,EAC7C,EAAO,eAAe,GACb,GAAS,kBAClB,KAAK,WAAW,EAAoB,KAAK,MAAM,WAAW,EAAG,IAAA,GAAW,IAAI,GAAK,CAAC,EAEpF,KAAK,iBAAiB,CAAK,EACrB,CACR,QAAU,CACR,KAAK,mBAAmB,QAAQ,EAChC,EAAO,EACP,KAAK,UAAU,QAAQ,CAAU,CACnC,CACF,CAGA,MAAM,OAAO,EAAiD,CAC5D,OAAO,KAAK,WAAW,EAAS,EAAK,CACvC,CAGA,kBAAkB,EAA6E,CAC7F,OAAO,OAAkC,KAAK,WAAW,EAAS,EAAI,EAAG,EAAQ,OAAO,CAC1F,CAEA,MAAc,WACZ,EACA,EACiB,CACjB,IAAM,EAAU,CAAE,GAAG,EAAO,cAAe,gBAAgB,EAAM,aAAa,CAAE,EAChF,GAAI,KAAK,aAAc,MAAU,MAAM,sCAAsC,EAE7E,GADA,KAAK,wBAAwB,EACzB,KAAK,kBAAoB,KAAK,iBAAiB,cAAgB,EAAQ,YACzE,MAAM,IAAI,EACR,8BACA,0CACF,EACF,IAAM,EAAa,KAAK,UAAU,MAAM,EAClC,EAAS,EAAiB,EAAY,EAAQ,MAAM,EACpD,EAAW,IAAI,IACrB,GAAI,CACF,EAAW,OAAO,eAAe,EACjC,MAAM,KAAK,wBAA0B,KAAK,kBAAkB,CAAC,EAC7D,IAAM,EAAU,GACd,GAAqB,EAAQ,QAAS,MAAgB,CAChD,KAAK,cAAc,OAAS,YAAW,KAAK,cAAc,UAAY,GAC5E,CAAC,EACD,KAAK,UACL,KAAK,IACJ,GAAa,KAAK,mBAAmB,UAAU,EAAU,CAAqB,EAC/E,KAAK,gBACP,EACM,EAAW,MAAM,GAAc,KAAK,gBAAgB,EAAI,EAAG,CAC/D,GAAG,EACH,UACA,OAAQ,EAAW,MACrB,CAAC,EAGD,OAFA,KAAK,WAAW,IAAA,EAAS,EACzB,KAAK,mBAAmB,EAAQ,WAAW,EACpC,CACT,OAAS,EAAO,CAWd,MAVI,aAAiB,GACnB,KAAK,WAAW,GAAiB,CAAK,EAAG,EAAI,EAC7C,EAAW,OAAO,eAAe,GAGjC,KAAK,WACH,EAAoB,KAAK,MAAM,WAAW,EAAG,KAAK,iBAAkB,CAAQ,CAC9E,EAEF,KAAK,iBAAiB,CAAK,EACrB,CACR,QAAU,CACR,KAAK,mBAAmB,QAAQ,EAChC,EAAO,EACP,KAAK,UAAU,QAAQ,CAAU,CACnC,CACF,CAGA,qBAA4D,CAC1D,OAAO,gBAAgB,KAAK,gBAAgB,CAC9C,CAOA,wBAAwB,EAA2B,CACjD,GAAI,KAAK,aAAc,MAAU,MAAM,sCAAsC,EAC7E,GAAI,KAAK,cAAc,OAAS,gBAAiB,CAC/C,GAAI,KAAK,cAAc,cAAgB,GAAe,KAAK,iBACzD,MAAM,IAAI,EACR,8BACA,oDACF,EACF,KAAK,WAAW,EAChB,MACF,CACA,IAAM,EAAU,KAAK,iBACrB,GAAI,CAAC,EAAS,OACd,GAAI,EAAQ,cAAgB,EAC1B,MAAM,IAAI,EACR,8BACA,0CACF,EACF,IAAM,EAAa,KAAK,UAAU,MAAM,EACxC,GAAI,CACF,IAAM,EAAY,KAAK,cAAc,OAAS,WAAa,KAAK,cAAc,UAC9E,IAAK,IAAM,KAAW,GAAsB,KAAK,MAAM,WAAW,EAAG,EAAS,CAAS,EACrF,KAAK,MAAM,iBAAiB,CAAO,EACrC,KAAK,WAAW,IAAA,EAAS,EACzB,KAAK,mBAAmB,CAAW,CACrC,QAAU,CACR,KAAK,UAAU,QAAQ,CAAU,CACnC,CACF,CAGA,oBAA2B,CACzB,GAAI,KAAK,aAAc,MAAU,MAAM,sCAAsC,EAE7E,GADA,KAAK,wBAAwB,EACzB,KAAK,iBACP,MAAM,IAAI,EACR,8BACA,6EACF,CACJ,CACA,yBAAwC,CACtC,GAAI,KAAK,eAAiB,KAAK,cAAc,OAAS,gBACpD,MAAM,IAAI,EACR,8BACA,0EACF,CACJ,CACA,yBAAwD,CACtD,OAAO,gBAAgB,CACrB,GAAI,KAAK,UACT,IAAK,KAAK,iBAAiB,KAAK,GAAG,EACnC,SAAU,KAAK,MAAM,WAAW,EAChC,GAAI,KAAK,iBAAmB,CAAE,iBAAkB,KAAK,gBAAiB,EAAI,CAAC,CAC7E,CAAC,CACH,CAEA,oBAAoB,EAA8C,CAEhE,GADA,KAAK,mBAAmB,EAEtB,KAAK,UAAU,UAAU,GACzB,KAAK,MAAM,WAAW,CAAC,CAAC,KAAM,GAAY,EAAQ,OAAS,QAAQ,EAEnE,MAAU,MAAM,uDAAuD,EACzE,GACE,EAAS,KAAO,KAAK,WACrB,KAAK,iBAAiB,EAAS,GAAG,IAAM,KAAK,iBAAiB,KAAK,GAAG,EAEtE,MAAM,IAAI,EACR,8BACA,yCACF,EACF,IAAM,EAAU,EAA+B,CAC7C,GAAI,EAAS,GACb,IAAK,EAAS,IACd,SAAU,EAAS,SACnB,GAAI,EAAS,mBAAqB,IAAA,GAC9B,CAAC,EACD,CAAE,iBAAkB,EAAS,gBAAiB,EAClD,UAAW,IAAI,KAAK,CAAA,CAAE,YAAY,EAClC,UAAW,IAAI,KAAK,CAAA,CAAE,YAAY,CACpC,CAAC,EACD,GAAI,EAAQ,SAAW,QACrB,MAAM,IAAI,EACR,6BACA,wCACF,EACF,IAAM,EAAe,gBAAgB,EAAQ,MAAM,EACnD,KAAK,MAAM,aAAa,EACxB,IAAK,IAAM,KAAW,EAAa,SAAU,KAAK,MAAM,iBAAiB,CAAO,EAChF,KAAK,WAAW,EAAa,gBAAgB,EAC7C,KAAK,uBAAuB,CAC9B,CAEA,YAAmB,CACjB,GAAI,KAAK,UAAU,UAAU,GAAK,KAAK,cACrC,MAAU,MAAM,+CAA+C,EACjE,GAAI,CAAC,KAAK,2BAA6B,CAAC,KAAK,aAC3C,MAAU,MAAM,gDAAgD,EAClE,KAAK,mBACH,KAAK,cAAc,OAAS,gBAAkB,KAAK,cAAc,YAAc,IAAA,EACjF,CACF,CACA,mBAA2B,EAA4B,CACrD,GAAI,CAAC,KAAK,2BAA6B,CAAC,KAAK,aAAc,OAC3D,GAAI,KAAK,cAAe,MAAU,MAAM,8BAA8B,EACtE,IAAM,EAAU,KAAK,iBACf,EAAY,KAAK,cAAc,OAAS,SAAW,KAAK,cAAc,UAC5E,KAAK,cAAgB,GACrB,GAAI,CACE,KAAK,0BACP,GAA4B,KAAK,0BAA0B,CAAC,EACzD,KAAK,uBAAuB,EAAI,EACrC,KAAK,cAAgB,EAAU,CAAE,KAAM,UAAW,UAAS,WAAU,EAAI,CAAE,KAAM,OAAQ,CAC3F,OAAS,EAAO,CAOd,KANA,MAAK,cAAgB,CACnB,KAAM,gBACN,UACA,YACA,YAAa,GAAe,GAAS,WACvC,EACM,CACR,QAAU,CACR,KAAK,cAAgB,EACvB,CACF,CACA,iBAAyB,EAAsB,CACzC,QAAK,cAAc,OAAS,gBAChC,GAAI,CACF,KAAK,mBAAmB,CAC1B,OAAS,EAAW,CAClB,GAAI,aAAiB,EAAyB,MAAM,EACpD,GAAI,CACF,KAAK,IAAI,QAAS,CAChB,QAAS,mEAAmE,aAAqB,MAAQ,EAAU,QAAU,OAAO,CAAS,IAC7I,MAAO,aAAqB,MAAS,EAAU,OAAS,GAAM,GAC9D,cAAe,KAAK,MAAM,WAAW,CAAC,CAAC,MACzC,CAAC,CACH,MAAQ,CAER,CACF,CACF,CAaA,SAAS,EAAqE,CAK5E,OAJA,KAAK,mBAAmB,EACpB,KAAK,aACA,QAAQ,OAAW,MAAM,sCAAsC,CAAC,EAElE,KAAK,oBAAoB,SAAY,CAC1C,IAAM,EAAQ,IAAI,IAAI,CACpB,GAAG,KAAK,YAAY,IAAK,GAAW,EAAO,IAAI,EAC/C,GAAG,KAAK,aAAa,IAAK,GAAS,EAAK,OAAO,IAAI,CACrD,CAAC,EACK,EAAiC,CAAC,EACxC,IAAK,IAAM,KAAQ,EACb,EAAM,IAAI,EAAK,OAAO,IAAI,IAC9B,EAAM,IAAI,EAAK,OAAO,IAAI,EAC1B,EAAM,KAAK,CAAI,GAKjB,OAHI,EAAM,SAAW,EAAU,CAAC,GAChC,KAAK,aAAa,KAAK,GAAG,CAAK,EAC1B,KAAK,UAAU,UAAU,GAAG,MAAM,KAAK,kBAAkB,EACvD,EAAM,IAAK,GAAS,EAAK,OAAO,IAAI,EAC7C,CAAC,CACH,CAGA,oBAA+B,EAAsC,CACnE,IAAM,EAAS,KAAK,WAAW,KAAK,CAAM,EAK1C,MAJA,MAAK,WAAa,EAAO,SACjB,IAAA,OACA,IAAA,EACR,EACO,CACT,CAGA,MAAc,mBAAmC,CAC/C,GAAI,KAAK,aAAa,SAAW,EAAG,OACpC,IAAM,EAAQ,KAAK,aAAa,OAAO,CAAC,EACxC,MAAM,KAAK,MAAM,YAAY,EAC7B,IAAM,EAAU,KAAK,mBAAmB,UAAU,KAAK,iBAAiB,CAAK,GAAK,CAAK,EACvF,MAAM,KAAK,MAAM,YAAY,CAAC,GAAI,KAAK,MAAM,UAAU,CAAC,CAAC,OAAS,CAAC,EAAI,GAAG,CAAO,CAAC,EAClF,KAAK,YAAY,KAAK,GAAG,EAAQ,IAAK,GAAS,EAAK,MAAM,CAAC,CAC7D,CAEA,MAAM,kBAA2C,CAC/C,GAAI,KAAK,aAAc,MAAU,MAAM,sCAAsC,EAC7E,OAAO,KAAK,MAAM,iBAAiB,CACrC,CAEA,MAAM,kBACJ,EACA,EACA,EAC+B,CAC/B,GAAI,KAAK,aAAc,MAAU,MAAM,sCAAsC,EAE7E,OADA,KAAK,mBAAmB,EACjB,KAAK,aAAa,OAAO,EAAM,EAAY,CAAO,CAC3D,CAMA,iBAAiC,CAC/B,OAAO,KAAK,YACd,CAEA,IAAY,EAAe,EAA6B,CACtD,KAAK,eAAe,IAAI,KAAK,UAAW,EAAO,CAAI,CACrD,CAEA,uBAA+B,EAAS,GAAa,CACnD,GAAI,CAAC,KAAK,aAAc,OACxB,IAAM,EAAU,GAAe,CAC7B,UAAW,KAAK,UAChB,IAAK,KAAK,IACV,aAAc,KAAK,cACnB,YAAa,KAAK,YAClB,aAAc,KAAK,aACnB,aAAc,KAAK,wBAAwB,EAC3C,mBAAsB,KAAK,eAAe,CAC5C,CAAC,EACD,GAAI,GAAU,EAAQ,SAAW,QAC/B,MAAU,MAAM,sDAAsD,EAAQ,QAAQ,CAE1F,CAQA,SAAS,EAAmC,CAAC,EAAkB,CAC7D,GAAI,KAAK,gBAAiB,OAAO,KAAK,gBACtC,KAAK,aAAe,GACpB,IAAM,EAAS,EAAQ,QAAU,QAC3B,EAAO,MAAO,EAAe,IAAmD,CACpF,GAAI,CACF,MAAM,EAAI,CACZ,OAAS,EAAO,CAEd,KAAK,IAAI,8BAA+B,CACtC,KAAM,EACN,MAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAC9D,CAAC,CACH,CACF,EAwBA,MAvBA,MAAK,iBAAmB,SAAY,CAClC,MAAM,EAAK,YAAe,KAAK,MAAM,CAAC,EACtC,MAAM,EAAK,wBAA2B,KAAK,aAAa,MAAM,CAAC,EAC/D,MAAM,EAAK,iBAAoB,KAAK,UAAU,OAAO,EACrD,KAAK,IAAI,mBAAoB,CAAE,QAAO,CAAC,EACvC,MAAM,EAAK,cAAiB,KAAK,uBAAuB,CAAC,EACzD,MAAM,EAAK,uBACT,GACE,KAAK,UACL,KAAK,IACL,EACA,KAAK,MACL,KAAK,kBACL,KAAK,eACL,KAAK,cACP,CACF,EAGA,MAAM,EAAK,gBAAiB,SAAY,CACtC,MAAM,KAAK,MAAM,QAAQ,CAC3B,CAAC,CACH,EAAA,CAAG,EACI,KAAK,eACd,CAEA,aACE,EACA,EACA,EACM,CACN,KAAK,mBAAmB,EACxB,KAAK,MAAM,oBAAoB,EAAa,EAAO,CAAO,EACtD,oBAAqB,IACvB,EAEE,iBAAmB,EAAc,IACjC,KAAK,IAAI,cAAe,CAAE,KAAM,EAAM,GAAG,CAAM,CAAC,GAEpD,KAAK,WAAa,EAClB,KAAK,eAAe,CAAK,CAC3B,CAEA,MAAM,QACJ,EACA,EAA2B,SAC3B,EACA,EACe,CACf,KAAK,mBAAmB,EACxB,IAAM,EAAa,KAAK,UAAU,MAAM,EAClC,EAAS,EAAiB,EAAY,CAAM,EAClD,GAAI,CACF,MAAM,KAAK,YAAY,EAAc,EAAS,EAAW,OAAQ,IAAA,GAAW,CAAgB,CAC9F,QAAU,CACR,EAAO,EACP,KAAK,UAAU,QAAQ,CAAU,CACnC,CACF,CAGA,MAAc,YACZ,EACA,EACA,EACA,EACA,EACe,CACf,IAAM,EAAS,CACb,cAAe,KAAK,cACpB,uBAAwB,KAAK,uBAC7B,kBAAmB,KAAK,kBACxB,uBAAwB,KAAK,uBAC7B,UACA,GAAI,EAAe,CAAE,cAAa,EAAI,CAAC,EACvC,GAAI,EAAmB,CAAE,kBAAiB,EAAI,CAAC,CACjD,EACA,MAAM,GAAQ,EAAc,GAAoB,KAAK,gBAAgB,EAAG,CAAM,EAAG,CAAM,CACzF,CAEA,gBAAwB,EAAY,GAAoB,CACtD,MAAO,CACL,UAAW,KAAK,UAChB,IAAK,KAAK,IACV,MAAO,KAAK,MACZ,OAAQ,KAAK,eAAe,EAC5B,MAAO,KAAK,MACZ,WAAY,KAAK,WACjB,eAAgB,KAAK,eACrB,MAAO,KAAK,MACZ,kBAAmB,KAAK,kBACxB,mBAAoB,KAAK,mBACzB,KAAM,EAAe,IAA0B,KAAK,IAAI,EAAO,CAAI,EACnE,SAAU,EAAQ,EAAc,IAC9B,KAAK,YAAY,IAAA,GAAW,OAAQ,EAAQ,EAAc,CAAgB,EAC5E,mBAAsB,KAAK,uBAAuB,EAClD,kBACE,KAAK,2BAA6B,KAAK,iBAC7B,CACA,GACF,KAAK,WACH,EAAoB,KAAK,MAAM,WAAW,EAAG,KAAK,iBAAkB,IAAI,GAAK,CAC/E,EACF,KAAK,mBAAmB,CAC1B,EACA,IAAA,GAEN,oBAAuB,CAAC,CAAC,KAAK,aAC9B,4BAA+B,KAAM,KAAK,mBAAqB,IAC/D,eAAgB,KAAK,eACrB,eAAgB,KAAK,eACrB,SAAU,KAAK,SACf,YAAa,KAAK,oBAClB,gBAAiB,KAAK,wBACtB,gBAAiB,KAAK,wBACtB,gBAAiB,KAAK,wBACtB,0BAA4B,GAC1B,KAAK,aAAa,KAChB,EAAqB,UACrB,CAAE,UAAW,IAAI,KAAQ,GAAG,CAAY,EACxC,CACE,UAAW,UACX,QAAS,KAAK,UACd,UAAW,CAAC,CAAE,KAAM,UAAW,GAAI,KAAK,SAAU,CAAC,CACrD,CACF,EACF,qBAAuB,GACrB,KAAK,aAAa,KAChB,GAAyB,SACzB,CACE,UAAW,IAAI,KACf,aAAc,EAAO,KAAK,SAC1B,UAAW,EAAO,KAAK,MACvB,WAAY,EAAO,GAAG,SACtB,QAAS,EAAO,GAAG,MACnB,OAAQ,EAAO,MACjB,EACA,CACE,UAAW,UACX,QAAS,KAAK,UACd,UAAW,CAAC,CAAE,KAAM,UAAW,GAAI,KAAK,SAAU,CAAC,CACrD,CACF,EACF,eAAgB,KAAK,YAAY,IAAK,GAAS,EAAK,IAAI,CAC1D,CACF,CACF,EChvBA,SAAS,GAAmB,EAAwB,CAClD,GAAI,CACF,OAAO,KAAK,MAAM,CAAK,CACzB,MAAQ,CACN,MAAU,MAAM,mDAAmD,CACrE,CACF,CAMA,SAAgB,GACd,EACA,EAA4C,CAAC,EACrC,CAER,IAAM,EAA+C,CACnD,cAAA,EACA,OAHc,EAAQ,OAAS,EAAQ,OAAO,CAAM,EAAI,CAI1D,EACA,OAAO,KAAK,UAAU,EAAU,KAAM,CAAC,CACzC,CAMA,SAAgB,GAA2B,EAA0C,CACnF,IAAM,EAAU,EAAwC,GAAmB,CAAK,CAAC,EACjF,GAAI,EAAQ,SAAW,cACrB,MAAU,MACR,+CAA+C,EAAQ,eAAiB,2BAA2B,uBAErG,EAEF,GAAI,EAAQ,SAAW,UAAW,CAIhC,IAAM,EAAQ,EAAQ,OAAO,MAAM,EAAG,CAAmB,EACnD,EAAS,EACZ,IAAK,GAAU,GAAG,EAAM,OAAS,GAAK,SAAW,EAAM,KAAK,IAAI,EAAM,SAAS,CAAC,CAChF,KAAK,IAAI,EACN,EACJ,EAAQ,OAAO,OAAS,EAAM,OAC1B,MAAM,EAAQ,OAAO,OAAS,EAAM,OAAO,QAC3C,GACN,MAAU,MAAM,6BAA6B,IAAS,GAAQ,CAChE,CACA,OAAO,EAAQ,MACjB,CC5FA,MAQa,GACX,qHAGF,SAAgB,EAAe,EAAsB,CACnD,OAAO,GAAsB,KAAK,CAAG,CACvC,CAEA,SAAS,EACP,EACA,EACA,EACkB,CAClB,GAAI,IAAQ,IAAA,IAAa,EAAe,CAAG,EACzC,OAAO,EAET,GAAI,MAAM,QAAQ,CAAK,EACrB,OAAO,EAAM,IAAK,GAAS,EAAW,IAAA,GAAW,EAA0B,CAAa,CAAC,EAG3F,GAAsB,OAAO,GAAU,UAAnC,GAA+C,EAAE,aAAiB,MAAO,CAC3E,IAAM,EAAS,EACT,EAAwC,CAAC,EAC/C,IAAK,GAAM,CAAC,EAAU,KAAe,OAAO,QAAQ,CAAM,EACxD,EAAI,GAAY,EAAW,EAAU,EAAY,CAAa,EAEhE,OAAO,CACT,CACA,OAAO,CACT,CAMA,SAAgB,GAAsB,EAAU,EAAwBC,aAA2B,CACjG,OAAO,EAAW,IAAA,GAAW,EAA2B,CAAa,CACvE,CClCA,SAAgB,GACd,EACA,EACA,EACA,EACiB,CACjB,IAAM,EAA8B,CAAC,EACrC,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAI,EAC5C,EAAW,GAAO,EAAkB,EAAW,EAAK,EAAO,EAAS,CAAmB,EAEzF,OAAO,CACT,CAEA,SAAS,EACP,EACA,EACA,EACA,EACA,EACkB,CAClB,GAAI,EAAe,CAAG,EACpB,OAAO,EAAQ,cAEjB,GACE,GAAU,MAEV,OAAO,GAAU,UACjB,OAAO,GAAU,SAEjB,OAAO,EAAwB,EAAW,EAAO,EAAS,CAAmB,EAE/E,GAAI,OAAO,GAAU,UACnB,OAAO,EAET,GAAI,aAAiB,KACnB,OAAO,EAAM,YAAY,EAE3B,GAAI,MAAM,QAAQ,CAAK,EAIrB,OAAO,EAAwB,EAHP,EAAM,IAAK,GACjC,EAAkB,EAAW,EAAK,EAA0B,EAAS,CAAmB,CAElC,EAAG,EAAS,CAAmB,EAEzF,GAAI,OAAO,GAAU,SAAU,CAC7B,IAAM,EAAS,EACT,EAAqD,CAAC,EAC5D,IAAK,GAAM,CAAC,EAAU,KAAe,OAAO,QAAQ,CAAM,EACxD,EAAiB,GAAY,EAC3B,EACA,EACA,EACA,EACA,CACF,EAEF,OAAO,EAAwB,EAAW,EAAkB,EAAS,CAAmB,CAC1F,CACA,OAAO,OAAO,CAAK,CACrB,CAEA,SAAS,EACP,EACA,EACA,EACA,EACkB,CAClB,IAAM,EAAa,KAAK,UAAU,CAAK,EAKvC,GAJI,IAAe,IAAA,IAGA,GAAe,CACrB,GAAK,EAAQ,+BAAiC,IAAwB,IAAA,GACjF,OAAO,EAGT,IAAM,EAAS,GAAU,CAAU,EACnC,OAAO,EAAoB,UAAU,EAAW,EAAQ,CAAU,CACpE,CCxEA,MAAM,GAAS,EAAa,mBAAmB,EAUzC,GAAuC,IAAI,IAAI,CAAC,YAAY,CAAC,EAgB7D,EAAc,IAAI,IAElB,GAAa,IAAI,QAEvB,SAAS,IAAyB,CAChC,IAAK,IAAM,KAAQ,EACjB,EAAK,MAAM,CAEf,CAEA,SAAS,GACP,EACA,EACM,CACN,EAAY,IAAI,CAAc,EAC1B,MAAsB,IAAA,IAAa,GAAW,IAAI,CAAiB,KACvE,GAAW,IAAI,CAAiB,EAChC,EAAkB,EAAgB,EACpC,CAgCA,IAAa,GAAb,KAAyD,CAQpC,KAPnB,QACA,kBAEA,QAA2B,IAAI,IAC/B,aAAuB,EAEvB,YACE,EACA,EAAqC,CAAC,EACtC,CAFiB,KAAA,KAAA,EAGjB,KAAK,QAAU,CACb,8BACE,EAAQ,+BAAiC,MAC3C,cAAe,EAAQ,eAAiB,YAC1C,EACA,KAAK,kBAAoB,EAAQ,iBACnC,CAEA,IAAI,EAAmB,EAAe,EAA6B,CAK5D,KAAgB,CAAS,EAC9B,GAAI,CACF,IAAM,EAAiB,GACrB,EACA,EACA,KAAK,QACL,KAAK,KAAK,mBACZ,EACM,EACJ,KAAK,UAAU,CACb,GAAG,EACH,cAAA,EACA,UAAW,IAAI,KAAK,CAAA,CAAE,YAAY,EAClC,YACA,OACF,CAAC,EAAI;EAEP,GAAI,GAAgB,IAAI,CAAK,EAAG,CAC9B,KAAK,OAAO,EAAW,CAAK,EAC5B,MACF,CAGA,KAAK,MAAM,EACX,KAAK,MAAM,EAAW,CAAK,CAC7B,OAAS,EAAO,CAId,KAAK,OAAO,EAAW,EAAO,CAAK,CACrC,CACF,CAGA,OAAc,CACZ,GAAI,KAAK,QAAQ,OAAS,EAAG,CAC3B,EAAY,OAAO,IAAI,EACvB,MACF,CACA,IAAM,EAAU,CAAC,GAAG,KAAK,QAAQ,QAAQ,CAAC,EAC1C,KAAK,QAAQ,MAAM,EACnB,KAAK,aAAe,EACpB,EAAY,OAAO,IAAI,EACvB,IAAK,GAAM,CAAC,EAAW,KAAU,EAC/B,GAAI,CACF,KAAK,MAAM,EAAW,EAAM,KAAK,EAAE,CAAC,CACtC,OAAS,EAAO,CACd,KAAK,OAAO,EAAW,QAAS,CAAK,CACvC,CAEJ,CAEA,OAAe,EAAmB,EAAqB,CACrD,GAAgB,KAAM,KAAK,iBAAiB,EAC5C,IAAM,EAAQ,KAAK,QAAQ,IAAI,CAAS,GAAK,CAAC,EAC9C,EAAM,KAAK,CAAK,EAChB,KAAK,QAAQ,IAAI,EAAW,CAAK,EACjC,KAAK,cAAgB,EAAM,OAGvB,KAAK,cAAgB,OACvB,KAAK,MAAM,CAEf,CAEA,MAAc,EAAmB,EAAoB,CACnD,KAAK,KAAK,OAAO,EAAW,CAAI,CAClC,CAEA,OAAe,EAAmB,EAAe,EAAsB,CACrE,GAAO,KAAK,2BAA4B,CACtC,YACA,QACA,MAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAC9D,CAAC,CACH,CACF,EAEa,GAAb,KAA2D,CACzD,KAAY,CAEZ,CACF,EChNA,MAAM,GAAiB,kBAEvB,SAAgB,GACd,EAC2B,CAC3B,GAAI,CAAC,GAA0C,CAAK,EAClD,MAAM,IAAI,EACR,oBACA,kDACF,EAEF,MAAO,CACL,KAAM,mBACN,SAAU,OACV,OAAQ,OAAO,EAAM,MAAM,CAAC,CAAC,YAAY,EACzC,WAAY,OAAO,EAAM,UAAU,EACnC,aAAc,OAAO,EAAM,YAAY,CACzC,CACF,CAGA,SAAgB,GAA0C,EAAyC,CACjG,IAAM,EAAO,OAAO,KAAK,CAAK,CAAC,CAAC,KAAK,EAC/B,EAAe,CAAC,aAAc,WAAY,OAAQ,eAAgB,QAAQ,EAChF,OACE,EAAK,SAAW,EAAa,QAC7B,EAAK,OAAO,EAAK,IAAU,IAAQ,EAAa,EAAM,GACtD,EAAM,OAAS,oBACf,EAAM,WAAa,QACnB,OAAO,EAAM,QAAW,UACxB,GAAe,KAAK,EAAM,MAAM,GAChC,OAAO,EAAM,YAAe,UAC5B,OAAO,cAAc,EAAM,UAAU,GACrC,EAAM,YAAc,GACpB,OAAO,EAAM,cAAiB,UAC9B,EAAM,aAAa,KAAK,CAAC,CAAC,OAAS,CAEvC,CCvBA,MAAM,GAAkB,IAAI,YAAY,QAAS,CAAE,UAAW,EAAK,CAAC,EAoBpE,SAAgB,GACd,EACA,EACS,CAET,OAAO,EAAa,EADN,GAAsB,CACL,EAAG,CAAC,CACrC,CAEA,SAAS,GAAsB,EAAgE,CAC7F,IAAM,EAAW,GAAc,WAAY,EAAQ,UAAY,EAAiB,EAC1E,EAAgB,GACpB,gBACA,EAAQ,eAAiB,QAC3B,EACA,MAAO,CACL,OAAQ,EAAQ,OAChB,WACA,gBACA,WAAY,EACZ,mBAAoB,IAAI,IACxB,cAAe,IAAI,OACrB,CACF,CAEA,SAAS,GAAc,EAAc,EAAuB,CAC1D,GAAI,CAAC,OAAO,SAAS,CAAK,GAAK,CAAC,OAAO,cAAc,CAAK,GAAK,EAAQ,EACrE,MAAM,IAAI,EACR,gBACA,GAAG,EAAK,+CACR,CAAE,OAAQ,OAAO,CAAK,CAAE,CAC1B,EAEF,OAAO,CACT,CAEA,SAAS,EAAa,EAAgB,EAAyB,EAAiC,CAC9F,GACE,IAAU,MACV,OAAO,GAAU,UACjB,OAAO,GAAU,WAChB,OAAO,GAAU,UAAY,OAAO,SAAS,CAAK,EAEnD,OAAO,EAET,GAAI,OAAO,GAAU,SACnB,MAAM,EAAiB,2DAA2D,EAEpF,GAAI,OAAO,GAAU,SACnB,MAAM,EAAiB,4CAA4C,OAAO,EAAM,EAAE,EAEpF,GAAI,EAAM,cAAc,IAAI,CAAK,EAC/B,MAAM,IAAI,EACR,qBACA,0EACA,CAAE,MAAO,CAAe,CAC1B,EAEF,GAAI,GAAoC,CAAK,EAC3C,OAAO,GAAiB,EAAO,EAAO,CAAc,EAEtD,GAAI,MAAM,QAAQ,CAAK,EAAG,CACxB,EAAM,cAAc,IAAI,CAAK,EAC7B,GAAI,CACF,OAAO,EAAM,IAAK,GAAS,EAAa,EAAM,EAAO,CAAc,CAAC,CACtE,QAAU,CACR,EAAM,cAAc,OAAO,CAAK,CAClC,CACF,CACA,GAAI,CAAC,GAAc,CAAK,EACtB,MAAM,EAAiB,yDAAyD,EAElF,EAAM,cAAc,IAAI,CAAK,EAC7B,GAAI,CACF,OAAO,OAAO,YACZ,OAAO,QAAQ,CAAK,CAAC,CAAC,KAAK,CAAC,EAAK,KAAW,CAC1C,EACA,EAAa,EAAO,EAAO,CAAc,CAC3C,CAAC,CACH,CACF,QAAU,CACR,EAAM,cAAc,OAAO,CAAK,CAClC,CACF,CAEA,SAAS,GACP,EACA,EACA,EACS,CACT,IAAM,EAAY,GAAiC,CAAK,EACxD,GAAI,GAAkB,EAAM,SAC1B,MAAM,IAAI,EACR,qBACA,sEAAsE,EAAM,SAAS,GACrF,CAAE,aAAc,EAAU,aAAc,MAAO,CAAe,CAChE,EAEF,GAAI,EAAM,SAAW,IAAA,GACnB,MAAM,IAAI,EACR,uBACA,uEACA,CAAE,aAAc,EAAU,aAAc,MAAO,CAAe,CAChE,EAEF,GAAI,EAAM,mBAAmB,IAAI,EAAU,YAAY,EACrD,MAAM,IAAI,EACR,qBACA,oBAAoB,EAAU,aAAa,4CAC3C,CAAE,aAAc,EAAU,aAAc,MAAO,CAAe,CAChE,EAGF,IAAM,EAAS,GAAwB,EAAW,CAAK,EACvD,EAAM,mBAAmB,IAAI,EAAU,YAAY,EACnD,GAAI,CACF,OAAO,EAAa,EAAQ,EAAO,EAAiB,CAAC,CACvD,QAAU,CACR,EAAM,mBAAmB,OAAO,EAAU,YAAY,CACxD,CACF,CAEA,SAAS,GACP,EACA,EACS,CACT,IAAM,EAAiB,EAAM,cAAgB,EAAM,WAC7C,EAAQ,EAAM,QAAQ,UAAU,EAAU,aAAc,CAAc,EAC5E,GAAI,IAAU,IAAA,GACZ,MAAM,IAAI,EACR,oBACA,mCAAmC,EAAU,aAAa,GAC1D,CAAE,aAAc,EAAU,YAAa,CACzC,EAEF,IAAM,EAAiB,EAAM,WAAa,EAAM,WAChD,GAAI,CAAC,OAAO,cAAc,CAAc,GAAK,EAAiB,EAAM,cAClE,MAAM,IAAI,EACR,2BACA,2DAA2D,EAAM,cAAc,GAC/E,CAAE,SAAU,EAAM,cAAe,OAAQ,CAAe,CAC1D,EAGF,GADA,EAAM,WAAa,EACf,EAAM,aAAe,EAAU,WACjC,MAAM,IAAI,EACR,uBACA,8DAA8D,EAAU,aAAa,GACrF,CACE,aAAc,EAAU,aACxB,SAAU,EAAU,WACpB,OAAQ,EAAM,UAChB,CACF,EAEF,IAAM,EAAe,GAAU,CAAK,EACpC,GAAI,IAAiB,EAAU,OAC7B,MAAM,IAAI,EACR,kBACA,yDAAyD,EAAU,aAAa,GAChF,CAAE,aAAc,EAAU,aAAc,SAAU,EAAU,OAAQ,OAAQ,CAAa,CAC3F,EAEF,GAAI,CACF,OAAO,KAAK,MAAM,GAAgB,OAAO,CAAK,CAAC,CACjD,OAAS,EAAO,CACd,MAAM,IAAI,EACR,eACA,uCAAuC,EAAU,aAAa,GAC9D,CAAE,aAAc,EAAU,YAAa,EACvC,CACF,CACF,CACF,CAEA,SAAS,EAAiB,EAAmD,CAC3E,OAAO,IAAI,EAAiC,eAAgB,CAAO,CACrE,CAEA,SAAS,GAAoC,EAAiD,CAC5F,MAAO,CAAC,MAAM,QAAQ,CAAK,GAAK,SAAU,GAAS,EAAM,OAAS,kBACpE,CAEA,SAAS,GAAc,EAAiD,CACtE,IAAM,EAAY,OAAO,eAAe,CAAK,EAC7C,OAAO,IAAc,OAAO,WAAa,IAAc,IACzD,CCrMA,SAAgB,GACd,EACgC,CAChC,IAAM,EAA0C,CAAC,EAC3C,EAAiB,GAA+B,EAChD,EAAa,GAA2B,EAY9C,OAVA,EAAwB,CAAO,CAAC,CAAC,SAAS,EAAO,IAAU,CACzD,EAA8B,EAAO,EAAO,CAAM,EAClD,GAAoC,EAAO,EAAO,CAAM,EACxD,GAA2B,EAAgB,EAAO,CAAK,EACvD,GAAuB,EAAY,EAAO,CAAK,CACjD,CAAC,EAED,GAA2B,EAAgB,CAAM,EACjD,GAAuB,EAAY,CAAM,EAElC,CAAE,GAAI,EAAO,SAAW,EAAG,QAAO,CAC3C,CA0BA,SAAS,IAA4D,CACnE,MAAO,CACL,SAAU,IAAI,IACd,kBAAmB,IAAI,IACvB,aAAc,IAAI,IAClB,oBAAqB,IAAI,GAC3B,CACF,CAEA,SAAS,IAAoD,CAC3D,MAAO,CACL,SAAU,IAAI,IACd,QAAS,IAAI,GACf,CACF,CAEA,SAAS,GACP,EACA,EACA,EACM,CACN,IAAM,EAAM,GAAY,CAAK,EACxB,IACD,EAAM,QAAU,oBAClB,EAAO,SAAS,IAAI,EAAI,IAAK,CAC3B,YAAa,EAAI,YACjB,MAAO,EAAI,MACX,OACF,CAAC,EAEC,EAAM,QAAU,yBAClB,EAAO,aAAa,IAAI,EAAI,GAAG,EAG/B,EAAM,QAAU,gCACf,EAAM,cAAgB,YAAc,EAAM,cAAgB,iBAE3D,EAAO,kBAAkB,IAAI,EAAI,GAAG,EAGpC,EAAM,QAAU,gCAChB,CAAC,EAAiC,EAAM,QAAQ,GAEhD,EAAO,oBAAoB,IAAI,EAAI,GAAG,EAE1C,CAEA,SAAS,GACP,EACA,EACA,EACM,CAOD,EALH,EAAM,QAAU,mBACZ,EAAM,QACN,EAAM,QAAU,+BACd,EAAM,SACN,IAAA,EACyC,GACjD,EAAO,KAAK,CACV,KAAM,4BACN,QAAS,oBAAoB,EAAM,MAAM,2CACzC,aACA,YAAa,OAAO,EAAM,aAAgB,SAAW,EAAM,YAAc,IAAA,GACzE,MAAO,OAAO,EAAM,OAAU,SAAW,EAAM,MAAQ,IAAA,EACzD,CAAC,CACH,CAEA,SAAS,GACP,EACA,EACA,EACM,CACN,IAAM,EAAM,GAAQ,CAAK,EACpB,IACD,EAAM,QAAU,0BAClB,EAAO,SAAS,IAAI,EAAI,IAAK,CAC3B,YAAa,EAAI,YACjB,WAAY,EAAI,WAChB,OACF,CAAC,EAEC,EAAM,QAAU,yBAClB,EAAO,QAAQ,IAAI,EAAI,GAAG,EAE9B,CAEA,SAAS,GACP,EACA,EACM,CACN,IAAK,GAAM,CAAC,EAAK,KAAY,EAAO,SAC7B,EAAO,kBAAkB,IAAI,CAAG,GACnC,EAAO,KAAK,CACV,KAAM,sCACN,QAAS,oBAAoB,EAAI,+DACjC,WAAY,EAAQ,MACpB,YAAa,EAAQ,YACrB,MAAO,EAAQ,KACjB,CAAC,EAEE,EAAO,aAAa,IAAI,CAAG,GAC9B,EAAO,KAAK,CACV,KAAM,gCACN,QAAS,oBAAoB,EAAI,6BACjC,WAAY,EAAQ,MACpB,YAAa,EAAQ,YACrB,MAAO,EAAQ,KACjB,CAAC,EAEE,EAAO,oBAAoB,IAAI,CAAG,GACrC,EAAO,KAAK,CACV,KAAM,uCACN,QAAS,oBAAoB,EAAI,oCACjC,WAAY,EAAQ,MACpB,YAAa,EAAQ,YACrB,MAAO,EAAQ,KACjB,CAAC,CAGP,CAEA,SAAS,GACP,EACA,EACM,CACN,IAAK,GAAM,CAAC,EAAK,KAAY,EAAO,SAC7B,EAAO,QAAQ,IAAI,CAAG,GACzB,EAAO,KAAK,CACV,KAAM,sBACN,QAAS,gBAAgB,EAAI,gCAC7B,WAAY,EAAQ,MACpB,YAAa,EAAQ,YACrB,WAAY,EAAQ,UACtB,CAAC,CAGP,CAEA,SAAS,GACP,EACiE,CACjE,GAAI,OAAO,EAAM,aAAgB,SAAU,OAC3C,IAAM,EAAQ,OAAO,EAAM,OAAU,SAAW,EAAM,MAAQ,OAAO,EAAM,KAAK,EAC3E,UAAO,SAAS,CAAK,EAC1B,MAAO,CAAE,IAAK,GAAG,EAAM,YAAY,GAAG,IAAS,YAAa,EAAM,YAAa,OAAM,CACvF,CAEA,SAAS,GACP,EACsE,CACtE,GAAI,OAAO,EAAM,aAAgB,SAAU,OAC3C,IAAM,EACJ,OAAO,EAAM,YAAe,SACxB,EAAM,WACN,OAAO,EAAM,iBAAoB,SAC/B,EAAM,gBACN,IAAA,GACH,KACL,MAAO,CAAE,IAAK,GAAG,EAAM,YAAY,GAAG,IAAc,YAAa,EAAM,YAAa,YAAW,CACjG,CAEA,SAAS,EACP,EACA,EACA,EACM,CACN,GAAI,MAAM,QAAQ,CAAK,EAAG,CACxB,EAAM,QAAS,GAAS,EAA8B,EAAM,EAAY,CAAM,CAAC,EAC/E,MACF,CACK,MAAS,CAAK,EACnB,IAAI,EAAM,OAAS,mBAAoB,CAChC,GAA0C,CAAK,GAClD,EAAO,KAAK,CACV,KAAM,4BACN,QAAS,gEACT,YACF,CAAC,EAEH,MACF,CACA,OAAO,OAAO,CAAK,CAAC,CAAC,QAAS,GAAU,EAA8B,EAAO,EAAY,CAAM,CAAC,CADhG,CAEF,CAEA,SAAS,EAAiC,EAAiC,CAMzE,OALI,MAAM,QAAQ,CAAK,EACd,EAAM,KAAM,GAAS,EAAiC,CAAI,CAAC,EAE/D,GAAS,CAAK,EACf,EAAM,OAAS,oBACZ,OAAO,OAAO,CAAK,CAAC,CAAC,KAAM,GAAU,EAAiC,CAAK,CAAC,EAFtD,EAG/B,CAEA,SAAS,GAAS,EAAkE,CAClF,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CCrOA,SAAgB,GACd,EACA,EAAkC,CAAC,EACf,CACpB,IAAM,EAAO,EAAO,SAAS,EACvB,EAAwD,CAAC,EAsB/D,OArBI,IAAS,IAAA,IACX,EAAK,MAAM;CAAI,CAAC,CAAC,SAAS,EAAM,IAAU,CACpC,KAAK,KAAK,CAAC,CAAC,SAAW,EAC3B,GAAI,CACF,EAAY,KAAK,CAAE,MAAO,KAAK,MAAM,CAAI,EAAc,WAAY,EAAQ,CAAE,CAAC,CAChF,OAAS,EAAO,CACd,MAAM,IAAI,EACR,eACA,CAAC,CAAE,KAAM,QAAQ,EAAQ,IAAK,QAAS,qBAAsB,CAAC,EAC9D,CAAE,OAAM,CACV,CACF,CACF,CAAC,EASI,EAPU,GACf,EAAY,KAAK,CAAE,WAAY,CAAK,EACpC,CACE,OAAQ,EAAQ,uBAAyB,EAAO,sBAChD,GAAG,CACL,CAEoC,EAAG,CACvC,YAAa,EAAY,KAAK,CAAE,gBAAiB,CAAU,CAC7D,CAAC,CACH,CAEA,SAAgB,GACd,EACsB,CACtB,IAAM,EAAgC,CAAC,EACjC,EAA2B,CAAC,EAC5B,EAA0C,CAC9C,qBAAsB,CAAC,EACvB,yBAA0B,CAAC,EAC3B,aAAc,CAAC,CACjB,EACI,EACA,EACA,EACA,EAEJ,IAAK,IAAM,KAAS,EAAwB,CAAO,EACjD,IAAyB,EAAM,UAC/B,IAAyB,EAAM,UAC/B,EAAY,EAAM,UAEd,EAAM,QAAU,iBAClB,EAAM,OAAO,EAAM,KAAQ,SAAW,EAAM,IAAM,GAGhD,EAAM,QAAU,oBAAsB,EAAM,WAAa,mBAC3D,EAAS,KAAK,EAAM,OAAO,EAC3B,EAAQ,KAAK,GAAsB,EAAM,OAAO,CAAC,GAGnD,GAA4B,EAAO,CAAe,EAGpD,MAAO,CACL,YACA,MACA,YACA,YACA,WACA,UACA,qBAAsB,EAAgB,qBACtC,yBAA0B,EAAgB,yBAC1C,aAAc,EAAgB,YAChC,CACF,CAQA,SAAS,GACP,EACA,EACM,CACN,GAAI,EAAM,QAAU,wBAAyB,CAC3C,EAAkB,EAAgB,qBAAsB,EAAO,kBAAmB,MAAM,EACxF,MACF,CACA,GAAI,EAAM,QAAU,6BAA8B,CAChD,EACE,EAAgB,yBAChB,EACA,0BACA,MACF,EACA,MACF,CACI,EAAM,QAAU,gBAClB,EAAkB,EAAgB,aAAc,EAAO,cAAe,MAAM,CAEhF,CAEA,SAAS,GAAiB,EAAyB,EAAiC,CAClF,IAAM,EAAQ,EAAM,GAElB,YAAO,GAAU,WACjB,GACA,MAAM,QAAQ,CAAK,GACnB,aAAiB,MAInB,OAAO,CACT,CAEA,SAAS,EACP,EACA,EACA,EACA,EACM,CACN,IAAM,EAAU,GAAiB,EAAO,CAAU,GAAK,GAAiB,EAAO,CAAW,EACtF,GAAS,EAAO,KAAK,CAAO,CAClC,CC9IA,IAAa,GAAb,MAAa,CAAe,CAC1B,MAAyB,IAAI,IAE7B,SAA4B,IAAI,IAChC,SAQA,OAAO,UAAU,EAA0B,EAAmC,CAC5E,IAAM,EAAO,IAAI,EAEjB,IAAK,IAAM,KAAQ,EAAO,CACxB,GAAI,EAAK,MAAM,IAAI,EAAK,EAAE,EAAG,MAAU,MAAM,mCAAmC,EAAK,GAAG,EAAE,EAC1F,EAAK,MAAM,IAAI,EAAK,GAAI,EAAK,WAAa,IAAA,GAAY,CAAE,GAAI,EAAK,EAAG,EAAI,CAAE,GAAG,CAAK,CAAC,CACrF,CAEA,IAAK,IAAM,KAAQ,EACjB,GAAI,EAAK,WAAa,IAAA,GAAW,CAC/B,IAAM,EAAW,EAAK,SAAS,IAAI,EAAK,QAAQ,GAAK,CAAC,EACtD,EAAS,KAAK,EAAK,EAAE,EACrB,EAAK,SAAS,IAAI,EAAK,SAAU,CAAQ,CAC3C,CAGF,MADA,GAAK,SAAW,EACT,CACT,CAMA,cAAc,EAAkB,CAC9B,GAAI,KAAK,MAAM,IAAI,CAAE,EAAG,MAAU,MAAM,4CAA4C,EAAG,EAAE,EACzF,IAAM,EAAW,KAAK,SAEtB,GADA,KAAK,MAAM,IAAI,EAAI,IAAa,IAAA,GAAY,CAAE,IAAG,EAAI,CAAE,KAAI,UAAS,CAAC,EACjE,IAAa,IAAA,GAAW,CAC1B,IAAM,EAAW,KAAK,SAAS,IAAI,CAAQ,GAAK,CAAC,EACjD,EAAS,KAAK,CAAE,EAChB,KAAK,SAAS,IAAI,EAAU,CAAQ,CACtC,CACA,KAAK,SAAW,CAClB,CAMA,KAAK,EAAwB,CAC3B,GAAI,CAAC,KAAK,MAAM,IAAI,CAAM,EAAG,MAAU,MAAM,uCAAuC,EAAO,EAAE,EAE7F,MADA,MAAK,SAAW,EACT,CACT,CAGA,OAAO,EAAsB,CAC3B,GAAI,CAAC,KAAK,MAAM,IAAI,CAAM,EAAG,MAAU,MAAM,uCAAuC,EAAO,EAAE,EAC7F,KAAK,SAAW,CAClB,CAGA,YAAiC,CAC/B,OAAO,KAAK,QACd,CAGA,cAAyB,CACvB,IAAM,EAAmB,CAAC,EAC1B,IAAK,IAAM,KAAM,KAAK,MAAM,KAAK,GAC1B,KAAK,SAAS,IAAI,CAAE,CAAC,EAAE,QAAU,KAAO,GAAG,EAAO,KAAK,CAAE,EAEhE,OAAO,CACT,CAQA,UAAU,EAAsB,CAC9B,IAAM,EAAkB,CAAC,EACnB,EAAO,IAAI,IACb,EAA6B,EACjC,KAAO,IAAW,IAAA,IAAa,KAAK,MAAM,IAAI,CAAM,GAAK,CAAC,EAAK,IAAI,CAAM,GACvE,EAAM,KAAK,CAAM,EACjB,EAAK,IAAI,CAAM,EACf,EAAS,KAAK,MAAM,IAAI,CAAM,CAAC,CAAE,SAEnC,OAAO,CACT,CAGA,IAAI,EAAqB,CACvB,OAAO,KAAK,MAAM,IAAI,CAAE,CAC1B,CAGA,IAAI,MAAe,CACjB,OAAO,KAAK,MAAM,IACpB,CACF"}