@wrongstack/core 0.285.0 → 0.287.0
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/autophase/phase-orchestrator.d.ts.map +1 -1
- package/dist/boot.d.ts.map +1 -1
- package/dist/coordination/agent-monitor.d.ts +29 -0
- package/dist/coordination/agent-monitor.d.ts.map +1 -1
- package/dist/coordination/autonomous-brain.d.ts +0 -2
- package/dist/coordination/autonomous-brain.d.ts.map +1 -1
- package/dist/coordination/autonomous-coordinator.d.ts +4 -0
- package/dist/coordination/autonomous-coordinator.d.ts.map +1 -1
- package/dist/coordination/brain-ledger.d.ts +142 -0
- package/dist/coordination/brain-ledger.d.ts.map +1 -0
- package/dist/coordination/brain-monitor.d.ts +46 -2
- package/dist/coordination/brain-monitor.d.ts.map +1 -1
- package/dist/coordination/brain.d.ts +43 -0
- package/dist/coordination/brain.d.ts.map +1 -1
- package/dist/coordination/delegate-tool.d.ts +8 -14
- package/dist/coordination/delegate-tool.d.ts.map +1 -1
- package/dist/coordination/director/director-btw-notes.d.ts +20 -0
- package/dist/coordination/director/director-btw-notes.d.ts.map +1 -0
- package/dist/coordination/director/director-collab.d.ts +40 -0
- package/dist/coordination/director/director-collab.d.ts.map +1 -0
- package/dist/coordination/director.d.ts +28 -9
- package/dist/coordination/director.d.ts.map +1 -1
- package/dist/coordination/fleet-manager.d.ts +21 -0
- package/dist/coordination/fleet-manager.d.ts.map +1 -1
- package/dist/coordination/fleet.d.ts +6 -7
- package/dist/coordination/fleet.d.ts.map +1 -1
- package/dist/coordination/global-mailbox.d.ts +96 -21
- package/dist/coordination/global-mailbox.d.ts.map +1 -1
- package/dist/coordination/icoordinator.d.ts +1 -3
- package/dist/coordination/icoordinator.d.ts.map +1 -1
- package/dist/coordination/index.d.ts +2 -1
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +2176 -806
- package/dist/coordination/index.js.map +4 -4
- package/dist/coordination/knowledge-graph.d.ts +13 -1
- package/dist/coordination/knowledge-graph.d.ts.map +1 -1
- package/dist/coordination/mail-tools.d.ts.map +1 -1
- package/dist/coordination/mailbox-actions.d.ts +12 -13
- package/dist/coordination/mailbox-actions.d.ts.map +1 -1
- package/dist/coordination/mailbox-constants.d.ts +64 -0
- package/dist/coordination/mailbox-constants.d.ts.map +1 -0
- package/dist/coordination/mailbox-events.d.ts +32 -0
- package/dist/coordination/mailbox-events.d.ts.map +1 -0
- package/dist/coordination/mailbox-message-codec.d.ts +28 -0
- package/dist/coordination/mailbox-message-codec.d.ts.map +1 -0
- package/dist/coordination/mailbox-types.d.ts +130 -5
- package/dist/coordination/mailbox-types.d.ts.map +1 -1
- package/dist/coordination/mailbox.d.ts +21 -2
- package/dist/coordination/mailbox.d.ts.map +1 -1
- package/dist/coordination/package-author-tracker.d.ts.map +1 -1
- package/dist/coordination/subagent-result-tool.d.ts.map +1 -1
- package/dist/core/agent-loop.d.ts.map +1 -1
- package/dist/core/agent-response.d.ts +10 -1
- package/dist/core/agent-response.d.ts.map +1 -1
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/agent.d.ts +8 -0
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/context.d.ts +25 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/conversation-state.d.ts +6 -0
- package/dist/core/conversation-state.d.ts.map +1 -1
- package/dist/core/fallback-model.d.ts.map +1 -1
- package/dist/core/mailbox-loop.d.ts +12 -5
- package/dist/core/mailbox-loop.d.ts.map +1 -1
- package/dist/core/system-prompt-builder.d.ts +14 -6
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/defaults/index.d.ts +0 -1
- package/dist/defaults/index.d.ts.map +1 -1
- package/dist/defaults/index.js +1690 -1998
- package/dist/defaults/index.js.map +4 -4
- package/dist/execution/auto-compaction-middleware.d.ts +8 -1
- package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
- package/dist/execution/autonomous-runner.d.ts +7 -1
- package/dist/execution/autonomous-runner.d.ts.map +1 -1
- package/dist/execution/autonomy-brain.d.ts +72 -11
- package/dist/execution/autonomy-brain.d.ts.map +1 -1
- package/dist/execution/brain-chain.d.ts +53 -0
- package/dist/execution/brain-chain.d.ts.map +1 -0
- package/dist/execution/brain-evaluation.d.ts +66 -0
- package/dist/execution/brain-evaluation.d.ts.map +1 -0
- package/dist/execution/brain-runtime.d.ts +171 -0
- package/dist/execution/brain-runtime.d.ts.map +1 -0
- package/dist/execution/compaction-core.d.ts.map +1 -1
- package/dist/execution/council-brain.d.ts +79 -0
- package/dist/execution/council-brain.d.ts.map +1 -0
- package/dist/execution/council-orchestrator.d.ts +32 -0
- package/dist/execution/council-orchestrator.d.ts.map +1 -0
- package/dist/execution/council-personas.d.ts +19 -0
- package/dist/execution/council-personas.d.ts.map +1 -0
- package/dist/execution/council-profiles.d.ts +34 -0
- package/dist/execution/council-profiles.d.ts.map +1 -0
- package/dist/execution/council-prompts.d.ts +21 -0
- package/dist/execution/council-prompts.d.ts.map +1 -0
- package/dist/execution/council-resolution.d.ts +57 -0
- package/dist/execution/council-resolution.d.ts.map +1 -0
- package/dist/execution/design-kit-loader.d.ts.map +1 -1
- package/dist/execution/enhance-recovery.d.ts +35 -0
- package/dist/execution/enhance-recovery.d.ts.map +1 -0
- package/dist/execution/error-handler.d.ts.map +1 -1
- package/dist/execution/eternal-autonomy.d.ts +12 -0
- package/dist/execution/eternal-autonomy.d.ts.map +1 -1
- package/dist/execution/index.d.ts +11 -1
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +3076 -526
- package/dist/execution/index.js.map +4 -4
- package/dist/execution/intelligent-compactor.d.ts +18 -7
- package/dist/execution/intelligent-compactor.d.ts.map +1 -1
- package/dist/execution/one-shot-llm.d.ts +52 -0
- package/dist/execution/one-shot-llm.d.ts.map +1 -0
- package/dist/execution/parallel-eternal-engine.d.ts +12 -0
- package/dist/execution/parallel-eternal-engine.d.ts.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +23 -4
- package/dist/execution/prompt-enhancer.d.ts.map +1 -1
- package/dist/execution/prompt-enhancer.js +24 -6
- package/dist/execution/prompt-enhancer.js.map +2 -2
- package/dist/execution/selective-compactor.d.ts +4 -4
- package/dist/execution/strategy-compactor.d.ts +7 -0
- package/dist/execution/strategy-compactor.d.ts.map +1 -1
- package/dist/execution/tool-executor.d.ts +19 -5
- package/dist/execution/tool-executor.d.ts.map +1 -1
- package/dist/hooks/registry.d.ts.map +1 -1
- package/dist/hooks/runner.d.ts.map +1 -1
- package/dist/hooks/shell-executor.d.ts.map +1 -1
- package/dist/hq/alerts.d.ts +5 -0
- package/dist/hq/alerts.d.ts.map +1 -1
- package/dist/hq/auth-store.d.ts +38 -10
- package/dist/hq/auth-store.d.ts.map +1 -1
- package/dist/hq/commands.d.ts +8 -4
- package/dist/hq/commands.d.ts.map +1 -1
- package/dist/hq/cost-bridge.d.ts +5 -3
- package/dist/hq/cost-bridge.d.ts.map +1 -1
- package/dist/hq/factory.d.ts +3 -0
- package/dist/hq/factory.d.ts.map +1 -1
- package/dist/hq/fleet-bridge.d.ts.map +1 -1
- package/dist/hq/index.d.ts +0 -1
- package/dist/hq/index.d.ts.map +1 -1
- package/dist/hq/index.js +1016 -283
- package/dist/hq/index.js.map +4 -4
- package/dist/hq/persistence.d.ts +42 -0
- package/dist/hq/persistence.d.ts.map +1 -1
- package/dist/hq/protocol/brain.d.ts +24 -0
- package/dist/hq/protocol/brain.d.ts.map +1 -0
- package/dist/hq/protocol/browser.d.ts +12 -0
- package/dist/hq/protocol/browser.d.ts.map +1 -0
- package/dist/hq/protocol/client.d.ts +69 -0
- package/dist/hq/protocol/client.d.ts.map +1 -0
- package/dist/hq/protocol/core.d.ts +130 -0
- package/dist/hq/protocol/core.d.ts.map +1 -0
- package/dist/hq/protocol/fleet.d.ts +73 -0
- package/dist/hq/protocol/fleet.d.ts.map +1 -0
- package/dist/hq/protocol/mailbox.d.ts +76 -0
- package/dist/hq/protocol/mailbox.d.ts.map +1 -0
- package/dist/hq/protocol/mcp.d.ts +51 -0
- package/dist/hq/protocol/mcp.d.ts.map +1 -0
- package/dist/hq/protocol/project.d.ts +26 -0
- package/dist/hq/protocol/project.d.ts.map +1 -0
- package/dist/hq/protocol/session.d.ts +153 -0
- package/dist/hq/protocol/session.d.ts.map +1 -0
- package/dist/hq/protocol/tool.d.ts +31 -0
- package/dist/hq/protocol/tool.d.ts.map +1 -0
- package/dist/hq/protocol.d.ts +10 -559
- package/dist/hq/protocol.d.ts.map +1 -1
- package/dist/hq/publisher.d.ts +4 -0
- package/dist/hq/publisher.d.ts.map +1 -1
- package/dist/index.d.ts +24 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7676 -3432
- package/dist/index.js.map +4 -4
- package/dist/infrastructure/index.js +13 -11
- package/dist/infrastructure/index.js.map +2 -2
- package/dist/infrastructure/token-counter.d.ts.map +1 -1
- package/dist/kernel/events/agent-events.d.ts +283 -0
- package/dist/kernel/events/agent-events.d.ts.map +1 -0
- package/dist/kernel/events/brain-events.d.ts +176 -0
- package/dist/kernel/events/brain-events.d.ts.map +1 -0
- package/dist/kernel/events/fleet-events.d.ts +39 -0
- package/dist/kernel/events/fleet-events.d.ts.map +1 -0
- package/dist/kernel/events/memory-events.d.ts +165 -0
- package/dist/kernel/events/memory-events.d.ts.map +1 -0
- package/dist/kernel/events/provider-events.d.ts +98 -0
- package/dist/kernel/events/provider-events.d.ts.map +1 -0
- package/dist/kernel/events/sdd-events.d.ts +118 -0
- package/dist/kernel/events/sdd-events.d.ts.map +1 -0
- package/dist/kernel/events/session-events.d.ts +227 -0
- package/dist/kernel/events/session-events.d.ts.map +1 -0
- package/dist/kernel/events/tool-events.d.ts +185 -0
- package/dist/kernel/events/tool-events.d.ts.map +1 -0
- package/dist/kernel/events/worktree-events.d.ts +58 -0
- package/dist/kernel/events/worktree-events.d.ts.map +1 -0
- package/dist/kernel/events.d.ts +12 -1282
- package/dist/kernel/events.d.ts.map +1 -1
- package/dist/kernel/index.js +22 -2
- package/dist/kernel/index.js.map +2 -2
- package/dist/kernel/run-controller.d.ts +15 -0
- package/dist/kernel/run-controller.d.ts.map +1 -1
- package/dist/mailbox-attach.d.ts.map +1 -1
- package/dist/models/index.js +19 -6
- package/dist/models/index.js.map +2 -2
- package/dist/models/llm-selector.d.ts +7 -0
- package/dist/models/llm-selector.d.ts.map +1 -1
- package/dist/observability/index.js +16 -15
- package/dist/observability/index.js.map +2 -2
- package/dist/plugins/auto-review-plugin.d.ts +36 -0
- package/dist/plugins/auto-review-plugin.d.ts.map +1 -0
- package/dist/plugins/chimera-plugin.d.ts +8 -0
- package/dist/plugins/chimera-plugin.d.ts.map +1 -1
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +260 -23
- package/dist/security/index.js.map +4 -4
- package/dist/security/permission-policy-schema.d.ts +77 -0
- package/dist/security/permission-policy-schema.d.ts.map +1 -0
- package/dist/security/permission-policy.d.ts +5 -0
- package/dist/security/permission-policy.d.ts.map +1 -1
- package/dist/security/secret-scrubber.d.ts.map +1 -1
- package/dist/security/secret-vault.d.ts +9 -0
- package/dist/security/secret-vault.d.ts.map +1 -1
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/skills/index.js.map +2 -2
- package/dist/storage/config-loader.d.ts +10 -0
- package/dist/storage/config-loader.d.ts.map +1 -1
- package/dist/storage/goal-kanban.d.ts +69 -0
- package/dist/storage/goal-kanban.d.ts.map +1 -0
- package/dist/storage/goal-store.d.ts +1 -1
- package/dist/storage/goal-store.d.ts.map +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +572 -879
- package/dist/storage/index.js.map +4 -4
- package/dist/storage/memory-consolidator.d.ts +9 -1
- package/dist/storage/memory-consolidator.d.ts.map +1 -1
- package/dist/storage/plan-store.d.ts +1 -1
- package/dist/storage/plan-store.d.ts.map +1 -1
- package/dist/storage/queue-store.d.ts +4 -0
- package/dist/storage/queue-store.d.ts.map +1 -1
- package/dist/storage/session-store.d.ts +9 -0
- package/dist/storage/session-store.d.ts.map +1 -1
- package/dist/storage/task-store.d.ts +1 -1
- package/dist/storage/task-store.d.ts.map +1 -1
- package/dist/storage/todos-checkpoint.d.ts +2 -2
- package/dist/storage/todos-checkpoint.d.ts.map +1 -1
- package/dist/tools/council-tool.d.ts +25 -0
- package/dist/tools/council-tool.d.ts.map +1 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1884 -12
- package/dist/tools/index.js.map +4 -4
- package/dist/tools/one-shot-llm-tool.d.ts +51 -0
- package/dist/tools/one-shot-llm-tool.d.ts.map +1 -0
- package/dist/types/config.d.ts +236 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/context-window.d.ts.map +1 -1
- package/dist/types/council.d.ts +134 -0
- package/dist/types/council.d.ts.map +1 -0
- package/dist/types/default-config.d.ts +3 -3
- package/dist/types/hooks.d.ts +14 -0
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +40 -8
- package/dist/types/index.js.map +3 -3
- package/dist/types/multi-agent.d.ts +8 -0
- package/dist/types/multi-agent.d.ts.map +1 -1
- package/dist/types/observability.d.ts +5 -0
- package/dist/types/observability.d.ts.map +1 -1
- package/dist/types/one-shot-llm.d.ts +128 -0
- package/dist/types/one-shot-llm.d.ts.map +1 -0
- package/dist/types/plugin.d.ts +6 -0
- package/dist/types/plugin.d.ts.map +1 -1
- package/dist/types/provider.d.ts +17 -0
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/system-prompt.d.ts +2 -1
- package/dist/types/system-prompt.d.ts.map +1 -1
- package/dist/types/tool-executor.d.ts +10 -0
- package/dist/types/tool-executor.d.ts.map +1 -1
- package/dist/types/tool.d.ts +7 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/utils/atomic-write.d.ts.map +1 -1
- package/dist/utils/glob-expand.d.ts.map +1 -1
- package/dist/utils/incoming-images.d.ts +39 -0
- package/dist/utils/incoming-images.d.ts.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +156 -11
- package/dist/utils/index.js.map +4 -4
- package/dist/utils/tool-wire-compact.d.ts +1 -0
- package/dist/utils/tool-wire-compact.d.ts.map +1 -1
- package/dist/utils/wstack-paths.d.ts +20 -4
- package/dist/utils/wstack-paths.d.ts.map +1 -1
- package/dist/worktree/worktree-manager.d.ts +13 -4
- package/dist/worktree/worktree-manager.d.ts.map +1 -1
- package/instructions/agents/browser.md +25 -16
- package/instructions/cli/next-task-predictor.md +5 -3
- package/instructions/coordination/subagent-baseline.md +17 -2
- package/instructions/leader-after-task.md +19 -8
- package/instructions/llm/council-judge.md +19 -0
- package/instructions/llm/council-voter.md +22 -0
- package/instructions/modes/architect.md +14 -9
- package/instructions/modes/audit-lite.md +9 -7
- package/instructions/modes/brief-system.md +12 -12
- package/instructions/modes/brief.md +7 -5
- package/instructions/modes/code-auditor.md +13 -8
- package/instructions/modes/code-reviewer.md +13 -8
- package/instructions/modes/debug-lite.md +7 -6
- package/instructions/modes/debugger.md +13 -9
- package/instructions/modes/devops.md +13 -9
- package/instructions/modes/plan-lite.md +7 -6
- package/instructions/modes/refactor-lite.md +7 -6
- package/instructions/modes/refactorer.md +14 -9
- package/instructions/modes/research-lite.md +8 -6
- package/instructions/modes/research-web.md +18 -55
- package/instructions/modes/review-lite.md +7 -6
- package/instructions/modes/teach-system.md +15 -15
- package/instructions/modes/teach.md +13 -12
- package/instructions/modes/test-lite.md +6 -5
- package/instructions/modes/tester.md +13 -9
- package/instructions/modes/ui-design.md +14 -8
- package/instructions/sections/tool/commit-hygiene.md +1 -7
- package/instructions/sections/tool/common-patterns.md +7 -1
- package/instructions/sections/tool/delegation-full.md +3 -31
- package/instructions/sections/tool/mailbox-compact.md +1 -1
- package/instructions/sections/tool/mailbox-full.md +6 -22
- package/instructions/system.md +294 -8
- package/package.json +2 -2
- package/skills/docker-deploy/SKILL.md +1 -1
- package/skills/mailbox-bridge/SKILL.md +16 -7
- package/skills/output-standards/SKILL.md +16 -16
- package/skills/wrongstack-mailbox/SKILL.md +57 -12
- package/dist/coordination/director-construction.d.ts +0 -90
- package/dist/coordination/director-construction.d.ts.map +0 -1
- package/dist/hq/agent-bridge.d.ts +0 -27
- package/dist/hq/agent-bridge.d.ts.map +0 -1
- package/dist/plugins/git-plugin.d.ts +0 -15
- package/dist/plugins/git-plugin.d.ts.map +0 -1
- package/dist/plugins/observability-plugin.d.ts +0 -19
- package/dist/plugins/observability-plugin.d.ts.map +0 -1
- package/dist/plugins/plan-plugin.d.ts +0 -16
- package/dist/plugins/plan-plugin.d.ts.map +0 -1
- package/dist/storage/memory-store.d.ts +0 -119
- package/dist/storage/memory-store.d.ts.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../src/storage/session-store.ts", "../../src/utils/atomic-write.ts", "../../src/utils/child-env.ts", "../../src/utils/term.ts", "../../src/utils/color.ts", "../../src/utils/deep-merge.ts", "../../src/utils/error.ts", "../../src/utils/expect-defined.ts", "../../src/utils/message-invariants.ts", "../../src/utils/regex-guard.ts", "../../src/utils/safe-json.ts", "../../src/utils/session-scoped-path.ts", "../../src/utils/slug.ts", "../../src/utils/ulid.ts", "../../src/utils/wstack-paths.ts", "../../src/types/errors.ts", "../../src/storage/file-session-writer.ts", "../../src/storage/session-helpers.ts", "../../src/storage/session-checkpoint-cas.ts", "../../src/storage/storage-concurrency.ts", "../../src/storage/session-resume-validation.ts", "../../src/storage/session-id.ts", "../../src/storage/session-summary.ts", "../../src/storage/session-tool-call-ends.ts", "../../src/storage/queue-store.ts", "../../src/storage/attachment-store.ts", "../../src/storage/memory-backend.ts", "../../src/types/memory.ts", "../../src/storage/memory-
|
|
4
|
-
"sourcesContent": ["import { createHash } from 'node:crypto';\nimport { createReadStream, type Dirent } from 'node:fs';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { createInterface } from 'node:readline';\nimport type { EventBus } from '../kernel/events.js';\nimport type { ContentBlock } from '../types/blocks.js';\nimport type { Message } from '../types/messages.js';\nimport type { SecretScrubber } from '../types/secret-scrubber.js';\nimport type {\n ForkedSession,\n ResumedSession,\n SessionData,\n SessionEvent,\n SessionForkOptions,\n SessionMetadata,\n SessionStore,\n SessionSummary,\n SessionWriter,\n WorkspaceCheckpointRef,\n WorkspaceMaterializationResult,\n} from '../types/session.js';\nimport { atomicWrite, ensureDir, withFileLock } from '../utils/atomic-write.js';\nimport { repairToolUseAdjacency } from '../utils/message-invariants.js';\nimport { toErrorMessage } from '../utils/index.js';\nimport { sessionScopedPath } from '../utils/session-scoped-path.js';\nimport { FileSessionWriter } from './file-session-writer.js';\nimport { SessionCheckpointCas } from './session-checkpoint-cas.js';\nimport {\n formatResumeValidationNotice,\n validateResumeFileObservations,\n} from './session-resume-validation.js';\nimport { userInputTitle } from './session-helpers.js';\nimport { generateSessionId } from './session-id.js';\nimport { compareSessionSummaries, matchesSessionFilter } from './session-summary.js';\nimport { extractToolCallEnds } from './session-tool-call-ends.js';\nimport { mapWithConcurrency } from './storage-concurrency.js';\n\nexport interface SessionStoreOptions {\n dir: string;\n /**\n * Active project root used to revalidate persisted file-observation hashes\n * during resume. Omit for stores that only inspect/archive transcripts.\n */\n projectRoot?: string | undefined;\n /** Optional EventBus for emitting session diagnostics. */\n events?: EventBus | undefined;\n /**\n * Optional secret scrubber. When set, `user_input` and `llm_response` event\n * content is scrubbed before being persisted to the JSONL log and the\n * summary sidecar \u00E2\u20AC\u201D so a secret a user pastes or the model echoes does not\n * sit in cleartext on disk (and does not ride along in history cloud-sync).\n * Tool output is already scrubbed upstream by the executor; this closes the\n * conversation-turn gap (finding F-06).\n */\n secretScrubber?: SecretScrubber | undefined;\n /**\n * Optional guard consulted by {@link DefaultSessionStore.delete} before\n * removing a session. Returns `true` if the session is currently in use by\n * any live process (e.g. it is the active session of another terminal, TUI,\n * or WebUI in this project). The store ALWAYS also checks `active.json`\n * directly; this callback widens the check to cross-process live sessions\n * via the SessionRegistry. When omitted, only the `active.json` check runs.\n * Resolves to a human-readable reason when in use, or `null` when safe.\n */\n isSessionInUse?: ((sessionId: string) => Promise<string | null>) | undefined;\n}\n\n/**\n * Cache entry for load() \u00E2\u20AC\u201D stores the parsed SessionData along with the\n * file's mtimeMs and size at the time of loading. On subsequent calls,\n * if the file's mtimeMs+size match, we return the cached data without\n * re-reading or re-parsing the JSONL.\n */\ninterface LoadCacheEntry {\n mtimeMs: number;\n size: number;\n data: SessionData;\n}\n\ninterface IndexCacheEntry {\n mtimeMs: number;\n size: number;\n summaries: SessionSummary[];\n}\n\ninterface SessionFileRef {\n id: string;\n filePath: string;\n}\n\ninterface DirectorySummaryCandidate {\n summary: SessionSummary;\n needsBackfill: boolean;\n}\n\ninterface ShardManifestEntry {\n summaries: SessionSummary[];\n ids: string[];\n}\n\nfunction isReplayableMessage(value: unknown): value is Message {\n if (!value || typeof value !== 'object') return false;\n const candidate = value as Partial<Message>;\n return (\n (candidate.role === 'user' ||\n candidate.role === 'assistant' ||\n candidate.role === 'system') &&\n (typeof candidate.content === 'string' || Array.isArray(candidate.content))\n );\n}\n\nfunction applyContextSnapshot(\n target: Message[],\n openToolUses: Set<string>,\n snapshot: unknown,\n): boolean {\n if (!Array.isArray(snapshot) || !snapshot.every(isReplayableMessage)) return false;\n target.length = 0;\n openToolUses.clear();\n for (const raw of snapshot) {\n const { _estTokens: _ignored, ...message } = raw;\n target.push(message);\n if (!Array.isArray(message.content)) continue;\n for (const block of message.content) {\n if (block.type === 'tool_use') openToolUses.add(block.id);\n else if (block.type === 'tool_result') openToolUses.delete(block.tool_use_id);\n }\n }\n return true;\n}\n\nfunction inheritsIntoFork(event: SessionEvent): boolean {\n switch (event.type) {\n case 'session_start':\n case 'session_resumed':\n case 'session_forked':\n case 'session_end':\n case 'in_flight_start':\n case 'in_flight_end':\n case 'rewound':\n return false;\n // Parent snapshots describe mutations owned by the parent journal. A\n // child that shares the current workspace must not inherit authority to\n // rewind those historical side effects.\n case 'file_snapshot':\n return false;\n default:\n return true;\n }\n}\n\nexport class DefaultSessionStore implements SessionStore {\n private readonly dir: string;\n private readonly events?: EventBus | undefined;\n private readonly secretScrubber?: SecretScrubber | undefined;\n private readonly projectRoot?: string | undefined;\n private readonly checkpointCas?: SessionCheckpointCas | undefined;\n private readonly isSessionInUse?: ((sessionId: string) => Promise<string | null>) | undefined;\n\n /**\n * In-memory cache for load() results, keyed by session ID. The cache is\n * invalidated when the file's mtimeMs or size changes (indicating the\n * file was written to). This eliminates redundant full-file reads and\n * JSON parses when the same session is loaded multiple times within the\n * store's lifetime (e.g., webui session detail views, list() fallbacks).\n *\n * Max size is capped to prevent unbounded memory growth in long-running\n * processes. When the limit is reached, the oldest entry is evicted.\n */\n private readonly _loadCache = new Map<string, LoadCacheEntry>();\n private _indexCache: IndexCacheEntry | null = null;\n private readonly shardManifestCache = new Map<string, ShardManifestEntry>();\n private static readonly LOAD_CACHE_MAX_ENTRIES = 50;\n private static readonly LIST_SCAN_CONCURRENCY = 32;\n\n constructor(opts: SessionStoreOptions) {\n this.dir = opts.dir;\n this.projectRoot = opts.projectRoot ? path.resolve(opts.projectRoot) : undefined;\n this.checkpointCas = this.projectRoot\n ? new SessionCheckpointCas({\n rootDir: path.join(this.dir, '_cas'),\n projectRoot: this.projectRoot,\n })\n : undefined;\n this.events = opts.events;\n this.secretScrubber = opts.secretScrubber;\n this.isSessionInUse = opts.isSessionInUse;\n }\n\n /**\n * Clear the load() cache. Useful for testing or when the caller knows\n * the file has changed externally (e.g., another process wrote to it).\n */\n clearLoadCache(sessionId?: string): void {\n if (sessionId !== undefined) {\n this._loadCache.delete(sessionId);\n } else {\n this._loadCache.clear();\n }\n }\n\n // \u00E2\u201D\u20AC\u00E2\u201D\u20AC Storage event helpers \u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\n\n private emitRead(\n sessionId: string,\n filePath: string,\n operation: 'load' | 'load_events_only' | 'list' | 'summary' | 'index_read',\n outcome: 'success' | 'failure',\n durationMs: number,\n error?: string,\n ): void {\n this.events?.emit('storage.read', {\n sessionId,\n store: 'session',\n filePath,\n operation,\n outcome,\n durationMs,\n ...(error !== undefined ? { error } : {}),\n });\n }\n\n private emitWrite(\n sessionId: string,\n filePath: string,\n operation: 'create' | 'resume' | 'append' | 'flush' | 'close' | 'index_append' | 'compact' | 'checkpoint',\n outcome: 'success' | 'failure',\n durationMs: number,\n eventCount?: number,\n error?: string,\n ): void {\n this.events?.emit('storage.write', {\n sessionId,\n store: 'session',\n filePath,\n operation,\n outcome,\n durationMs,\n ...(eventCount !== undefined ? { eventCount } : {}),\n ...(error !== undefined ? { error } : {}),\n });\n }\n\n private emitError(\n sessionId: string,\n filePath: string,\n operation: string,\n error: string,\n recoverable: boolean,\n ): void {\n this.events?.emit('storage.error', {\n sessionId,\n store: 'session',\n filePath,\n operation,\n error,\n recoverable,\n });\n }\n\n /** Absolute path to the session index file. */\n private get indexFile(): string {\n return path.join(this.dir, '_index.jsonl');\n }\n\n /** Join session ID to its absolute path within the store directory. */\n private sessionPath(id: string, ext: '.jsonl' | '.summary.json'): string {\n return sessionScopedPath(this.dir, id, ext);\n }\n\n private shardManifestPath(shardKey: string): string {\n return shardKey ? path.join(this.dir, shardKey, '_manifest.json') : path.join(this.dir, '_manifest.json');\n }\n\n private shardKeyForSessionId(id: string): string {\n const dirName = path.dirname(id);\n return dirName === '.' ? '' : dirName;\n }\n\n private invalidateShardManifestBySessionId(id: string): void {\n this.shardManifestCache.delete(this.shardKeyForSessionId(id));\n }\n\n /**\n * Ensure the directory implied by the session ID exists. When the ID\n * contains a date prefix like `2026-06-06/...`, this creates the date\n * subdirectory so sessions group naturally by day.\n */\n private async ensureShardDir(id: string): Promise<string> {\n const dirPath = path.dirname(sessionScopedPath(this.dir, id, ''));\n await ensureDir(dirPath);\n return dirPath;\n }\n\n async create(meta: Omit<SessionMetadata, 'startedAt'>): Promise<SessionWriter> {\n const startedAt = new Date().toISOString();\n const id =\n meta.id && meta.id.length > 0\n ? meta.id\n : generateSessionId(startedAt);\n const shardDir = await this.ensureShardDir(id);\n const file = this.sessionPath(id, '.jsonl');\n const t0 = Date.now();\n let handle: fsp.FileHandle;\n try {\n handle = await fsp.open(file, 'a', 0o600);\n } catch (err) {\n this.emitError(id, file, 'create', toErrorMessage(err), false);\n throw new Error(\n `Failed to open session file: ${toErrorMessage(err)}`,\n { cause: err },\n );\n }\n try {\n const writer = new FileSessionWriter(id, handle, startedAt, meta, this.events, {\n dir: shardDir,\n filePath: file,\n secretScrubber: this.secretScrubber,\n checkpointCas: this.checkpointCas,\n onClose: (s) => this.appendToIndex(s),\n });\n this.emitWrite(id, file, 'create', 'success', Date.now() - t0);\n return writer;\n /* v8 ignore start -- defensive: FileSessionWriter ctor does not throw in practice */\n } catch (err) {\n await handle.close().catch((e) => console.warn(JSON.stringify({\n level: 'warn',\n event: 'session_store.handle_close_failed',\n message: e instanceof Error ? e.message : String(e),\n timestamp: new Date().toISOString(),\n })));\n this.emitError(id, file, 'create', toErrorMessage(err), true);\n throw err;\n }\n /* v8 ignore stop */\n }\n\n async fork(id: string, opts: SessionForkOptions = {}): Promise<ForkedSession> {\n const parent = await this.load(id);\n let boundary = parent.events.length - 1;\n let targetCheckpoint: Extract<SessionEvent, { type: 'checkpoint' }> | undefined;\n if (opts.checkpointPromptIndex !== undefined) {\n boundary = -1;\n for (let i = 0; i < parent.events.length; i++) {\n const event = parent.events[i];\n if (\n event?.type === 'checkpoint' &&\n event.promptIndex === opts.checkpointPromptIndex\n ) {\n // Prefer the latest matching checkpoint if a legacy/non-truncated\n // journal reused prompt indices after a rewind.\n boundary = i;\n targetCheckpoint = event;\n }\n }\n if (boundary === -1) {\n throw new Error(\n `Checkpoint ${opts.checkpointPromptIndex} not found in session \"${id}\"`,\n );\n }\n }\n\n const parentPrefix = parent.events.slice(0, boundary + 1);\n const workspaceCheckpoint = targetCheckpoint?.workspaceCheckpoint;\n const checkpointHash = createHash('sha256')\n .update(parentPrefix.map((event) => JSON.stringify(event)).join('\\n') + '\\n', 'utf8')\n .digest('hex');\n const inherited = parentPrefix.filter(inheritsIntoFork);\n const writer = await this.create({\n id: '',\n title: parent.metadata.title,\n model: parent.metadata.model,\n provider: parent.metadata.provider,\n });\n\n try {\n await writer.append({\n type: 'session_forked',\n ts: new Date().toISOString(),\n parentSessionId: id,\n parentCheckpointPromptIndex: opts.checkpointPromptIndex,\n parentCheckpointHash: checkpointHash,\n workspace: 'shared-current',\n workspaceCheckpointHash: workspaceCheckpoint?.manifestHash,\n });\n const batchSize = 250;\n for (let offset = 0; offset < inherited.length; offset += batchSize) {\n await writer.appendBatch(inherited.slice(offset, offset + batchSize));\n }\n await writer.flush();\n await writer.close();\n const data = await this.load(writer.id);\n return {\n id: writer.id,\n data,\n parentSessionId: id,\n checkpointPromptIndex: opts.checkpointPromptIndex,\n checkpointHash,\n workspace: 'shared-current',\n workspaceCheckpoint,\n };\n } catch (err) {\n await writer.close().catch(() => undefined);\n await this.delete(writer.id).catch(() => undefined);\n throw err;\n }\n }\n\n async materializeWorkspaceCheckpoint(\n checkpoint: WorkspaceCheckpointRef,\n targetRoot: string,\n ): Promise<WorkspaceMaterializationResult> {\n if (!this.checkpointCas) {\n throw new Error('Workspace checkpoint materialization requires a projectRoot-aware session store');\n }\n return this.checkpointCas.materialize(checkpoint, targetRoot);\n }\n\n async resume(id: string): Promise<ResumedSession> {\n const file = this.sessionPath(id, '.jsonl');\n const t0 = Date.now();\n const data = await this.load(id);\n let resumedData = data;\n if (this.projectRoot) {\n try {\n const resumeValidation = await validateResumeFileObservations(data.events, this.projectRoot);\n const notice = formatResumeValidationNotice(resumeValidation, this.projectRoot);\n resumedData = {\n ...data,\n resumeValidation,\n ...(notice\n ? {\n messages: [\n ...data.messages,\n { role: 'system' as const, content: notice, ts: resumeValidation.checkedAt },\n ],\n }\n : {}),\n };\n } catch (err) {\n // Validation is a safety signal, not a reason to make an otherwise\n // readable session impossible to resume. Surface diagnostics and\n // continue with the replay if an unexpected filesystem error occurs.\n this.emitError(id, file, 'resume_validation', toErrorMessage(err), true);\n }\n }\n let handle: fsp.FileHandle;\n try {\n handle = await fsp.open(file, 'a', 0o600);\n /* v8 ignore start -- defensive: load() above already validated the file is readable */\n } catch (err) {\n this.emitError(id, file, 'resume', toErrorMessage(err), false);\n throw new Error(\n `Failed to open session \"${id}\" for append: ${toErrorMessage(err)}`,\n { cause: err },\n );\n }\n /* v8 ignore stop */\n try {\n const writer = new FileSessionWriter(\n id,\n handle,\n new Date().toISOString(),\n {\n id,\n model: data.metadata.model,\n provider: data.metadata.provider,\n },\n this.events,\n {\n resumed: true,\n // Shard directory (sessions/<date>/) \u00E2\u20AC\u201D must match create() so the\n // .summary.json sidecar lands next to the JSONL instead of the\n // sessions root (where summaryFor() would never find it).\n dir: path.dirname(file),\n filePath: file,\n secretScrubber: this.secretScrubber,\n checkpointCas: this.checkpointCas,\n onClose: (s) => this.appendToIndex(s),\n },\n );\n this.emitWrite(id, file, 'resume', 'success', Date.now() - t0);\n return { writer, data: resumedData };\n /* v8 ignore start -- defensive: FileSessionWriter ctor does not throw in practice */\n } catch (err) {\n await handle.close().catch((e) => console.warn(JSON.stringify({\n level: 'warn',\n event: 'session_store.handle_close_failed',\n message: e instanceof Error ? e.message : String(e),\n timestamp: new Date().toISOString(),\n })));\n this.emitError(id, file, 'resume', toErrorMessage(err), true);\n throw err;\n }\n /* v8 ignore stop */\n }\n\n async load(id: string): Promise<SessionData> {\n return this.loadInternal(id, { full: true });\n }\n\n /**\n * Fast-path loader that skips message reconstruction and adjacency repair.\n *\n * Use this for callers that only need the raw event stream + session\n * metadata \u2014 e.g. session listers, analytics, audit, and the TUI's\n * \"events only\" views. It avoids the message array build and\n * repairToolUseAdjacency cost on large session files (a long agent\n * run can have 50k+ events; rebuilding messages is O(events) and\n * allocates per-block, so skipping it is a meaningful win).\n *\n * The returned data.messages is an empty array; data.toolCallEnds\n * is computed from the raw events. usage is the sum across all\n * llm_response events \u2014 same as full load().\n */\n async loadEventsOnly(id: string): Promise<SessionData> {\n return this.loadInternal(id, { full: false });\n }\n\n private async loadInternal(\n id: string,\n mode: { full: true } | { full: false },\n ): Promise<SessionData> {\n const file = this.sessionPath(id, '.jsonl');\n const t0 = Date.now();\n let outcome: 'success' | 'failure' = 'success';\n let errorMsg: string | undefined;\n let cacheHit = false;\n try {\n // Stat the file first to check the cache. The stat is cheap (no content\n // read) and lets us skip the full readFile + JSON parse when the file\n // hasn't changed since the last load.\n const s = await fsp.stat(file);\n const stat: { mtimeMs: number; size: number } = { mtimeMs: s.mtimeMs, size: s.size };\n\n // Check cache: if mtimeMs AND size match, the file hasn't changed.\n const cached = this._loadCache.get(id);\n if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {\n cacheHit = true;\n // Update insertion order to prevent frequent-access sessions from being\n // evicted by the LRU eviction logic.\n this._loadCache.delete(id);\n this._loadCache.set(id, cached);\n if (mode.full) return cached.data;\n return { ...cached.data, messages: [] };\n }\n\n // Cache miss \u00E2\u20AC\u201D do the full read + parse.\n // Fused single pass: parse events + build messages + extract metadata together.\n // Streams the file line-by-line so we don't materialize the whole JSONL\n // (multi-MB for long sessions). Events-only requests skip the message\n // build and the adjacency repair entirely.\n const events: SessionEvent[] = [];\n\n // Metadata extracted in the same single pass over the raw lines.\n let sessionStartEvent: SessionEvent | undefined;\n let sessionEndEvent: SessionEvent | undefined;\n let sessionModel: string | undefined;\n let sessionProvider: string | undefined;\n let sessionPendingToolUses: string[] | undefined;\n let sessionForkedEvent: Extract<SessionEvent, { type: 'session_forked' }> | undefined;\n\n // Message builder state \u2014 only allocated when mode.full.\n const messages: Message[] | undefined = mode.full ? [] : undefined;\n const openToolUses: Set<string> | undefined = mode.full ? new Set<string>() : undefined;\n let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };\n\n const stream = createReadStream(file, { encoding: 'utf8' });\n const rl = createInterface({ input: stream, crlfDelay: Infinity });\n\n try {\n for await (const line of rl) {\n if (!line.trim()) continue;\n try {\n const parsed: unknown = JSON.parse(line);\n if (\n parsed !== null &&\n typeof parsed === 'object' &&\n typeof (parsed as { type?: unknown | undefined }).type === 'string' &&\n typeof (parsed as { ts?: unknown | undefined }).ts === 'string'\n ) {\n const ev = parsed as SessionEvent;\n events.push(ev);\n\n // Track metadata in the same pass.\n if (ev.type === 'session_start' && !sessionStartEvent) {\n sessionStartEvent = ev;\n sessionModel = ev.model;\n sessionProvider = ev.provider;\n }\n if (ev.type === 'session_end') {\n sessionEndEvent = ev;\n sessionPendingToolUses = ev.pendingToolUses;\n }\n if (ev.type === 'session_forked' && !sessionForkedEvent) {\n sessionForkedEvent = ev;\n }\n\n // Build messages in the same pass (replay() logic inlined).\n // Skipped entirely when mode.full is false.\n if (mode.full && messages !== undefined && openToolUses !== undefined) {\n if (ev.type === 'context_snapshot') {\n if (!applyContextSnapshot(messages, openToolUses, ev.messages)) {\n this.events?.emit('session.damaged', {\n sessionId: id,\n detail: 'Ignored malformed context_snapshot event',\n });\n }\n } else if (ev.type === 'user_input') {\n openToolUses.clear();\n messages.push({ role: 'user', content: ev.content, ts: ev.ts });\n } else if (ev.type === 'llm_response') {\n messages.push({ role: 'assistant', content: ev.content, ts: ev.ts });\n for (const b of ev.content) {\n if (b.type === 'tool_use') openToolUses.add(b.id);\n }\n usage = {\n input: usage.input + (ev.usage.input ?? 0),\n output: usage.output + (ev.usage.output ?? 0),\n cacheRead: (usage.cacheRead ?? 0) + (ev.usage.cacheRead ?? 0),\n cacheWrite: (usage.cacheWrite ?? 0) + (ev.usage.cacheWrite ?? 0),\n };\n } else if (ev.type === 'tool_result') {\n if (!openToolUses.has(ev.id)) {\n this.events?.emit('session.damaged', {\n sessionId: id,\n detail: `Orphan tool_result \"${ev.id}\" has no matching tool_use`,\n });\n continue;\n }\n openToolUses.delete(ev.id);\n const resultBlock: ContentBlock = {\n type: 'tool_result',\n tool_use_id: ev.id,\n content: typeof ev.content === 'string' ? ev.content : JSON.stringify(ev.content),\n is_error: ev.isError,\n };\n const last = messages[messages.length - 1];\n const lastIsToolResultUser =\n last?.role === 'user' &&\n Array.isArray(last.content) &&\n last.content.every((b) => (b as ContentBlock).type === 'tool_result');\n if (lastIsToolResultUser && Array.isArray(last.content)) {\n last.content.push(resultBlock);\n } else {\n messages.push({ role: 'user', content: [resultBlock], ts: ev.ts });\n }\n }\n } else if (ev.type === 'llm_response') {\n // events-only path still accumulates usage.\n usage = {\n input: usage.input + (ev.usage.input ?? 0),\n output: usage.output + (ev.usage.output ?? 0),\n cacheRead: (usage.cacheRead ?? 0) + (ev.usage.cacheRead ?? 0),\n cacheWrite: (usage.cacheWrite ?? 0) + (ev.usage.cacheWrite ?? 0),\n };\n }\n }\n } catch {\n // skip malformed JSON\n }\n }\n } finally {\n rl.close();\n stream.close();\n }\n\n let finalMessages: Message[] = [];\n if (mode.full && messages !== undefined && openToolUses !== undefined) {\n // Repair tool adjacency after the single parse + replay loop.\n if (openToolUses.size > 0) {\n this.events?.emit('session.damaged', {\n sessionId: id,\n detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`,\n });\n }\n const repaired = repairToolUseAdjacency(messages);\n if (repaired.report.changed) {\n this.events?.emit('session.damaged', {\n sessionId: id,\n detail:\n `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ` +\n `${repaired.report.removedToolResults.length} tool_result, ` +\n `${repaired.report.removedMessages} empty messages`,\n });\n }\n finalMessages = repaired.messages;\n }\n\n // Build metadata from the extracted session_start/end events.\n const meta: SessionMetadata = {\n id,\n startedAt: sessionStartEvent?.ts ?? new Date(0).toISOString(),\n endedAt: sessionEndEvent?.ts,\n model: sessionModel,\n provider: sessionProvider,\n pendingToolUses: sessionPendingToolUses,\n forkedFrom: sessionForkedEvent\n ? {\n sessionId: sessionForkedEvent.parentSessionId,\n checkpointPromptIndex: sessionForkedEvent.parentCheckpointPromptIndex,\n checkpointHash: sessionForkedEvent.parentCheckpointHash,\n workspace: sessionForkedEvent.workspace,\n workspaceCheckpointHash: sessionForkedEvent.workspaceCheckpointHash,\n }\n : undefined,\n };\n\n // Extract tool_call_end events for TUI tool entry rendering on resume.\n const toolCallEnds = extractToolCallEnds(events);\n const data: SessionData = { metadata: meta, events, messages: finalMessages, usage, toolCallEnds };\n\n // Update the cache. Evict oldest entry if at capacity.\n // Only full loads populate the cache; events-only loads always read\n // through (they're cheap, and a hot loop on events-only would\n // otherwise evict full-load entries that callers also need).\n if (mode.full) {\n if (this._loadCache.size >= DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {\n // Map iteration order is insertion order \u00E2\u20AC\u201D delete the first key.\n const oldest = this._loadCache.keys().next().value;\n if (oldest !== undefined) {\n this._loadCache.delete(oldest);\n }\n }\n this._loadCache.set(id, { mtimeMs: stat.mtimeMs, size: stat.size, data });\n }\n\n return data;\n } catch (err) {\n outcome = 'failure';\n errorMsg = toErrorMessage(err);\n throw err;\n } finally {\n this.emitRead(id, file, mode.full ? 'load' : 'load_events_only', outcome, Date.now() - t0, errorMsg);\n if (cacheHit) {\n this.events?.emit('storage.cache_hit', {\n sessionId: id,\n store: 'session',\n filePath: file,\n operation: mode.full ? 'load' : 'load_events_only',\n durationMs: Date.now() - t0,\n });\n }\n }\n }\n\n /**\n * Streaming search over a session's JSONL. Walks the file once, parses\n * each event lazily, and yields only the events that match `predicate`.\n * Stops as soon as `opts.limit` matches are collected.\n *\n * Why this exists: `load()` parses the entire file into memory and\n * rebuilds `messages`/`toolCallEnds` for every caller. `search()` only\n * needs to know which events contain matching text \u2014 a per-line\n * predicate is enough. The full parse work (and the `_loadCache` poll)\n * is wasted in that case.\n *\n * Memory: O(hits) regardless of file size. Disk: one linear scan,\n * terminated at `limit` if the caller asked for one.\n *\n * Errors: missing file yields []. Corrupt lines are skipped (same\n * policy as `load()`). Aborting via `signal` rejects with `AbortError`.\n */\n async searchEvents(\n id: string,\n predicate: (event: SessionEvent, eventIndex: number, ts: string) => boolean,\n opts?: { limit?: number | undefined; signal?: AbortSignal | undefined },\n ): Promise<Array<{ event: SessionEvent; eventIndex: number; ts: string }>> {\n const file = this.sessionPath(id, '.jsonl');\n const limit = opts?.limit;\n const signal = opts?.signal;\n const out: Array<{ event: SessionEvent; eventIndex: number; ts: string }> = [];\n\n // Try to stat first so a missing file returns [] instead of throwing\n // \u2014 matches `load()` ENOENT semantics that callers already depend on.\n let stat: import('node:fs').Stats;\n try {\n stat = await fsp.stat(file);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw err;\n }\n if (stat.size === 0) return [];\n\n let fh: fsp.FileHandle | undefined;\n try {\n fh = await fsp.open(file, 'r');\n // Read in 64KB chunks; lines can straddle a chunk boundary so we\n // carry the trailing partial line forward between iterations.\n const CHUNK = 64 * 1024;\n const buf = Buffer.alloc(CHUNK);\n let leftover = '';\n let eventIndex = 0;\n for (let position = 0; ; position += buf.byteLength) {\n if (signal?.aborted) {\n const reason = signal.reason ?? new DOMException('Aborted', 'AbortError');\n throw reason;\n }\n const { bytesRead } = await fh.read(buf, 0, CHUNK, position);\n if (bytesRead === 0) break;\n const text = leftover + buf.subarray(0, bytesRead).toString('utf8');\n // Split into lines; the last element is either '' (file ended on a\n // newline) or a partial line \u2014 keep it as the new leftover.\n const parts = text.split('\\n');\n leftover = parts.pop() ?? '';\n for (const line of parts) {\n if (!line) continue;\n let ev: SessionEvent;\n try {\n const parsed: unknown = JSON.parse(line);\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n typeof (parsed as { type?: unknown }).type !== 'string' ||\n typeof (parsed as { ts?: unknown }).ts !== 'string'\n ) {\n // Skip lines that don't match the SessionEvent shape \u2014 same\n // tolerance as `load()` (which silently drops non-events).\n continue;\n }\n ev = parsed as SessionEvent;\n } catch {\n // Skip malformed JSON, matching `load()` behavior.\n continue;\n }\n if (predicate(ev, eventIndex, ev.ts)) {\n out.push({ event: ev, eventIndex, ts: ev.ts });\n if (limit !== undefined && out.length >= limit) {\n return out;\n }\n }\n eventIndex++;\n }\n }\n // Flush a trailing line that lacks a final newline.\n if (leftover.trim()) {\n try {\n const parsed: unknown = JSON.parse(leftover);\n if (\n parsed !== null &&\n typeof parsed === 'object' &&\n typeof (parsed as { type?: unknown }).type === 'string' &&\n typeof (parsed as { ts?: unknown }).ts === 'string'\n ) {\n const ev = parsed as SessionEvent;\n if (predicate(ev, eventIndex, ev.ts)) {\n out.push({ event: ev, eventIndex, ts: ev.ts });\n }\n }\n } catch {\n /* partial trailing line \u2014 drop */\n }\n }\n return out;\n } finally {\n if (fh) await fh.close().catch(() => undefined);\n }\n }\n\n async list(limit = 20): Promise<SessionSummary[]> {\n try {\n await ensureDir(this.dir);\n // Try the index first; fall back to directory scan if the index is\n // missing, empty, or unreadable.\n const indexed = await this.readIndex();\n if (indexed.length > 0) {\n // `readIndex()` already sorted the array by startedAt DESC, id\n // ASC, so we just slice the prefix.\n return indexed.slice(0, limit);\n }\n // Index unavailable \u2014 fall back to a directory scan. Prefer summary\n // sidecars and only backfill full JSONL-derived summaries for the page\n // we are about to return.\n return await this.listFromDirectoryScan(limit);\n } catch {\n return [];\n }\n }\n\n /**\n * List sessions matching filter criteria, using the cached index.\n * Filters are applied BEFORE sorting and slicing, so the caller gets\n * exactly `limit` matching sessions \u2014 not a slice of a larger fetch.\n *\n * This avoids the DefaultSessionReader pattern of fetching 1000 sessions\n * then linear-filtering: the index is already in memory (readIndex\n * caches it), and the filter runs over the cached array without any\n * additional disk I/O.\n */\n async listFiltered(criteria: {\n since?: string | undefined;\n until?: string | undefined;\n provider?: string | undefined;\n model?: string | undefined;\n minTokens?: number | undefined;\n titleContains?: string | undefined;\n limit?: number | undefined;\n }): Promise<SessionSummary[]> {\n const limit = criteria.limit ?? 100;\n try {\n await ensureDir(this.dir);\n const indexed = await this.readIndex();\n if (indexed.length === 0) {\n // No index \u2014 fall back to list() + in-process filter.\n const raw = await this.list(Math.max(limit, 100));\n return raw.filter((s) => matchesSessionFilter(s, criteria)).slice(0, limit);\n }\n const filtered = indexed.filter((s) => matchesSessionFilter(s, criteria));\n filtered.sort((a, b) => {\n if (a.startedAt < b.startedAt) return 1;\n if (a.startedAt > b.startedAt) return -1;\n return a.id.localeCompare(b.id);\n });\n return filtered.slice(0, limit);\n } catch {\n return [];\n }\n }\n\n // \u00E2\u201D\u20AC\u00E2\u201D\u20AC Session index (_index.jsonl) \u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\n //\n // One JSON line per closed session, appended atomically on close().\n // When a session is deleted, a tombstone {action:\"delete\",id:\"...\"} is\n // appended. On read, tombstones filter out matching session entries.\n // This keeps listing O(lines-in-index) instead of O(files-on-disk).\n //\n // The index auto-compacts every N appends to prevent unbounded growth\n // from tombstones and duplicate entries (resume cycles).\n\n private indexAppendCount = 0;\n private static readonly COMPACT_EVERY = 30;\n\n /** Append a session summary to the index. */\n private async appendToIndex(summary: SessionSummary): Promise<void> {\n // Note: storage.write for this operation is emitted by FileSessionWriter.doClose()\n // so it can include the traceId. Do NOT emit here to avoid duplicates.\n try {\n await ensureDir(this.dir);\n // Serialize the append (and any compaction it triggers) under the index\n // file lock. The lock is per-FILE, so it also guards against a SECOND\n // wstack process in the same project appending/compacting concurrently \u2014\n // without it, a compact() rewrite racing an append() silently drops the\n // appended line (the source-of-truth .jsonl survives, but the listing\n // cache loses the entry until rebuildIndex()).\n let shouldCompact = false;\n await withFileLock(this.indexFile, async () => {\n const line = JSON.stringify(summary) + '\\n';\n await fsp.appendFile(this.indexFile, line, 'utf8');\n this._indexCache = null;\n this.invalidateShardManifestBySessionId(summary.id);\n this.indexAppendCount++;\n // Auto-compact periodically to remove tombstones and duplicates.\n // compactIndexInner() is called WHILE the lock is held \u2014 it must not\n // re-acquire it (withFileLock is not reentrant) or it would deadlock.\n if (this.indexAppendCount >= DefaultSessionStore.COMPACT_EVERY) {\n shouldCompact = true;\n this.indexAppendCount = 0;\n }\n });\n if (shouldCompact) {\n await withFileLock(this.indexFile, () => this.compactIndexInner());\n }\n } catch {\n // best-effort \u00E2\u20AC\u201D error surfaced via the storage.write event in doClose()\n }\n }\n\n /** Append a tombstone entry for a deleted session. */\n private async writeTombstone(id: string): Promise<void> {\n try {\n await ensureDir(this.dir);\n await withFileLock(this.indexFile, async () => {\n const line = JSON.stringify({ action: 'delete', id }) + '\\n';\n await fsp.appendFile(this.indexFile, line, 'utf8');\n this._indexCache = null;\n this.invalidateShardManifestBySessionId(id);\n this.indexAppendCount++;\n });\n } catch {\n // best-effort\n }\n }\n\n /**\n * Compact the index: read all entries, drop tombstones, deduplicate\n * (keep latest per session), and rewrite atomically. Acquires the index\n * file lock so a concurrent append (this process or another wstack in the\n * same project) can't be overwritten by the rewrite.\n */\n private async compactIndex(): Promise<void> {\n const t0 = Date.now();\n let outcome: 'success' | 'failure' = 'success';\n let errorMsg: string | undefined;\n try {\n await withFileLock(this.indexFile, () => this.compactIndexInner());\n } catch (err) {\n outcome = 'failure';\n errorMsg = toErrorMessage(err);\n } finally {\n // Compact is internal \u00E2\u20AC\u201D use 'session' as the session ID placeholder.\n this.emitWrite('~compact~', this.indexFile, 'compact', outcome, Date.now() - t0, undefined, errorMsg);\n }\n }\n\n /**\n * Lock-free compaction body. The caller MUST already hold the index file\n * lock (via withFileLock(this.indexFile, ...)). Uses atomicWrite for the\n * rewrite so the temp file gets a random suffix (no collision between two\n * compactions) and the Windows transient-EPERM rename retry.\n */\n private async compactIndexInner(): Promise<void> {\n const entries = await this.readIndex();\n if (entries.length === 0) return;\n const lines = entries.map((s) => JSON.stringify(s)).join('\\n') + '\\n';\n await atomicWrite(this.indexFile, lines, { mode: 0o600 });\n this._indexCache = null;\n }\n\n /**\n * Read the index file and return deduplicated session summaries.\n * Entries with a matching tombstone are filtered out.\n * Returns empty array when the index doesn't exist or is corrupt.\n */\n private async readIndex(): Promise<SessionSummary[]> {\n let stat: { mtimeMs: number; size: number };\n try {\n const s = await fsp.stat(this.indexFile);\n stat = { mtimeMs: s.mtimeMs, size: s.size };\n } catch {\n this._indexCache = null;\n return [];\n }\n\n if (\n this._indexCache !== null &&\n this._indexCache.mtimeMs === stat.mtimeMs &&\n this._indexCache.size === stat.size\n ) {\n return [...this._indexCache.summaries];\n }\n\n let raw: string;\n try {\n raw = await fsp.readFile(this.indexFile, 'utf8');\n } catch {\n this._indexCache = null;\n return [];\n }\n const deleted = new Set<string>();\n const seen = new Map<string, SessionSummary>();\n for (const line of raw.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const entry = JSON.parse(line) as { action?: string | undefined; id?: string | undefined } & SessionSummary;\n if (entry.action === 'delete' && entry.id) {\n deleted.add(entry.id);\n seen.delete(entry.id);\n continue;\n }\n if (entry.id && !deleted.has(entry.id)) {\n // Keep the latest entry for each session (multiple appends on resume).\n seen.set(entry.id, entry as SessionSummary);\n }\n } catch {\n // skip corrupt lines\n }\n }\n const summaries = Array.from(seen.values());\n // Sort once when the index is (re)loaded so `list()` callers can\n // take a prefix without re-sorting the whole array per request.\n // Sort key mirrors the original `list()` comparator:\n // startedAt DESC, then id ASC for tie-breaks.\n summaries.sort((a, b) => {\n if (a.startedAt < b.startedAt) return 1;\n if (a.startedAt > b.startedAt) return -1;\n return a.id.localeCompare(b.id);\n });\n this._indexCache = { ...stat, summaries };\n return [...summaries];\n }\n\n /**\n * Rebuild the index from disk by scanning all sessions and writing a\n * fresh _index.jsonl. Useful after manual cleanup or index corruption.\n */\n async rebuildIndex(): Promise<number> {\n const ids = await this.collectSessionIds(this.dir);\n /* v8 ignore next -- summaryFor() never rejects for a collected id (its .jsonl exists) */\n const summaries = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null))); /* best-effort */\n const valid = summaries.filter((s): s is SessionSummary => s !== null);\n // Atomic rewrite under the index lock so it can't clobber a concurrent\n // append (or be clobbered by a concurrent compaction). atomicWrite gives\n // a random temp suffix (no collision with compactIndexInner's temp) and\n // the Windows transient-EPERM rename retry. The expensive disk scan above\n // runs OUTSIDE the lock to avoid holding it for the whole rebuild.\n const lines = valid.map((s) => JSON.stringify(s)).join('\\n') + '\\n';\n await withFileLock(this.indexFile, async () => {\n await atomicWrite(this.indexFile, lines, { mode: 0o600 });\n this._indexCache = null;\n });\n return valid.length;\n }\n\n private async listFromDirectoryScan(limit: number): Promise<SessionSummary[]> {\n const shardKeys = await this.collectShardKeys();\n const shardEntries = await mapWithConcurrency(\n shardKeys,\n DefaultSessionStore.LIST_SCAN_CONCURRENCY,\n async (shardKey) => await this.readOrBuildShardManifest(shardKey),\n );\n\n const out: DirectorySummaryCandidate[] = [];\n for (const entry of shardEntries) {\n for (const summary of entry.summaries) {\n out.push({ summary, needsBackfill: false });\n }\n }\n out.sort((a, b) => compareSessionSummaries(a.summary, b.summary));\n\n const selected = out.slice(0, limit);\n const summaries = await mapWithConcurrency(\n selected,\n Math.min(DefaultSessionStore.LIST_SCAN_CONCURRENCY, Math.max(1, limit)),\n async (candidate): Promise<SessionSummary | null> => candidate.summary,\n );\n return summaries.filter((s): s is SessionSummary => s !== null);\n }\n\n private async collectShardKeys(): Promise<string[]> {\n let entries: Dirent[];\n try {\n entries = await fsp.readdir(this.dir, { withFileTypes: true });\n } catch {\n return [''];\n }\n\n const shardKeys = [''];\n for (const entry of entries) {\n if (entry.name.startsWith('.') && entry.name !== '.wrongstack') continue;\n if (entry.name === 'shared' || entry.name === 'subagents' || entry.name === 'attachments') continue;\n if (entry.isDirectory()) shardKeys.push(entry.name);\n }\n return shardKeys;\n }\n\n private async readOrBuildShardManifest(shardKey: string): Promise<ShardManifestEntry> {\n const cached = this.shardManifestCache.get(shardKey);\n if (cached) return cached;\n\n const manifestPath = this.shardManifestPath(shardKey);\n try {\n const raw = await fsp.readFile(manifestPath, 'utf8');\n const parsed = JSON.parse(raw) as ShardManifestEntry;\n const entry: ShardManifestEntry = {\n summaries: Array.isArray(parsed.summaries) ? parsed.summaries : [],\n ids: Array.isArray(parsed.ids) ? parsed.ids : [],\n };\n this.shardManifestCache.set(shardKey, entry);\n return entry;\n } catch {\n // build below\n }\n\n const refs = await this.collectSessionFilesInShard(shardKey);\n const candidates = await mapWithConcurrency(\n refs,\n DefaultSessionStore.LIST_SCAN_CONCURRENCY,\n async (ref): Promise<DirectorySummaryCandidate | null> => {\n const manifest = await this.readSummaryManifest(ref.id);\n if (manifest) return { summary: manifest, needsBackfill: false };\n const summary = await this.summaryHeaderFor(ref);\n if (!summary) return null;\n const hydrated = await this.summaryFor(summary.id).catch(() => summary);\n return { summary: hydrated, needsBackfill: false };\n },\n );\n const summaries = candidates\n .filter((candidate): candidate is DirectorySummaryCandidate => candidate !== null)\n .map((candidate) => candidate.summary);\n summaries.sort(compareSessionSummaries);\n const entry: ShardManifestEntry = { summaries, ids: summaries.map((summary) => summary.id) };\n this.shardManifestCache.set(shardKey, entry);\n await atomicWrite(manifestPath, JSON.stringify(entry), { mode: 0o600 }).catch(() => undefined);\n return entry;\n }\n\n private async collectSessionFilesInShard(shardKey: string): Promise<SessionFileRef[]> {\n const dir = shardKey ? path.join(this.dir, shardKey) : this.dir;\n const entries = await this.collectSessionFiles(dir, shardKey);\n return shardKey\n ? entries.filter((entry) => entry.id.startsWith(`${shardKey}/`))\n : entries.filter((entry) => !entry.id.includes('/'));\n }\n\n private async collectSessionFiles(\n dir: string,\n prefix = '',\n depth = 0,\n ): Promise<SessionFileRef[]> {\n let entries: Dirent[];\n try {\n entries = await fsp.readdir(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n\n const dirEntries: Dirent[] = [];\n const files: SessionFileRef[] = [];\n for (const entry of entries) {\n if (entry.name.startsWith('.') && entry.name !== '.wrongstack') continue;\n if (entry.name === 'shared' || entry.name === 'subagents' || entry.name === 'attachments')\n continue;\n if (entry.isDirectory()) {\n dirEntries.push(entry);\n } else if (entry.isFile() && entry.name.endsWith('.jsonl')) {\n if (entry.name === '_index.jsonl') continue;\n const base = entry.name.replace(/\\.jsonl$/, '');\n const id = prefix ? `${prefix}/${base}` : base;\n files.push({ id, filePath: path.join(dir, entry.name) });\n }\n }\n\n const childFileArrays = await Promise.all(\n dirEntries.map((entry) => {\n const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;\n return this.collectSessionFiles(path.join(dir, entry.name), childPrefix, depth + 1);\n }),\n );\n\n return [...childFileArrays.flat(), ...files];\n }\n\n /** Recursively collect session IDs from date-shard subdirectories.\n * IDs include the date-prefix path (e.g. \"2026-06-06/17-46-57Z_\u00E2\u20AC\u00A6\").\n * Skips `.jsonl`/`.summary.json` root files, dot-files, and\n * sub-directories that belong to fleet/subagent sessions. */\n private async collectSessionIds(\n dir: string,\n prefix = '',\n depth = 0,\n ): Promise<string[]> {\n let entries: Dirent[];\n try {\n entries = await fsp.readdir(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n\n // Separate dirs and files in one pass \u00E2\u20AC\u201D avoids a second iteration.\n const dirEntries: Dirent[] = [];\n const fileIds: string[] = [];\n for (const entry of entries) {\n if (entry.name.startsWith('.') && entry.name !== '.wrongstack') continue;\n if (entry.name === 'shared' || entry.name === 'subagents' || entry.name === 'attachments')\n continue;\n if (entry.isDirectory()) {\n dirEntries.push(entry);\n } else if (entry.isFile() && entry.name.endsWith('.jsonl')) {\n if (entry.name === '_index.jsonl') continue;\n const base = entry.name.replace(/\\.jsonl$/, '');\n fileIds.push(prefix ? `${prefix}/${base}` : base);\n }\n }\n\n // At depth 0 the date-shard directories are independent \u00E2\u20AC\u201D parallelize across\n // them. Deeper recursion (intra-shard) is sequential since shards are small.\n const childIdArrays = await Promise.all(\n dirEntries.map((entry) => {\n const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;\n return this.collectSessionIds(path.join(dir, entry.name), childPrefix, depth + 1);\n }),\n );\n\n return [...childIdArrays.flat(), ...fileIds];\n }\n\n private async summaryFor(id: string): Promise<SessionSummary> {\n const manifest = this.sessionPath(id, '.summary.json');\n const t0 = Date.now();\n let outcome: 'success' | 'failure' = 'success';\n let errorMsg: string | undefined;\n const fromManifest = await this.readSummaryManifest(id, t0);\n if (fromManifest) return fromManifest;\n\n try {\n const full = this.sessionPath(id, '.jsonl');\n const stat = await fsp.stat(full);\n const summary = await this.summarize(id, stat.mtime.toISOString());\n await atomicWrite(manifest, JSON.stringify(summary), { mode: 0o600 }).catch((err) => {\n const msg = toErrorMessage(err);\n this.emitError(id, manifest, 'summary_fallback', msg, true);\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'session_store.manifest_write_failed',\n sessionId: id,\n message: msg,\n timestamp: new Date().toISOString(),\n }));\n });\n outcome = 'failure';\n errorMsg = 'summary fallback \u00E2\u20AC\u201D manifest rebuilt';\n this.emitRead(id, manifest, 'summary', outcome, Date.now() - t0, errorMsg);\n return summary;\n } catch (err) {\n outcome = 'failure';\n errorMsg = toErrorMessage(err);\n this.emitRead(id, manifest, 'summary', outcome, Date.now() - t0, errorMsg);\n return {\n id,\n title: '(damaged)',\n startedAt: new Date().toISOString(),\n model: 'unknown',\n provider: 'unknown',\n tokenTotal: 0,\n };\n }\n }\n\n private async readSummaryManifest(\n id: string,\n startTime = Date.now(),\n ): Promise<SessionSummary | null> {\n const manifest = this.sessionPath(id, '.summary.json');\n try {\n const raw = await fsp.readFile(manifest, 'utf8');\n this.emitRead(id, manifest, 'summary', 'success', Date.now() - startTime);\n return JSON.parse(raw) as SessionSummary;\n } catch {\n return null;\n }\n }\n\n private async summaryHeaderFor(ref: SessionFileRef): Promise<SessionSummary | null> {\n let mtime = new Date(0).toISOString();\n try {\n const stat = await fsp.stat(ref.filePath);\n if (!stat.isFile()) {\n return {\n id: ref.id,\n title: '(damaged)',\n startedAt: stat.mtime.toISOString(),\n model: 'unknown',\n provider: 'unknown',\n tokenTotal: 0,\n };\n }\n mtime = stat.mtime.toISOString();\n } catch {\n return null;\n }\n\n try {\n for await (const event of this.iterSessionEvents(ref.filePath)) {\n if (event.type === 'session_start') {\n return {\n id: ref.id,\n title: '(empty session)',\n startedAt: event.ts,\n model: event.model ?? 'unknown',\n provider: event.provider ?? 'unknown',\n tokenTotal: 0,\n };\n }\n }\n return {\n id: ref.id,\n title: '(empty session)',\n startedAt: new Date(0).toISOString(),\n model: 'unknown',\n provider: 'unknown',\n tokenTotal: 0,\n };\n } catch {\n return {\n id: ref.id,\n title: '(damaged)',\n startedAt: mtime,\n model: 'unknown',\n provider: 'unknown',\n tokenTotal: 0,\n };\n }\n }\n\n /**\n * Delete a session and all associated files: JSONL, summary, plan/todos\n * sidecars, and the session directory (fleet.json, shared/, subagents/).\n *\n * Individual file deletions are best-effort (logged as structured warnings),\n * but a tombstone is always written so readIndex() filters this session out.\n * If the session directory itself can't be removed, the error is surfaced\n * to the caller so prune() can report it.\n */\n private async deleteSession(id: string): Promise<void> {\n const jsonlPath = this.sessionPath(id, '.jsonl');\n const summaryPath = this.sessionPath(id, '.summary.json');\n const shardDir = path.dirname(jsonlPath);\n const base = path.basename(id);\n const sessDir = path.join(shardDir, base);\n\n const deletions: Array<Promise<void>> = [\n fsp.unlink(jsonlPath),\n fsp.unlink(summaryPath),\n fsp.unlink(sessionScopedPath(this.dir, id, '.plan.json')),\n fsp.unlink(sessionScopedPath(this.dir, id, '.tasks.json')),\n fsp.unlink(sessionScopedPath(this.dir, id, '.todos.json')),\n ];\n\n const results = await Promise.allSettled(deletions);\n for (const r of results) {\n if (r.status === 'rejected') {\n const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);\n // ENOENT is expected (file may not exist \u00E2\u20AC\u201D sidecars are optional).\n if ((r.reason as NodeJS.ErrnoException)?.code !== 'ENOENT') {\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'session_store.delete_failed',\n sessionId: id,\n message: msg,\n timestamp: new Date().toISOString(),\n }));\n }\n }\n }\n\n // Remove the session directory (may contain fleet.json, shared/, subagents/).\n /* v8 ignore start -- defensive: rm with force:true rarely rejects */\n await fsp.rm(sessDir, { recursive: true, force: true }).catch((err) => {\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'session_store.rmdir_failed',\n sessionId: id,\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }));\n });\n /* v8 ignore stop */\n\n // Write an index tombstone so readIndex() filters this session out.\n await this.writeTombstone(id);\n }\n\n /**\n * Read the session id currently marked active in `active.json`, or `null`\n * when the lock is absent/unreadable. Shared by {@link delete} and\n * {@link prune} to avoid clobbering a session a live process is writing to.\n */\n private async readActiveSessionId(): Promise<string | null> {\n try {\n const raw = await fsp.readFile(path.join(this.dir, 'active.json'), 'utf8');\n const active = JSON.parse(raw) as { sessionId?: string | undefined };\n return active.sessionId ?? null;\n } catch {\n // no active.json \u2014 nothing to protect\n return null;\n }\n }\n\n async delete(id: string): Promise<void> {\n // Guard 1: never delete the session another process in this project is\n // actively writing to. active.json is the per-project RecoveryLock; every\n // CLI/TUI/WebUI writes it on session start. Without this check, deleting\n // a session that a parallel surface holds open would silently drop every\n // subsequent append (the JSONL is gone) while the writer keeps buffering \u2014\n // a data-loss + recovery-inconsistency bug.\n const activeId = await this.readActiveSessionId();\n if (activeId && id === activeId) {\n throw new Error(\n `Session ${id} is currently active in this project and cannot be deleted. Resume or start another session first.`,\n );\n }\n // Guard 2: cross-process live-session registry. active.json only tracks\n // the *latest* active session per project; the registry lists every live\n // process (multiple terminals/TUIs/WebUIs can each have their own active\n // session). When wired, this catches a delete targeting a session that a\n // *different* surface is using, even though it isn't in our active.json.\n if (this.isSessionInUse) {\n const reason = await this.isSessionInUse(id);\n if (reason) {\n throw new Error(`Session ${id} is in use (${reason}) and cannot be deleted.`);\n }\n }\n await this.deleteSession(id);\n }\n\n async rename(id: string, name: string): Promise<SessionSummary> {\n const trimmed = name.trim();\n const manifest = this.sessionPath(id, '.summary.json');\n const jsonlPath = this.sessionPath(id, '.jsonl');\n // Refuse to name a session that has no JSONL on disk. `summaryFor`\n // would otherwise synthesize a '(damaged)' summary and persist it,\n // creating a phantom entry. ENOENT \u2192 throw a typed error so callers\n // can surface \"session not found\" cleanly.\n try {\n await fsp.stat(jsonlPath);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException)?.code;\n if (code === 'ENOENT') {\n throw new Error(`Session not found: ${id}`);\n }\n throw err;\n }\n\n // Load the current summary (sidecar first, rebuild on miss) and apply\n // the name mutation. summaryFor() already emits read/failure events.\n // Build the updated summary with explicit name handling so we stay\n // compatible with exactOptionalPropertyTypes: a cleared name is omitted\n // from the object entirely (not set to undefined).\n const summary = await this.summaryFor(id);\n const { name: _drop, ...rest } = summary;\n const updated: SessionSummary = trimmed ? { ...rest, name: trimmed } : rest;\n\n const t0 = Date.now();\n let outcome: 'success' | 'failure' = 'success';\n let errorMsg: string | undefined;\n try {\n await atomicWrite(manifest, JSON.stringify(updated), { mode: 0o600 });\n } catch (err) {\n outcome = 'failure';\n errorMsg = toErrorMessage(err);\n this.emitError(id, manifest, 'rename', errorMsg, false);\n throw err;\n } finally {\n this.emitWrite(id, manifest, 'close', outcome, Date.now() - t0, undefined, errorMsg);\n }\n\n // Mirror the change into the index so list() reflects it immediately.\n // appendToIndex dedupes by id (\"latest wins\"), so the stale entry is\n // shadowed without a full compact.\n await this.appendToIndex(updated);\n this.invalidateShardManifestBySessionId(id);\n this._indexCache = null;\n this.clearLoadCache(id);\n return updated;\n }\n\n async prune(maxAgeDays = 30): Promise<number> {\n const cutoff = Date.now() - maxAgeDays * 86_400_000;\n let deleted = 0;\n\n // Read the active session lock to avoid pruning the current session.\n const activeSessionId = await this.readActiveSessionId();\n\n const isPrunableJsonl = (name: string): boolean =>\n name.endsWith('.jsonl') &&\n name !== '_index.jsonl' &&\n name !== '_mailbox.jsonl' &&\n !name.endsWith('.replay.jsonl') &&\n !name.endsWith('.audit.jsonl');\n\n const pruneFile = async (dir: string, name: string, prefix: string): Promise<void> => {\n const jsonlPath = path.join(dir, name);\n try {\n const stat = await fsp.stat(jsonlPath);\n if (stat.mtimeMs >= cutoff) return;\n /* v8 ignore start -- defensive: file vanished between readdir and stat */\n } catch {\n return;\n }\n /* v8 ignore stop */\n const base = name.replace(/\\.jsonl$/, '');\n const id = prefix ? `${prefix}/${base}` : base;\n // Never prune the currently active session.\n if (activeSessionId && id === activeSessionId) return;\n await this.deleteSession(id);\n deleted++;\n };\n\n /* v8 ignore next -- defensive: store dir is ensured before prune runs */\n const entries = await fsp.readdir(this.dir, { withFileTypes: true }).catch(() => []);\n for (const entry of entries) {\n if (entry.isFile()) {\n // Flat legacy sessions at the sessions root \u00E2\u20AC\u201D pre-shard layout.\n // A shard-only scan left these accumulating forever.\n if (isPrunableJsonl(entry.name)) await pruneFile(this.dir, entry.name, '');\n continue;\n }\n /* v8 ignore next -- defensive: root entries are only files or directories */\n if (!entry.isDirectory()) continue;\n // entry.name is a date-shard like \"2026-06-06\"\n const dateDir = path.join(this.dir, entry.name);\n /* v8 ignore next -- defensive: dateDir came from readdir and is readable */\n const files = await fsp.readdir(dateDir, { withFileTypes: true }).catch(() => []);\n for (const file of files) {\n if (!file.isFile() || !isPrunableJsonl(file.name)) continue;\n await pruneFile(dateDir, file.name, entry.name);\n }\n }\n if (deleted > 0) {\n // Compact the index to remove tombstones for deleted sessions.\n /* v8 ignore next -- best-effort: compactIndex swallows its own errors */\n await this.compactIndex().catch(() => undefined); /* best-effort */\n }\n // Clean up empty date-shard directories left behind after pruning.\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const dateDir = path.join(this.dir, entry.name);\n try {\n const remaining = await fsp.readdir(dateDir);\n if (remaining.length === 0) {\n /* v8 ignore next -- best-effort: rmdir of a confirmed-empty dir does not reject */\n await fsp.rmdir(dateDir).catch(() => undefined);\n }\n } catch {\n // best-effort\n }\n }\n return deleted;\n }\n\n async clearHistory(id: string): Promise<void> {\n await this.ensureShardDir(id);\n const file = this.sessionPath(id, '.jsonl');\n const meta = this.sessionPath(id, '.summary.json');\n const record = `${JSON.stringify({\n type: 'session_start',\n ts: new Date().toISOString(),\n id,\n model: 'unknown',\n provider: 'unknown',\n })}\\n`;\n await fsp.writeFile(file, record, 'utf8');\n await fsp.unlink(meta).catch(() => undefined);\n }\n\n private async summarize(id: string, mtime: string): Promise<SessionSummary> {\n try {\n const file = this.sessionPath(id, '.jsonl');\n let title = '(empty session)';\n let startedAt = new Date(0).toISOString();\n let endedAt: string | undefined;\n let model = 'unknown';\n let provider = 'unknown';\n let tokenIn = 0;\n let tokenOut = 0;\n let iterationCount = 0;\n let toolCallCount = 0;\n let toolErrorCount = 0;\n let fileChangeCount = 0;\n const toolBreakdown: Record<string, number> = {};\n let outcome: SessionSummary['outcome'] ;\n let lastEventType: SessionEvent['type'] | undefined;\n let hasError = false;\n let sawStart = false;\n\n for await (const e of this.iterSessionEvents(file)) {\n lastEventType = e.type;\n if (e.type === 'session_start') {\n if (!sawStart) {\n sawStart = true;\n startedAt = e.ts;\n model = e.model ?? 'unknown';\n provider = e.provider ?? 'unknown';\n }\n } else if (e.type === 'session_end') {\n endedAt = e.ts;\n } else if (e.type === 'user_input') {\n if (title === '(empty session)') title = userInputTitle(e.content);\n } else if (e.type === 'llm_response') {\n tokenIn += e.usage.input ?? 0;\n tokenOut += e.usage.output ?? 0;\n } else if (e.type === 'in_flight_start') iterationCount++;\n else if (e.type === 'tool_call_start') {\n toolCallCount++;\n toolBreakdown[e.name] = (toolBreakdown[e.name] ?? 0) + 1;\n } else if (e.type === 'tool_result' && e.isError) toolErrorCount++;\n else if (e.type === 'file_snapshot') fileChangeCount += e.files.length;\n else if (e.type === 'error' || e.type === 'provider_error') hasError = true;\n }\n\n // Determine outcome from the last event.\n if (lastEventType === 'session_end') {\n outcome = 'completed';\n } else if (lastEventType === 'in_flight_start') {\n outcome = 'aborted';\n } else if (hasError) {\n outcome = 'error';\n }\n\n return {\n id,\n title,\n startedAt,\n endedAt,\n model,\n provider,\n tokenTotal: tokenIn + tokenOut,\n iterationCount: iterationCount > 0 ? iterationCount : undefined,\n toolCallCount: toolCallCount > 0 ? toolCallCount : undefined,\n toolErrorCount: toolErrorCount > 0 ? toolErrorCount : undefined,\n fileChangeCount: fileChangeCount > 0 ? fileChangeCount : undefined,\n toolBreakdown: Object.keys(toolBreakdown).length > 0 ? toolBreakdown : {},\n outcome,\n };\n } catch {\n return {\n id,\n title: '(damaged)',\n startedAt: mtime,\n model: 'unknown',\n provider: 'unknown',\n tokenTotal: 0,\n };\n }\n }\n\n private async *iterSessionEvents(file: string): AsyncGenerator<SessionEvent> {\n const stream = createReadStream(file, { encoding: 'utf8' });\n const lines = createInterface({ input: stream, crlfDelay: Infinity });\n try {\n for await (const line of lines) {\n if (!line.trim()) continue;\n try {\n const parsed: unknown = JSON.parse(line);\n if (\n parsed !== null &&\n typeof parsed === 'object' &&\n typeof (parsed as { type?: unknown | undefined }).type === 'string' &&\n typeof (parsed as { ts?: unknown | undefined }).ts === 'string'\n ) {\n yield parsed as SessionEvent;\n }\n } catch {\n // skip malformed JSON\n }\n }\n } finally {\n lines.close();\n stream.destroy();\n }\n }\n\n\n}\n", "import { randomBytes } from 'node:crypto';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { FsError } from '../types/errors.js';\n\nexport interface AtomicWriteOptions {\n mode?: number | undefined;\n encoding?: BufferEncoding | undefined;\n}\n\nexport interface FileLockOptions {\n timeoutMs?: number | undefined;\n staleMs?: number | undefined;\n}\n\nexport async function atomicWrite(\n targetPath: string,\n content: string | Uint8Array,\n opts: AtomicWriteOptions = {},\n): Promise<void> {\n const dir = path.dirname(targetPath);\n await fs.mkdir(dir, { recursive: true });\n const tmp = path.join(dir, `.${path.basename(targetPath)}.${randomBytes(6).toString('hex')}.tmp`);\n\n // Write content to tmp first; 'wx' ensures exclusive creation (fails if\n // tmp already exists \u2014 extremely unlikely with 6-byte random suffix).\n try {\n if (typeof content === 'string') {\n await fs.writeFile(tmp, content, { flag: 'wx', encoding: opts.encoding ?? 'utf8' });\n } else {\n await fs.writeFile(tmp, content, { flag: 'wx' });\n }\n try {\n const fh = await fs.open(tmp, 'r+');\n try {\n await fh.sync();\n } finally {\n await fh.close();\n }\n } catch {\n // fsync best-effort\n }\n // Now safely read mode from target (if it exists) and apply to tmp before rename.\n // Prefer opts.mode for new files; for existing files preserve their mode.\n let mode: number | undefined;\n try {\n const stat = await fs.stat(targetPath);\n mode = stat.mode & 0o777;\n } catch {\n mode = opts.mode;\n }\n if (mode !== undefined) {\n await fs.chmod(tmp, mode);\n }\n await renameWithRetry(tmp, targetPath);\n // P3 #20 (before-release.md): on Windows, fs.rename (MoveFileExW) does\n // not preserve Unix permission bits \u2014 the chmod above applies to the tmp\n // file, but the rename may reset the destination's mode to the Windows\n // default. Re-apply the mode after rename on win32 so an edited file\n // keeps its executable bit (or any non-default permission). On POSIX,\n // rename preserves metadata so this is a no-op (chmod is idempotent and\n // cheap), but we gate it on win32 to avoid the extra stat+chmod on the\n // common path.\n if (mode !== undefined && process.platform === 'win32') {\n try {\n await fs.chmod(targetPath, mode);\n } catch {\n // Best-effort: a transient EPERM (antivirus lock) should not fail\n // the write \u2014 the content is already on disk.\n }\n }\n } catch (err) {\n try {\n await fs.unlink(tmp);\n } catch {\n // ignore cleanup error\n }\n throw err;\n }\n}\n\nexport async function ensureDir(dir: string): Promise<void> {\n await fs.mkdir(dir, { recursive: true });\n}\n\nexport async function withFileLock<T>(\n targetPath: string,\n fn: () => Promise<T>,\n opts: FileLockOptions = {},\n): Promise<T> {\n const dir = path.dirname(targetPath);\n await fs.mkdir(dir, { recursive: true });\n const lockPath = path.join(dir, `.${path.basename(targetPath)}.lock`);\n const timeoutMs = opts.timeoutMs ?? 5_000;\n const staleMs = opts.staleMs ?? 30_000;\n const started = Date.now();\n let handle: fs.FileHandle | undefined;\n\n for (;;) {\n try {\n handle = await fs.open(lockPath, 'wx');\n await handle.writeFile(`${process.pid}:${Date.now()}`);\n break;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n // ENOENT means the directory was deleted (e.g. by concurrent cleanup).\n // Recreate it and retry acquiring the lock.\n if (code === 'ENOENT') {\n await fs.mkdir(dir, { recursive: true });\n continue;\n }\n if (code !== 'EEXIST') throw err;\n try {\n const stat = await fs.stat(lockPath);\n if (Date.now() - stat.mtimeMs > staleMs) {\n await fs.unlink(lockPath);\n continue;\n }\n } catch {\n continue;\n }\n if (Date.now() - started >= timeoutMs) {\n throw new FsError({\n message: `Timed out waiting for file lock: ${targetPath}`,\n code: 'FS_ATOMIC_WRITE_FAILED',\n path: targetPath,\n context: { timeoutMs },\n });\n }\n await new Promise((resolve) => setTimeout(resolve, 25));\n }\n }\n\n try {\n return await fn();\n } finally {\n try {\n await handle?.close();\n } catch {\n // ignore\n }\n try {\n await fs.unlink(lockPath);\n } catch {\n // ignore\n }\n }\n}\n\n// On Windows, fs.rename over an existing file can fail with EPERM/EBUSY/EACCES\n// when antivirus, file indexers, editor file watchers, or a concurrent writer\n// briefly hold a handle on the destination. These are transient \u2014 retry with a\n// short backoff before giving up. POSIX renames are atomic and won't hit this.\nconst TRANSIENT_RENAME_CODES = new Set(['EPERM', 'EBUSY', 'EACCES', 'ENOTEMPTY']);\n\nasync function renameWithRetry(from: string, to: string): Promise<void> {\n if (process.platform !== 'win32') {\n await fs.rename(from, to);\n return;\n }\n const delays = [10, 25, 60, 120, 250];\n let lastErr: unknown;\n for (let i = 0; i <= delays.length; i++) {\n try {\n await fs.rename(from, to);\n return;\n } catch (err) {\n lastErr = err;\n const code = (err as NodeJS.ErrnoException)?.code;\n if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {\n throw err;\n }\n await new Promise((resolve) => setTimeout(resolve, delays[i]));\n }\n }\n throw lastErr;\n}\n", "/**\n * Build a sanitized child-process environment.\n *\n * The bash/exec tools and MCP stdio transports execute LLM-generated or\n * configured commands. The parent process carries provider API keys\n * (ANTHROPIC_API_KEY, OPENAI_API_KEY, ...), VCS tokens (GITHUB_TOKEN),\n * and cloud credentials. Forwarding those to a child is an exfiltration\n * vector even with `permission: 'confirm'` \u2014 a compromised MCP server\n * or a cleverly composed shell pipeline can leak secrets.\n *\n * Strategy: copy a small, explicit allowlist of variables that real builds\n * need, then copy anything else that does NOT look secret-bearing. This\n * preserves user-friendly behavior (locale, terminal, npm config) while\n * blocking the obvious leak channels. Two value-side guards back up the\n * name-based filter:\n * - any value carrying an embedded URI credential (`scheme://user:pass@host`,\n * e.g. `DATABASE_URL`/`REDIS_URL`/`*_DSN`) is dropped (WS-01);\n * - `NODE_OPTIONS` is forwarded but with module-preload directives\n * (`--require`/`--import`/`--loader`) stripped so a parent-set value can't\n * inject code into node children (WS-02).\n *\n * Override with `WRONGSTACK_CHILD_ENV_PASSTHROUGH=1` to forward the full\n * parent environment unchanged (opt-in for advanced users who understand\n * the risk).\n */\n\nconst ALLOWED_KEYS = new Set<string>([\n 'PATH',\n 'HOME',\n 'USER',\n 'USERNAME',\n 'LOGNAME',\n 'SHELL',\n 'LANG',\n 'LC_ALL',\n 'LC_CTYPE',\n 'TERM',\n 'TZ',\n 'TMPDIR',\n 'TEMP',\n 'TMP',\n 'PWD',\n 'OLDPWD',\n 'COMSPEC',\n 'SYSTEMROOT',\n 'SYSTEMDRIVE',\n 'WINDIR',\n 'PROGRAMFILES',\n 'PROGRAMFILES(X86)',\n 'PROGRAMDATA',\n 'APPDATA',\n 'LOCALAPPDATA',\n 'USERPROFILE',\n 'PUBLIC',\n 'PATHEXT',\n]);\n\n// Substring match against env-var names (case-insensitive). Bias toward\n// false-positives \u2014 a missing var is recoverable, an exfiltrated key is not.\n// Only consulted for vars NOT on the curated allowlist; PWD/PASSWD-style\n// false positives there are avoided by checking allowlist first.\nconst SECRET_NAME_PARTS = [\n 'TOKEN',\n 'SECRET',\n 'PASSWORD',\n 'PASSWD',\n 'AUTH',\n 'CRED',\n 'BEARER',\n 'COOKIE',\n 'PRIVATE',\n];\n\nfunction looksSecret(name: string): boolean {\n const upper = name.toUpperCase();\n for (const p of SECRET_NAME_PARTS) {\n if (upper.includes(p)) return true;\n }\n // KEY is tricky \u2014 PUBLIC_KEY is fine to forward but most _KEY vars are\n // secrets. Require word boundary so KEYBOARD_LAYOUT etc. are not flagged.\n if (/(?:^|_)KEY(?:$|_|S$)/i.test(upper)) return true;\n if (/API[_-]?KEY/i.test(upper)) return true;\n if (/ACCESS[_-]?KEY/i.test(upper)) return true;\n if (/SESSION[_-]?ID/i.test(upper) === false && /SESSION/i.test(upper)) {\n // SESSION_ID is metadata (we set our own); other SESSION_* often holds\n // session cookies. Be conservative.\n return true;\n }\n return false;\n}\n\n/**\n * Value-side secret detection (WS-01). The name-based `looksSecret` filter\n * misses connection-string variables whose NAME is innocuous but whose VALUE\n * embeds a password \u2014 e.g. `DATABASE_URL=postgres://user:pass@host`,\n * `REDIS_URL=redis://:pass@host`, `MONGO_URI`, `AMQP_URL`, `*_DSN`. Forwarding\n * these to a child (bash/exec/MCP server) leaks the embedded credential.\n *\n * Matches a URI userinfo component that contains a password, i.e.\n * `scheme://[user]:<password>@host`. Deliberately precise: a credential-free\n * URL (`https://api.example.com`, `https://user@host` with no password) is NOT\n * matched, so non-secret `*_URL` knobs (registries, endpoints) still forward.\n */\nfunction valueHasEmbeddedCredential(value: string): boolean {\n // scheme:// then optional user, a ':' , a non-empty password, then '@'.\n // Userinfo chars stop at '/', whitespace, ':' (separator) and '@'.\n return /\\b[a-z][a-z0-9+.-]*:\\/\\/[^/\\s:@]*:[^/\\s@]+@/i.test(value);\n}\n\n/**\n * Code-injection directives that turn `NODE_OPTIONS` into an RCE channel by\n * preloading an arbitrary module into every node child process (WS-02).\n */\nconst NODE_OPTIONS_INJECTION_FLAG =\n /^(?:--require|-r|--import|--loader|--experimental-loader)$/;\nconst NODE_OPTIONS_INJECTION_FLAG_EQ =\n /^(?:--require|-r|--import|--loader|--experimental-loader)=/;\n\n/**\n * Strip module-preload directives from a `NODE_OPTIONS` value while preserving\n * benign flags (`--no-warnings`, `--max-old-space-size=\u2026`, etc.). Handles both\n * the `--require=./x.js` and space-separated `--require ./x.js` forms. Returns\n * the sanitized string (possibly empty).\n */\nexport function sanitizeNodeOptions(value: string): string {\n const tokens = value.split(/\\s+/).filter(Boolean);\n const kept: string[] = [];\n for (let i = 0; i < tokens.length; i++) {\n const tok = tokens[i] as string;\n if (NODE_OPTIONS_INJECTION_FLAG_EQ.test(tok)) continue; // --require=./x\n if (NODE_OPTIONS_INJECTION_FLAG.test(tok)) {\n i++; // also drop the following path token (--require ./x)\n continue;\n }\n kept.push(tok);\n }\n return kept.join(' ');\n}\n\nexport interface BuildChildEnvOptions {\n /** Session ID to inject as WRONGSTACK_SESSION_ID. */\n sessionId?: string | undefined;\n /** Additional env vars to merge (takes priority over filtered parent env). */\n extra?: NodeJS.ProcessEnv | undefined;\n}\n\n/**\n * Commit identity applied to every git-touching child process via the\n * `GIT_AUTHOR_*` / `GIT_COMMITTER_*` env vars. Env-var identity outranks\n * every `git config` layer (repo/global/system), so commits made by any\n * tool (git tool, bash/exec, worktree manager, plugins) carry this\n * name/email without touching the user's git config \u2014 hand-made commits\n * in a normal terminal are unaffected.\n */\nexport interface GitIdentity {\n name?: string | undefined;\n email?: string | undefined;\n}\n\nlet gitIdentity: GitIdentity | null = null;\n\n/**\n * Set (or clear with `null`) the git commit identity injected by\n * `buildChildEnv()`. Wired at boot from the user-level `config.git.identity`\n * (the config loader strips `git` from repo-committed in-project configs \u2014\n * identity spoofing must not be repo-controllable) and re-applied at runtime\n * by the `/gitid` slash command.\n */\nexport function configureChildEnvGitIdentity(identity: GitIdentity | null | undefined): void {\n const name = identity?.name?.trim();\n const email = identity?.email?.trim();\n gitIdentity = name || email ? { name: name || undefined, email: email || undefined } : null;\n}\n\n/** Current configured git identity, or null when none is set. */\nexport function getChildEnvGitIdentity(): Readonly<GitIdentity> | null {\n return gitIdentity;\n}\n\n/**\n * Build a filtered child-process environment suitable for bash, exec, and\n * MCP server subprocesses. Strips API keys, tokens, and other credentials\n * while preserving system/tooling variables.\n */\nexport function buildChildEnv(optsOrSessionId?: BuildChildEnvOptions | string): NodeJS.ProcessEnv {\n const opts: BuildChildEnvOptions =\n typeof optsOrSessionId === 'string'\n ? { sessionId: optsOrSessionId }\n : (optsOrSessionId ?? {});\n\n // WRONGSTACK_CHILD_ENV_PASSTHROUGH may NOT be set via config file.\n // It is a privileged override that opt-outs the entire credential filter\n // and must only be set by the operator's shell environment (real env var,\n // not something a config file injects into process.env). Config-file\n // sources do NOT go through process.env \u2014 only the actual shell environment\n // does \u2014 so checking Object.prototype.hasOwnProperty.call(process.env, ...)\n // is sufficient to exclude config-driven values.\n const hasOwn = Object.hasOwn(process.env, 'WRONGSTACK_CHILD_ENV_PASSTHROUGH');\n const legacyHasOwn = Object.hasOwn(process.env, 'WRONGSTACK_BASH_ENV_PASSTHROUGH');\n const passthrough = (hasOwn && process.env['WRONGSTACK_CHILD_ENV_PASSTHROUGH'] === '1')\n || (legacyHasOwn && process.env['WRONGSTACK_BASH_ENV_PASSTHROUGH'] === '1');\n if (passthrough && !process.env['CI']) {\n console.warn(\n '[agent] WARNING: WRONGSTACK_*_ENV_PASSTHROUGH=1 is active \u2014\\n' +\n ' all parent env vars (including API keys) forwarded to child processes.\\n' +\n ' Do not use on shared or multi-tenant systems.'\n );\n }\n const out: NodeJS.ProcessEnv = {};\n\n // The CLI entry defaults NODE_ENV=production (so React/Ink resolve their\n // production builds \u2014 see cli-main) and marks the injection with this\n // flag. The injected value must NOT reach children: NODE_ENV=production\n // makes `pnpm install` skip devDependencies and flips test-runner\n // behavior. Strip both vars whenever the flag says wrongstack set them \u2014\n // a NODE_ENV genuinely exported by the operator's shell (flag absent)\n // is forwarded unchanged. Applies in passthrough mode too: passthrough\n // means \"the operator's real environment\", which this value is not.\n const nodeEnvDefaulted = process.env['WRONGSTACK_NODE_ENV_DEFAULTED'] === '1';\n\n for (const [k, v] of Object.entries(process.env)) {\n if (v === undefined) continue;\n if (nodeEnvDefaulted && (k === 'NODE_ENV' || k === 'WRONGSTACK_NODE_ENV_DEFAULTED')) continue;\n if (passthrough) {\n out[k] = v;\n continue;\n }\n const upper = k.toUpperCase();\n // 0. Strip any value with an embedded URI credential (user:pass@host),\n // regardless of the variable name (WS-01). Applied before the allowlist\n // so even a \"system\" name carrying a connection string is caught.\n if (valueHasEmbeddedCredential(v)) continue;\n // 1. Forward names on the explicit allowlist \u2014 these are well-known\n // non-secret system variables (PATH, HOME, LANG, ...).\n if (ALLOWED_KEYS.has(upper)) {\n out[k] = v;\n continue;\n }\n // 2. Strip anything that looks like a secret.\n if (looksSecret(upper)) continue;\n // NODE_OPTIONS is forwarded (builds rely on flags like --no-warnings) but\n // module-preload directives (--require/--import/--loader) are stripped \u2014\n // they would let a parent-set NODE_OPTIONS inject code into every node\n // child (WS-02 defense-in-depth).\n if (upper === 'NODE_OPTIONS') {\n const sanitized = sanitizeNodeOptions(v);\n if (sanitized) out[k] = sanitized;\n continue;\n }\n // 3. Forward tooling-prefixed vars that builds commonly need, unless\n // they already failed the secret check above.\n if (\n upper.startsWith('NODE_') ||\n upper.startsWith('NPM_') ||\n upper.startsWith('PNPM_') ||\n upper.startsWith('YARN_') ||\n upper.startsWith('GIT_') ||\n upper.startsWith('CI') ||\n upper.startsWith('XDG_') ||\n // Our own non-secret knobs (WRONGSTACK_HOME, WRONGSTACK_SESSION_ID, \u2026).\n // Secrets never live in WRONGSTACK_* env vars (they're in the encrypted\n // vault). Forwarding keeps child wstack processes \u2014 e.g. ones spawned\n // by the test suite \u2014 inside the same redirected global root.\n upper.startsWith('WRONGSTACK_') ||\n upper === 'EDITOR' ||\n upper === 'VISUAL' ||\n upper === 'PAGER'\n ) {\n out[k] = v;\n }\n }\n\n // Configured commit identity. Applied in passthrough mode too \u2014 it is the\n // operator's explicit intent, not a parent-env leak. Placed BEFORE the\n // extras merge so a caller-provided GIT_* override still wins.\n if (gitIdentity) {\n if (gitIdentity.name) {\n out['GIT_AUTHOR_NAME'] = gitIdentity.name;\n out['GIT_COMMITTER_NAME'] = gitIdentity.name;\n }\n if (gitIdentity.email) {\n out['GIT_AUTHOR_EMAIL'] = gitIdentity.email;\n out['GIT_COMMITTER_EMAIL'] = gitIdentity.email;\n }\n }\n\n // Merge explicit extras AFTER filtering. Callers MUST treat `opts.extra`\n // as a small, user-authored allowlist (e.g. MCP server tokens, LSP env\n // overrides from config). Do NOT pass `process.env` or any object derived\n // from it \u2014 that would defeat the parent-env scrub above. The secret\n // filter is intentionally skipped here so legitimate secret-bearing\n // tokens the user explicitly configured can still reach the child.\n if (opts.extra) {\n Object.assign(out, opts.extra);\n }\n\n if (opts.sessionId) out['WRONGSTACK_SESSION_ID'] = opts.sessionId;\n return out;\n}\n", "/**\n * TTY detection helpers \u2014 the single source of truth for \"is this process\n * running against a real terminal?\". Replaces ad-hoc `process.stdin.isTTY`\n * / `process.stdout.isTTY` checks scattered across the codebase so that:\n *\n * 1. test code can mock a single module instead of stubbing `isTTY` on\n * every ReadStream/WriteStream the test happens to touch;\n * 2. a future TTY-detection source (an env var override, a Windows\n * ConPTY workaround, \u2026) lands in one place;\n * 3. `isInteractive()` encodes the rule the project already used inline\n * (\"both streams are TTYs AND we're not running under CI\") in one\n * testable helper instead of the same 3-condition check in two\n * different files.\n *\n * Scope: detection only. Raw-mode control (`setRawMode`), resize\n * subscriptions, and write-injection belong to a future, larger TTY\n * abstraction; this module is the smallest pull that gives us a\n * testable seam and dedups 20+ call sites.\n */\n\nconst hasStdout = (): boolean => typeof process !== 'undefined' && !!process.stdout;\nconst hasStdin = (): boolean => typeof process !== 'undefined' && !!process.stdin;\n\n/** True when `process.stdout` is attached to a terminal (not a pipe/file). */\nexport function isStdoutTTY(): boolean {\n return hasStdout() && Boolean(process.stdout.isTTY);\n}\n\n/** True when `process.stdin` is attached to a terminal (not a pipe/file). */\nexport function isStdinTTY(): boolean {\n return hasStdin() && Boolean(process.stdin.isTTY);\n}\n\n/**\n * True when the current process is an interactive session: both stdin and\n * stdout are TTYs. Callers that also need a \"not a single-shot invocation\"\n * or \"not under CI\" check should layer that on top \u2014 keeping this helper\n * minimal preserves the original inline checks it replaces.\n */\nexport function isInteractive(): boolean {\n return isStdinTTY() && isStdoutTTY();\n}\n\n/** Current terminal size in characters, with a 24\u00D780 fallback for non-TTYs. */\nexport function getTermSize(): { rows: number; cols: number } {\n if (!hasStdout()) return { rows: 24, cols: 80 };\n return {\n rows: process.stdout.rows ?? 24,\n cols: process.stdout.columns ?? 80,\n };\n}\n\n/**\n * Subscribe to terminal resize events. `cb` is called with the new size each\n * time the underlying stream emits `resize`. Returns a cleanup function the\n * caller MUST call on dispose to remove the listener \u2014 leaving a stale\n * `resize` listener on a disposed component leaks the closure (and the\n * component itself, transitively) until the process exits.\n *\n * The stream argument defaults to `process.stdout`. Pass an explicit\n * `NodeJS.WriteStream` when the caller already owns one (e.g. a status line\n * that targets an injected `out` for testability). For non-TTY streams no\n * listener is registered and the returned cleanup is a no-op.\n */\nexport function onResize(\n cb: (size: { rows: number; cols: number }) => void,\n stream: NodeJS.WriteStream = process.stdout,\n): () => void {\n if (!stream || typeof stream.on !== 'function') return () => {};\n const handler = (): void => {\n cb({\n rows: stream.rows ?? 24,\n cols: stream.columns ?? 80,\n });\n };\n stream.on('resize', handler);\n return () => {\n stream.off('resize', handler);\n };\n}\n\n/**\n * Toggle raw mode on a TTY stdin stream. Returns `true` when the toggle was\n * applied, `false` when the stream is null, not a TTY, or doesn't expose\n * `setRawMode` (pipes, file descriptors, Windows ConPTY edge cases). Callers\n * that need to restore the previous mode should snapshot `input.isRaw`\n * BEFORE the call and pass the value to a second call to flip back.\n *\n * Use this helper to drop the now-redundant\n * `if (input.isTTY) input.setRawMode(...)` ceremony at every call site.\n */\nexport function setRawMode(input: NodeJS.ReadStream, mode: boolean): boolean {\n if (input?.isTTY !== true) return false;\n if (typeof input.setRawMode !== 'function') return false;\n input.setRawMode(mode);\n return true;\n}\n\n/**\n * Bracket installed by the interactive input reader while a `readline`\n * prompt is on screen. Out-of-band terminal writes \u2014 logger WARN/INFO\n * lines, async activity from the Telegram bridge, etc. \u2014 go to the same\n * physical terminal as the half-typed prompt but readline has no idea they\n * happened, so it never repaints. The result is the classic corruption the\n * user sees: every async line strands the in-progress draft as a fresh\n * scrollback row (sometimes with its cursor underline).\n *\n * The guard closes that gap. `suspend()` wipes the draft row so the message\n * prints clean; `resume()` repaints the prompt + draft (cursor preserved).\n * When no prompt is active the guard is `null` and writes pass straight\n * through \u2014 so agent-turn output (spinner, renderer) is untouched.\n */\nexport interface OutputLineGuard {\n /** Clear the current input row right before an out-of-band write. */\n suspend(): void;\n /** Repaint the prompt + in-progress draft right after the write. */\n resume(): void;\n}\n\nlet activeOutputGuard: OutputLineGuard | null = null;\n\n/**\n * Register (or clear, with `null`) the guard that brackets out-of-band\n * writes. Installed by {@link writeOut}/{@link writeErr} consumers \u2014 in\n * practice the CLI's readline input reader \u2014 only while a prompt is live.\n * Idempotent; the most recent caller wins.\n */\nexport function setOutputLineGuard(guard: OutputLineGuard | null): void {\n activeOutputGuard = guard;\n}\n\n/**\n * Stream-agnostic write primitive. Returns `false` when the stream is\n * missing or doesn't expose `write` so callers can degrade silently under\n * hostile host environments (closed pipe, mock injects `null`, test\n * replaces the stream with a stub).\n *\n * When an {@link OutputLineGuard} is installed (a readline prompt is on\n * screen) the write is bracketed by `suspend()`/`resume()` so the user's\n * half-typed input survives the interruption instead of being stranded in\n * scrollback. The guard's own redraw uses raw stream writes \u2014 never\n * `writeOut`/`writeErr` \u2014 so there is no re-entrancy here.\n *\n * **Not exported in the public API.** Exposed only inside `term.ts` for\n * `writeOut` / `writeErr` to share a single implementation. If a caller\n * needs to write to an arbitrary stream, they should call `writeOut` (or\n * `writeErr`) with an explicit `stream` argument \u2014 the named functions\n * are the public surface so the \"this is the standard error stream\"\n * intent stays visible at every call site.\n */\nfunction writeTo(\n s: string,\n stream: NodeJS.WriteStream | undefined,\n): boolean {\n if (!stream || typeof stream.write !== 'function') return false;\n const guard = activeOutputGuard;\n if (!guard) {\n stream.write(s);\n return true;\n }\n // A prompt is live \u2014 wipe the draft row, emit the message, repaint.\n guard.suspend();\n stream.write(s);\n guard.resume();\n return true;\n}\n\n/**\n * Write `s` to `stream` (defaults to `process.stdout`). Returns `false`\n * when the stream is missing or doesn't expose `write` so callers can\n * degrade silently under hostile host environments (closed pipe, mock\n * injects `null`, test replaces the stream with a stub).\n *\n * Why a helper:\n * 1. **Single seam for output capture in tests** \u2014 stub `writeOut` once\n * and assert on what the rest of the codebase intended to print,\n * without spying on `process.stdout.write` (which is brittle and\n * leaks across parallel test files).\n * 2. **Stream swap without grep** \u2014 routing the CLI's output to a\n * logger or `out.log` becomes a one-line change at process boot.\n * 3. **Defensive default** \u2014 closes the \"what if `process.stdout` is\n * `null`\" gap that currently exists at ~50 call sites that just\n * call `process.stdout.write(s)` and crash on certain Windows\n * redirect invocations.\n *\n * Call-site migration is staged: this commit introduces the helper, a\n * follow-up commit replaces the 50+ `process.stdout.write(...)` sites\n * with `writeOut(...)`. Until that migration lands, both forms coexist\n * and `writeOut` is the preferred form for new code.\n */\nexport function writeOut(\n s: string,\n stream: NodeJS.WriteStream = process.stdout,\n): boolean {\n return writeTo(s, stream);\n}\n\n/**\n * Symmetric partner of `writeOut` for the standard error stream. Same shape,\n * same defensive contract, same single-seam-for-tests story \u2014 just defaults to\n * `process.stderr` instead of `process.stdout`.\n *\n * Use this in code paths that emit error/diagnostic/warning text. Keeping\n * these two helpers split (rather than a single `writeTo(s, stream)`) means\n * the call site reads as a clear intent signal: \"I am writing an error\" vs.\n * \"I am writing a result\" \u2014 which matters for callers that decide between\n * stdout/stderr routing (e.g. `--quiet` flags, log-level filtering,\n * structured-log rewriters that fork on stream).\n *\n * Stderr writes from the core logger (see `infrastructure/logger.ts`) and from\n * the TUI guard (see `tui/run-tui.ts`) used to call `process.stderr.write`\n * directly. Routing them through this helper lets tests stub the stream at\n * one boundary and lets future logging middleware (e.g. a JSON-line rewriter)\n * swap the destination for the entire process in one place.\n */\nexport function writeErr(\n s: string,\n stream: NodeJS.WriteStream = process.stderr,\n): boolean {\n return writeTo(s, stream);\n}\n", "import { isStdoutTTY } from './term.js';\n\nconst isColorTty = (): boolean => {\n if (envFlag(process.env.NO_COLOR)) return false;\n if (envFlag(process.env.FORCE_COLOR)) return true;\n return isStdoutTTY();\n};\n\nfunction envFlag(value: string | undefined): boolean {\n if (value === undefined) return false;\n if (value.trim() === '') return false;\n return !/^(0|false|no|off)$/i.test(value.trim());\n}\n\nconst COLOR = isColorTty();\n\nconst wrap =\n (open: string, close: string) =>\n (s: string): string =>\n COLOR ? `\\x1b[${open}m${s}\\x1b[${close}m` : s;\n\nexport const color = {\n reset: wrap('0', '0'),\n bold: wrap('1', '22'),\n dim: wrap('2', '22'),\n italic: wrap('3', '23'),\n underline: wrap('4', '24'),\n red: wrap('31', '39'),\n green: wrap('32', '39'),\n yellow: wrap('33', '39'),\n blue: wrap('34', '39'),\n magenta: wrap('35', '39'),\n cyan: wrap('36', '39'),\n gray: wrap('90', '39'),\n amber: wrap('38;5;214', '39'),\n pink: wrap('38;5;205', '39'),\n bgRed: wrap('41', '49'),\n bgGreen: wrap('42', '49'),\n};\n\nexport function stripAnsi(s: string): string {\n return s.replace(/\\x1b\\[[0-9;]*[A-Za-z]/g, '');\n}\n", "/**\n * Deep merge utility \u2014 safely merges nested objects with configurable\n * conflict resolution, array merging, and prototype-pollution guarding.\n *\n * Used by:\n * - config-loader (config layer merging with primitive-array concatenation)\n * - secret-vault (config patching)\n * - json-path (json_merge tool with prefer-base / prefer-patch semantics)\n *\n * @module utils/deep-merge\n */\n\n// ---------------------------------------------------------------------------\n// Prototype-pollution guard \u2014 shared set of forbidden __proto__ keys\n// ---------------------------------------------------------------------------\n\nexport const FORBIDDEN_PROTO_KEYS = new Set([\n '__proto__',\n 'constructor',\n 'prototype',\n '__defineGetter__',\n '__defineSetter__',\n '__lookupGetter__',\n '__lookupSetter__',\n]);\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** True when every element is a primitive or null (no nested objects/arrays). */\nexport function isPrimitiveArray(a: unknown[]): boolean {\n return a.every((v) => v === null || (typeof v !== 'object' && typeof v !== 'function'));\n}\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface DeepMergeOptions {\n /**\n * Which side wins on collision for scalars and arrays.\n *\n * - `'prefer-patch'` (default): patch value replaces base value.\n * - `'prefer-base'`: base value is kept, patch value is ignored.\n */\n conflictResolution?: 'prefer-base' | 'prefer-patch';\n\n /**\n * How to handle array values.\n *\n * - `'replace'` (default): patch array replaces base array entirely.\n * - `'concat-primitives'`: when both values are primitive arrays,\n * they are concatenated and deduped (via Set). Non-primitive\n * arrays still replace the base wholesale.\n */\n arrayMode?: 'replace' | 'concat-primitives';\n\n /**\n * Skip prototype-pollution keys (`__proto__`, `constructor`, etc.).\n * Enabled by default. Only disable when you control both inputs\n * and the keyset (e.g. when merging trusted JSON schemas).\n */\n protectProto?: boolean;\n\n /**\n * Optional callback fired when a non-primitive (object) array is\n * replaced wholesale (only relevant with `arrayMode: 'concat-primitives'`).\n * Receives the key name, existing array length, and patch array length.\n * Used by config-loader for debug logging.\n */\n onNonPrimitiveArrayReplace?: (\n key: string,\n existingLen: number,\n patchLen: number,\n ) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively merge `patch` into `base`, returning a new object.\n *\n * - Nested plain objects are merged recursively.\n * - Arrays are handled per `options.arrayMode`.\n * - Scalar collisions are resolved per `options.conflictResolution`.\n * - `null` and non-object values in `patch` replace the base value\n * (unless `conflictResolution` is `'prefer-base'`).\n * - Keys in `base` that are absent from `patch` are preserved.\n * - `FORBIDDEN_PROTO_KEYS` are skipped in the patch (unless\n * `options.protectProto` is set to `false`).\n *\n * The function is generic over `T extends Record<string, unknown>` for\n * callers that pass typed config objects, but the runtime signature\n * also accepts `unknown` inputs (used by the json-path plugin).\n */\nexport function deepMerge<T extends Record<string, unknown>>(\n base: T,\n patch: Record<string, unknown>,\n options?: DeepMergeOptions,\n): T;\n\nexport function deepMerge(\n base: unknown,\n patch: unknown,\n options?: DeepMergeOptions,\n): unknown;\n\nexport function deepMerge(\n base: unknown,\n patch: unknown,\n options: DeepMergeOptions = {},\n): unknown {\n const {\n conflictResolution = 'prefer-patch',\n arrayMode = 'replace',\n protectProto = true,\n onNonPrimitiveArrayReplace,\n } = options;\n\n // Non-object / null handling \u2014 delegate to conflict resolution.\n if (typeof base !== 'object' || base === null) {\n return conflictResolution === 'prefer-patch' ? patch : base;\n }\n if (typeof patch !== 'object' || patch === null) {\n return conflictResolution === 'prefer-patch' ? patch : base;\n }\n\n // Arrays \u2014 handled *before* the object merge so array-of-objects\n // aren't accidentally treated as plain records.\n if (Array.isArray(base) && Array.isArray(patch)) {\n if (\n arrayMode === 'concat-primitives' &&\n isPrimitiveArray(base) &&\n isPrimitiveArray(patch)\n ) {\n return [...new Set([...base, ...patch])];\n }\n return conflictResolution === 'prefer-patch' ? patch : base;\n }\n\n // If only one side is an array, treat as scalar collision.\n if (Array.isArray(base) || Array.isArray(patch)) {\n return conflictResolution === 'prefer-patch' ? patch : base;\n }\n\n // Plain object merge.\n const baseObj = base as Record<string, unknown>;\n const patchObj = patch as Record<string, unknown>;\n const out: Record<string, unknown> = { ...baseObj };\n\n for (const [k, v] of Object.entries(patchObj)) {\n if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;\n\n const existing = out[k];\n if (\n v !== null &&\n typeof v === 'object' &&\n !Array.isArray(v) &&\n existing !== null &&\n typeof existing === 'object' &&\n !Array.isArray(existing)\n ) {\n // Recursive merge for nested plain objects.\n out[k] = deepMerge(existing, v, options);\n } else if (Array.isArray(v) && Array.isArray(existing)) {\n // Delegate to top-level array handling so arrayMode\n // (e.g. 'concat-primitives') applies to nested arrays too.\n // Fire debug hook when a non-primitive array replaces an existing\n // array (for non-primitive arrays, concat-primitives is a no-op and\n // the result is always a wholesale replacement).\n if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {\n onNonPrimitiveArrayReplace(k, existing.length, v.length);\n }\n out[k] = deepMerge(existing, v, options);\n } else if (v !== undefined) {\n // Fire debug hook when a non-primitive (object) array replaces an\n // existing value in concat-primitives mode.\n if (\n onNonPrimitiveArrayReplace &&\n Array.isArray(v) &&\n !isPrimitiveArray(v)\n ) {\n const existingLen = Array.isArray(existing) ? existing.length : 0;\n onNonPrimitiveArrayReplace(k, existingLen, v.length);\n }\n out[k] = v;\n }\n // When v === undefined, leave the existing value untouched\n // (this matches config-loader's behaviour: undefined in patch\n // means \"don't change this key\").\n }\n\n return out;\n}\n", "/**\n * Converts an unknown error value to a human-readable string.\n * Used in 40+ files across the codebase to normalize error messaging.\n */\nexport function toErrorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n", "/** Assert a value is neither null nor undefined. Throws if it is.\n * Useful after optional chaining and indexed access when the\n * control flow guarantees the value exists but TypeScript can't\n * prove it (e.g. after a check on a related field). */\nexport function expectDefined<T>(value: T | null | undefined, label?: string): T {\n if (value === null || value === undefined) {\n const err = new Error(label ? `Expected ${label} to be defined` : 'Expected value to be defined');\n err.name = 'ExpectDefinedError';\n throw err;\n }\n return value;\n}\n", "import { expectDefined } from './expect-defined.js';\nimport type { ContentBlock, ToolResultBlock, ToolUseBlock } from '../types/blocks.js';\nimport type { Message } from '../types/messages.js';\nexport interface MessageRepairReport {\n changed: boolean;\n removedToolUses: string[];\n removedToolResults: string[];\n removedMessages: number;\n}\n\nexport interface MessageRepairResult {\n messages: Message[];\n report: MessageRepairReport;\n}\n\n/**\n * Repair provider-level tool-call adjacency invariants.\n *\n * Anthropic requires every assistant `tool_use` block to have a matching\n * `tool_result` block in the immediately following user message. Manual\n * context surgery (summary/prune) can cut through the middle of such an\n * exchange. This function removes only the now-orphaned protocol blocks,\n * preserving surrounding text/images/thinking blocks where possible.\n */\nexport function repairToolUseAdjacency(messages: Message[]): MessageRepairResult {\n const removedToolUses: string[] = [];\n const removedToolResults: string[] = [];\n let removedMessages = 0;\n let changed = false;\n const out: Message[] = [];\n\n for (let i = 0; i < messages.length; i++) {\n const original = expectDefined(messages[i]);\n let msg = original;\n\n if (hasToolUse(msg)) {\n const nextIds = toolResultIds(messages[i + 1]);\n const filtered = mapContent(msg, (blocks) => {\n const next: ContentBlock[] = [];\n for (const block of blocks) {\n if (block.type === 'tool_use' && !nextIds.has(block.id)) {\n removedToolUses.push(block.id);\n changed = true;\n continue;\n }\n next.push(block);\n }\n return next;\n });\n msg = filtered ?? msg;\n }\n\n if (hasToolResult(msg)) {\n const allowed = toolUseIds(out[out.length - 1]);\n const filtered = mapContent(msg, (blocks) => {\n const next: ContentBlock[] = [];\n for (const block of blocks) {\n if (block.type === 'tool_result' && !allowed.has(block.tool_use_id)) {\n removedToolResults.push(block.tool_use_id);\n changed = true;\n continue;\n }\n next.push(block);\n }\n return next;\n });\n msg = filtered ?? msg;\n }\n\n if (isEmptyMessage(msg)) {\n removedMessages++;\n changed = true;\n continue;\n }\n out.push(msg);\n }\n\n return {\n messages: changed ? out : messages,\n report: { changed, removedToolUses, removedToolResults, removedMessages },\n };\n}\n\nfunction hasToolUse(msg: Message | undefined): boolean {\n return contentBlocks(msg).some((b): b is ToolUseBlock => b.type === 'tool_use');\n}\n\nfunction hasToolResult(msg: Message | undefined): boolean {\n return contentBlocks(msg).some((b): b is ToolResultBlock => b.type === 'tool_result');\n}\n\nfunction toolUseIds(msg: Message | undefined): Set<string> {\n const ids = new Set<string>();\n if (msg?.role !== 'assistant') return ids;\n for (const block of contentBlocks(msg)) {\n if (block.type === 'tool_use') ids.add(block.id);\n }\n return ids;\n}\n\nfunction toolResultIds(msg: Message | undefined): Set<string> {\n const ids = new Set<string>();\n if (msg?.role !== 'user') return ids;\n for (const block of contentBlocks(msg)) {\n if (block.type === 'tool_result') ids.add(block.tool_use_id);\n }\n return ids;\n}\n\nfunction contentBlocks(msg: Message | undefined): ContentBlock[] {\n return msg && Array.isArray(msg.content) ? msg.content : [];\n}\n\nfunction mapContent(\n msg: Message,\n fn: (blocks: ContentBlock[]) => ContentBlock[],\n): Message | null {\n if (!Array.isArray(msg.content)) return msg;\n const next = fn(msg.content);\n if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {\n return msg;\n }\n return { ...msg, content: next };\n}\n\nfunction isEmptyMessage(msg: Message): boolean {\n if (typeof msg.content === 'string') return msg.content.trim().length === 0;\n return msg.content.length === 0;\n}\n", "/**\n * Compile a user-supplied regex with conservative bounds against ReDoS.\n *\n * Duplicated from @wrongstack/tools/_regex.ts to avoid a circular\n * dependency (tools depends on core, not vice versa). Keep both copies\n * in sync if the heuristics change.\n *\n * V8's regex engine is backtracking-based and cannot interrupt a\n * synchronous match \u2014 a pattern like `(a+)+$` against a sufficiently\n * long line will pin a worker for seconds.\n */\n\nconst MAX_PATTERN_LEN = 512;\n\n// Heuristics for catastrophic-backtracking constructs.\nconst DANGEROUS_PATTERNS: ReadonlyArray<RegExp> = [\n /(\\([^)]*[+*][^)]*\\))[+*]/, // (a+)+, (.*)+, etc\n /(\\(\\?:[^)]*[+*][^)]*\\))[+*]/, // same, with non-capturing group\n];\n\nexport interface CompileResult {\n ok: true;\n regex: RegExp;\n}\n\nexport interface CompileFail {\n ok: false;\n reason: string;\n}\n\nexport function compileUserRegex(pattern: string, flags: string): CompileResult | CompileFail {\n if (typeof pattern !== 'string') {\n return { ok: false, reason: 'pattern must be a string' };\n }\n if (pattern.length === 0) {\n return { ok: false, reason: 'pattern is empty' };\n }\n if (pattern.length > MAX_PATTERN_LEN) {\n return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };\n }\n for (const rx of DANGEROUS_PATTERNS) {\n if (rx.test(pattern)) {\n return {\n ok: false,\n reason:\n 'pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers',\n };\n }\n }\n try {\n return { ok: true, regex: new RegExp(pattern, flags) };\n } catch (err) {\n return {\n ok: false,\n reason: err instanceof Error ? err.message : 'invalid regex',\n };\n }\n}\n", "import { toErrorMessage } from './error.js';\n\nexport interface SafeParseResult<T> {\n ok: boolean;\n value?: T | undefined;\n error?: string | undefined;\n}\n\nexport function safeParse<T = unknown>(input: string, maxBytes = 5_000_000): SafeParseResult<T> {\n if (input.length > maxBytes) {\n return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };\n }\n try {\n return { ok: true, value: JSON.parse(input) as T };\n } catch (err) {\n return {\n ok: false,\n error: toErrorMessage(err),\n };\n }\n}\n\nexport function safeStringify(value: unknown, pretty = false): string {\n const seen = new WeakSet();\n const replacer = (_k: string, v: unknown): unknown => {\n if (typeof v === 'bigint') return v.toString();\n if (v instanceof Error) {\n return { name: v.name, message: v.message, stack: v.stack };\n }\n if (typeof v === 'object' && v !== null) {\n if (seen.has(v as object)) return '[Circular]';\n seen.add(v as object);\n }\n return v;\n };\n try {\n return JSON.stringify(value, replacer, pretty ? 2 : undefined) ?? 'null';\n } catch (err) {\n return JSON.stringify({\n __serialization_error: toErrorMessage(err),\n });\n }\n}\n\n/**\n * Attempt to parse JSON5-style input and return a valid JSON string.\n * Handles trailing commas, single-line comments, and unquoted keys\n * that are common in provider output.\n *\n * Returns the sanitized string if it parses successfully as JSON,\n * or `null` if the input cannot be made valid. Callers use this to\n * decide whether to proceed with the parsed result or fall back to\n * raw handling.\n */\nexport function sanitizeJsonString(s: string): string | null {\n let out = s.trim();\n\n // Stage 1: strip single-line comments (// to end of line) that appear\n // outside of string values. This is a heuristic: comments inside strings\n // are preserved because we only strip // when preceded by a char that\n // strongly suggests we're not in a string (quote count modulo 2 is even).\n out = stripSingleLineComments(out);\n\n // Stage 2: strip trailing commas before } or ]\n out = out.replace(/,(\\s*[}\\]])/g, '$1');\n\n // Stage 3: escape literal control characters that appear *inside* string\n // values. Models frequently emit raw newlines/tabs inside a code payload\n // (e.g. edit's old_string/new_string) instead of the required \\n / \\t, which\n // makes JSON.parse throw. This is the single most common malformed-args case.\n out = escapeControlCharsInStrings(out);\n\n // Stage 4: attempt full parse; return null if it fails so callers can\n // distinguish \"already valid JSON\" from \"unrecoverable\".\n try {\n JSON.parse(out);\n return out;\n } catch {\n return null; // stripped but still not valid JSON; caller handles it\n }\n}\n\n/**\n * Strip a Markdown code-fence wrapper from a payload.\n *\n * Models occasionally return tool-call arguments wrapped in ```json fences\n * (or embedded in prose around one) instead of bare JSON. Returns the inner\n * content when the input starts with a fence (closing fence optional, so a\n * truncated stream still unwraps) or contains one complete fenced block;\n * returns null when no fence is present. Callers should only invoke this\n * after a direct parse failed, so fences inside legitimate string values are\n * never touched.\n */\nexport function stripCodeFences(s: string): string | null {\n const trimmed = s.trim();\n // Whole-payload fence: ```lang? \u2026 ```? (closer optional for truncation)\n const opener = /^```[\\w+-]*[ \\t]*\\r?\\n?/.exec(trimmed);\n if (opener) {\n const inner = trimmed.slice(opener[0].length).replace(/(\\r?\\n)?[ \\t]*```[ \\t]*$/, '');\n return inner.trim();\n }\n // Fence embedded in prose: extract the first complete fenced block.\n const embedded = /```[\\w+-]*[ \\t]*\\r?\\n([\\s\\S]*?)\\r?\\n[ \\t]*```/.exec(trimmed);\n if (embedded) return (embedded[1] ?? '').trim();\n return null;\n}\n\n/**\n * Walk the string tracking whether we are inside a JSON string literal and\n * replace raw control characters (U+0000\u2013U+001F) that appear inside strings\n * with their valid JSON escape sequences. Characters outside strings are left\n * untouched (insignificant whitespace stays as-is). Already-escaped sequences\n * are not double-escaped because we only act on *literal* control bytes.\n */\nfunction escapeControlCharsInStrings(s: string): string {\n let inString = false;\n let out = '';\n for (let i = 0; i < s.length; i++) {\n const c = s.charAt(i);\n if (c === '\"' && (i === 0 || s[i - 1] !== '\\\\')) {\n inString = !inString;\n out += c;\n continue;\n }\n const code = c.charCodeAt(0);\n if (inString && code < 0x20) {\n switch (c) {\n case '\\n':\n out += '\\\\n';\n break;\n case '\\r':\n out += '\\\\r';\n break;\n case '\\t':\n out += '\\\\t';\n break;\n case '\\b':\n out += '\\\\b';\n break;\n case '\\f':\n out += '\\\\f';\n break;\n default:\n out += `\\\\u${code.toString(16).padStart(4, '0')}`;\n }\n continue;\n }\n out += c;\n }\n return out;\n}\n\nfunction stripSingleLineComments(s: string): string {\n let inString = false;\n const chars: string[] = [];\n let i = 0;\n while (i < s.length) {\n const c = s.charAt(i);\n if (c === '\"' && (i === 0 || s.charAt(i - 1) !== '\\\\')) {\n inString = !inString;\n chars.push(c);\n } else if (c === '/' && s.charAt(i + 1) === '/' && !inString) {\n // skip to end of line\n while (i < s.length && s.charAt(i) !== '\\n') i++;\n } else {\n chars.push(c);\n }\n i++;\n }\n return chars.join('');\n}\n", "import * as path from 'node:path';\nimport { ERROR_CODES, FsError } from '../types/errors.js';\n\n/**\n * Resolve `<dir>/<sessionId><suffix>` for per-session sidecar files\n * (annotations, audit chain, replay log, the session JSONL itself).\n *\n * Modern session ids are date-sharded (\"2026-06-11/sess_<ULID>\"),\n * so a forward slash is a legitimate shard separator \u2014 NOT traversal.\n * Escape attempts are blocked two ways: an explicit ban on `..` and\n * backslashes, plus a resolved-path containment check that rejects any\n * id whose resolved target leaves `dir`. Character bans alone are how\n * several stores ended up throwing on every modern session id.\n */\nexport function sessionScopedPath(dir: string, sessionId: string, suffix: string): string {\n if (!sessionId || sessionId.includes('\\\\') || sessionId.includes('..')) {\n throw invalid(sessionId);\n }\n const resolved = path.resolve(dir, `${sessionId}${suffix}`);\n const rel = path.relative(path.resolve(dir), resolved);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw invalid(sessionId);\n }\n return resolved;\n}\n\nfunction invalid(sessionId: string): FsError {\n return new FsError({\n message: `Invalid sessionId: ${sessionId}`,\n code: ERROR_CODES.FS_DELETE_FAILED,\n path: sessionId,\n context: { reason: 'path_traversal' },\n });\n}\n", "/**\n * Turn an arbitrary string into a filesystem- and URL-safe lowercase slug.\n *\n * Collapses every run of non-alphanumeric characters into a single hyphen,\n * trims leading/trailing hyphens, and caps the length. Returns `fallback`\n * when the input slugifies to the empty string.\n *\n * Used as the stable dedup + registry key for prompts. (Distinct from the\n * project-folder slug in `wstack-paths.ts`, which has its own `'project'`\n * fallback and shorter cap.)\n */\nexport function slugify(name: string, fallback = 'prompt', maxLen = 64): string {\n return (\n name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, maxLen)\n .replace(/-+$/g, '') || fallback\n );\n}\n", "import { randomBytes } from 'node:crypto';\n\n/**\n * Crockford base32 alphabet (excludes I, L, O, U to avoid ambiguity).\n */\nconst ENCODING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';\nconst ENCODING_LEN = ENCODING.length;\nconst TIME_LEN = 10;\nconst RANDOM_LEN = 16;\n\nfunction encodeTime(now: number, len: number): string {\n let mod: number;\n let str = '';\n for (let i = len - 1; i >= 0; i--) {\n mod = now % ENCODING_LEN;\n str = ENCODING[mod] + str;\n now = (now - mod) / ENCODING_LEN;\n }\n return str;\n}\n\nfunction encodeRandom(len: number): string {\n const bytes = randomBytes(len);\n let str = '';\n for (let i = 0; i < len; i++) {\n str += ENCODING[(bytes[i] as number) % ENCODING_LEN];\n }\n return str;\n}\n\n/**\n * Generate a ULID \u2014 a 26-char Crockford-base32 identifier whose first 10 chars\n * encode the millisecond timestamp (so IDs sort lexicographically by creation\n * time) followed by 16 chars of randomness. Zero runtime dependencies.\n *\n * The codebase convention is \"IDs are ULIDs\"; use this for any new store key.\n */\nexport function ulid(seedTime: number = Date.now()): string {\n return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN);\n}\n\n/** True for a well-formed 26-char Crockford-base32 ULID. */\nexport function isUlid(value: string): boolean {\n if (value.length !== TIME_LEN + RANDOM_LEN) return false;\n for (const ch of value) {\n if (!ENCODING.includes(ch)) return false;\n }\n return true;\n}\n", "import { createHash } from 'node:crypto';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\n/**\n * Path layout. All developer-level state lives in ~/.wrongstack/.\n * Per-project state is keyed by sha256(absoluteProjectRoot).slice(0,12)\n * under ~/.wrongstack/projects/<hash>/.\n *\n * The ONLY thing inside the project tree is the optional\n * .wrongstack/AGENTS.md (committed) and .wrongstack/skills/ (committed).\n */\n\nexport interface WstackPaths {\n /** ~/.wrongstack \u2014 global root. */\n globalRoot: string;\n /** Absolute project root. */\n projectRoot: string;\n /** Home directory (~) \u2014 base for foreign tool state (~/.codex, ~/.agents, \u2026). */\n homeDir: string;\n /**\n * ~/.wrongstack \u2014 directory for user-global stateful config files\n * (mode.json, theme.json, \u2026). Currently an alias for `globalRoot`;\n * separate name lets us split out per-OS XDG_CONFIG_HOME later\n * without rewriting callers.\n */\n configDir: string;\n /** ~/.wrongstack/config.json */\n globalConfig: string;\n /** ~/.wrongstack/.key \u2014 32 random bytes, mode 0600, AES-GCM key for the secret vault. */\n secretsKey: string;\n /** ~/.wrongstack/memory.md \u2014 user-global memory. */\n globalMemory: string;\n /** ~/.wrongstack/skills \u2014 user-global skills. */\n globalSkills: string;\n /** ~/.claude/skills \u2014 user-global skills from foreign coding agents (Claude Code, Codex, \u2026). Read-only. */\n globalClaudeSkills: string;\n /** ~/.wrongstack/design-kits \u2014 user-global Design Studio kits. */\n globalDesignKits: string;\n /** ~/.wrongstack/prompts \u2014 user-global prompt library. */\n globalPrompts: string;\n /** ~/.wrongstack/instructions \u2014 user-global system instruction overrides. */\n globalInstructions: string;\n /** ~/.wrongstack/prompt-usage.json \u2014 per-slug insert counts (recent/popular). */\n promptUsage: string;\n /** ~/.wrongstack/cache \u2014 fetched data (models.dev, etc.). */\n cacheDir: string;\n /** ~/.wrongstack/cache/models.dev.json */\n modelsCache: string;\n /** ~/.wrongstack/cache/models-overlay.json \u2014 cached curated overlay. */\n modelsOverlayCache: string;\n /**\n * Per-project codebase symbol index (SQLite). Lives under the global project\n * dir \u2014 NOT inside the repo \u2014 so it never clutters the working tree or needs\n * gitignoring. `~/.wrongstack/projects/<hash>/codebase-index`.\n */\n projectCodebaseIndex: string;\n /** ~/.wrongstack/history \u2014 REPL line history. */\n historyFile: string;\n /** ~/.wrongstack/logs/wrongstack.log */\n logFile: string;\n /** ~/.wrongstack/projects/<hash> */\n projectDir: string;\n /** ~/.wrongstack/projects/<hash>/memory.md */\n projectMemory: string;\n /** ~/.wrongstack/projects/<hash>/sessions */\n projectSessions: string;\n /** ~/.wrongstack/projects/<hash>/trust.json */\n projectTrust: string;\n /** ~/.wrongstack/projects/<hash>/meta.json */\n projectMeta: string;\n /** ~/.wrongstack/projects/<hash>/config.local.json \u2014 optional override */\n projectLocalConfig: string;\n /** <project>/.wrongstack/config.json \u2014 per-project settings (safe fields only).\n * This lives inside the project root so it can be gitignored or shared. */\n inProjectConfig: string;\n /** <project>/.wrongstack/AGENTS.md \u2014 committed project memory. */\n inProjectAgentsFile: string;\n /** <project>/.wrongstack/skills \u2014 committed project skills. */\n inProjectSkills: string;\n /** <project>/.claude/skills \u2014 project skills authored for foreign coding agents (Claude Code, \u2026). Read-only. */\n inProjectClaudeSkills: string;\n /** <project>/.wrongstack/prompts \u2014 committed project prompt library. */\n inProjectPrompts: string;\n /** <project>/.wrongstack/instructions \u2014 committed project instruction overrides. */\n inProjectInstructions: string;\n /** <project>/.wrongstack/design-kits \u2014 committed project Design Studio kits. */\n inProjectDesignKits: string;\n /** <project>/.wrongstack/worktrees \u2014 git worktrees for per-phase isolation (gitignored). */\n inProjectWorktrees: string;\n /** Stable hash for the project root. */\n projectHash: string;\n /** Human-readable project slug: `wrongstack-a1b2c3` instead of `3024e5e6fa58`. */\n projectSlug: string;\n /** ~/.wrongstack/projects/<hash>/goal.json \u2014 goal persistence */\n projectGoal: string;\n /** ~/.wrongstack/projects/<hash>/input-history.json \u2014 TUI prompt input history */\n projectInputHistory: string;\n /** ~/.wrongstack/projects/<hash>/specs \u2014 SDD spec files */\n projectSpecs: string;\n /** ~/.wrongstack/projects/<hash>/task-graphs \u2014 SDD task graphs */\n projectTaskGraphs: string;\n /** ~/.wrongstack/projects/<hash>/sdd-session.json \u2014 SDD session state */\n projectSddSession: string;\n /** ~/.wrongstack/projects/<hash>/plan.json \u2014 plan persistence */\n projectPlan: string;\n /** ~/.wrongstack/projects/<hash>/autophase \u2014 AutoPhase phase-graph JSON files */\n projectAutophase: string;\n /** ~/.wrongstack/projects/<hash>/sdd-boards \u2014 live SDD board snapshots + JSONL event logs */\n projectSddBoards: string;\n /** ~/.wrongstack/sync.json \u2014 CloudSync configuration */\n syncConfig: string;\n /** Function to get the status.json path for a project given its hash. */\n projectStatus: (projectHash: string) => string;\n}\n\nexport function projectHash(absRoot: string): string {\n return createHash('sha256').update(path.resolve(absRoot)).digest('hex').slice(0, 12);\n}\n\n/**\n * Human-readable project directory name: slugified folder name + short hash\n * suffix for uniqueness. e.g. `wrongstack-a1b2c3` instead of `3024e5e6fa58`.\n */\nexport function projectSlug(absRoot: string): string {\n const base = slugify(path.basename(absRoot));\n const hash = createHash('sha256').update(path.resolve(absRoot)).digest('hex').slice(0, 6);\n return `${base}-${hash}`;\n}\n\n/** Turn a folder name into a filesystem-safe lowercase slug. */\nfunction slugify(name: string): string {\n return (\n name\n .toLowerCase()\n // Collapse any run of non-alphanumeric chars into a single hyphen.\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 40) || 'project'\n );\n}\n\nexport interface WstackPathOptions {\n userHome?: string | undefined;\n projectRoot: string;\n /** Override the global root (e.g. for tests). Default: `${userHome}/.wrongstack`. */\n globalRoot?: string | undefined;\n}\n\n/**\n * The global `~/.wrongstack` root, honoring the `WRONGSTACK_HOME` env\n * override. The override exists so tests (and sandboxed runs) can redirect\n * ALL global state \u2014 config, secrets, logs, projects/, mailboxes \u2014 away from\n * the real user home. Before it existed, `pnpm test` booted runtimes against\n * the real `~/.wrongstack`: it read the user's real config.json (starting a\n * second live Telegram poller), appended to the real wrongstack.log, and left\n * ~20k orphaned fixture dirs under projects/.\n *\n * Every code path that wants the global dir must come through here (or\n * through `resolveWstackPaths`) instead of `path.join(os.homedir(), '.wrongstack')`.\n */\nexport function wstackGlobalRoot(): string {\n const fromEnv = process.env['WRONGSTACK_HOME'];\n if (fromEnv && fromEnv.trim().length > 0) return path.resolve(fromEnv);\n return path.join(os.homedir(), '.wrongstack');\n}\n\nexport function resolveWstackPaths(opts: WstackPathOptions): WstackPaths {\n // Precedence: explicit globalRoot > explicit userHome (callers/tests that\n // pass one expect paths under it) > WRONGSTACK_HOME env > real home dir.\n const globalRoot =\n opts.globalRoot ?? (opts.userHome ? path.join(opts.userHome, '.wrongstack') : wstackGlobalRoot());\n // Home dir for FOREIGN tool state (Claude Code's ~/.claude). Independent of\n // WRONGSTACK_HOME, which redirects only WrongStack state: a real user's\n // Claude skills live in their real home, but tests pass `userHome` to keep\n // both `.wrongstack` and `.claude` under a temp dir.\n const homeDir = opts.userHome ?? os.homedir();\n const hash = projectHash(opts.projectRoot);\n const slug = projectSlug(opts.projectRoot);\n const projectDir = path.join(globalRoot, 'projects', slug);\n return {\n globalRoot,\n projectRoot: opts.projectRoot,\n homeDir,\n configDir: globalRoot,\n globalConfig: path.join(globalRoot, 'config.json'),\n secretsKey: path.join(globalRoot, '.key'),\n globalMemory: path.join(globalRoot, 'memory.md'),\n globalSkills: path.join(globalRoot, 'skills'),\n globalClaudeSkills: path.join(homeDir, '.claude', 'skills'),\n globalDesignKits: path.join(globalRoot, 'design-kits'),\n globalPrompts: path.join(globalRoot, 'prompts'),\n globalInstructions: path.join(globalRoot, 'instructions'),\n promptUsage: path.join(globalRoot, 'prompt-usage.json'),\n cacheDir: path.join(globalRoot, 'cache'),\n modelsCache: path.join(globalRoot, 'cache', 'models.dev.json'),\n modelsOverlayCache: path.join(globalRoot, 'cache', 'models-overlay.json'),\n historyFile: path.join(globalRoot, 'history'),\n logFile: path.join(globalRoot, 'logs', 'wrongstack.log'),\n projectDir,\n projectCodebaseIndex: path.join(projectDir, 'codebase-index'),\n projectMemory: path.join(projectDir, 'memory.md'),\n projectSessions: path.join(projectDir, 'sessions'),\n projectTrust: path.join(projectDir, 'trust.json'),\n projectMeta: path.join(projectDir, 'meta.json'),\n projectLocalConfig: path.join(projectDir, 'config.local.json'),\n inProjectConfig: path.join(opts.projectRoot, '.wrongstack', 'config.json'),\n inProjectAgentsFile: path.join(opts.projectRoot, '.wrongstack', 'AGENTS.md'),\n inProjectSkills: path.join(opts.projectRoot, '.wrongstack', 'skills'),\n inProjectClaudeSkills: path.join(opts.projectRoot, '.claude', 'skills'),\n inProjectPrompts: path.join(opts.projectRoot, '.wrongstack', 'prompts'),\n inProjectInstructions: path.join(opts.projectRoot, '.wrongstack', 'instructions'),\n inProjectDesignKits: path.join(opts.projectRoot, '.wrongstack', 'design-kits'),\n inProjectWorktrees: path.join(opts.projectRoot, '.wrongstack', 'worktrees'),\n projectHash: hash,\n projectSlug: slug,\n projectGoal: path.join(projectDir, 'goal.json'),\n projectInputHistory: path.join(projectDir, 'input-history.json'),\n projectSpecs: path.join(projectDir, 'specs'),\n projectTaskGraphs: path.join(projectDir, 'task-graphs'),\n projectSddSession: path.join(projectDir, 'sdd-session.json'),\n projectPlan: path.join(projectDir, 'plan.json'),\n projectAutophase: path.join(projectDir, 'autophase'),\n projectSddBoards: path.join(projectDir, 'sdd-boards'),\n syncConfig: path.join(globalRoot, 'sync.json'),\n projectStatus: (projectHash: string) => path.join(globalRoot, 'projects', projectHash, 'status.json'),\n };\n}\n", "import { toErrorMessage } from '../utils/index.js';\n\n/**\n * WrongStack error hierarchy.\n *\n * Every error thrown by the framework is a `WrongStackError` with a\n * machine-readable `code`, a `subsystem` tag, and a `severity` level.\n * This lets consumers (CLI, TUI, plugins, tests) branch on structured\n * data instead of parsing error messages.\n */\n\n// \u2500\u2500 Error codes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Machine-readable error codes as frozen constants.\n *\n * Use `ERROR_CODES.X` instead of raw string literals for:\n * - IDE autocomplete and compile-time validation\n * - Safe refactoring (rename updates all usages)\n * - Plugin extensibility (extend the object to add custom codes)\n *\n * The `ErrorCode` type is derived from this object, so adding a new\n * code here automatically updates the type without extra changes.\n */\nexport const ERROR_CODES = {\n // Provider\n PROVIDER_RATE_LIMITED: 'PROVIDER_RATE_LIMITED',\n PROVIDER_AUTH_FAILED: 'PROVIDER_AUTH_FAILED',\n PROVIDER_OVERLOADED: 'PROVIDER_OVERLOADED',\n PROVIDER_INVALID_REQUEST: 'PROVIDER_INVALID_REQUEST',\n PROVIDER_SERVER_ERROR: 'PROVIDER_SERVER_ERROR',\n PROVIDER_NETWORK_ERROR: 'PROVIDER_NETWORK_ERROR',\n PROVIDER_CONTEXT_OVERFLOW: 'PROVIDER_CONTEXT_OVERFLOW',\n // Tool\n TOOL_NOT_FOUND: 'TOOL_NOT_FOUND',\n TOOL_PERMISSION_DENIED: 'TOOL_PERMISSION_DENIED',\n TOOL_EXECUTION_FAILED: 'TOOL_EXECUTION_FAILED',\n TOOL_TIMEOUT: 'TOOL_TIMEOUT',\n TOOL_INPUT_INVALID: 'TOOL_INPUT_INVALID',\n // Config\n CONFIG_INVALID: 'CONFIG_INVALID',\n CONFIG_NOT_FOUND: 'CONFIG_NOT_FOUND',\n CONFIG_PARSE_FAILED: 'CONFIG_PARSE_FAILED',\n CONFIG_MIGRATION_NEEDED: 'CONFIG_MIGRATION_NEEDED',\n // Plugin\n PLUGIN_LOAD_FAILED: 'PLUGIN_LOAD_FAILED',\n PLUGIN_API_MISMATCH: 'PLUGIN_API_MISMATCH',\n PLUGIN_MISSING_DEPENDENCY: 'PLUGIN_MISSING_DEPENDENCY',\n // Agent\n AGENT_ITERATION_LIMIT: 'AGENT_ITERATION_LIMIT',\n AGENT_CONTEXT_OVERFLOW: 'AGENT_CONTEXT_OVERFLOW',\n AGENT_ABORTED: 'AGENT_ABORTED',\n AGENT_RUN_FAILED: 'AGENT_RUN_FAILED',\n // Session\n SESSION_NOT_FOUND: 'SESSION_NOT_FOUND',\n SESSION_CORRUPTED: 'SESSION_CORRUPTED',\n SESSION_WRITE_FAILED: 'SESSION_WRITE_FAILED',\n // Container / Registry\n CONTAINER_TOKEN_ALREADY_BOUND: 'CONTAINER_TOKEN_ALREADY_BOUND',\n CONTAINER_TOKEN_NOT_BOUND: 'CONTAINER_TOKEN_NOT_BOUND',\n CONTAINER_CIRCULAR_DEPENDENCY: 'CONTAINER_CIRCULAR_DEPENDENCY',\n REGISTRY_DUPLICATE: 'REGISTRY_DUPLICATE',\n REGISTRY_NOT_FOUND: 'REGISTRY_NOT_FOUND',\n REGISTRY_INVALID: 'REGISTRY_INVALID',\n // File system\n FS_READ_FAILED: 'FS_READ_FAILED',\n FS_WRITE_FAILED: 'FS_WRITE_FAILED',\n FS_MKDIR_FAILED: 'FS_MKDIR_FAILED',\n FS_DELETE_FAILED: 'FS_DELETE_FAILED',\n FS_ATOMIC_WRITE_FAILED: 'FS_ATOMIC_WRITE_FAILED',\n // SDD (Spec-Driven Development)\n SDD_VALIDATION_FAILED: 'SDD_VALIDATION_FAILED',\n SDD_PARSE_FAILED: 'SDD_PARSE_FAILED',\n SDD_INVALID_STATE: 'SDD_INVALID_STATE',\n SDD_NOT_READY: 'SDD_NOT_READY',\n // General\n VALIDATION_ERROR: 'VALIDATION_ERROR',\n PARSE_FAILED: 'PARSE_FAILED',\n UNKNOWN: 'UNKNOWN',\n} as const;\n\n/**\n * Union type derived from `ERROR_CODES`. Using `typeof ERROR_CODES[keyof typeof ERROR_CODES]`\n * instead of a string literal union means TypeScript auto-updates the type whenever\n * a new code is added to `ERROR_CODES` \u2014 no need to keep two lists in sync.\n */\nexport type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];\n\nexport type ErrorSubsystem =\n | 'provider'\n | 'tool'\n | 'config'\n | 'plugin'\n | 'agent'\n | 'session'\n | 'sdd'\n | 'container'\n | 'fs'\n | 'general';\nexport type ErrorSeverity = 'fatal' | 'error' | 'warning';\n\n// \u2500\u2500 Base error class \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class WrongStackError extends Error {\n readonly code: ErrorCode;\n readonly subsystem: ErrorSubsystem;\n readonly severity: ErrorSeverity;\n readonly recoverable: boolean;\n readonly context?: Record<string, unknown> | undefined;\n\n constructor(opts: {\n message: string;\n code: ErrorCode;\n subsystem: ErrorSubsystem;\n severity?: ErrorSeverity | undefined;\n recoverable?: boolean | undefined;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super(opts.message, { cause: opts.cause });\n this.name = 'WrongStackError';\n this.code = opts.code;\n this.subsystem = opts.subsystem;\n this.severity = opts.severity ?? 'error';\n this.recoverable = opts.recoverable ?? false;\n this.context = opts.context;\n }\n\n /**\n * Render a one-line user-facing description.\n * Subclasses should override for domain-specific formatting.\n */\n describe(): string {\n const ctx = this.context ? ` ${formatContext(this.context)}` : '';\n return `${this.code}: ${this.message}${ctx}`;\n }\n}\n\nfunction formatContext(ctx: Record<string, unknown>): string {\n const parts = Object.entries(ctx)\n .filter(([, v]) => v !== undefined)\n .slice(0, 3)\n .map(([k, v]) => `${k}=${String(v)}`);\n return parts.length > 0 ? `[${parts.join(' ')}]` : '';\n}\n\n// \u2500\u2500 Specific error classes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Tool execution errors \u2014 thrown by ToolExecutor and individual tools.\n */\nexport class ToolError extends WrongStackError {\n readonly toolName: string;\n\n constructor(opts: {\n message: string;\n code: Extract<\n ErrorCode,\n | 'TOOL_NOT_FOUND'\n | 'TOOL_PERMISSION_DENIED'\n | 'TOOL_EXECUTION_FAILED'\n | 'TOOL_TIMEOUT'\n | 'TOOL_INPUT_INVALID'\n >;\n toolName: string;\n recoverable?: boolean | undefined;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: opts.code,\n subsystem: 'tool',\n recoverable: opts.recoverable,\n context: { tool: opts.toolName, ...opts.context },\n cause: opts.cause,\n });\n this.name = 'ToolError';\n this.toolName = opts.toolName;\n }\n}\n\n/**\n * Config loading / validation errors.\n */\nexport class ConfigError extends WrongStackError {\n constructor(opts: {\n message: string;\n code: Extract<\n ErrorCode,\n 'CONFIG_INVALID' | 'CONFIG_NOT_FOUND' | 'CONFIG_PARSE_FAILED' | 'CONFIG_MIGRATION_NEEDED'\n >;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: opts.code,\n subsystem: 'config',\n severity: 'fatal',\n recoverable: false,\n context: opts.context,\n cause: opts.cause,\n });\n this.name = 'ConfigError';\n }\n}\n\n/**\n * Plugin loading / lifecycle errors.\n */\nexport class PluginError extends WrongStackError {\n readonly pluginName: string;\n\n constructor(opts: {\n message: string;\n code: Extract<\n ErrorCode,\n 'PLUGIN_LOAD_FAILED' | 'PLUGIN_API_MISMATCH' | 'PLUGIN_MISSING_DEPENDENCY'\n >;\n pluginName: string;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: opts.code,\n subsystem: 'plugin',\n severity: 'error',\n recoverable: opts.code === ERROR_CODES.PLUGIN_MISSING_DEPENDENCY,\n context: { plugin: opts.pluginName, ...opts.context },\n cause: opts.cause,\n });\n this.name = 'PluginError';\n this.pluginName = opts.pluginName;\n }\n}\n\n/**\n * Agent runtime errors \u2014 thrown by Agent.run when a non-WrongStackError\n * escapes the inner loop, so callers always see a structured error.\n */\nexport class AgentError extends WrongStackError {\n constructor(opts: {\n message: string;\n code: Extract<\n ErrorCode,\n 'AGENT_ITERATION_LIMIT' | 'AGENT_CONTEXT_OVERFLOW' | 'AGENT_ABORTED' | 'AGENT_RUN_FAILED'\n >;\n recoverable?: boolean | undefined;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: opts.code,\n subsystem: 'agent',\n severity: opts.code === ERROR_CODES.AGENT_ABORTED ? 'warning' : 'error',\n recoverable: opts.recoverable ?? opts.code === ERROR_CODES.AGENT_ITERATION_LIMIT,\n context: opts.context,\n cause: opts.cause,\n });\n this.name = 'AgentError';\n }\n}\n\n/**\n * Wrap an arbitrary thrown value into a `WrongStackError` so the caller\n * always gets a structured error. Pass-throughs WrongStackError instances\n * unchanged; raw `Error`s and primitives get an `AGENT_RUN_FAILED` wrapper\n * with the original preserved as `cause`.\n */\nexport function toWrongStackError(\n err: unknown,\n code: Extract<ErrorCode, 'AGENT_RUN_FAILED' | 'AGENT_ABORTED' | 'UNKNOWN'> = ERROR_CODES.AGENT_RUN_FAILED,\n): WrongStackError {\n if (err instanceof WrongStackError) return err;\n const message = toErrorMessage(err);\n return new AgentError({\n message,\n code: code === 'UNKNOWN' ? ERROR_CODES.AGENT_RUN_FAILED : code,\n cause: err,\n });\n}\n\n/**\n * Session storage errors.\n */\nexport class SessionError extends WrongStackError {\n readonly sessionId?: string | undefined;\n\n constructor(opts: {\n message: string;\n code: Extract<ErrorCode, 'SESSION_NOT_FOUND' | 'SESSION_CORRUPTED' | 'SESSION_WRITE_FAILED'>;\n sessionId?: string | undefined;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: opts.code,\n subsystem: 'session',\n severity: opts.code === ERROR_CODES.SESSION_WRITE_FAILED ? 'error' : 'warning',\n recoverable: opts.code !== ERROR_CODES.SESSION_CORRUPTED,\n context: { sessionId: opts.sessionId, ...opts.context },\n cause: opts.cause,\n });\n this.name = 'SessionError';\n this.sessionId = opts.sessionId;\n }\n}\n\n/**\n * SDD (Spec-Driven Development) errors \u2014 spec validation, parsing, and\n * state machine violations in the AISpecBuilder, TaskFlow, and TaskTracker.\n */\nexport class SddError extends WrongStackError {\n constructor(opts: {\n message: string;\n code: Extract<\n ErrorCode,\n 'SDD_VALIDATION_FAILED' | 'SDD_PARSE_FAILED' | 'SDD_INVALID_STATE' | 'SDD_NOT_READY'\n >;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: opts.code,\n subsystem: 'sdd',\n severity: opts.code === ERROR_CODES.SDD_PARSE_FAILED ? 'warning' : 'error',\n recoverable: opts.code === ERROR_CODES.SDD_NOT_READY,\n context: opts.context,\n cause: opts.cause,\n });\n this.name = 'SddError';\n }\n}\n\n/**\n * File system operation errors.\n */\nexport class FsError extends WrongStackError {\n readonly path?: string | undefined;\n\n constructor(opts: {\n message: string;\n code: Extract<\n ErrorCode,\n 'FS_READ_FAILED' | 'FS_WRITE_FAILED' | 'FS_MKDIR_FAILED' | 'FS_DELETE_FAILED' | 'FS_ATOMIC_WRITE_FAILED'\n >;\n path?: string | undefined;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: opts.code,\n subsystem: 'fs',\n severity: 'error',\n recoverable: opts.code !== ERROR_CODES.FS_READ_FAILED,\n context: { path: opts.path, ...opts.context },\n cause: opts.cause,\n });\n this.name = 'FsError';\n this.path = opts.path;\n }\n}\n\n/**\n * HTTP fetch error \u2014 thrown when a network request returns a non-OK status.\n * Carries the response status so {@link classifyToolError} can branch on it\n * (429 \u2192 transient, 404 \u2192 not_found, 401 \u2192 permission) without duck-typing\n * the error via `'response' in err`.\n *\n * P3 #18 (before-release.md): the previous `'response' in err` check caught\n * any Error with a `response` property, including custom errors, proxy\n * objects, or mocked errors in tests. `instanceof FetchError` is reliable.\n *\n * Tools and providers that make HTTP requests and need the executor to\n * classify their failures should throw `new FetchError({ status, message })`\n * instead of a bare `Error` with an ad-hoc `response` field.\n */\nexport class FetchError extends WrongStackError {\n readonly status: number;\n\n constructor(opts: {\n message: string;\n status: number;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: ERROR_CODES.VALIDATION_ERROR,\n subsystem: 'general',\n severity: 'error',\n recoverable: opts.status === 429 || opts.status >= 500,\n context: { status: opts.status, ...opts.context },\n cause: opts.cause,\n });\n this.name = 'FetchError';\n this.status = opts.status;\n }\n}\n\n/**\n * Tool input validation error \u2014 thrown when a tool's input fails a validation\n * check that the JSON Schema cannot express (e.g. `old_string === new_string`\n * in edit, or a cross-field invariant). Use this instead of a bare\n * `throw new Error('...validation...')` so {@link classifyToolError} can\n * match on `instanceof` rather than a locale-dependent message substring.\n *\n * P2 #6 (before-release.md): the previous `err.message.includes('validation')`\n * check misclassified any error whose message happened to contain \"validation\"\n * (e.g. a third-party \"input validation timeout\") as a VALIDATION error.\n *\n * Named `ToolValidationError` (not `ValidationError`) to avoid colliding with\n * the existing `ValidationError` interface exported by json-schema-validate.ts\n * (a validation-result shape, not an Error subclass).\n */\nexport class ToolValidationError extends WrongStackError {\n constructor(opts: {\n message: string;\n /** Field path or tool name that failed validation, for diagnostics. */\n field?: string | undefined;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: ERROR_CODES.VALIDATION_ERROR,\n subsystem: 'general',\n severity: 'error',\n recoverable: false,\n context: { field: opts.field, ...opts.context },\n cause: opts.cause,\n });\n this.name = 'ToolValidationError';\n }\n}\n\n/**\n * Response / payload parse error \u2014 thrown when an upstream HTTP response,\n * file, or data structure is well-formed at the transport layer (HTTP 200,\n * valid JSON) but is missing required fields or has an unexpected shape.\n *\n * Distinct from `ConfigError(CONFIG_PARSE_FAILED)` (which is specifically\n * for config-file parsing) and `FetchError` (which covers HTTP non-OK\n * responses). `ParseError` fills the gap: the request succeeded but the\n * response body couldn't be interpreted.\n *\n * Common sites: OAuth token responses missing `access_token`, device-code\n * responses missing `device_code`, registry responses with unexpected\n * schemas.\n */\nexport class ParseError extends WrongStackError {\n readonly source?: string | undefined;\n\n constructor(opts: {\n message: string;\n /**\n * What was being parsed \u2014 e.g. `'oauth-token-response'`,\n * `'device-code-response'`. Lets consumers distinguish parse failures\n * from different upstream APIs without parsing the message.\n */\n source?: string | undefined;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: ERROR_CODES.PARSE_FAILED,\n subsystem: 'general',\n severity: 'error',\n recoverable: false,\n context: { source: opts.source, ...opts.context },\n cause: opts.cause,\n });\n this.name = 'ParseError';\n this.source = opts.source;\n }\n}\n\n// \u2500\u2500 Type guards \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function isWrongStackError(err: unknown): err is WrongStackError {\n return err instanceof WrongStackError;\n}\n\nexport function isToolError(err: unknown): err is ToolError {\n return err instanceof ToolError;\n}\n\nexport function isConfigError(err: unknown): err is ConfigError {\n return err instanceof ConfigError;\n}\n\nexport function isPluginError(err: unknown): err is PluginError {\n return err instanceof PluginError;\n}\n\nexport function isSessionError(err: unknown): err is SessionError {\n return err instanceof SessionError;\n}\n\nexport function isAgentError(err: unknown): err is AgentError {\n return err instanceof AgentError;\n}\n\nexport function isFsError(err: unknown): err is FsError {\n return err instanceof FsError;\n}\n\nexport function isToolValidationError(err: unknown): err is ToolValidationError {\n return err instanceof ToolValidationError;\n}\n\nexport function isFetchError(err: unknown): err is FetchError {\n return err instanceof FetchError;\n}\n\nexport function isParseError(err: unknown): err is ParseError {\n return err instanceof ParseError;\n}\n\nexport function isSddError(err: unknown): err is SddError {\n return err instanceof SddError;\n}\n", "import { closeSync, fsyncSync, openSync, writeSync } from 'node:fs';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { EventBus } from '../kernel/events.js';\nimport type {\n FileSnapshot,\n SessionEvent,\n SessionMetadata,\n SessionSummary,\n SessionWriter,\n WorkspaceCheckpointRef,\n} from '../types/session.js';\nimport type { SecretScrubber } from '../types/secret-scrubber.js';\nimport { atomicWrite } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/index.js';\nimport type { SessionCheckpointCas } from './session-checkpoint-cas.js';\nimport { userInputTitle } from './session-helpers.js';\n\n/**\n * Append-mode JSONL session writer with batched writes, write serialization,\n * and enriched summary tracking.\n *\n * Extracted from session-store.ts to keep each module focused: this class\n * owns the per-session write path (append/flush/close/checkpoint/truncate),\n * while `DefaultSessionStore` owns the store-level read/list/index/delete path.\n */\nexport class FileSessionWriter implements SessionWriter {\n private closed = false;\n private closePromise: Promise<void> | null = null;\n private manifestFile: string;\n private summary: SessionSummary;\n private tokenIn = 0;\n private tokenOut = 0;\n private readonly filePath: string;\n get transcriptPath(): string | undefined {\n return this.filePath || undefined;\n }\n /**\n * Lazy session_start/session_resumed init, shared by all appenders.\n * A single promise (not a boolean) so a second append racing the first\n * can't push its event into the buffer BEFORE the first append's event \u2014\n * every appender awaits the same init and resumes in FIFO call order.\n */\n private initPromise: Promise<void> | null = null;\n private ensureInit(): Promise<void> {\n if (!this.initPromise) this.initPromise = this.writeSessionStartLazy();\n return this.initPromise;\n }\n private readonly resumed: boolean;\n private appendFailCount = 0;\n private lastAppendWarnAt = 0;\n private readonly secretScrubber?: SecretScrubber | undefined;\n private readonly checkpointCas?: SessionCheckpointCas | undefined;\n private readonly onCloseCb?: (((summary: SessionSummary) => void | Promise<void>)) | undefined;\n /** Implements SessionWriter.traceId \u2014 propagated from ContextInit.traceId. */\n traceId: string | undefined;\n\n // \u2500\u2500 Write buffer \u2014 batches events to reduce per-event disk I/O \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n //\n // Every append() pushes the scrubbed event into an in-memory buffer instead\n // of calling handle.appendFile() synchronously. The buffer flushes to disk\n // when it reaches FLUSH_SIZE events OR after FLUSH_INTERVAL_MS of inactivity.\n // This cuts the number of disk writes by ~95% without changing the on-disk\n // format \u2014 the JSONL is still one JSON object per line.\n private writeBuffer: SessionEvent[] = [];\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n private static readonly FLUSH_INTERVAL_MS = 500;\n private static readonly FLUSH_SIZE = 50;\n\n // \u2500\u2500 Write serialization \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n //\n // All disk writes are funneled through a FIFO promise chain. Without it,\n // a timer-driven flush racing an explicit flush()/close() issues two\n // concurrent appendFile() calls on the shared O_APPEND handle \u2014 the kernel\n // may complete them out of order (chronology breaks) or, for large\n // batches, interleave partial writes (torn JSONL lines). The write chain\n // serializes handle I/O; flushChain additionally serializes the act of\n // draining/re-queueing the in-memory event buffer so a failed older batch\n // can never be overtaken by a newer successful one.\n private writeChain: Promise<void> = Promise.resolve();\n private flushChain: Promise<void> = Promise.resolve();\n\n /** Enqueue a write on the FIFO chain. Resolves/rejects with that write. */\n private enqueueWrite(data: string): Promise<void> {\n const write = this.writeChain.then(() => this.handle.appendFile(data, 'utf8'));\n this.writeChain = write.then(\n () => undefined,\n () => undefined,\n );\n return write;\n }\n\n // \u2500\u2500 Enriched summary tracking \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n private iterationCount = 0;\n private toolCallCount = 0;\n private toolErrorCount = 0;\n private toolBreakdown: Record<string, number> = {};\n private fileChangeCount = 0;\n private compactionCount = 0;\n private outcome: SessionSummary['outcome'] = undefined;\n\n /**\n * Scrub secrets out of conversation-turn events before they are observed\n * for the summary, written to the JSONL log, or surfaced on resume. Only\n * `user_input`, `llm_response`, and `context_snapshot` carry free-form\n * user/model text; other event types either have no secret-bearing content\n * or are already scrubbed upstream (tool results). Snapshot normalization\n * also strips transient token-cache fields even when no scrubber is set.\n */\n private scrubEvent(event: SessionEvent): SessionEvent {\n const s = this.secretScrubber;\n if (event.type === 'context_snapshot') {\n return {\n ...event,\n messages: event.messages.map((message) => {\n const { _estTokens: _ignored, ...persisted } = message;\n return {\n ...persisted,\n content:\n typeof persisted.content === 'string'\n ? (s?.scrub(persisted.content) ?? persisted.content)\n : (s?.scrubObject(persisted.content) ?? persisted.content),\n };\n }),\n };\n }\n if (!s) return event;\n if (event.type === 'user_input') {\n return {\n ...event,\n content:\n typeof event.content === 'string' ? s.scrub(event.content) : s.scrubObject(event.content),\n };\n }\n if (event.type === 'llm_response') {\n return { ...event, content: s.scrubObject(event.content) };\n }\n return event;\n }\n\n private pendingFileSnapshots: Array<{\n path: string;\n action: 'created' | 'modified' | 'deleted';\n before: string | null;\n after: string | null;\n }> = [];\n /** Prompt whose tool work is currently executing. Set by writeCheckpoint. */\n private activePromptIndex: number | null = null;\n /** Tracks open tool_use IDs during the current run to serialize on close for resume. */\n private openToolUses = new Set<string>();\n\n /**\n * Buffer an event from a synchronous Context callback. ensureInit() starts\n * by pushing the lifecycle preamble synchronously, so session_start always\n * precedes observations even though these callbacks cannot await append().\n */\n private bufferSynchronousEvent(event: SessionEvent): void {\n if (this.closed) return;\n void this.ensureInit();\n this.observeForSummary(event);\n this.writeBuffer.push(event);\n if (this.writeBuffer.length >= FileSessionWriter.FLUSH_SIZE) {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n void this.flushBuffer().catch(() => {\n // Retained at the head of writeBuffer for the boundary retry.\n });\n } else {\n this.scheduleFlush();\n }\n }\n\n recordFileChange(input: {\n path: string;\n action: 'created' | 'modified' | 'deleted';\n before: string | null;\n after: string | null;\n }): void {\n if (this.closed) return;\n if (this.activePromptIndex === null) {\n // Compatibility path for embedders that mutate before their first\n // checkpoint. writeCheckpoint()/close() will attach these changes to the\n // first available prompt index.\n this.pendingFileSnapshots.push(input);\n return;\n }\n\n // This method is intentionally synchronous because file tools call it\n // immediately after their atomic mutation. Put the reconstruct event in\n // the writer buffer before the tool returns; agent-tools flushes the buffer\n // together with the matching tool_result boundary.\n const event: SessionEvent = {\n type: 'file_snapshot',\n ts: new Date().toISOString(),\n promptIndex: this.activePromptIndex,\n files: [input],\n };\n this.bufferSynchronousEvent(event);\n }\n\n recordFileObservation(input: {\n path: string;\n hash: string;\n mtimeMs: number;\n source: 'user' | 'write';\n }): void {\n if (!input.path || !/^[a-f\\d]{64}$/i.test(input.hash) || !Number.isFinite(input.mtimeMs)) return;\n this.bufferSynchronousEvent({\n type: 'file_observation',\n ts: new Date().toISOString(),\n path: input.path,\n hash: input.hash.toLowerCase(),\n mtimeMs: input.mtimeMs,\n source: input.source,\n });\n }\n\n recordSideEffect(input: {\n toolUseId: string;\n toolName: string;\n input: Record<string, unknown>;\n outcome?: string | undefined;\n risk: 'fs.write' | 'shell' | 'package' | 'network' | 'config';\n }): void {\n // Fire-and-forget \u2014 side-effect recording must never block tool execution.\n this.append({\n type: 'side_effect',\n ts: new Date().toISOString(),\n toolUseId: input.toolUseId,\n toolName: input.toolName,\n input: input.input,\n outcome: input.outcome,\n risk: input.risk,\n }).catch(() => { /* best-effort */ });\n }\n\n constructor(\n public readonly id: string,\n private handle: fsp.FileHandle,\n private readonly startedAt: string,\n private readonly meta: Omit<SessionMetadata, 'startedAt'>,\n private readonly events?: EventBus | undefined,\n opts: {\n resumed?: boolean | undefined;\n dir?: string | undefined;\n filePath?: string | undefined;\n secretScrubber?: SecretScrubber | undefined;\n checkpointCas?: SessionCheckpointCas | undefined;\n /** Called on close() with the finalized summary for index/sidecar writes. */\n onClose?: (((summary: SessionSummary) => void | Promise<void>)) | undefined;\n } = {},\n traceId?: string | undefined,\n ) {\n this.resumed = opts.resumed ?? false;\n // id already contains a date-prefix shard (e.g. \"2026-06-06/sess_<ULID>\").\n // opts.dir is the shard directory \u2014 join with basename so the manifest\n // lives next to the JSONL file instead of creating a double-nested path.\n this.manifestFile = opts.dir ? path.join(opts.dir, `${path.basename(id)}.summary.json`) : '';\n this.filePath = opts.filePath ?? '';\n this.secretScrubber = opts.secretScrubber;\n this.checkpointCas = opts.checkpointCas;\n this.onCloseCb = opts.onClose;\n this.summary = {\n id,\n title: '(empty session)',\n startedAt,\n model: meta.model ?? 'unknown',\n provider: meta.provider ?? 'unknown',\n tokenTotal: 0,\n };\n // Propagated from ContextInit.traceId via SessionWriter.traceId so that\n // storage events carry the run-level trace ID without needing a Context\n // handle in every storage operation.\n this.traceId = traceId;\n }\n\n get pendingToolUses(): string[] {\n return Array.from(this.openToolUses);\n }\n\n private async writeSessionStartLazy(): Promise<void> {\n // Keep the lifecycle preamble in the same retryable buffer as every other\n // reconstruct event. Writing it through a separate one-shot append meant\n // an ENOSPC/transient handle failure could permanently lose session_start\n // while later events survived, leaving an unidentifiable transcript.\n this.writeBuffer.push({\n type: this.resumed ? 'session_resumed' : 'session_start',\n ts: this.startedAt,\n id: this.id,\n model: this.meta.model ?? 'unknown',\n provider: this.meta.provider ?? 'unknown',\n });\n }\n\n async append(event: SessionEvent): Promise<void> {\n if (this.closed) return;\n await this.ensureInit();\n // Scrub before observing (the summary title is derived from user_input\n // content) and before buffering, so neither the JSONL nor the sidecar\n // ever holds a cleartext secret.\n const scrubbed = this.scrubEvent(event);\n // observeForSummary MUST run synchronously here \u2014 the summary counters\n // (toolCallCount, tokenIn/Out, outcome) drive the .summary.json sidecar\n // and the session index. Deferring observation to flush time would leave\n // the summary stale if close() fires before the next timer tick.\n this.observeForSummary(scrubbed);\n this.writeBuffer.push(scrubbed);\n\n if (this.writeBuffer.length >= FileSessionWriter.FLUSH_SIZE) {\n // Buffer full \u2014 flush immediately. Cancel any pending timer so we\n // don't double-flush on the next tick.\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n await this.flushBuffer().catch(() => {\n // append() is intentionally best-effort. The failed batch remains at\n // the front of writeBuffer; an explicit boundary flush can surface the\n // error while ordinary audit appends do not abort the agent loop.\n });\n } else {\n this.scheduleFlush();\n }\n }\n\n async appendBatch(events: SessionEvent[]): Promise<void> {\n if (this.closed || events.length === 0) return;\n await this.ensureInit();\n for (const event of events) {\n const scrubbed = this.scrubEvent(event);\n this.observeForSummary(scrubbed);\n this.writeBuffer.push(scrubbed);\n }\n if (this.writeBuffer.length >= FileSessionWriter.FLUSH_SIZE) {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n await this.flushBuffer().catch(() => {\n // Same best-effort append contract as append(); batch is retained.\n });\n } else {\n this.scheduleFlush();\n }\n }\n\n /**\n * Flush buffered events to disk immediately. Critical events\n * (user_input, llm_response) call this so they survive SIGKILL/crash\n * instead of sitting in the in-memory buffer for up to 500ms.\n *\n * Idempotent \u2014 cancels any pending timer, writes whatever has accumulated,\n * then asks the OS to synchronize the file data before resolving. Even an\n * empty-buffer flush synchronizes any earlier timer-driven append.\n */\n async flush(): Promise<void> {\n if (this.closed) return;\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n await this.flushBuffer();\n await this.writeChain;\n await this.handle.datasync();\n }\n\n /**\n * Last-gasp synchronous drain for hard-exit paths (process.exit after\n * rapid Ctrl+C). The async write chain cannot be awaited when the process\n * is about to die, but whatever still sits in the in-memory buffer CAN be\n * saved with a blocking append. Best-effort: an in-flight async write may\n * be cut off by the exit regardless; errors here are swallowed.\n */\n flushSync(): void {\n if (this.writeBuffer.length === 0 || !this.filePath) return;\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n const batch = this.writeBuffer.map((e) => JSON.stringify(e)).join('\\n') + '\\n';\n this.writeBuffer = [];\n let fd: number | undefined;\n try {\n fd = openSync(this.filePath, 'a');\n writeSync(fd, batch, null, 'utf8');\n fsyncSync(fd);\n } catch {\n // best-effort \u2014 the process is exiting either way\n } finally {\n if (fd !== undefined) {\n try {\n closeSync(fd);\n } catch {\n // best-effort \u2014 the process is exiting either way\n }\n }\n }\n }\n\n /** Schedule a deferred flush. No-op if a timer is already pending. */\n private scheduleFlush(): void {\n if (this.flushTimer) return;\n this.flushTimer = setTimeout(() => {\n this.flushTimer = null;\n /* v8 ignore start -- defensive: flushBuffer logs its own errors; this guards the timer callback */\n this.flushBuffer().catch(() => {\n // flushBuffer already logs via the throttled-warning path;\n // this catch prevents an unhandled rejection in the timer callback.\n });\n /* v8 ignore stop */\n }, FileSessionWriter.FLUSH_INTERVAL_MS);\n }\n\n /**\n * Flush all buffered events to disk as a single appendFile call.\n * Concurrent callers join a FIFO flush chain. On failure the drained batch\n * is prepended to the live buffer, preserving chronology and allowing the\n * next timer/boundary flush to retry it. Warnings retain the existing\n * throttled behavior.\n */\n private flushBuffer(): Promise<void> {\n const flush = this.flushChain.then(() => this.flushBufferOnce());\n this.flushChain = flush.then(\n () => undefined,\n () => undefined,\n );\n return flush;\n }\n\n private async flushBufferOnce(): Promise<void> {\n if (this.writeBuffer.length === 0) return;\n const events = this.writeBuffer;\n const eventCount = events.length;\n const batch = events.map((e) => JSON.stringify(e)).join('\\n') + '\\n';\n this.writeBuffer = [];\n const t0 = Date.now();\n let outcome: 'success' | 'failure' = 'success';\n let errorMsg: string | undefined;\n try {\n await this.enqueueWrite(batch);\n } catch (err) {\n outcome = 'failure';\n errorMsg = toErrorMessage(err);\n // No newer batch can be draining concurrently (flushChain serializes\n // this section), so prepending restores the exact original chronology.\n this.writeBuffer = [...events, ...this.writeBuffer];\n this.appendFailCount += eventCount;\n const now = Date.now();\n if (now - this.lastAppendWarnAt > 5000) {\n const suppressed = this.appendFailCount - 1;\n const tail = suppressed > 0 ? ` (+${suppressed} suppressed)` : '';\n console.warn(\n '[session] flush failed:',\n toErrorMessage(err),\n tail,\n );\n this.lastAppendWarnAt = now;\n this.appendFailCount = 0;\n }\n if (!this.closed) this.scheduleFlush();\n throw err;\n } finally {\n this.events?.emit('storage.write', {\n sessionId: this.id,\n store: 'session',\n filePath: this.filePath,\n operation: 'flush',\n outcome,\n durationMs: Date.now() - t0,\n ...(errorMsg !== undefined ? { error: errorMsg } : {}),\n ...(eventCount !== undefined ? { eventCount } : {}),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n }\n }\n\n private observeForSummary(event: SessionEvent): void {\n // Track open tool uses so we can serialize them on close for resume.\n // The authoritative source is the llm_response content (a core event,\n // always written at every audit level); the legacy 'tool_use' event is\n // kept for alternate writers that still emit it.\n if (event.type === 'llm_response') {\n for (const block of event.content) {\n if (block.type === 'tool_use') this.openToolUses.add(block.id);\n }\n }\n if (event.type === 'tool_use') {\n this.openToolUses.add(event.id);\n } else if (event.type === 'tool_call_start') {\n this.toolCallCount++;\n this.toolBreakdown[event.name] = (this.toolBreakdown[event.name] ?? 0) + 1;\n } else if (event.type === 'tool_result') {\n this.openToolUses.delete(event.id);\n if (event.isError) {\n this.toolErrorCount++;\n this.outcome = 'error';\n }\n } else if (event.type === 'file_snapshot') {\n this.fileChangeCount += event.files.length;\n } else if (event.type === 'compaction') {\n this.compactionCount++;\n }\n // Error events (provider errors, execution errors) mark the session as failed.\n if (event.type === 'error' || event.type === 'provider_error') {\n this.outcome = 'error';\n }\n if (event.type === 'user_input' && this.summary.title === '(empty session)') {\n this.summary = { ...this.summary, title: userInputTitle(event.content) };\n } else if (event.type === 'llm_response') {\n this.tokenIn += event.usage.input;\n this.tokenOut += event.usage.output;\n this.summary = { ...this.summary, tokenTotal: this.tokenIn + this.tokenOut };\n } else if (event.type === 'session_end') {\n const total = event.usage.input + event.usage.output;\n if (total > 0) this.summary = { ...this.summary, tokenTotal: total };\n } else if (event.type === 'in_flight_start') {\n this.iterationCount++;\n }\n }\n\n async close(): Promise<void> {\n // Idempotent AND awaitable: concurrent/repeat callers share the same\n // promise, so nobody proceeds (e.g. to tear down the session directory)\n // while the first close is still flushing.\n if (this.closePromise) return this.closePromise;\n this.closePromise = this.doClose().catch((err) => {\n // A failed durable drain must not permanently brick the writer. Keep the\n // handle open and allow the timer or a later close() call to retry.\n this.closed = false;\n this.closePromise = null;\n if (this.writeBuffer.length > 0) this.scheduleFlush();\n throw err;\n });\n return this.closePromise;\n }\n\n private async doClose(): Promise<void> {\n if (this.pendingFileSnapshots.length > 0) {\n await this.writeFileSnapshot(\n this.activePromptIndex ?? 0,\n [...this.pendingFileSnapshots],\n );\n this.pendingFileSnapshots = [];\n }\n this.closed = true;\n // Flush any buffered events before finalizing. The summary counters\n // (toolCallCount, tokenIn/Out, outcome) are already up to date because\n // observeForSummary runs synchronously on every append, but the JSONL\n // must have all events on disk before we write the .summary.json sidecar.\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n await this.flushBuffer();\n // Drain any write enqueued outside flushBuffer (e.g. the lazy\n // session_start record) before the handle is closed.\n await this.writeChain;\n await this.handle.datasync();\n // Finalize the summary before writing.\n this.summary = {\n ...this.summary,\n endedAt: new Date().toISOString(),\n iterationCount: this.iterationCount,\n toolCallCount: this.toolCallCount,\n toolErrorCount: this.toolErrorCount,\n fileChangeCount: this.fileChangeCount,\n compactionCount: this.compactionCount > 0 ? this.compactionCount : undefined,\n toolBreakdown:\n { ...this.toolBreakdown },\n outcome: this.outcome ?? 'completed',\n };\n // Emit storage.write for the manifest sidecar.\n if (this.manifestFile) {\n const t0 = Date.now();\n let outcome: 'success' | 'failure' = 'success';\n let errorMsg: string | undefined;\n try {\n await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 0o600 });\n } catch (err) {\n outcome = 'failure';\n errorMsg = toErrorMessage(err);\n // manifest write is best-effort\n } finally {\n this.events?.emit('storage.write', {\n sessionId: this.id,\n store: 'session',\n filePath: this.manifestFile,\n operation: 'close',\n outcome,\n durationMs: Date.now() - t0,\n ...(errorMsg !== undefined ? { error: errorMsg } : {}),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n }\n }\n // Notify the store so it can update the session index. Await so the\n // index write completes before close() resolves \u2014 otherwise the\n // fire-and-forget _index.jsonl append races callers that tear down the\n // session directory right after close() (e.g. ENOTEMPTY on Windows).\n // Emit storage.write here so it carries this.traceId; the actual I/O\n // is delegated to onCloseCb (appendToIndex) which no longer emits.\n const idxT0 = Date.now();\n let idxOutcome: 'success' | 'failure' = 'success';\n let idxError: string | undefined;\n try {\n await this.onCloseCb?.(this.summary);\n /* v8 ignore start -- best-effort: appendToIndex swallows its own errors */\n } catch (err) {\n idxOutcome = 'failure';\n idxError = toErrorMessage(err);\n // best-effort\n } finally {\n /* v8 ignore stop */\n this.events?.emit('storage.write', {\n sessionId: this.summary.id,\n store: 'session',\n filePath: this.filePath,\n operation: 'index_append',\n outcome: idxOutcome,\n durationMs: Date.now() - idxT0,\n ...(idxError !== undefined ? { error: idxError } : {}),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n }\n try {\n await this.handle.close();\n } catch {\n // ignore\n }\n }\n\n async writeCheckpoint(promptIndex: number, promptPreview: string): Promise<void> {\n const fileCount = this.pendingFileSnapshots.length;\n if (fileCount > 0) {\n await this.writeFileSnapshot(promptIndex, [...this.pendingFileSnapshots]);\n this.pendingFileSnapshots = [];\n }\n let workspaceCheckpoint: WorkspaceCheckpointRef | undefined;\n try {\n workspaceCheckpoint = await this.checkpointCas?.capture(this.id, promptIndex);\n } catch (err) {\n // Conversation checkpoints remain usable even when Git/CAS capture is\n // unavailable. The missing workspaceCheckpoint field makes the reduced\n // guarantee explicit to fork/materialization callers.\n console.warn(\n JSON.stringify({\n level: 'warn',\n event: 'session.workspace_checkpoint_capture_failed',\n sessionId: this.id,\n promptIndex,\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }),\n );\n }\n await this.append({\n type: 'checkpoint',\n ts: new Date().toISOString(),\n promptIndex,\n promptPreview,\n ...(workspaceCheckpoint ? { workspaceCheckpoint } : {}),\n });\n this.activePromptIndex = promptIndex;\n this.events?.emit('checkpoint.written', {\n sessionId: this.id,\n promptIndex,\n promptPreview,\n ts: new Date().toISOString(),\n fileCount,\n });\n }\n\n async writeFileSnapshot(\n promptIndex: number,\n files: FileSnapshot[],\n ): Promise<void> {\n await this.append({\n type: 'file_snapshot',\n ts: new Date().toISOString(),\n promptIndex,\n files,\n });\n }\n\n /**\n * Truncate the session file to the checkpoint with the given promptIndex,\n * removing all events that follow it. Uses a single-pass byte-offset scan\n * so post-checkpoint content is never read or parsed \u2014 O(1) memory instead\n * of O(N) JSON.parse calls over the full file.\n */\n async truncateToCheckpoint(targetPromptIndex: number): Promise<number> {\n /* v8 ignore next -- defensive: filePath is always set for a live writer */\n if (!this.filePath) return 0;\n\n // Flush buffered events to disk before reading \u2014 otherwise the in-memory\n // events that haven't hit the JSONL yet would be invisible to the\n // truncation logic and would be silently dropped by the rewrite.\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n await this.flushBuffer();\n // Drain the write chain so no in-flight write straddles the close/rename/reopen.\n await this.writeChain;\n\n // Single-pass scan: track byte offset of each line start. Stop as soon as\n // the target checkpoint is found \u2014 no I/O or parsing for post-checkpoint data.\n const CHUNK_SIZE = 65_536;\n let fd: fsp.FileHandle | undefined;\n let fileOffset = 0; // cumulative byte position of the start of the current chunk\n let lineStartOffset = 0; // byte offset within the file where the current line begins\n let checkpointByteOffset = -1; // byte offset where we will truncate the file\n let removedCount = 0;\n let targetCheckpointSeen = false; // has the target checkpoint been found yet?\n\n try {\n fd = await fsp.open(this.filePath, 'r', 0o600);\n\n while (true) {\n const buf = Buffer.alloc(CHUNK_SIZE);\n const { bytesRead } = await fd.read(buf, 0, CHUNK_SIZE, fileOffset);\n if (bytesRead === 0) break;\n\n let chunkPos = 0;\n while (chunkPos < bytesRead) {\n const idx = buf.indexOf('\\n', chunkPos);\n if (idx === -1) {\n // No complete line in this chunk \u2014 save partial for next iteration.\n lineStartOffset = fileOffset + chunkPos;\n break;\n }\n\n if (checkpointByteOffset !== -1) {\n // Target already found \u2014 every subsequent line is removed.\n removedCount++;\n } else {\n // Only parse lines that could precede or be the checkpoint.\n const lineBytes = buf.subarray(chunkPos, idx);\n // eslint-disable-next-line no-sync\n const line = new TextDecoder('utf-8', { fatal: false }).decode(lineBytes);\n if (line.trim()) {\n try {\n const event = JSON.parse(line) as { type?: string; promptIndex?: number };\n if (event.type === 'checkpoint') {\n if (event.promptIndex === targetPromptIndex) {\n // Target found \u2014 record its byte offset and stop scanning.\n checkpointByteOffset = lineStartOffset;\n targetCheckpointSeen = true;\n } else if (event.promptIndex !== undefined && event.promptIndex > targetPromptIndex) {\n // A checkpoint with a higher promptIndex means the target is absent.\n // Truncate before this line (exclusive) \u2014 it and all following events\n // will be replaced by the new rewinded history.\n checkpointByteOffset = lineStartOffset;\n }\n } else if (targetCheckpointSeen && event.promptIndex !== undefined && event.promptIndex > targetPromptIndex) {\n // Post-target event with a later promptIndex \u2014 count as removed.\n removedCount++;\n } else if (targetCheckpointSeen && event.promptIndex === undefined) {\n // After the target checkpoint was found: remove events with no\n // promptIndex. (In the original this is the afterTarget &&\n // targetCheckpointLine !== -1 branch.)\n removedCount++;\n } else if (!targetCheckpointSeen && event.promptIndex === undefined) {\n // Past a higher checkpoint but the target checkpoint not yet found.\n // Matches original: remove events with undefined promptIndex\n // (malformed lines, file_snapshots, etc.) that appear after a\n // higher checkpoint but before the target.\n removedCount++;\n } else if (!targetCheckpointSeen && event.promptIndex !== undefined && event.promptIndex > targetPromptIndex) {\n // Past a higher checkpoint but the target not yet found.\n // Matches original: remove events with promptIndex > target that\n // appear before the target checkpoint (e.g. user_inputs belonging\n // to a later prompt).\n removedCount++;\n }\n // Events with promptIndex <= targetPromptIndex (before the target is\n // found) are implicitly kept \u2014 no action needed.\n } catch {\n // Malformed JSON \u2014 matches original: keep it.\n }\n }\n }\n\n // Move to start of next line.\n chunkPos = idx + 1;\n lineStartOffset = fileOffset + chunkPos;\n }\n\n fileOffset += bytesRead;\n if (chunkPos >= bytesRead) {\n // Finished all complete lines; prepare for next chunk.\n lineStartOffset = fileOffset;\n }\n }\n } finally {\n await fd?.close();\n }\n\n if (checkpointByteOffset === -1) return 0;\n\n // Windows EPERM fix: close the append-mode handle before replacing the\n // file. Windows rejects rename() when the destination still has an open\n // handle, even if that handle belongs to this process.\n await this.writeChain;\n await this.handle.close();\n const tmpPath = `${this.filePath}.rewind.tmp`;\n const src = await fsp.open(this.filePath, 'r', 0o600);\n try {\n const statResult = await src.stat();\n const totalSize = statResult.size;\n // checkpointByteOffset points to the start of the checkpoint line.\n // We want to keep everything up to and including that line's '\\n'.\n // Since the file ends with '\\n', keeping bytes [0 .. lineStartAfterCheckpoint]\n // means we include the trailing newline. We find that '\\n' by scanning\n // from checkpointByteOffset forward (at most one chunk's worth).\n const prefixBytes = checkpointByteOffset;\n let newlineAfterCheckpoint = prefixBytes;\n\n if (prefixBytes < totalSize) {\n const probeBuf = Buffer.alloc(Math.min(CHUNK_SIZE, totalSize - prefixBytes));\n const { bytesRead: probeRead } = await src.read(probeBuf, 0, probeBuf.length, prefixBytes);\n if (probeRead > 0) {\n const nl = probeBuf.indexOf('\\n');\n newlineAfterCheckpoint = nl !== -1 ? prefixBytes + nl + 1 : totalSize;\n }\n } else {\n newlineAfterCheckpoint = totalSize;\n }\n\n const writeFd = await fsp.open(tmpPath, 'w', 0o600);\n try {\n let readOffset = 0;\n while (readOffset < newlineAfterCheckpoint) {\n const toCopy = Math.min(CHUNK_SIZE, newlineAfterCheckpoint - readOffset);\n const copyBuf = Buffer.alloc(toCopy);\n const { bytesRead: r } = await src.read(copyBuf, 0, toCopy, readOffset);\n if (r === 0) break;\n await writeFd.write(copyBuf, 0, r);\n readOffset += r;\n }\n\n // Preserve malformed JSONL records even after the rewind target. They\n // are not replayable session events, but keeping them avoids silently\n // deleting diagnostic/corruption evidence during a truncate.\n // Stream the tail from the already-open src handle instead of\n // re-reading the entire file \u2014 the prefix was already streamed above,\n // so reading via readFile() would duplicate all of that I/O.\n let tailOffset = newlineAfterCheckpoint;\n let leftover = '';\n while (tailOffset < totalSize) {\n const toRead = Math.min(CHUNK_SIZE, totalSize - tailOffset);\n const tailBuf = Buffer.alloc(toRead);\n const { bytesRead: tr } = await src.read(tailBuf, 0, toRead, tailOffset);\n if (tr === 0) break;\n const chunk = leftover + tailBuf.subarray(0, tr).toString('utf8');\n const lastNl = chunk.lastIndexOf('\\n');\n if (lastNl === -1) {\n // No complete line in this chunk \u2014 accumulate into leftover.\n leftover = chunk;\n } else {\n for (const line of chunk.slice(0, lastNl + 1).split('\\n')) {\n if (!line.trim()) continue;\n try {\n JSON.parse(line);\n } catch {\n await writeFd.write(`${line}\\n`, undefined, 'utf8');\n }\n }\n leftover = chunk.slice(lastNl + 1);\n }\n tailOffset += tr;\n }\n // Flush trailing partial line (file may not end with \\n).\n if (leftover.trim()) {\n try {\n JSON.parse(leftover);\n } catch {\n await writeFd.write(`${leftover}\\n`, undefined, 'utf8');\n }\n }\n } finally {\n await writeFd.close();\n }\n\n await src.close();\n await fsp.rename(tmpPath, this.filePath);\n // Re-open in append mode for continued use of this file.\n this.handle = await fsp.open(this.filePath, 'a', 0o600);\n /* v8 ignore start -- defensive: close/rename/reopen of a just-written temp file */\n } catch (err) {\n await fsp.unlink(tmpPath).catch(() => undefined);\n this.handle = await fsp.open(this.filePath, 'a', 0o600).catch(() => this.handle);\n throw err;\n }\n /* v8 ignore stop */\n\n await this.append({\n type: 'rewound',\n ts: new Date().toISOString(),\n toPromptIndex: targetPromptIndex,\n revertedFiles: [],\n });\n this.activePromptIndex = targetPromptIndex;\n\n this.events?.emit('session.rewound', {\n sessionId: this.id,\n toPromptIndex: targetPromptIndex,\n revertedFiles: [],\n removedEvents: removedCount,\n });\n\n return removedCount;\n }\n\n async clearSession(): Promise<void> {\n /* v8 ignore next -- defensive: filePath is always set for a live writer */\n if (!this.filePath) return;\n // Discard any buffered events \u2014 the caller is explicitly resetting the\n // session to a clean slate. Cancel the timer so it doesn't fire and\n // append stale events to the freshly-cleared file.\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n // Wait for a currently-draining batch first. If it failed and re-queued\n // itself, the explicit reset below discards it along with the rest of the\n // old conversation; if it succeeded, writeChain has already serialized it.\n await this.flushChain;\n this.writeBuffer = [];\n // Let any in-flight append land first \u2014 otherwise it would re-append\n // stale events AFTER the reset record below.\n await this.writeChain;\n const record = `${JSON.stringify({\n type: 'session_start',\n ts: new Date().toISOString(),\n id: this.id,\n model: this.meta.model ?? 'unknown',\n provider: this.meta.provider ?? 'unknown',\n })}\\n`;\n await fsp.writeFile(this.filePath, record, 'utf8');\n this.activePromptIndex = null;\n this.pendingFileSnapshots = [];\n }\n\n /**\n * Write an in-flight marker. The agent loop should call\n * this at the start of each long-running operation; a matching\n * `clearInFlightMarker` follows on clean exit. A stale marker\n * (no end) is what `SessionRecovery.detectStale` looks for.\n */\n async writeInFlightMarker(context: string): Promise<void> {\n if (!context || context.length > 500) {\n throw new Error('In-flight context must be 1..500 chars');\n }\n await this.append({\n type: 'in_flight_start',\n ts: new Date().toISOString(),\n context,\n });\n this.events?.emit('in_flight.started', { sessionId: this.id, context, ts: new Date().toISOString() });\n }\n\n /**\n * Close the in-flight marker. Idempotent in spirit\n * (you can call it after a successful iteration even if you\n * didn't open one this round) \u2014 but the session log records\n * every call so postmortem tooling can see \"the agent finished\n * cleanly X times, then died without finishing Y\".\n */\n async clearInFlightMarker(reason: 'clean' | 'aborted' | 'recovered'): Promise<void> {\n if (this.pendingFileSnapshots.length > 0) {\n await this.writeFileSnapshot(\n this.activePromptIndex ?? 0,\n [...this.pendingFileSnapshots],\n );\n this.pendingFileSnapshots = [];\n }\n await this.append({\n type: 'in_flight_end',\n ts: new Date().toISOString(),\n reason,\n });\n this.events?.emit('in_flight.ended', { sessionId: this.id, reason, ts: new Date().toISOString() });\n }\n}\n", "import type { ContentBlock } from '../types/blocks.js';\n\n/**\n * Derive a short title from a user_input event's content. Used by both\n * `DefaultSessionStore.summarize()` (offline summary rebuild) and\n * `FileSessionWriter.observeForSummary()` (live tracking), so it lives\n * here to avoid a bidirectional import between those two modules.\n */\nexport function userInputTitle(content: string | ContentBlock[]): string {\n const text =\n typeof content === 'string'\n ? content\n : content\n .filter((b): b is { type: 'text'; text: string } => b.type === 'text')\n .map((b) => b.text)\n .join(' ');\n return (text || '(non-text input)').slice(0, 60);\n}\n", "import { spawn } from 'node:child_process';\nimport { createHash, randomUUID } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type {\n WorkspaceCheckpointRef,\n WorkspaceMaterializationResult,\n} from '../types/session.js';\nimport { atomicWrite } from '../utils/atomic-write.js';\nimport { buildChildEnv } from '../utils/child-env.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport { mapWithConcurrency } from './storage-concurrency.js';\n\nexport interface CheckpointGitResult {\n code: number;\n stdout: string;\n stderr: string;\n /** Set when the runner discarded output after its safety limit. */\n stdoutTruncated?: boolean | undefined;\n stderrTruncated?: boolean | undefined;\n}\n\nexport interface SessionCheckpointCasOptions {\n rootDir: string;\n projectRoot: string;\n runGit?: ((args: string[], cwd: string) => Promise<CheckpointGitResult>) | undefined;\n}\n\ntype WorkspaceCheckpointEntry =\n | { path: string; state: 'file'; blobHash: string; mode: number }\n | { path: string; state: 'absent' }\n | { path: string; state: 'symlink'; linkTarget: string };\n\ninterface WorkspaceCheckpointManifest {\n version: 1;\n baseHead: string;\n coverage: 'git-head-plus-dirty';\n entries: WorkspaceCheckpointEntry[];\n unresolved: Array<{ path: string; reason: string }>;\n}\n\nconst HASH_RE = /^[a-f\\d]{64}$/;\nconst CAPTURE_CONCURRENCY = 8;\nconst MAX_GIT_OUTPUT = 16 * 1024 * 1024;\nconst MAX_BLOB_BYTES = 64 * 1024 * 1024;\nconst MAX_CHECKPOINT_BYTES = 512 * 1024 * 1024;\n\nfunction sha256(content: string | Uint8Array): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nfunction isInside(root: string, target: string): boolean {\n const relative = path.relative(root, target);\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction normalizeRelative(input: string): string | null {\n if (!input || path.isAbsolute(input)) return null;\n const normalized = input.replace(/\\\\/g, '/').replace(/^\\.\\//, '');\n const resolved = path.posix.normalize(normalized);\n if (!resolved || resolved === '.' || resolved === '..' || resolved.startsWith('../')) return null;\n return resolved;\n}\n\nfunction parseNulPaths(output: string): string[] {\n return output\n .split('\\0')\n .map(normalizeRelative)\n .filter((value): value is string => value !== null);\n}\n\nfunction isWrongStackWorktreePath(relative: string): boolean {\n return relative === '.wrongstack/worktrees' || relative.startsWith('.wrongstack/worktrees/');\n}\n\n/**\n * Content-addressed workspace checkpoint store.\n *\n * A manifest references a Git base tree plus CAS blobs for every changed or\n * untracked, non-ignored path. Applying it to a checkout at the same baseHead\n * reproduces the checkpoint without touching the parent working tree.\n */\nexport class SessionCheckpointCas {\n private readonly rootDir: string;\n private readonly projectRoot: string;\n private readonly runGit: (args: string[], cwd: string) => Promise<CheckpointGitResult>;\n\n constructor(opts: SessionCheckpointCasOptions) {\n this.rootDir = path.resolve(opts.rootDir);\n this.projectRoot = path.resolve(opts.projectRoot);\n this.runGit = opts.runGit ?? defaultRunGit;\n }\n\n async capture(_sessionId: string, _promptIndex: number): Promise<WorkspaceCheckpointRef | undefined> {\n const head = await this.runGit(['rev-parse', '--verify', 'HEAD'], this.projectRoot);\n const baseHead = head.stdout.trim();\n if (\n head.code !== 0 ||\n head.stdoutTruncated ||\n !/^[a-f\\d]{40,64}$/i.test(baseHead)\n ) {\n return undefined;\n }\n\n const [tracked, untracked] = await Promise.all([\n this.runGit(['diff', 'HEAD', '--name-only', '-z', '--'], this.projectRoot),\n this.runGit(['ls-files', '--others', '--exclude-standard', '-z'], this.projectRoot),\n ]);\n if (\n tracked.code !== 0 ||\n untracked.code !== 0 ||\n tracked.stdoutTruncated ||\n untracked.stdoutTruncated\n ) {\n return undefined;\n }\n\n const relativePaths = [\n ...new Set([...parseNulPaths(tracked.stdout), ...parseNulPaths(untracked.stdout)]),\n ]\n // Never recursively capture WrongStack's own allocated checkouts, even\n // if a repository accidentally tracks that administrative directory.\n .filter((relative) => !isWrongStackWorktreePath(relative))\n .sort();\n const unresolved: WorkspaceCheckpointManifest['unresolved'] = [];\n let scheduledBytes = 0;\n const captured = await mapWithConcurrency(\n relativePaths,\n CAPTURE_CONCURRENCY,\n async (relative): Promise<WorkspaceCheckpointEntry | null> => {\n const absolute = path.resolve(this.projectRoot, ...relative.split('/'));\n if (!isInside(this.projectRoot, absolute)) {\n unresolved.push({ path: relative, reason: 'path escapes project root' });\n return null;\n }\n try {\n const stat = await fsp.lstat(absolute);\n if (stat.isSymbolicLink()) {\n const linkTarget = await fsp.readlink(absolute);\n const resolvedLink = path.resolve(path.dirname(absolute), linkTarget);\n if (path.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {\n unresolved.push({\n path: relative,\n reason: 'symlink target escapes project root',\n });\n return null;\n }\n return { path: relative, state: 'symlink', linkTarget };\n }\n if (!stat.isFile()) {\n unresolved.push({ path: relative, reason: 'changed path is not a regular file' });\n return null;\n }\n if (stat.size > MAX_BLOB_BYTES) {\n unresolved.push({\n path: relative,\n reason: `file exceeds ${MAX_BLOB_BYTES}-byte checkpoint blob limit`,\n });\n return null;\n }\n if (scheduledBytes + stat.size > MAX_CHECKPOINT_BYTES) {\n unresolved.push({\n path: relative,\n reason: `checkpoint exceeds ${MAX_CHECKPOINT_BYTES}-byte aggregate blob limit`,\n });\n return null;\n }\n scheduledBytes += stat.size;\n const content = await fsp.readFile(absolute);\n const blobHash = sha256(content);\n await this.putBlob(blobHash, content);\n return { path: relative, state: 'file', blobHash, mode: stat.mode & 0o777 };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return { path: relative, state: 'absent' };\n }\n unresolved.push({ path: relative, reason: toErrorMessage(err) });\n return null;\n }\n },\n );\n\n const capturedAt = new Date().toISOString();\n const manifest: WorkspaceCheckpointManifest = {\n version: 1,\n baseHead: baseHead.toLowerCase(),\n coverage: 'git-head-plus-dirty',\n entries: captured.filter((entry): entry is WorkspaceCheckpointEntry => entry !== null),\n unresolved: [...unresolved].sort((a, b) => a.path.localeCompare(b.path)),\n };\n const encoded = JSON.stringify(manifest);\n const manifestHash = sha256(encoded);\n await this.putManifest(manifestHash, encoded);\n return {\n manifestHash,\n baseHead: manifest.baseHead,\n entryCount: manifest.entries.length,\n unresolvedCount: manifest.unresolved.length,\n capturedAt,\n coverage: manifest.coverage,\n };\n }\n\n async materialize(\n checkpoint: WorkspaceCheckpointRef,\n targetRoot: string,\n ): Promise<WorkspaceMaterializationResult> {\n const target = path.resolve(targetRoot);\n if (target === this.projectRoot) {\n throw new Error('Refusing to materialize a workspace checkpoint over the parent project root');\n }\n const targetStat = await fsp.stat(target);\n if (!targetStat.isDirectory()) throw new Error(`Checkpoint target is not a directory: ${target}`);\n\n const manifest = await this.loadManifest(checkpoint.manifestHash);\n if (\n manifest.baseHead !== checkpoint.baseHead.toLowerCase() ||\n manifest.coverage !== checkpoint.coverage ||\n manifest.entries.length !== checkpoint.entryCount ||\n manifest.unresolved.length !== checkpoint.unresolvedCount\n ) {\n throw new Error('Workspace checkpoint reference does not match its CAS manifest');\n }\n if (manifest.unresolved.length > 0) {\n throw new Error(\n `Workspace checkpoint has ${manifest.unresolved.length} unresolved path(s); exact materialization refused`,\n );\n }\n const targetHead = await this.runGit(['rev-parse', '--verify', 'HEAD'], target);\n if (\n targetHead.code !== 0 ||\n targetHead.stdoutTruncated ||\n targetHead.stdout.trim().toLowerCase() !== manifest.baseHead\n ) {\n throw new Error(\n `Checkpoint target HEAD must equal ${manifest.baseHead}; got ${targetHead.stdout.trim() || 'unknown'}`,\n );\n }\n const targetStatus = await this.runGit(\n ['status', '--porcelain=v1', '-z', '--untracked-files=all', '--ignored=matching'],\n target,\n );\n if (targetStatus.code !== 0 || targetStatus.stdoutTruncated) {\n throw new Error('Unable to verify that the checkpoint target is clean');\n }\n if (targetStatus.stdout.length > 0) {\n throw new Error('Checkpoint target must be a clean checkout before materialization');\n }\n\n const realTarget = await fsp.realpath(target);\n const writtenFiles: string[] = [];\n const deletedFiles: string[] = [];\n const errors: string[] = [];\n const prepared: Array<{\n entry: WorkspaceCheckpointEntry;\n output: string;\n content?: Buffer | undefined;\n }> = [];\n for (const entry of manifest.entries) {\n try {\n const output = await this.safeOutputPath(target, realTarget, entry.path);\n if (entry.state === 'symlink') {\n if (path.isAbsolute(entry.linkTarget)) {\n throw new Error('absolute symlink target refused');\n }\n const resolvedLink = path.resolve(path.dirname(output), entry.linkTarget);\n if (!isInside(target, resolvedLink)) throw new Error('symlink target escapes checkpoint root');\n }\n prepared.push({\n entry,\n output,\n ...(entry.state === 'file' ? { content: await this.readBlob(entry.blobHash) } : {}),\n });\n } catch (err) {\n errors.push(`${entry.path}: ${toErrorMessage(err)}`);\n }\n }\n // Integrity and path validation happen before the first mutation. This is\n // not a cross-file filesystem transaction, but corrupt input cannot leave\n // an otherwise clean target half-applied.\n if (errors.length > 0) {\n return { targetRoot: target, writtenFiles, deletedFiles, errors };\n }\n\n for (const { entry, output, content } of prepared) {\n try {\n if (entry.state === 'absent') {\n await fsp.unlink(output).catch((err) => {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;\n });\n deletedFiles.push(entry.path);\n continue;\n }\n if (entry.state === 'symlink') {\n await fsp.unlink(output).catch((err) => {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;\n });\n await fsp.mkdir(path.dirname(output), { recursive: true });\n await fsp.symlink(entry.linkTarget, output);\n writtenFiles.push(entry.path);\n continue;\n }\n if (!content) throw new Error('CAS blob was not prepared');\n await atomicWrite(output, content, { mode: entry.mode });\n await fsp.chmod(output, entry.mode).catch(() => undefined);\n writtenFiles.push(entry.path);\n } catch (err) {\n errors.push(`${entry.path}: ${toErrorMessage(err)}`);\n }\n }\n return { targetRoot: target, writtenFiles, deletedFiles, errors };\n }\n\n private objectPath(hash: string): string {\n if (!HASH_RE.test(hash)) throw new Error(`Invalid CAS object hash: ${hash}`);\n return path.join(this.rootDir, 'objects', hash.slice(0, 2), hash.slice(2));\n }\n\n private manifestPath(hash: string): string {\n if (!HASH_RE.test(hash)) throw new Error(`Invalid checkpoint manifest hash: ${hash}`);\n return path.join(this.rootDir, 'manifests', `${hash}.json`);\n }\n\n private async putBlob(hash: string, content: Buffer): Promise<void> {\n const target = this.objectPath(hash);\n await fsp.mkdir(path.dirname(target), { recursive: true });\n try {\n const existing = await fsp.readFile(target);\n if (sha256(existing) !== hash) throw new Error(`Corrupt CAS object collision: ${hash}`);\n return;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;\n }\n\n // Publish only a fully-written object. Creating the final path first would\n // expose a partial blob to another process (or leave one after a crash).\n // A same-directory hard link is an atomic create-if-absent operation.\n const temp = path.join(\n path.dirname(target),\n `.${path.basename(target)}.${process.pid}.${randomUUID()}.tmp`,\n );\n let handle: fsp.FileHandle | undefined;\n try {\n handle = await fsp.open(temp, 'wx', 0o600);\n await handle.writeFile(content);\n await handle.sync();\n await handle.close();\n handle = undefined;\n try {\n await fsp.link(temp, target);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;\n const existing = await fsp.readFile(target);\n if (sha256(existing) !== hash) throw new Error(`Corrupt CAS object collision: ${hash}`);\n }\n } finally {\n await handle?.close().catch(() => undefined);\n await fsp.unlink(temp).catch(() => undefined);\n }\n }\n\n private async putManifest(hash: string, encoded: string): Promise<void> {\n const target = this.manifestPath(hash);\n const existing = await fsp.readFile(target).catch(() => null);\n if (existing) {\n if (sha256(existing) !== hash) throw new Error(`Corrupt checkpoint manifest collision: ${hash}`);\n return;\n }\n await atomicWrite(target, encoded, { mode: 0o600 });\n }\n\n private async readBlob(hash: string): Promise<Buffer> {\n const content = await fsp.readFile(this.objectPath(hash));\n if (sha256(content) !== hash) throw new Error(`CAS blob integrity check failed: ${hash}`);\n return content;\n }\n\n private async loadManifest(hash: string): Promise<WorkspaceCheckpointManifest> {\n const raw = await fsp.readFile(this.manifestPath(hash));\n if (sha256(raw) !== hash) throw new Error(`Checkpoint manifest integrity check failed: ${hash}`);\n const parsed = JSON.parse(raw.toString('utf8')) as Partial<WorkspaceCheckpointManifest>;\n if (\n parsed.version !== 1 ||\n typeof parsed.baseHead !== 'string' ||\n parsed.coverage !== 'git-head-plus-dirty' ||\n !Array.isArray(parsed.entries) ||\n !Array.isArray(parsed.unresolved)\n ) {\n throw new Error(`Invalid workspace checkpoint manifest: ${hash}`);\n }\n const entriesValid = parsed.entries.every((entry) => {\n if (!entry || typeof entry !== 'object') return false;\n const candidate = entry as Partial<WorkspaceCheckpointEntry>;\n if (typeof candidate.path !== 'string' || !normalizeRelative(candidate.path)) return false;\n if (candidate.state === 'absent') return true;\n if (candidate.state === 'symlink') return typeof candidate.linkTarget === 'string';\n return (\n candidate.state === 'file' &&\n typeof candidate.blobHash === 'string' &&\n HASH_RE.test(candidate.blobHash) &&\n typeof candidate.mode === 'number' &&\n Number.isInteger(candidate.mode) &&\n candidate.mode >= 0 &&\n candidate.mode <= 0o777\n );\n });\n const unresolvedValid = parsed.unresolved.every(\n (entry) =>\n entry !== null &&\n typeof entry === 'object' &&\n typeof entry.path === 'string' &&\n typeof entry.reason === 'string',\n );\n if (!entriesValid || !unresolvedValid) {\n throw new Error(`Invalid workspace checkpoint manifest entries: ${hash}`);\n }\n return parsed as WorkspaceCheckpointManifest;\n }\n\n private async safeOutputPath(target: string, realTarget: string, relative: string): Promise<string> {\n const normalized = normalizeRelative(relative);\n if (!normalized) throw new Error('invalid relative path');\n const output = path.resolve(target, ...normalized.split('/'));\n if (!isInside(target, output)) throw new Error('path escapes checkpoint target');\n\n let probe = output;\n for (;;) {\n try {\n const real = await fsp.realpath(probe);\n if (!isInside(realTarget, real)) throw new Error('path resolves through a symlink outside checkpoint target');\n return output;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;\n const parent = path.dirname(probe);\n if (parent === probe) throw err;\n probe = parent;\n }\n }\n }\n}\n\nfunction defaultRunGit(args: string[], cwd: string): Promise<CheckpointGitResult> {\n return new Promise((resolve) => {\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let stdoutBytes = 0;\n let stderrBytes = 0;\n let stdoutTruncated = false;\n let stderrTruncated = false;\n const child = spawn('git', args, {\n cwd,\n env: buildChildEnv(),\n stdio: ['ignore', 'pipe', 'pipe'],\n signal: AbortSignal.timeout(30_000),\n windowsHide: true,\n });\n child.stdout?.on('data', (chunk: Buffer) => {\n const remaining = MAX_GIT_OUTPUT - stdoutBytes;\n if (remaining <= 0) {\n stdoutTruncated = true;\n return;\n }\n const kept = chunk.subarray(0, remaining);\n stdoutChunks.push(kept);\n stdoutBytes += kept.length;\n if (kept.length < chunk.length) stdoutTruncated = true;\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n const remaining = MAX_GIT_OUTPUT - stderrBytes;\n if (remaining <= 0) {\n stderrTruncated = true;\n return;\n }\n const kept = chunk.subarray(0, remaining);\n stderrChunks.push(kept);\n stderrBytes += kept.length;\n if (kept.length < chunk.length) stderrTruncated = true;\n });\n const result = (code: number, extraStderr?: string): CheckpointGitResult => ({\n code,\n stdout: Buffer.concat(stdoutChunks).toString('utf8'),\n stderr: `${Buffer.concat(stderrChunks).toString('utf8')}${extraStderr ?? ''}`,\n stdoutTruncated,\n stderrTruncated,\n });\n child.on('error', (err) => resolve(result(1, err.message)));\n child.on('close', (code) => resolve(result(code ?? 1)));\n });\n}\n", "export async function mapWithConcurrency<T, R>(\n items: readonly T[],\n concurrency: number,\n mapper: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n if (items.length === 0) return [];\n const limit = Math.max(1, Math.floor(concurrency));\n const results = new Array<R>(items.length);\n let next = 0;\n\n async function worker(): Promise<void> {\n while (true) {\n const index = next;\n next++;\n if (index >= items.length) return;\n results[index] = await mapper(items[index] as T, index);\n }\n }\n\n const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());\n await Promise.all(workers);\n return results;\n}\n", "import { createHash } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type {\n ResumeFileValidationEntry,\n ResumeValidation,\n SessionEvent,\n} from '../types/session.js';\nimport { toErrorMessage } from '../utils/index.js';\nimport { mapWithConcurrency } from './storage-concurrency.js';\n\nconst MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;\nconst VALIDATION_CONCURRENCY = 8;\nconst NOTICE_PATH_LIMIT = 20;\n\ninterface FileObservation {\n path: string;\n hash: string;\n ts: string;\n}\n\nfunction isInside(root: string, target: string): boolean {\n const relative = path.relative(root, target);\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction errno(err: unknown): string | undefined {\n return err && typeof err === 'object' && 'code' in err\n ? String((err as NodeJS.ErrnoException).code)\n : undefined;\n}\n\nfunction latestObservations(events: readonly SessionEvent[], projectRoot: string): FileObservation[] {\n const latest = new Map<string, FileObservation>();\n for (const event of events) {\n if (\n event.type !== 'file_observation' ||\n typeof event.path !== 'string' ||\n event.path.length === 0 ||\n typeof event.hash !== 'string' ||\n !/^[a-f\\d]{64}$/i.test(event.hash)\n ) {\n continue;\n }\n const normalized = path.resolve(projectRoot, event.path);\n latest.set(normalized, {\n path: normalized,\n hash: event.hash.toLowerCase(),\n ts: event.ts,\n });\n }\n return [...latest.values()];\n}\n\nasync function validateOne(\n observation: FileObservation,\n lexicalRoot: string,\n realRoot: string,\n): Promise<ResumeFileValidationEntry | null> {\n const base = {\n path: observation.path,\n observedAt: observation.ts,\n expectedHash: observation.hash,\n };\n\n if (!isInside(lexicalRoot, observation.path)) {\n return {\n ...base,\n status: 'outside_project',\n detail: 'Recorded path is outside the active project root.',\n };\n }\n\n let realFile: string;\n try {\n realFile = await fsp.realpath(observation.path);\n } catch (err) {\n if (errno(err) === 'ENOENT') return { ...base, status: 'deleted' };\n return { ...base, status: 'unreadable', detail: toErrorMessage(err) };\n }\n\n if (!isInside(realRoot, realFile)) {\n return {\n ...base,\n status: 'outside_project',\n detail: 'Recorded path resolves through a symlink outside the active project root.',\n };\n }\n\n try {\n const stat = await fsp.stat(realFile);\n if (!stat.isFile()) {\n return { ...base, status: 'unreadable', detail: 'Path is no longer a regular file.' };\n }\n if (stat.size > MAX_REVALIDATE_BYTES) {\n return {\n ...base,\n status: 'unreadable',\n detail: `File now exceeds the ${MAX_REVALIDATE_BYTES}-byte validation limit.`,\n };\n }\n // File tools persist sha256(UTF-8 text), so validation deliberately uses\n // the same decoding/hash semantics rather than hashing raw bytes.\n const content = await fsp.readFile(realFile, 'utf8');\n const actualHash = createHash('sha256').update(content, 'utf8').digest('hex');\n if (actualHash === observation.hash) return null;\n return { ...base, status: 'modified', actualHash };\n } catch (err) {\n if (errno(err) === 'ENOENT') return { ...base, status: 'deleted' };\n return { ...base, status: 'unreadable', detail: toErrorMessage(err) };\n }\n}\n\n/** Revalidate the latest persisted hash for every distinct observed path. */\nexport async function validateResumeFileObservations(\n events: readonly SessionEvent[],\n projectRoot: string,\n): Promise<ResumeValidation> {\n const lexicalRoot = path.resolve(projectRoot);\n const realRoot = await fsp.realpath(lexicalRoot).catch(() => lexicalRoot);\n const observations = latestObservations(events, lexicalRoot);\n const results = await mapWithConcurrency(\n observations,\n VALIDATION_CONCURRENCY,\n (observation) => validateOne(observation, lexicalRoot, realRoot),\n );\n return {\n checkedAt: new Date().toISOString(),\n checkedFileCount: observations.length,\n staleFiles: results.filter((entry): entry is ResumeFileValidationEntry => entry !== null),\n };\n}\n\n/** Build the ephemeral system message injected into the first resumed turn. */\nexport function formatResumeValidationNotice(\n validation: ResumeValidation,\n projectRoot: string,\n): string | null {\n if (validation.staleFiles.length === 0) return null;\n const root = path.resolve(projectRoot);\n const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {\n const relative = path.relative(root, entry.path);\n const display = isInside(root, entry.path) ? relative || '.' : entry.path;\n return `- ${JSON.stringify(display)} [${entry.status}]`;\n });\n const omitted = validation.staleFiles.length - shown.length;\n if (omitted > 0) shown.push(`- ... and ${omitted} more stale path(s)`);\n\n return [\n '[SESSION RESUME FILE VALIDATION]',\n `Journal hash validation found ${validation.staleFiles.length} previously observed file(s) whose current state cannot be trusted.`,\n 'Prior tool results and reasoning based on these paths are stale. Re-read each relevant file before relying on it or editing it.',\n ...shown,\n ].join('\\n');\n}\n", "/**\n * Session ID generation \u2014 extracted from session-store.ts.\n *\n * Pure functions: no I/O, no class state, no side effects.\n * Safe to unit-test in isolation.\n */\nimport { ulid } from '../utils/ulid.js';\n\n/**\n * @deprecated Legacy helper kept for callers that still need filename-safe\n * labels. New session ids are opaque and do not include model/provider names.\n */\nexport function sanitizeModel(model: string): string {\n return model\n .replace(/[^a-zA-Z0-9_-]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n .slice(0, 40);\n}\n\n/**\n * Generate a session ID in the format:\n * `YYYY-MM-DD/sess_<ULID>`\n *\n * Examples:\n * `2026-06-06/sess_01JX2S9V7T5M6N7P8Q9R0STXVW`\n *\n * The date prefix becomes a subdirectory so sessions group naturally by day.\n * The leaf is an opaque sortable id; provider/model names belong in metadata,\n * not file paths. Older IDs that contain model/provider text remain readable.\n */\nexport function generateSessionId(startedAt: string, _model?: string): string {\n const date = startedAt.slice(0, 10); // \"2026-06-06\"\n const seedTime = Number.isNaN(Date.parse(startedAt)) ? Date.now() : Date.parse(startedAt);\n return `${date}/sess_${ulid(seedTime)}`;\n}\n", "import type { SessionSummary } from '../types/session.js';\n\nexport interface SessionFilterCriteria {\n since?: string | undefined;\n until?: string | undefined;\n provider?: string | undefined;\n model?: string | undefined;\n minTokens?: number | undefined;\n titleContains?: string | undefined;\n}\n\nexport function compareSessionSummaries(a: SessionSummary, b: SessionSummary): number {\n if (a.startedAt < b.startedAt) return 1;\n if (a.startedAt > b.startedAt) return -1;\n return a.id.localeCompare(b.id);\n}\n\nexport function matchesSessionFilter(summary: SessionSummary, criteria: SessionFilterCriteria): boolean {\n if (criteria.since && summary.startedAt < criteria.since) return false;\n if (criteria.until && summary.startedAt > criteria.until) return false;\n if (criteria.provider && summary.provider !== criteria.provider) return false;\n if (criteria.model && summary.model !== criteria.model) return false;\n if (criteria.minTokens !== undefined && summary.tokenTotal < criteria.minTokens) return false;\n if (criteria.titleContains) {\n const needle = criteria.titleContains.toLocaleLowerCase();\n if (!summary.title.toLocaleLowerCase().includes(needle)) return false;\n }\n return true;\n}\n", "import type { SessionData, SessionEvent } from '../types/session.js';\n\ntype ToolCallEnd = SessionData['toolCallEnds'][number];\n\nexport function extractToolCallEnds(events: readonly SessionEvent[]): ToolCallEnd[] {\n const out: ToolCallEnd[] = [];\n for (const event of events) {\n if (event.type !== 'tool_call_end') continue;\n out.push({\n name: event.name,\n id: event.id,\n durationMs: event.durationMs,\n ok: event.ok ?? true,\n outputBytes: event.outputBytes ?? event.outputSize,\n outputTokens: event.outputTokens,\n outputLines: event.outputLines,\n });\n }\n return out;\n}\n", "import * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { EventBus } from '../kernel/events.js';\nimport type { ContentBlock } from '../types/blocks.js';\nimport { atomicWrite } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/error.js';\n\n/**\n * The persisted form of a single queued user message. The TUI's\n * in-memory QueueItem has a render id; that's pure UI bookkeeping, so\n * we drop it when serializing \u2014 fresh ids are assigned on rehydrate.\n */\nexport interface PersistedQueueItem {\n displayText: string;\n blocks: ContentBlock[];\n}\n\n/**\n * Side-file storage for a session's pending input queue. Lives at\n * `<sessionDir>/queue.json` next to the attachment spool. Reads are\n * tolerant (missing/malformed file \u2192 empty array); writes are atomic\n * (tmp + rename) so a crash mid-write can never leave a partial file\n * the next launch would choke on.\n *\n * The contract is \"snapshot replacement\": every mutation hands the\n * full queue and we rewrite the file. The queue is small (rarely more\n * than a handful of messages), so this is cheaper than delta logging\n * and avoids the replay complexity.\n */\nexport class QueueStore {\n private readonly file: string;\n // Use `| undefined` (not `?`) so exactOptionalPropertyTypes doesn't\n // reject assigning an optional constructor parameter to these fields.\n private readonly events: EventBus | undefined;\n private readonly traceId: string | undefined;\n\n constructor(opts: { dir: string; events?: EventBus; traceId?: string }) {\n this.file = path.join(opts.dir, 'queue.json');\n this.events = opts.events;\n this.traceId = opts.traceId;\n }\n\n async write(items: PersistedQueueItem[]): Promise<void> {\n const t0 = Date.now();\n if (items.length === 0) {\n // Empty queue \u2192 remove the file rather than write `[]`. Keeps\n // a clean idle state on disk and makes `read()` cheaper.\n await this.clear();\n return;\n }\n try {\n await atomicWrite(this.file, JSON.stringify(items), { mode: 0o600 });\n this.events?.emit('storage.write', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'write',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n } catch (err) {\n this.events?.emit('storage.error', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'write',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: false,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'queue_store.write_failed',\n path: this.file,\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }));\n }\n }\n\n async read(): Promise<PersistedQueueItem[]> {\n const t0 = Date.now();\n let raw: string;\n try {\n raw = await fsp.readFile(this.file, 'utf8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') {\n this.events?.emit('storage.read', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'read',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n return [];\n }\n this.events?.emit('storage.error', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'read',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: true,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'queue_store.read_failed',\n path: this.file,\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }));\n return [];\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n this.events?.emit('storage.read', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'read',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'parse_failed',\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n return [];\n }\n if (!Array.isArray(parsed)) {\n this.events?.emit('storage.read', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'read',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'invalid_schema',\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n return [];\n }\n this.events?.emit('storage.read', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'read',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n const out: PersistedQueueItem[] = [];\n for (const v of parsed) {\n if (isPersistedQueueItem(v)) out.push(v);\n }\n return out;\n }\n\n async clear(): Promise<void> {\n const t0 = Date.now();\n try {\n await fsp.unlink(this.file);\n this.events?.emit('storage.write', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'clear',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') return;\n this.events?.emit('storage.error', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'clear',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: true,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n // Best-effort: a permission/lock error during clear is rare and\n // the queue slash command is non-critical. Warn so it's observable\n // but don't throw so the slash command doesn't crash.\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'queue_store.clear_failed',\n path: this.file,\n message: (err as Error).message,\n timestamp: new Date().toISOString(),\n }));\n }\n }\n}\n\nfunction isPersistedQueueItem(v: unknown): v is PersistedQueueItem {\n if (typeof v !== 'object' || v === null) return false;\n const o = v as Record<string, unknown>;\n return typeof o['displayText'] === 'string' && Array.isArray(o['blocks']);\n}\n", "import { randomBytes } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { atomicWrite } from '../utils/atomic-write.js';\nimport type {\n AddAttachmentInput,\n Attachment,\n AttachmentKind,\n AttachmentRef,\n AttachmentStore,\n} from '../types/attachment.js';\nimport type { ContentBlock } from '../types/blocks.js';\n\nexport interface AttachmentStoreOptions {\n /**\n * Directory for spooling payloads larger than `spoolThresholdBytes`.\n * When omitted, all payloads stay in memory.\n */\n spoolDir?: string | undefined;\n spoolThresholdBytes?: number | undefined;\n}\n\nconst DEFAULT_SPOOL_THRESHOLD = 256 * 1024; // 256 KB\n// Two placeholder shapes:\n// - seq-keyed `[<kind> #<seq>\u2026]` \u2014 kind is `pasted` / `image` / `file`. A\n// cosmetic suffix after the seq (e.g. `, 123 lines`) is tolerated so the\n// TUI can render `[pasted #1, 123 lines]` while still resolving by seq.\n// - path-keyed `[file:<path>]` \u2014 resolves to the most recent file ref whose\n// stored path matches, so the TUI can show a human-readable file chip.\nconst PLACEHOLDER_RE = /\\[(pasted|image|file) #(\\d+)[^\\]]*\\]|\\[file:([^\\]]+)\\]/g;\n\n/**\n * In-memory attachment store with optional disk spool. Placeholder syntax\n * is `[<kind> #<seq>]` (seq-keyed) or `[file:<path>]` (path-keyed) where kind\n * is `pasted` / `image` / `file`. Unknown placeholders are passed through\n * as-is so users can write that literal text without losing it.\n */\nexport class DefaultAttachmentStore implements AttachmentStore {\n private readonly items = new Map<string, Attachment>();\n private readonly refs: AttachmentRef[] = [];\n private nextSeq: Record<AttachmentKind, number> = { text: 0, image: 0, file: 0 };\n private readonly spoolDir: string | undefined;\n private readonly spoolThreshold: number;\n\n constructor(opts: AttachmentStoreOptions = {}) {\n this.spoolDir = opts.spoolDir;\n this.spoolThreshold = opts.spoolThresholdBytes ?? DEFAULT_SPOOL_THRESHOLD;\n }\n\n async add(input: AddAttachmentInput): Promise<AttachmentRef> {\n const seq = ++this.nextSeq[input.kind];\n const id = `${kindPrefix(input.kind)}-${seq}-${randomBytes(3).toString('hex')}`;\n const bytes = Buffer.byteLength(input.data, input.kind === 'image' ? 'base64' : 'utf8');\n let spooledPath: string | undefined;\n let data: string | undefined = input.data;\n if (this.spoolDir && bytes >= this.spoolThreshold) {\n await fsp.mkdir(this.spoolDir, { recursive: true });\n spooledPath = path.join(this.spoolDir, `${id}.bin`);\n // atomicWrite: torn spool would silently corrupt the attachment;\n // the user would see garbled output the next time it's expanded.\n await atomicWrite(spooledPath, input.data, {\n encoding: input.kind === 'image' ? 'base64' : 'utf8',\n });\n data = undefined;\n }\n const att: Attachment = {\n id,\n kind: input.kind,\n meta: input.meta ?? {},\n data,\n path: spooledPath,\n bytes,\n createdAt: new Date().toISOString(),\n };\n this.items.set(id, att);\n const ref: AttachmentRef = { id, kind: input.kind, seq, meta: att.meta };\n this.refs.push(ref);\n return ref;\n }\n\n async get(id: string): Promise<Attachment | undefined> {\n return this.items.get(id);\n }\n\n list(): AttachmentRef[] {\n return [...this.refs];\n }\n\n async expand(text: string): Promise<ContentBlock[]> {\n const matches = [...text.matchAll(PLACEHOLDER_RE)];\n if (matches.length === 0) return text ? [{ type: 'text', text }] : [];\n const blocks: ContentBlock[] = [];\n let lastIndex = 0;\n for (const m of matches) {\n const idx = m.index ?? 0;\n const before = text.slice(lastIndex, idx);\n if (before) blocks.push({ type: 'text', text: before });\n let ref: AttachmentRef | undefined;\n if (m[3] !== undefined) {\n // Path-keyed `[file:<path>]` \u2014 most recent matching file ref wins.\n const wantPath = m[3];\n ref = findLast(this.refs, (r) => r.kind === 'file' && refPath(r) === wantPath);\n } else {\n const kind = prefixToKind(m[1] as string);\n const seq = Number(m[2]);\n ref = this.refs.find((r) => r.kind === kind && r.seq === seq);\n }\n const att = ref ? this.items.get(ref.id) : undefined;\n if (!att) {\n blocks.push({ type: 'text', text: m[0] });\n } else {\n blocks.push(await this.toBlock(att));\n }\n lastIndex = idx + m[0].length;\n }\n const tail = text.slice(lastIndex);\n if (tail) blocks.push({ type: 'text', text: tail });\n return mergeAdjacentText(blocks);\n }\n\n async clear(): Promise<void> {\n // Unlink any spooled files so we don't leak disk space.\n if (this.spoolDir) {\n const toDelete: string[] = [];\n for (const att of this.items.values()) {\n if (att.path) toDelete.push(att.path);\n }\n /* v8 ignore next -- best-effort: unlink of a just-spooled file does not reject */\n await Promise.all(toDelete.map((p) => fsp.unlink(p).catch(() => undefined)));\n }\n this.items.clear();\n this.refs.length = 0;\n this.nextSeq = { text: 0, image: 0, file: 0 };\n }\n\n private async toBlock(att: Attachment): Promise<ContentBlock> {\n if (att.kind === 'image') {\n const data =\n att.data ?? (att.path ? await fsp.readFile(att.path, { encoding: 'base64' }) : '');\n return {\n type: 'image',\n source: {\n type: 'base64',\n media_type: att.meta.mediaType ?? 'image/png',\n data,\n },\n };\n }\n const raw = att.data ?? (att.path ? await fsp.readFile(att.path, 'utf8') : '');\n const label = att.meta.filename ? `<file path=\"${att.meta.filename}\">` : '<pasted>';\n const close = att.meta.filename ? '</file>' : '</pasted>';\n return { type: 'text', text: `${label}\\n${raw}\\n${close}` };\n }\n}\n\nfunction kindPrefix(kind: AttachmentKind): string {\n return kind === 'text' ? 'pasted' : kind;\n}\n\nfunction prefixToKind(prefix: string): AttachmentKind {\n if (prefix === 'pasted') return 'text';\n if (prefix === 'image') return 'image';\n return 'file';\n}\n\n/** Path a file ref was registered under, for `[file:<path>]` lookup. */\nfunction refPath(ref: AttachmentRef): string | undefined {\n return ref.meta.filename ?? ref.meta.label;\n}\n\n/** Last element matching the predicate (Node < 20 lacks Array.findLast). */\nfunction findLast<T>(arr: readonly T[], pred: (v: T) => boolean): T | undefined {\n for (let i = arr.length - 1; i >= 0; i--) {\n if (pred(arr[i] as T)) return arr[i];\n }\n return undefined;\n}\n\nfunction mergeAdjacentText(blocks: ContentBlock[]): ContentBlock[] {\n const out: ContentBlock[] = [];\n for (const b of blocks) {\n const prev = out[out.length - 1];\n if (b.type === 'text' && prev && prev.type === 'text') {\n prev.text += b.text;\n } else {\n out.push(b);\n }\n }\n return out;\n}\n", "import { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { MemoryEntry, MemoryScope } from '../types/memory.js';\nimport { MEMORY_TYPE_LABELS, type MemoryPriority, type MemoryType } from '../types/memory.js';\nimport { atomicWrite, ensureDir, withFileLock } from '../utils/atomic-write.js';\nimport type { WstackPaths } from '../utils/wstack-paths.js';\n\n// \u2500\u2500 Backend interface \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface MemoryBackend {\n readonly kind: string;\n remember(scope: MemoryScope, entry: MemoryEntry, filePath: string): Promise<void>;\n forget(scope: MemoryScope, query: string, filePath: string): Promise<number>;\n readAll(scope: MemoryScope, filePath: string): Promise<string>;\n list(scope: MemoryScope, filePath: string, limit?: number | undefined): Promise<MemoryEntry[]>;\n search(scope: MemoryScope, query: string, filePath: string, limit?: number | undefined): Promise<MemoryEntry[]>;\n /** Find memories related to the given text via graph traversal. Optional \u2014 falls back to search. */\n findRelated?(scope: MemoryScope, filePath: string, text: string, limit: number): Promise<MemoryEntry[]>;\n clear(scope: MemoryScope, filePath: string): Promise<void>;\n consolidate(scope: MemoryScope, filePath: string): Promise<number>;\n}\n\n// \u2500\u2500 Entry serialization format \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Full format:\n// - [ISO] [TYPE|PRIORITY] mem_<id> text content #tag1 #tag2\n//\n// Examples:\n// - [2026-06-07T...] mem_1234_abcd Project uses pnpm #pnpm #build\n// - [2026-06-07T...] [convention|high] mem_5678_ef01 Use conventional commits #git #commit\n//\n// Old format (backward compatible):\n// - [ISO] mem_<id> text content\n\nconst TYPE_PRIORITY_RE = /^\\[(\\w+)\\|(\\w+)\\]\\s+/;\nconst TAG_RE = /#([\\w-]+)/g;\nconst MAX_MEMORY_CONSOLIDATE_BACKUPS = 5;\n\nfunction formatMetadata(entry: MemoryEntry): string {\n const parts: string[] = [];\n if (entry.type && entry.priority) {\n parts.push(`[${entry.type}|${entry.priority}]`);\n } else if (entry.type) {\n parts.push(`[${entry.type}]`);\n } else if (entry.priority) {\n parts.push(`[${entry.priority}]`);\n }\n if (entry.tags && entry.tags.length > 0) {\n parts.push(entry.tags.map((t) => `#${t}`).join(' '));\n }\n return parts.length > 0 ? ` ${parts.join(' ')}` : '';\n}\n\nfunction lineToEntry(line: string, scope: MemoryScope): MemoryEntry | null {\n const trimmed = line.trim();\n if (!trimmed.startsWith('- [')) return null;\n\n // Parse timestamp: `- [ISO] ...`\n const tsMatch = trimmed.match(/^-\\s*\\[([^\\]]+)\\]/);\n if (!tsMatch) return null;\n const ts = tsMatch[1] ?? '';\n\n let rest = trimmed.slice(tsMatch[0].length).trim();\n\n // Parse optional type|priority: `[convention|high]` or `[fact]`\n let type: MemoryType | undefined;\n let priority: MemoryPriority | undefined;\n const tpMatch = rest.match(TYPE_PRIORITY_RE);\n if (tpMatch) {\n const a = tpMatch[1] ?? '';\n const b = tpMatch[2] ?? '';\n if (isMemoryType(a)) {\n type = a;\n priority = isPriority(b) ? b : undefined;\n } else if (isPriority(a)) {\n priority = a;\n }\n rest = rest.slice(tpMatch[0].length).trim();\n }\n\n // Parse optional entry ID: `mem_<ts>_<rand>`\n const idMatch = rest.match(/^mem_\\d+_\\w+\\s+/);\n let text: string;\n if (idMatch) {\n text = rest.slice(idMatch[0].length).trim();\n } else {\n text = rest.trim();\n }\n\n // Extract #tags from text\n const tags: string[] = [];\n let tagMatch: RegExpExecArray | null;\n TAG_RE.lastIndex = 0;\n // biome-ignore lint/suspicious/noAssignInExpressions: standard regex loop\n while ((tagMatch = TAG_RE.exec(text)) !== null) {\n tags.push(tagMatch[1] ?? '');\n }\n // Remove tags from display text\n const cleanText = text.replace(TAG_RE, '').replace(/\\s{2,}/g, ' ').trim();\n\n if (!cleanText) return null;\n\n return {\n scope,\n text: cleanText,\n ts,\n type,\n priority,\n tags: tags.length > 0 ? tags : undefined,\n };\n}\n\nfunction isMemoryType(s: string): s is MemoryType {\n return s in MEMORY_TYPE_LABELS;\n}\n\nfunction isPriority(s: string): s is MemoryPriority {\n return s === 'critical' || s === 'high' || s === 'medium' || s === 'low';\n}\n\n// \u2500\u2500 Inverted index for fast search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface IndexedEntry {\n entry: MemoryEntry;\n /** Lower-cased words extracted from text. */\n words: string[];\n /** Lower-cased tags. */\n tags: string[];\n}\n\ninterface InvertedIndex {\n /** word -> entry indices */\n wordMap: Map<string, number[]>;\n /** tag -> entry indices */\n tagMap: Map<string, number[]>;\n entries: IndexedEntry[];\n /** Last known file mtime for cache invalidation. */\n mtimeMs: number;\n}\n\n// Exported for the perf microbenchmark (tests/perf/memory-search.bench.ts),\n// which exercises the O(1) exact-lookup fast path against a large vocabulary.\n// Not part of the public API surface \u2014 internal to memory search.\nexport function buildInvertedIndex(entries: MemoryEntry[]): InvertedIndex {\n const wordMap = new Map<string, number[]>();\n const tagMap = new Map<string, number[]>();\n const indexed: IndexedEntry[] = new Array(entries.length);\n\n for (let i = 0; i < entries.length; i++) {\n const e = entries[i]!;\n const words = e.text.toLowerCase().split(/\\s+/).filter((w) => w.length > 0);\n const tags = (e.tags ?? []).map((t) => t.toLowerCase());\n indexed[i] = { entry: e, words, tags };\n\n for (const w of words) {\n const arr = wordMap.get(w);\n if (arr) arr.push(i);\n else wordMap.set(w, [i]);\n }\n for (const t of tags) {\n const arr = tagMap.get(t);\n if (arr) arr.push(i);\n else tagMap.set(t, [i]);\n }\n }\n\n return { wordMap, tagMap, entries: indexed, mtimeMs: 0 };\n}\n\n/**\n * Needles shorter than this skip the O(vocabulary) substring fallback. A\n * 1\u20132 char needle (e.g. \"a\", \"go\") otherwise forces a full-vocabulary walk\n * AND matches almost everything via `n.includes(word)`, so it's both the\n * slowest and least selective case. Exact-match lookups still run for any\n * length, so short whole-word queries (\"go\", \"ci\") keep working.\n */\nconst MIN_SUBSTRING_NEEDLE_LEN = 3;\n\n// Exported alongside buildInvertedIndex for the perf microbenchmark.\nexport function searchIndex(\n index: InvertedIndex,\n query: string,\n limit?: number,\n): MemoryEntry[] {\n const needles = query.toLowerCase().split(/\\s+/).filter((n) => n.length > 0);\n if (needles.length === 0) return [];\n\n const scores = new Map<number, number>();\n\n for (const n of needles) {\n // Fast path: exact word/tag hit via O(1) Map.get. This is the dominant\n // case (whole-word queries) and avoids walking the whole vocabulary.\n let matched = false;\n\n const wordExact = index.wordMap.get(n);\n if (wordExact) {\n for (const idx of wordExact) scores.set(idx, (scores.get(idx) ?? 0) + 1);\n matched = true;\n }\n const tagExact = index.tagMap.get(n);\n if (tagExact) {\n for (const idx of tagExact) scores.set(idx, (scores.get(idx) ?? 0) + 2);\n matched = true;\n }\n\n // Bounded substring fallback: only when the needle had no exact hit and is\n // long enough to be selective. Preserves partial-match recall (\"perf\" \u2192\n // \"performance\", \"rebuilding\" \u2192 stored word \"build\") without paying the\n // full-vocabulary scan on every whole-word query.\n if (matched || n.length < MIN_SUBSTRING_NEEDLE_LEN) continue;\n\n for (const [word, indices] of index.wordMap) {\n if (word.includes(n) || n.includes(word)) {\n for (const idx of indices) {\n scores.set(idx, (scores.get(idx) ?? 0) + 1);\n }\n }\n }\n for (const [tag, indices] of index.tagMap) {\n if (tag.includes(n) || n.includes(tag)) {\n for (const idx of indices) {\n scores.set(idx, (scores.get(idx) ?? 0) + 2);\n }\n }\n }\n }\n\n if (scores.size === 0) return [];\n\n const scored = Array.from(scores.entries());\n scored.sort((a, b) => b[1] - a[1]);\n\n const result: MemoryEntry[] = [];\n const max = limit ? Math.min(limit, scored.length) : scored.length;\n for (let i = 0; i < max; i++) {\n result.push(index.entries[scored[i]![0]]!.entry);\n }\n return result;\n}\n\n// \u2500\u2500 File-based backend \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface FileMemoryBackendOptions {\n paths: WstackPaths;\n}\n\nexport class FileMemoryBackend implements MemoryBackend {\n readonly kind = 'file';\n private readonly files: Record<MemoryScope, string>;\n /** Cache of parsed entries per file path. */\n private readonly entryCache = new Map<string, MemoryEntry[]>();\n /** Inverted index per file path. */\n private readonly indexCache = new Map<string, InvertedIndex>();\n /** File mtime cache for invalidation. */\n private readonly mtimeCache = new Map<string, number>();\n\n constructor(opts: FileMemoryBackendOptions) {\n this.files = {\n 'project-agents': opts.paths.inProjectAgentsFile,\n 'project-memory': opts.paths.projectMemory,\n 'user-memory': opts.paths.globalMemory,\n };\n }\n\n private resolveFile(filePath: string, scope: MemoryScope): string {\n return filePath || this.files[scope];\n }\n\n private async getMtime(file: string): Promise<number> {\n try {\n const stat = await fs.stat(file);\n return stat.mtimeMs;\n } catch {\n return 0;\n }\n }\n\n private invalidateCache(file: string): void {\n this.entryCache.delete(file);\n this.indexCache.delete(file);\n this.mtimeCache.delete(file);\n }\n\n /**\n * Load (and cache) the parsed entries for a file. Callers that have already\n * stat'd the file this tick (e.g. `getIndex`) can pass the known `mtime` to\n * avoid a redundant `fs.stat` \u2014 otherwise it's fetched here.\n */\n private async loadEntries(\n file: string,\n scope: MemoryScope,\n mtime?: number,\n ): Promise<MemoryEntry[]> {\n const resolvedMtime = mtime ?? (await this.getMtime(file));\n const cachedMtime = this.mtimeCache.get(file);\n if (cachedMtime === resolvedMtime && this.entryCache.has(file)) {\n return this.entryCache.get(file)!;\n }\n\n const raw = await this.readAll(scope, file);\n if (!raw.trim()) {\n this.entryCache.set(file, []);\n this.mtimeCache.set(file, resolvedMtime);\n return [];\n }\n\n const entries = parseEntries(raw, scope);\n this.entryCache.set(file, entries);\n this.mtimeCache.set(file, resolvedMtime);\n return entries;\n }\n\n private async getIndex(file: string, scope: MemoryScope): Promise<InvertedIndex> {\n const mtime = await this.getMtime(file);\n const cached = this.indexCache.get(file);\n if (cached && cached.mtimeMs === mtime) {\n return cached;\n }\n\n // Reuse the mtime we just fetched \u2014 loadEntries would otherwise stat again.\n const entries = await this.loadEntries(file, scope, mtime);\n const index = buildInvertedIndex(entries);\n index.mtimeMs = mtime;\n this.indexCache.set(file, index);\n return index;\n }\n\n async remember(scope: MemoryScope, entry: MemoryEntry, filePath: string): Promise<void> {\n const file = this.resolveFile(filePath, scope);\n await ensureDir(path.dirname(file));\n let existing = '';\n try { existing = await fs.readFile(file, 'utf8'); } catch { /* new file */ }\n\n const id = `mem_${Date.now()}_${randomUUID().slice(0, 8)}`;\n const meta = formatMetadata(entry);\n const line = `\\n- [${entry.ts}] ${id}${meta} ${entry.text.replace(/\\n/g, ' ')}\\n`;\n const next = existing.trim()\n ? existing.replace(/\\n+$/, '') + line\n : `# Agent Memory\\n${line}`;\n await atomicWrite(file, next);\n this.invalidateCache(file);\n }\n\n async forget(scope: MemoryScope, query: string, filePath: string): Promise<number> {\n const file = this.resolveFile(filePath, scope);\n return withFileLock(file, async () => {\n let existing: string;\n try { existing = await fs.readFile(file, 'utf8'); } catch { return 0; /* best-effort */ }\n\n const needle = query.toLowerCase();\n const idMatcher = /mem_\\d+_\\w+/;\n let removed = 0;\n const lines = existing.split('\\n').filter((line) => {\n const trimmed = line.trim();\n if (!trimmed.startsWith('- ')) return true;\n if (idMatcher.test(query)) {\n const entryIdMatch = /mem_\\d+_\\w+/.exec(trimmed);\n if (entryIdMatch && entryIdMatch[0] === query) { removed++; return false; }\n }\n if (trimmed.toLowerCase().includes(needle)) { removed++; return false; }\n return true;\n });\n if (removed > 0) {\n if (lines.length === 0 || (lines.length === 1 && !lines[0]?.trim())) {\n await atomicWrite(file, '');\n } else {\n await atomicWrite(file, lines.join('\\n'));\n }\n }\n this.invalidateCache(file);\n return removed;\n });\n }\n\n async readAll(scope: MemoryScope, filePath: string): Promise<string> {\n const file = this.resolveFile(filePath, scope);\n try { return await fs.readFile(file, 'utf8'); } catch { return ''; /* best-effort */ }\n }\n\n async list(scope: MemoryScope, filePath: string, limit?: number): Promise<MemoryEntry[]> {\n const file = this.resolveFile(filePath, scope);\n const entries = await this.loadEntries(file, scope);\n return limit ? entries.slice(0, limit) : entries;\n }\n\n async search(scope: MemoryScope, query: string, filePath: string, limit?: number): Promise<MemoryEntry[]> {\n const file = this.resolveFile(filePath, scope);\n const index = await this.getIndex(file, scope);\n return searchIndex(index, query, limit);\n }\n\n async clear(scope: MemoryScope, filePath: string): Promise<void> {\n const file = this.resolveFile(filePath, scope);\n await atomicWrite(file, '');\n this.invalidateCache(file);\n }\n\n async consolidate(scope: MemoryScope, filePath: string): Promise<number> {\n const file = this.resolveFile(filePath, scope);\n let existing: string;\n try { existing = await fs.readFile(file, 'utf8'); } catch { return 0; /* best-effort */ }\n\n const seen = new Set<string>();\n let removed = 0;\n const lines = existing.split('\\n').filter((line) => {\n const trimmed = line.trim();\n if (!trimmed.startsWith('- ')) return true;\n // Normalize: strip timestamp, ID, type|priority, tags\n const norm = trimmed\n .replace(/\\[[^\\]]+\\]/g, '')\n .replace(/\\bmem_\\d+_\\w+\\s*/, '')\n .replace(/#[\\w-]+/g, '')\n .replace(/\\s+/g, ' ')\n .trim()\n .toLowerCase();\n if (seen.has(norm)) { removed++; return false; }\n seen.add(norm);\n return true;\n });\n\n const next = lines.join('\\n');\n const backup = `${file}.bak.${Date.now()}`;\n try {\n await fs.copyFile(file, backup);\n await pruneConsolidateBackups(file);\n } catch { /* best-effort */ }\n /* v8 ignore next -- best-effort: atomicWrite failure during consolidate is non-fatal */\n try { await atomicWrite(file, next); } catch { return 0; /* best-effort */ }\n this.invalidateCache(file);\n return removed;\n }\n}\n\nasync function pruneConsolidateBackups(file: string): Promise<void> {\n const dir = path.dirname(file);\n const base = path.basename(file);\n const prefix = `${base}.bak.`;\n const backups = (await fs.readdir(dir))\n .filter((name) => name.startsWith(prefix))\n .sort()\n .reverse();\n\n await Promise.all(\n backups.slice(MAX_MEMORY_CONSOLIDATE_BACKUPS).map(async (name) => {\n try {\n await fs.unlink(path.join(dir, name));\n } catch {\n // best-effort\n }\n }),\n );\n}\n\n// \u2500\u2500 Entry parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function parseEntries(raw: string, scope: MemoryScope = 'project-memory'): MemoryEntry[] {\n const entries: MemoryEntry[] = [];\n for (const line of raw.split('\\n')) {\n const entry = lineToEntry(line, scope);\n if (entry) entries.push(entry);\n }\n return entries.reverse(); // newest first\n}\n", "export type MemoryScope = 'project-agents' | 'project-memory' | 'user-memory';\n\n// \u2500\u2500 Memory categories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type MemoryType = 'fact' | 'decision' | 'convention' | 'preference' | 'reference' | 'anti_pattern';\n\nexport const MEMORY_TYPE_LABELS: Record<MemoryType, string> = {\n fact: 'Fact',\n decision: 'Decision',\n convention: 'Convention',\n preference: 'Preference',\n reference: 'Reference',\n anti_pattern: 'Anti-pattern',\n};\n\nexport type MemoryPriority = 'critical' | 'high' | 'medium' | 'low';\n\nexport interface MemoryEntry {\n scope: MemoryScope;\n text: string;\n ts: string;\n /** Category \u2014 helps the agent decide whether to inject or ignore. */\n type?: MemoryType | undefined;\n /** Free-form tags for grouping (e.g. [\"build\", \"pnpm\", \"typescript\"]). */\n tags?: string[] | undefined;\n /** Priority \u2014 critical entries are always injected; low may be skipped. */\n priority?: MemoryPriority | undefined;\n /** Session or agent that created this entry. */\n source?: string | undefined;\n /** 0.0\u20131.0 confidence. Low-confidence entries are injected less often. */\n confidence?: number | undefined;\n /** ISO timestamp of last access (read or injection into context). */\n lastAccessed?: string | undefined;\n}\n\n// \u2500\u2500 Memory events \u2014 emitted by DefaultMemoryStore so plugins can react \u2500\u2500\n\nexport interface MemoryRememberedPayload {\n scope: MemoryScope;\n text: string;\n ts: string;\n type?: MemoryType | undefined;\n tags?: string[] | undefined;\n priority?: MemoryPriority | undefined;\n}\n\nexport interface MemoryForgottenPayload {\n scope: MemoryScope;\n query: string;\n removed: number;\n}\n\nexport interface MemoryClearedPayload {\n /** Scope that was cleared, or undefined when all scopes were cleared. */\n scope?: MemoryScope | undefined;\n}\n\nexport interface MemoryConsolidatedPayload {\n scope: MemoryScope;\n /** Entries removed by deduplication. */\n removed: number;\n}\n\n// \u2500\u2500 Relevance scoring \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Context used to score memory relevance for context injection.\n * Passed by the system prompt builder.\n */\nexport interface MemoryRelevanceContext {\n /** Current user message or task description. */\n currentTask: string;\n /** Active skills in this session (e.g. [\"typescript-strict\", \"git-flow\"]). */\n activeSkills?: string[] | undefined;\n /** Active mode (e.g. \"Teach\", \"Brief\", \"Code Reviewer\"). */\n activeMode?: string | undefined;\n /** Available tools \u2014 memories referencing relevant tools score higher. */\n toolNames?: string[] | undefined;\n}\n\nexport interface ScoredEntry extends MemoryEntry {\n score: number;\n matchReason: string;\n}\n\n// \u2500\u2500 Store interface \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface MemoryStore {\n readAll(): Promise<string>;\n read(scope: MemoryScope): Promise<string>;\n remember(text: string, scope?: MemoryScope, metadata?: Omit<Partial<MemoryEntry>, 'scope' | 'text' | 'ts'>): Promise<void>;\n forget(query: string, scope?: MemoryScope): Promise<number>;\n consolidate(scope: MemoryScope): Promise<void>;\n clear(scope?: MemoryScope): Promise<void>;\n /** List entries, newest first. */\n list(scope?: MemoryScope, limit?: number): Promise<MemoryEntry[]>;\n /** Search by content (substring or semantic). */\n search(query: string, scope?: MemoryScope, limit?: number): Promise<MemoryEntry[]>;\n /** Access the backend for advanced queries. */\n getBackend?(): unknown;\n /** Graph-based related memory traversal. */\n findRelated?(text: string, scope?: MemoryScope, limit?: number): Promise<MemoryEntry[]>;\n /**\n * Score and rank memories by relevance to the current context.\n * Returns only entries that meet a relevance threshold.\n */\n scoreRelevant?(ctx: MemoryRelevanceContext, scope?: MemoryScope, limit?: number): Promise<ScoredEntry[]>;\n /**\n * Attach a trace ID to this store so that all subsequent `storage.*`\n * events include it for observability correlation. Mutates the store\n * in place and returns the same instance (convenience chaining).\n */\n withTraceId(traceId: string): MemoryStore;\n}\n", "import type {\n MemoryClearedPayload,\n MemoryConsolidatedPayload,\n MemoryEntry,\n MemoryForgottenPayload,\n MemoryRelevanceContext,\n MemoryRememberedPayload,\n MemoryScope,\n MemoryStore,\n ScoredEntry,\n} from '../types/memory.js';\nimport type { EventBus } from '../kernel/events.js';\nimport type { WstackPaths } from '../utils/wstack-paths.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport { type MemoryBackend, FileMemoryBackend } from './memory-backend.js';\n\nconst MAX_BYTES_TOTAL = 32_000; // ~8K tokens\n\nexport interface MemoryStoreOptions {\n paths: WstackPaths;\n /**\n * Optional event bus \u2014 when provided, mutations emit events so plugins\n * and other subsystems can react to memory changes.\n */\n events?: EventBus | undefined;\n /**\n * Storage backend. Defaults to FileMemoryBackend when omitted.\n * Plugins can register a custom backend (graph, semantic, vector)\n * via the DI container to override storage behavior.\n */\n backend?: MemoryBackend | undefined;\n}\n\n/**\n * Three scopes:\n * project-agents \u2192 <project>/.wrongstack/AGENTS.md (committed)\n * project-memory \u2192 ~/.wrongstack/projects/<hash>/memory.md (per-project agent notes)\n * user-memory \u2192 ~/.wrongstack/memory.md (global personal memory)\n */\nexport class DefaultMemoryStore implements MemoryStore {\n private readonly files: Record<MemoryScope, string>;\n private readonly events?: EventBus | undefined;\n private traceId?: string | undefined;\n private readonly backend: MemoryBackend;\n\n /**\n * Per-scope serialization queue. `remember` / `forget` / `consolidate` /\n * `clear` are read-modify-write against a single file; without a lock,\n * two concurrent calls on the same scope can read the same baseline and\n * the later write silently drops the earlier entry. We chain each\n * mutation onto the prior promise for the same scope so they run in\n * issue order. Different scopes still proceed in parallel.\n *\n * The chain tracks only the last pending write. If a write fails, its\n * error is caught and swallowed so the chain stays alive for subsequent\n * calls. The error is stored in `writeErrors` so callers can learn about\n * it on the next read operation.\n */\n private readonly writeChain = new Map<MemoryScope, Promise<unknown>>();\n /** Last write error per scope \u2014 surfaced as warnings on the next readAll(). */\n private readonly writeErrors = new Map<MemoryScope, Error>();\n\n /**\n * When the global root is a temporary directory (opencode, CI sandboxes),\n * memory files are also mirrored to the project tree so they survive\n * session cleanup. The primary path stays in the temp root for isolation;\n * this backup ensures memory is never lost.\n */\n private readonly persistBackup: boolean;\n private readonly backupDir: string;\n /**\n * Per-scope tracked byte sizes \u2014 incremented on `remember()`, decremented on\n * `forget()`, recalculated after `consolidate()`. Eliminates the redundant\n * readAll() call that previously checked the file size after every write.\n */\n private readonly _trackedByteSizes: Partial<Record<MemoryScope, number>> = {};\n\n /** Result cache for scoreRelevant() \u2014 keyed by scope + context hash, TTL 30s. */\n private readonly _scoreCache = new Map<string, { entries: MemoryEntry[]; scored: ScoredEntry[]; expiresAt: number }>();\n\n /**\n * Per-entry cached lowercase strings \u2014 lazily allocated once and reused\n * across scoreRelevant() calls so repeated scoring of the same entries skips\n * re-lowercasing. Keyed by entry object identity; cleared (set to null) on\n * every mutation (remember/forget/consolidate/clear) via invalidateScoreCaches().\n */\n private _cachedLower: WeakMap<MemoryEntry, { textLower: string; tagsLower: string[] }> | null = null;\n\n constructor(opts: MemoryStoreOptions) {\n this.files = {\n 'project-agents': opts.paths.inProjectAgentsFile,\n 'project-memory': opts.paths.projectMemory,\n 'user-memory': opts.paths.globalMemory,\n };\n this.events = opts.events;\n this.backend = opts.backend ?? new FileMemoryBackend({ paths: opts.paths });\n\n // Detect temporary global roots: opencode, CI, test sandboxes.\n // When detected, mirror writes to the project's .wrongstack/ dir.\n const root = opts.paths.globalRoot.toLowerCase();\n this.persistBackup = /[/\\\\](tmp|temp|cache)[/\\\\]/.test(root);\n this.backupDir = this.persistBackup\n ? opts.paths.inProjectAgentsFile.replace(/AGENTS\\.md$/, 'memory-persist')\n : '';\n }\n\n /** Expose the backend for plugin introspection and advanced queries. */\n getBackend(): MemoryBackend {\n return this.backend;\n }\n\n private async runSerialized<T>(scope: MemoryScope, work: () => Promise<T>): Promise<T> {\n const prior = this.writeChain.get(scope) ?? Promise.resolve();\n // Chain: catch errors from the prior write, then run the next work item.\n const next = prior\n .catch((err) => {\n this.writeErrors.set(scope, err as Error);\n })\n .then(() => work());\n this.writeChain.set(scope, next as Promise<unknown>);\n try {\n return await next;\n } catch (err) {\n this.writeErrors.set(scope, err as Error);\n throw err;\n } finally {\n if (this.writeChain.get(scope) === next) {\n this.writeChain.delete(scope);\n }\n }\n }\n\n /**\n * Recalculate the tracked byte size for a scope by re-reading the file and\n * summing the serialized byte length of each line. Called after consolidate()\n * (which modifies the file) to keep the tracker accurate.\n */\n private async _recalcTrackedByteSize(scope: MemoryScope): Promise<number> {\n const raw = await this.backend.readAll(scope, this.files[scope]);\n let total = 0;\n for (const line of raw.split('\\n')) {\n if (line.trim()) total += Buffer.byteLength(line, 'utf8');\n }\n this._trackedByteSizes[scope] = total;\n return total;\n }\n\n async readAll(): Promise<string> {\n const parts: string[] = [];\n for (const scope of ['project-agents', 'project-memory', 'user-memory'] as MemoryScope[]) {\n const writeErr = this.writeErrors.get(scope);\n if (writeErr) {\n parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr.message}`);\n }\n const t0 = Date.now();\n const filePath = this.files[scope];\n try {\n const body = await this.backend.readAll(scope, filePath);\n const dur = Date.now() - t0;\n this.events?.emit('storage.read', {\n sessionId: '~memory~',\n store: 'memory',\n filePath,\n operation: 'readAll',\n outcome: 'success',\n durationMs: dur,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n if (body.trim()) parts.push(`## ${labelOf(scope)}\\n\\n${body.trim()}`);\n } catch (err) {\n const dur = Date.now() - t0;\n this.events?.emit('storage.read', {\n sessionId: '~memory~',\n store: 'memory',\n filePath,\n operation: 'readAll',\n outcome: 'failure',\n durationMs: dur,\n error: toErrorMessage(err),\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n throw err;\n }\n }\n return parts.join('\\n\\n');\n }\n\n async read(scope: MemoryScope): Promise<string> {\n const t0 = Date.now();\n const filePath = this.files[scope];\n try {\n const body = await this.backend.readAll(scope, filePath);\n const dur = Date.now() - t0;\n this.events?.emit('storage.read', {\n sessionId: '~memory~',\n store: 'memory',\n filePath,\n operation: 'read',\n outcome: 'success',\n durationMs: dur,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n return body;\n } catch (err) {\n const dur = Date.now() - t0;\n this.events?.emit('storage.read', {\n sessionId: '~memory~',\n store: 'memory',\n filePath,\n operation: 'read',\n outcome: 'failure',\n durationMs: dur,\n error: toErrorMessage(err),\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n throw err;\n }\n }\n\n /**\n * List entries from a scope, newest first. Delegates to the backend\n * so graph/semantic backends can return enriched or filtered results.\n */\n async list(scope: MemoryScope = 'project-memory', limit?: number): Promise<MemoryEntry[]> {\n return this.backend.list(scope, this.files[scope], limit);\n }\n\n /**\n * Find memories related to the given text via graph traversal.\n * Falls back to content search when no graph backend is available.\n */\n async findRelated(text: string, scope: MemoryScope = 'project-memory', limit = 5): Promise<MemoryEntry[]> {\n if (this.backend.findRelated) {\n return this.backend.findRelated(scope, this.files[scope], text, limit);\n }\n return this.search(text, scope, limit);\n }\n\n async search(query: string, scope: MemoryScope = 'project-memory', limit?: number): Promise<MemoryEntry[]> {\n return this.backend.search(scope, query, this.files[scope], limit);\n }\n\n async remember(\n text: string,\n scope: MemoryScope = 'project-memory',\n metadata?: Omit<Partial<MemoryEntry>, 'scope' | 'text' | 'ts'>,\n ): Promise<void> {\n const ts = new Date().toISOString();\n return this.runSerialized(scope, async () => {\n const entry: MemoryEntry = { scope, text, ts, ...metadata };\n const filePath = this.files[scope];\n const t0 = Date.now();\n try {\n await this.backend.remember(scope, entry, filePath);\n // Invalidate the relevance caches \u2014 entries have changed.\n this.invalidateScoreCaches();\n const dur = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: '~memory~',\n store: 'memory',\n filePath,\n operation: 'remember',\n outcome: 'success',\n durationMs: dur,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n } catch (err) {\n const dur = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: '~memory~',\n store: 'memory',\n filePath,\n operation: 'remember',\n outcome: 'failure',\n durationMs: dur,\n error: toErrorMessage(err),\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n throw err;\n }\n\n // Size check \u2014 consolidate if the tracked byte size exceeds the cap.\n // Tracked size is updated incrementally; lazily initialized from the file\n // on first mutation if not yet tracked (handles the case where the store was\n // constructed but not yet mutated, or after a restart).\n let trackedSize = this._trackedByteSizes[scope];\n if (trackedSize === undefined) {\n trackedSize = await this._recalcTrackedByteSize(scope);\n }\n if (trackedSize > MAX_BYTES_TOTAL) {\n const removed = await this.backend.consolidate(scope, this.files[scope]);\n if (removed > 0) {\n this.events?.emit('memory.consolidated', {\n scope,\n removed,\n } satisfies MemoryConsolidatedPayload);\n // Recalculate after consolidation so the tracked size stays accurate.\n await this._recalcTrackedByteSize(scope);\n }\n }\n\n // Increment the tracked byte size for this scope.\n this._trackedByteSizes[scope] = (this._trackedByteSizes[scope] ?? 0) + Buffer.byteLength(JSON.stringify(entry), 'utf8');\n\n // Mirror to persistent backup when running in a temp sandbox.\n await this.mirrorBackup(scope);\n\n this.events?.emit('memory.remembered', {\n scope,\n text,\n ts,\n type: entry.type,\n tags: entry.tags,\n priority: entry.priority,\n } satisfies MemoryRememberedPayload);\n });\n }\n\n /**\n * Drop the relevance caches after a mutation. Both the per-context score\n * cache and the per-entry lowercase cache are keyed on entry objects that a\n * mutation invalidates, so they must be cleared together when entries change.\n */\n private invalidateScoreCaches(): void {\n this._scoreCache.clear();\n this._cachedLower = null;\n }\n\n /**\n * Score and rank memories by relevance to the current context.\n * Returns entries with score >= MIN_RELEVANCE_SCORE, sorted highest first.\n */\n async scoreRelevant(\n ctx: MemoryRelevanceContext,\n scope: MemoryScope = 'project-memory',\n limit = 8,\n ): Promise<ScoredEntry[]> {\n const all = await this.list(scope);\n if (all.length === 0) return [];\n\n // Build a context fingerprint for cache lookup.\n const ctxHash =\n `${scope}|${ctx.currentTask}|${(ctx.activeSkills ?? []).join(',')}|${(ctx.toolNames ?? []).join(',')}`;\n const now = Date.now();\n const TTL_MS = 30_000; // 30-second cache window\n\n // Check the TTL cache first \u2014 return cached scored results if the same entries\n // and context were scored within the last 30 seconds.\n const cached = this._scoreCache.get(ctxHash);\n if (cached && cached.expiresAt > now && cached.entries === all) {\n return cached.scored.slice(0, Math.min(limit, 15));\n }\n\n const taskWords = ctx.currentTask.toLowerCase().split(/\\s+/).filter((w) => w.length > 2);\n const skillWords = (ctx.activeSkills ?? []).flatMap((s) => s.split('-'));\n const toolWords = (ctx.toolNames ?? []).flatMap((t) => t.toLowerCase().split('_'));\n\n // Per-entry lowercase caches. Lazily allocated once and reused across\n // scoring passes \u2014 the WeakMap is keyed by entry object identity, so its\n // entries stay valid as long as the same entry objects are scored, and\n // GC away once a mutation replaces them with freshly-parsed objects. It is\n // cleared explicitly alongside the score cache on every mutation\n // (remember/forget/consolidate/clear) via invalidateScoreCaches().\n this._cachedLower ??= new WeakMap<MemoryEntry, { textLower: string; tagsLower: string[] }>();\n const lowerCache = this._cachedLower;\n\n const scored: ScoredEntry[] = [];\n\n for (const entry of all) {\n let score = 0;\n const reasons: string[] = [];\n\n // Use cached lowercase strings from the WeakMap; compute and store on first use.\n let cachedLower = lowerCache.get(entry);\n if (!cachedLower) {\n cachedLower = {\n textLower: entry.text.toLowerCase(),\n tagsLower: (entry.tags ?? []).map((t) => t.toLowerCase()),\n };\n lowerCache.set(entry, cachedLower);\n }\n const { textLower, tagsLower } = cachedLower;\n\n // Word overlap with current task (primary signal)\n let taskHits = 0;\n for (const w of taskWords) {\n if (textLower.includes(w)) { taskHits++; score += 2; }\n if (tagsLower.some((t) => t.includes(w))) { taskHits++; score += 3; }\n }\n if (taskHits > 0) reasons.push(`task match (${taskHits})`);\n\n // Skill/tool relevance\n let skillHits = 0;\n for (const w of skillWords) {\n if (w.length > 2 && (textLower.includes(w) || tagsLower.some((t) => t.includes(w)))) {\n skillHits++;\n }\n }\n score += skillHits;\n if (skillHits > 0) reasons.push(`skill match (${skillHits})`);\n\n for (const w of toolWords) {\n if (w.length > 2 && (textLower.includes(w) || tagsLower.some((t) => t.includes(w)))) {\n score += 1;\n reasons.push(`tool mention: ${w}`);\n }\n }\n\n // Priority boost\n switch (entry.priority) {\n case 'critical': score += 5; reasons.push('critical'); break;\n case 'high': score += 3; reasons.push('high priority'); break;\n case 'medium': score += 1; break;\n case 'low': score -= 2; reasons.push('low priority'); break;\n }\n\n // Type boost \u2014 decisions, conventions, anti-patterns are high-value\n switch (entry.type) {\n case 'decision': score += 2; reasons.push('decision'); break;\n case 'convention': score += 2; reasons.push('convention'); break;\n case 'anti_pattern': score += 3; reasons.push('anti-pattern'); break;\n case 'preference': score += 1; reasons.push('preference'); break;\n case 'reference': break;\n case 'fact': break;\n }\n\n // Recency boost \u2014 newer entries get +1, very old get 0\n const ageDays = (now - new Date(entry.ts).getTime()) / (1000 * 60 * 60 * 24);\n if (ageDays < 1) score += 1;\n else if (ageDays > 30) score -= 1;\n\n // Confidence penalty\n if (entry.confidence !== undefined && entry.confidence < 0.5) {\n score -= 2;\n reasons.push('low confidence');\n }\n\n // Repetition avoidance \u2014 recently accessed entries get slight penalty\n if (entry.lastAccessed) {\n const hoursSinceAccess = (now - new Date(entry.lastAccessed).getTime()) / (1000 * 60 * 60);\n if (hoursSinceAccess < 1) score -= 1;\n }\n\n if (score > 0) {\n scored.push({\n ...entry,\n score,\n matchReason: reasons.join(', ') || 'keyword match',\n });\n }\n }\n\n scored.sort((a, b) => b.score - a.score);\n\n // Filter to entries that meet the minimum relevance threshold.\n // Critical or high-priority entries always pass.\n const threshold = 2;\n const relevant = scored.filter(\n (s) => s.score >= threshold || s.priority === 'critical' || s.priority === 'high',\n );\n\n // Cache the full scored result for 30 seconds \u2014 cleared on every mutation.\n this._scoreCache.set(ctxHash, { entries: all, scored: relevant, expiresAt: now + TTL_MS });\n\n return relevant.slice(0, Math.min(limit, 15));\n }\n\n async forget(query: string, scope: MemoryScope = 'project-memory'): Promise<number> {\n return this.runSerialized(scope, async () => {\n const filePath = this.files[scope];\n const t0 = Date.now();\n let removed = 0;\n try {\n removed = await this.backend.forget(scope, query, filePath);\n // Invalidate the relevance caches \u2014 entries have changed.\n this.invalidateScoreCaches();\n const dur = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: '~memory~',\n store: 'memory',\n filePath,\n operation: 'forget',\n outcome: 'success',\n durationMs: dur,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n } catch (err) {\n const dur = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: '~memory~',\n store: 'memory',\n filePath,\n operation: 'forget',\n outcome: 'failure',\n durationMs: dur,\n error: toErrorMessage(err),\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n throw err;\n }\n if (removed > 0) {\n this.events?.emit('memory.forgotten', {\n scope,\n query,\n removed,\n } satisfies MemoryForgottenPayload);\n await this.mirrorBackup(scope);\n await this._recalcTrackedByteSize(scope);\n }\n return removed;\n });\n }\n\n async consolidate(scope: MemoryScope): Promise<void> {\n return this.runSerialized(scope, async () => {\n const filePath = this.files[scope];\n const t0 = Date.now();\n let removed = 0;\n try {\n removed = await this.backend.consolidate(scope, filePath);\n // Invalidate the relevance caches \u2014 entries have changed.\n this.invalidateScoreCaches();\n const dur = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: '~memory~',\n store: 'memory',\n filePath,\n operation: 'consolidate',\n outcome: 'success',\n durationMs: dur,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n } catch (err) {\n const dur = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: '~memory~',\n store: 'memory',\n filePath,\n operation: 'consolidate',\n outcome: 'failure',\n durationMs: dur,\n error: toErrorMessage(err),\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n throw err;\n }\n if (removed > 0) {\n this.events?.emit('memory.consolidated', {\n scope,\n removed,\n } satisfies MemoryConsolidatedPayload);\n await this.mirrorBackup(scope);\n await this._recalcTrackedByteSize(scope);\n }\n });\n }\n\n async clear(scope?: MemoryScope): Promise<void> {\n if (scope) {\n await this.runSerialized(scope, async () => {\n const filePath = this.files[scope];\n const t0 = Date.now();\n try {\n await this.backend.clear(scope, filePath);\n // Invalidate the relevance caches \u2014 entries have changed.\n this.invalidateScoreCaches();\n const dur = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: '~memory~',\n store: 'memory',\n filePath,\n operation: 'clear',\n outcome: 'success',\n durationMs: dur,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n } catch (err) {\n const dur = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: '~memory~',\n store: 'memory',\n filePath,\n operation: 'clear',\n outcome: 'failure',\n durationMs: dur,\n error: toErrorMessage(err),\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n throw err;\n }\n this.events?.emit('memory.cleared', { scope } satisfies MemoryClearedPayload);\n await this.mirrorBackup(scope);\n this._trackedByteSizes[scope] = 0;\n });\n return;\n }\n await Promise.all(\n (['project-agents', 'project-memory', 'user-memory'] as MemoryScope[]).map(async (s) =>\n this.runSerialized(s, async () => {\n const filePath = this.files[s];\n const t0 = Date.now();\n try {\n await this.backend.clear(s, filePath);\n const dur = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: '~memory~',\n store: 'memory',\n filePath,\n operation: 'clear',\n outcome: 'success',\n durationMs: dur,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n } catch (err) {\n const dur = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: '~memory~',\n store: 'memory',\n filePath,\n operation: 'clear',\n outcome: 'failure',\n durationMs: dur,\n error: toErrorMessage(err),\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n throw err;\n }\n this.events?.emit('memory.cleared', { scope: s } satisfies MemoryClearedPayload);\n await this.mirrorBackup(s);\n }),\n ),\n );\n // Every scope was just cleared \u2014 drop the relevance caches too.\n this.invalidateScoreCaches();\n }\n\n /**\n * Return a new MemoryStore proxy that carries `traceId` on every storage\n * event. The original store is left unchanged \u2014 callers that need a\n * trace-decorated view (e.g. session-run tools) receive the proxy while\n * the singleton remains trace-free for boot-time use.\n *\n * The proxy implements the full `MemoryStore` interface; all other\n * properties (backend, etc.) are delegated to the original store.\n */\n withTraceId(traceId: string): MemoryStore {\n // Mutate the singleton's traceId so that all subsequent I/O (including\n // calls from tools that captured the original store reference) emits\n // storage events with the correct session trace ID.\n this.traceId = traceId;\n return this;\n }\n private async mirrorBackup(scope: MemoryScope): Promise<void> {\n if (!this.persistBackup || scope === 'project-agents') return;\n try {\n const content = await this.backend.readAll(scope, this.files[scope]);\n const { writeFile, mkdir } = await import('node:fs/promises');\n await mkdir(this.backupDir, { recursive: true });\n await writeFile(`${this.backupDir}/${scope}.md`, content, 'utf8');\n } catch {\n // best-effort\n }\n }\n}\n\nfunction labelOf(scope: MemoryScope): string {\n switch (scope) {\n case 'project-agents':\n return 'Project AGENTS.md';\n case 'project-memory':\n return 'Project memory';\n case 'user-memory':\n return 'User memory';\n }\n}\n", "import * as fs from 'node:fs/promises';\nimport type { MemoryEntry, MemoryScope } from '../types/memory.js';\nimport { type FileMemoryBackendOptions, FileMemoryBackend } from './memory-backend.js';\nimport type { MemoryBackend } from './memory-backend.js';\n\n// \u2500\u2500 Graph node and edge types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface GraphNode {\n id: string;\n entry: MemoryEntry;\n firstSeen: string;\n count: number;\n /** Extracted metadata for fast lookup. */\n type?: MemoryEntry['type'] | undefined;\n tags?: string[] | undefined;\n priority?: MemoryEntry['priority'] | undefined;\n}\n\ninterface GraphEdge {\n from: string;\n to: string;\n /** Why these nodes are related. */\n relation: 'co_occurring' | 'similar' | 'same_turn' | 'explicit';\n weight: number;\n ts: string;\n}\n\n// \u2500\u2500 Backend \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface GraphMemoryBackendOptions extends FileMemoryBackendOptions {\n /**\n * Path to the graph metadata file (edges + node metadata).\n * Defaults to `<projectDir>/memory-graph.json`.\n */\n graphPath?: string | undefined;\n}\n\n/**\n * Graph-based memory backend that tracks relationships between entries.\n * Builds on top of FileMemoryBackend \u2014 entries are still persisted as\n * markdown bullets, but the graph layer adds:\n *\n * - Co-occurrence edges (entries from the same remember() batch)\n * - Content similarity edges (simple Jaccard on word overlap)\n * - Turn-based edges (entries created in the same LLM turn)\n * - Graph traversal queries (find related memories)\n *\n * The graph metadata persists to `<projectDir>/memory-graph.json`.\n */\nexport class GraphMemoryBackend implements MemoryBackend {\n readonly kind = 'graph';\n\n private readonly file: FileMemoryBackend;\n private readonly graphFile: string;\n\n // In-memory graph state \u2014 lazily loaded, saved on mutation.\n private nodes = new Map<string, GraphNode>();\n private edges: GraphEdge[] = [];\n private loadedScope: MemoryScope | null = null;\n private loaded = false;\n\n /**\n * Inverted index for O(1) term \u2192 node lookup during search.\n * Built incrementally on remember/forget, rebuilt on loadGraph if absent.\n * Maps lowercase term (min 3 chars) \u2192 Set of node IDs containing that term.\n */\n private invertedIndex = new Map<string, Set<string>>();\n\n /** Minimum term length to index \u2014 avoids noise from 1-2 char fragments. */\n private static readonly MIN_TERM_LEN = 3;\n\n /**\n * Promise that resolves when the current in-flight _saveGraph completes.\n * Tests call flush() to await this before deleting the backend or its temp dir.\n * Each save operation chains onto the previous one so concurrent saves are serialised.\n */\n private _saveDone: Promise<void> = Promise.resolve();\n\n constructor(opts: GraphMemoryBackendOptions) {\n this.file = new FileMemoryBackend({ paths: opts.paths });\n this.graphFile = opts.graphPath ?? `${opts.paths.projectDir}/memory-graph.json`;\n }\n\n // \u2500\u2500 Backend interface \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async remember(scope: MemoryScope, entry: MemoryEntry, filePath: string): Promise<void> {\n await this.file.remember(scope, entry, filePath);\n await this.loadGraph(scope);\n\n const nodeId = this.nodeId(entry);\n const existing = this.nodes.get(nodeId);\n if (existing) {\n existing.count++;\n existing.entry = entry;\n existing.type = entry.type;\n existing.tags = entry.tags;\n existing.priority = entry.priority;\n // Update inverted index for the modified node's text and tags\n this.updateNodeIndex(nodeId, entry);\n } else {\n this.nodes.set(nodeId, {\n id: nodeId,\n entry,\n firstSeen: entry.ts,\n count: 1,\n type: entry.type,\n tags: entry.tags,\n priority: entry.priority,\n });\n\n // Index the new node in the inverted index\n this.indexNode(nodeId, entry);\n\n // Create similarity edges \u2014 but only against the last SIMILARITY_WINDOW most\n // recent nodes instead of the full in-memory set. This converts O(N\u00B2) on\n // every remember() into O(K) where K is a small constant (100), making\n // remember() O(1) amortized regardless of how many entries are stored.\n const SIMILARITY_WINDOW = 100;\n const recentNodes = [...this.nodes.values()].slice(-SIMILARITY_WINDOW);\n for (const other of recentNodes) {\n if (other.id === nodeId) continue;\n const sim = wordOverlap(entry.text, other.entry.text);\n const tagSim = sharedTags(entry.tags ?? [], other.tags ?? []);\n const weight = Math.max(sim, tagSim * 0.5);\n if (weight > 0.15) {\n this.edges.push({\n from: nodeId,\n to: other.id,\n relation: sim >= tagSim ? 'similar' : 'same_turn',\n weight,\n ts: entry.ts,\n });\n }\n }\n }\n\n // Await the graph save to ensure flush() sees the file on disk before\n // cleanup. Unlike memory-consolidator (LLM call, 15s), this is a fast fs operation\n // and the caller's event loop is already freed by not awaiting file.remember().\n this._saveDone = this._saveGraph(scope);\n await this._saveDone;\n }\n\n async forget(scope: MemoryScope, query: string, filePath: string): Promise<number> {\n const removed = await this.file.forget(scope, query, filePath);\n if (removed > 0) {\n await this.loadGraph(scope);\n // Remove nodes whose entry text matches the query\n const n = query.toLowerCase();\n const toRemove: string[] = [];\n for (const [id, node] of this.nodes) {\n if (node.entry.text.toLowerCase().includes(n)) {\n toRemove.push(id);\n }\n }\n for (const id of toRemove) {\n // Remove from inverted index before deleting the node\n this.removeNodeFromIndex(id, this.nodes.get(id)?.entry);\n this.nodes.delete(id);\n }\n this.edges = this.edges.filter((e) => !toRemove.includes(e.from) && !toRemove.includes(e.to));\n this._saveDone = this._saveGraph(scope);\n await this._saveDone;\n }\n return removed;\n }\n\n async readAll(scope: MemoryScope, filePath: string): Promise<string> {\n return this.file.readAll(scope, filePath);\n }\n\n async list(scope: MemoryScope, filePath: string, limit?: number): Promise<MemoryEntry[]> {\n await this.loadGraph(scope);\n // Merge: file entries are canonical, graph adds metadata and dedup\n const fileEntries = await this.file.list(scope, filePath);\n const nodeMap = new Map(this.nodes.entries());\n\n // Enrich file entries with graph metadata\n const enriched = fileEntries.map((fe) => {\n const nodeId = this.nodeId(fe);\n const node = nodeMap.get(nodeId);\n if (node) {\n return {\n ...fe,\n type: node.type ?? fe.type,\n tags: node.tags ?? fe.tags,\n priority: node.priority ?? fe.priority,\n };\n }\n return fe;\n });\n\n // Add graph-only nodes not in file (shouldn't happen normally, but safety)\n const fileIds = new Set(fileEntries.map((e) => this.nodeId(e)));\n for (const [id, node] of this.nodes) {\n if (!fileIds.has(id)) {\n enriched.push(node.entry);\n }\n }\n\n enriched.sort((a, b) => b.ts.localeCompare(a.ts));\n return limit ? enriched.slice(0, limit) : enriched;\n }\n\n async search(scope: MemoryScope, query: string, _filePath: string, limit?: number): Promise<MemoryEntry[]> {\n await this.loadGraph(scope);\n const needle = query.toLowerCase().split(/\\s+/).filter((t) => t.length >= GraphMemoryBackend.MIN_TERM_LEN);\n\n // Use inverted index to find candidate nodes \u2014 O(K * avg_results) instead of O(N)\n const candidates = new Map<string, number>();\n for (const term of needle) {\n const nodeIds = this.invertedIndex.get(term);\n if (!nodeIds) continue;\n for (const nodeId of nodeIds) {\n const node = this.nodes.get(nodeId);\n if (!node || node.entry.scope !== scope) continue;\n // Count how many terms matched this node\n candidates.set(nodeId, (candidates.get(nodeId) ?? 0) + 1);\n }\n }\n\n // Also include high-priority entries even without lexical matches (priority boost > 0)\n // This preserves the original behavior where priority/count could surface entries\n // that don't match any query terms.\n for (const [nodeId, node] of this.nodes) {\n if (node.entry.scope !== scope) continue;\n if (candidates.has(nodeId)) continue; // Already in candidates\n // Include if priority boost > 0 (critical=3, high=2)\n if (node.priority === 'critical' || node.priority === 'high') {\n candidates.set(nodeId, 0); // 0 lexical matches but priority boost applies\n }\n }\n\n // Score candidates: text match (1pt/term), tag match (2pts/term), priority, count\n const scored: { entry: MemoryEntry; score: number }[] = [];\n for (const [nodeId] of candidates) {\n const node = this.nodes.get(nodeId)!;\n let score = 0;\n // Text match score: 1pt per matching term\n score += (candidates.get(nodeId) ?? 0) * 1;\n // Tag match score: 2pts per term that appears in any tag\n for (const term of needle) {\n if (node.entry.tags?.some((t) => t.toLowerCase().includes(term))) score += 2;\n }\n if (node.priority === 'critical') score += 3;\n else if (node.priority === 'high') score += 2;\n score += node.count * 0.5;\n scored.push({ entry: node.entry, score });\n }\n\n scored.sort((a, b) => b.score - a.score);\n const matched = scored.map((s) => s.entry);\n return limit ? matched.slice(0, limit) : matched;\n }\n\n async clear(scope: MemoryScope, filePath: string): Promise<void> {\n await this.file.clear(scope, filePath);\n this.nodes.clear();\n this.edges = [];\n this.invertedIndex = new Map();\n this.loadedScope = scope;\n this.loaded = true;\n // Write empty graph\n try { await fs.unlink(this.graphFile); } catch { /* ok */ }\n }\n\n async consolidate(scope: MemoryScope, filePath: string): Promise<number> {\n return this.file.consolidate(scope, filePath);\n }\n\n // \u2500\u2500 Graph-specific queries \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Find memories related to the given entry, ordered by edge weight.\n */\n async findRelated(scope: MemoryScope, _filePath: string, entryText: string, limit = 5): Promise<MemoryEntry[]> {\n await this.loadGraph(scope);\n const targetId = this.nodeId({ scope, text: entryText, ts: '' });\n const related = this.edges\n .filter((e) => e.from === targetId || e.to === targetId)\n .sort((a, b) => b.weight - a.weight)\n .slice(0, limit);\n\n const result: MemoryEntry[] = [];\n for (const edge of related) {\n const otherId = edge.from === targetId ? edge.to : edge.from;\n const node = this.nodes.get(otherId);\n if (node) result.push(node.entry);\n }\n return result;\n }\n\n /**\n * Get all edges for visualization or traversal.\n */\n getGraph(): { nodes: GraphNode[]; edges: GraphEdge[] } {\n return {\n nodes: [...this.nodes.values()],\n edges: [...this.edges],\n };\n }\n\n // \u2500\u2500 Persistence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private nodeId(entry: MemoryEntry): string {\n // Stable ID from scope + normalized text\n const norm = entry.text.toLowerCase().trim().replace(/\\s+/g, ' ');\n return `${entry.scope ?? 'mem'}::${simpleHash(norm)}`;\n }\n\n private async loadGraph(scope: MemoryScope): Promise<void> {\n if (this.loaded && this.loadedScope === scope) return;\n try {\n const raw = await fs.readFile(this.graphFile, 'utf8');\n const data: { nodes: Array<[string, GraphNode]>; edges: GraphEdge[] } = JSON.parse(raw);\n this.nodes = new Map(data.nodes);\n this.edges = data.edges;\n } catch {\n this.nodes = new Map();\n this.edges = [];\n }\n // Build inverted index from loaded nodes\n this.buildInvertedIndex();\n this.loadedScope = scope;\n this.loaded = true;\n }\n\n /** Fire-and-forget graph persistence. Named _saveGraph to signal it must not be awaited. */\n private async _saveGraph(scope: MemoryScope): Promise<void> {\n this.loadedScope = scope;\n this.loaded = true;\n try {\n const data = {\n nodes: [...this.nodes.entries()],\n edges: this.edges,\n };\n const dir = this.graphFile.substring(0, this.graphFile.lastIndexOf('/'));\n await fs.mkdir(dir, { recursive: true });\n // Atomic write via temp file\n const tmp = `${this.graphFile}.tmp`;\n await fs.writeFile(tmp, JSON.stringify(data));\n await fs.rename(tmp, this.graphFile);\n } catch {\n // best-effort \u2014 graph is an enhancement, not critical\n }\n }\n\n /**\n * Wait for all in-flight _saveGraph operations to complete.\n * Call this before deleting the backend or its temp directory.\n */\n async flush(): Promise<void> {\n await this._saveDone;\n }\n\n // \u2500\u2500 Inverted Index Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Build the inverted index from all currently loaded nodes.\n * Called on loadGraph() to reconstruct the index after loading from disk.\n */\n private buildInvertedIndex(): void {\n this.invertedIndex = new Map();\n for (const [nodeId, node] of this.nodes) {\n for (const term of this.extractTerms(node.entry)) {\n let nodeIds = this.invertedIndex.get(term);\n if (!nodeIds) {\n nodeIds = new Set();\n this.invertedIndex.set(term, nodeIds);\n }\n nodeIds.add(nodeId);\n }\n }\n }\n\n /**\n * Index a node in the inverted index. Adds the node's ID to all term entries.\n */\n private indexNode(nodeId: string, entry: MemoryEntry): void {\n for (const term of this.extractTerms(entry)) {\n let nodeIds = this.invertedIndex.get(term);\n if (!nodeIds) {\n nodeIds = new Set();\n this.invertedIndex.set(term, nodeIds);\n }\n nodeIds.add(nodeId);\n }\n }\n\n /**\n * Remove a node's terms from the inverted index before deleting it.\n * Used in forget() and when updating existing nodes.\n */\n private removeNodeFromIndex(nodeId: string, entry?: MemoryEntry): void {\n if (!entry) return;\n for (const term of this.extractTerms(entry)) {\n const nodeIds = this.invertedIndex.get(term);\n if (nodeIds) {\n nodeIds.delete(nodeId);\n if (nodeIds.size === 0) {\n this.invertedIndex.delete(term);\n }\n }\n }\n }\n\n /**\n * Update an existing node's entries in the inverted index.\n * Removes old terms and adds new ones.\n */\n private updateNodeIndex(nodeId: string, entry: MemoryEntry): void {\n // Remove old entries for this node\n const existing = this.nodes.get(nodeId);\n if (existing) {\n this.removeNodeFromIndex(nodeId, existing.entry);\n }\n // Add new entries\n this.indexNode(nodeId, entry);\n }\n\n /**\n * Extract searchable terms from a memory entry.\n * Returns lowercase words from text (min length) and full tag strings.\n */\n private extractTerms(entry: MemoryEntry): string[] {\n const terms: string[] = [];\n // Extract words from text (only terms >= MIN_TERM_LEN to avoid noise)\n const words = entry.text.toLowerCase().split(/\\s+/);\n for (const word of words) {\n if (word.length >= GraphMemoryBackend.MIN_TERM_LEN) {\n terms.push(word);\n }\n }\n // Index full tags as complete strings (not individual words)\n if (entry.tags) {\n for (const tag of entry.tags) {\n const lower = tag.toLowerCase();\n if (lower.length >= GraphMemoryBackend.MIN_TERM_LEN && !terms.includes(lower)) {\n terms.push(lower);\n }\n }\n }\n return terms;\n }\n}\n\n// \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Jaccard-style word overlap between two strings. 0.0 = no overlap, 1.0 = identical. */\nfunction wordOverlap(a: string, b: string): number {\n const wordsA = new Set(a.toLowerCase().split(/\\s+/).filter((w) => w.length > 2));\n const wordsB = new Set(b.toLowerCase().split(/\\s+/).filter((w) => w.length > 2));\n if (wordsA.size === 0 || wordsB.size === 0) return 0;\n let intersection = 0;\n for (const w of wordsA) {\n if (wordsB.has(w)) intersection++;\n }\n return intersection / Math.max(wordsA.size, wordsB.size);\n}\n\n/** Tag overlap ratio \u2014 how many tags two nodes share. */\nfunction sharedTags(a: string[], b: string[]): number {\n if (a.length === 0 || b.length === 0) return 0;\n const setB = new Set(b);\n let shared = 0;\n for (const t of a) if (setB.has(t)) shared++;\n return shared / Math.max(a.length, b.length);\n}\n\n/** Fast non-crypto hash for stable node IDs. */\nfunction simpleHash(s: string): string {\n let h = 0;\n for (let i = 0; i < s.length; i++) {\n h = ((h << 5) - h + s.charCodeAt(i)) | 0;\n }\n return Math.abs(h).toString(36);\n}\n", "import { readFileSync, statSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Cache of resolved instruction text, keyed by the relative path. Bundled\n * instruction files are immutable for the process lifetime, so re-reading them\n * on every call (some callers do so per LLM request) is wasted disk I/O.\n */\nconst textCache = new Map<string, string>();\n\n/** Resolved once \u2014 the candidate roots only depend on this module's location. */\nlet rootCandidates: string[] | undefined;\n\nexport function readBundledInstructionText(relativePath: string): string {\n const cached = textCache.get(relativePath);\n if (cached !== undefined) return cached;\n\n let resolved = '';\n for (const root of instructionRootCandidates()) {\n try {\n resolved = readFileSync(path.join(root, relativePath), 'utf8').trimEnd();\n break;\n } catch {\n // try next candidate\n }\n }\n textCache.set(relativePath, resolved);\n return resolved;\n}\n\nexport function renderInstructionTemplate(\n template: string,\n values: Record<string, string>,\n): string {\n return template.replace(/\\{\\{\\s*([a-zA-Z0-9_.-]+)\\s*\\}\\}/g, (match, key: string) =>\n Object.hasOwn(values, key) ? (values[key] ?? '') : match,\n );\n}\n\nfunction instructionRootCandidates(): string[] {\n if (rootCandidates !== undefined) return rootCandidates;\n const here = path.dirname(fileURLToPath(import.meta.url));\n const candidates = [\n path.resolve(here, '../../instructions'),\n path.resolve(here, '../instructions'),\n path.resolve(here, 'instructions'),\n ];\n rootCandidates = candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));\n return rootCandidates;\n}\n\nfunction isDirectory(candidate: string): boolean {\n try {\n return statSync(candidate).isDirectory();\n } catch {\n return false;\n }\n}\n", "import type { RunResult } from '../core/agent-types.js';\nimport type { Context } from '../core/context.js';\nimport type { AfterRunHook, AgentExtension } from '../extension/extension-points.js';\nimport type { MemoryEntry, MemoryStore } from '../types/memory.js';\nimport type { Provider } from '../types/provider.js';\nimport {\n readBundledInstructionText,\n renderInstructionTemplate,\n} from '../utils/instruction-file.js';\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ConsolidationOp {\n action: 'add' | 'edit' | 'delete';\n /** For add: the fact to remember. For edit: the new text replacing the old. */\n text?: string | undefined;\n /** For edit/delete: the query to match existing entries. */\n query?: string | undefined;\n /** Memory type for categorization. */\n type?: string | undefined;\n /** Tags for grouping. */\n tags?: string[] | undefined;\n /** Priority level. */\n priority?: string | undefined;\n}\n\ninterface ConsolidationResponse {\n operations: ConsolidationOp[];\n summary?: string | undefined;\n}\n\nexport interface MemoryConsolidatorOptions {\n memoryStore: MemoryStore;\n /**\n * Provider used for the consolidation LLM call. Uses the session's\n * provider by default.\n */\n provider?: Provider | undefined;\n /**\n * Model override for the consolidation call. Uses the session's model\n * by default. A smaller/faster model is recommended (e.g. haiku, flash).\n */\n model?: string | undefined;\n /**\n * Minimum session iterations before consolidation fires.\n * Sessions shorter than this are skipped (default 2).\n */\n minIterations?: number | undefined;\n /**\n * Maximum memory entries to include in the prompt as context.\n */\n maxExistingEntries?: number | undefined;\n}\n\n// \u2500\u2500 Prompt \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction buildConsolidationPrompt(\n finalText: string,\n iterations: number,\n existingEntries: MemoryEntry[],\n): string {\n const existingBlock =\n existingEntries.length > 0\n ? `\\n\\nExisting memory entries:\\n${existingEntries\n .map((e) => `- [${e.ts.slice(0, 10)}] ${e.text}`)\n .join('\\n')}`\n : '';\n\n return renderInstructionTemplate(readBundledInstructionText('llm/memory-consolidator.md'), {\n iterations: String(iterations),\n summary: finalText.slice(0, 3000),\n existingEntries: existingBlock,\n });\n}\n\n// \u2500\u2500 Consolidator \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class SessionMemoryConsolidator implements AgentExtension {\n name = 'session-memory-consolidator';\n owner = 'core';\n\n private readonly memoryStore: MemoryStore;\n private readonly provider?: Provider | undefined;\n private readonly model?: string | undefined;\n private readonly minIterations: number;\n private readonly maxExistingEntries: number;\n\n constructor(opts: MemoryConsolidatorOptions) {\n this.memoryStore = opts.memoryStore;\n this.provider = opts.provider;\n this.model = opts.model;\n this.minIterations = opts.minIterations ?? 2;\n this.maxExistingEntries = opts.maxExistingEntries ?? 15;\n }\n\n afterRun: AfterRunHook = (ctx: Context, result: RunResult) => {\n // Only consolidate successful sessions with meaningful output\n if (result.status !== 'done') return;\n if (!result.finalText || result.finalText.trim().length < 20) return;\n if (result.iterations < this.minIterations) return;\n\n const provider = this.provider ?? ctx.provider;\n if (!provider?.complete) return;\n\n // Capture narrowed values for the fire-and-forget closure.\n const _finalText: string = result.finalText;\n const _iterations: number = result.iterations;\n const _model: string | undefined = this.model ?? ctx.model;\n\n // Fire-and-forget: consolidation is best-effort and should never block\n // session teardown (the LLM call can take up to 15s). The catch block\n // below prevents unhandled rejections.\n void (async () => {\n try {\n // Load existing memory for dedup context\n const existingEntries = await this.memoryStore.list('project-memory', this.maxExistingEntries);\n const prompt = buildConsolidationPrompt(\n _finalText,\n _iterations,\n existingEntries,\n );\n\n // Call the LLM with a focused, one-shot prompt\n const signal = AbortSignal.timeout(15_000);\n const response = await provider.complete(\n {\n model: _model,\n system: [{ type: 'text', text: prompt }],\n messages: [\n { role: 'user', content: 'Review the session and return memory operations as JSON.' },\n ],\n maxTokens: 500,\n },\n { signal },\n );\n\n const text = response.content\n .filter((b): b is { type: 'text'; text: string } => b.type === 'text')\n .map((b) => b.text)\n .join('')\n .trim();\n if (!text) return;\n\n // Extract JSON from possible markdown wrapper\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n if (!jsonMatch) return;\n\n const parsed: ConsolidationResponse = JSON.parse(jsonMatch[0]);\n if (!Array.isArray(parsed.operations) || parsed.operations.length === 0) return;\n\n // Apply operations\n let added = 0;\n let edited = 0;\n let deleted = 0;\n\n for (const op of parsed.operations) {\n switch (op.action) {\n case 'add': {\n if (op.text?.trim()) {\n await this.memoryStore.remember(op.text.trim(), undefined, {\n type: op.type as MemoryEntry['type'],\n tags: op.tags,\n priority: op.priority as MemoryEntry['priority'],\n });\n added++;\n }\n break;\n }\n case 'edit': {\n if (op.query && op.text?.trim()) {\n await this.memoryStore.forget(op.query);\n await this.memoryStore.remember(op.text.trim(), undefined, {\n type: op.type as MemoryEntry['type'],\n tags: op.tags,\n priority: op.priority as MemoryEntry['priority'],\n });\n edited++;\n }\n break;\n }\n case 'delete': {\n if (op.query) {\n const n = await this.memoryStore.forget(op.query);\n deleted += n;\n }\n break;\n }\n }\n }\n\n if (added > 0 || edited > 0 || deleted > 0) {\n const parts: string[] = [];\n if (added) parts.push(`${added} added`);\n if (edited) parts.push(`${edited} edited`);\n if (deleted) parts.push(`${deleted} deleted`);\n // Log to stderr so it surfaces in the terminal\n process.stderr.write(`[memory] Session consolidation: ${parts.join(', ')}\\n`);\n }\n } catch {\n // Silent \u2014 memory consolidation is best-effort, never blocks session cleanup\n }\n })();\n };\n}\n", "import type { Config, ConfigStore } from '../types/config.js';\nimport { ConfigError, ERROR_CODES } from '../types/errors.js';\nimport { toErrorMessage } from '../utils/error.js';\n\n\n/**\n * Strip fields that originated from environment variables so they are never\n * persisted back to disk. This prevents an env-sourced secret (e.g.\n * WRONGSTACK_API_KEY) from being accidentally written to ~/.wrongstack/config.json.\n */\nfunction stripEphemeralFields(cfg: Partial<Config>): Partial<Config> {\n const env = (cfg as Partial<Config & { _envSource?: Set<string> | undefined}>)._envSource;\n if (!env?.size) return cfg;\n const out: Partial<Config> = { ...cfg };\n for (const field of env) {\n delete (out as Record<string, unknown>)[field];\n }\n delete (out as Record<string, unknown>)._envSource;\n return out;\n}\n\n/**\n * Reference implementation of `ConfigStore`. Stores a single frozen Config\n * and notifies watchers synchronously on every update. Updates use a deep\n * clone so callers can mutate their `partial` argument freely without\n * tainting state.\n *\n * For the CLI: instantiate once at boot, pass the store (not the Config)\n * to subsystems that care about runtime changes (provider switching,\n * extension reload).\n */\nexport class DefaultConfigStore implements ConfigStore {\n private current: Readonly<Config>;\n private watchers = new Set<(next: Readonly<Config>, prev: Readonly<Config>) => void>();\n\n constructor(initial: Config) {\n this.current = deepFreeze(structuredClone(initial));\n }\n\n get(): Readonly<Config> {\n return this.current;\n }\n\n getSection<K extends keyof Config>(key: K): Readonly<Config[K]> {\n return this.current[key] as Readonly<Config[K]>;\n }\n\n getExtension(pluginName: string): Readonly<Record<string, unknown>> {\n const ext = this.current.extensions?.[pluginName];\n return ext ? (ext as Readonly<Record<string, unknown>>) : FROZEN_EMPTY;\n }\n\n update(partial: Partial<Config>): Readonly<Config> {\n // Strip env-sourced fields before persisting to prevent secrets leaking\n // from in-memory env-derived config values into the on-disk config file.\n const scrubbed = stripEphemeralFields(partial);\n // Shallow merge \u2014 top-level fields replace, nested objects do too unless\n // the caller passes a fully-formed sub-object. That matches the JSON\n // config user mental model (replace `tools.maxIterations` by passing\n // the whole `tools` block, or by patching `extensions.<name>`).\n const next = deepFreeze(structuredClone({ ...this.current, ...scrubbed })) as Readonly<Config>;\n\n if (next.version !== 1) {\n throw new ConfigError({\n message: `ConfigStore.update: version must remain 1, got ${String(next.version)}`,\n code: ERROR_CODES.CONFIG_INVALID,\n context: { field: 'version', actual: next.version },\n });\n }\n\n const prev = this.current;\n this.current = next;\n // Notify watchers AFTER mutating `current` so re-entrant watcher reads\n // see the new state. Watcher exceptions are caught individually so one\n // misbehaving subscriber can't block the others.\n for (const w of this.watchers) {\n try {\n w(next, prev);\n } catch (err) {\n // A plugin watcher that crashes on /model switch or similar would\n // otherwise leave the system in a quietly-inconsistent state. We\n // still don't propagate (one bad subscriber must not break the\n // others), but we surface the error so it's discoverable.\n console.error(JSON.stringify({\n level: 'error',\n event: 'config_store.watcher_threw',\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }));\n }\n }\n return next;\n }\n\n watch(cb: (next: Readonly<Config>, prev: Readonly<Config>) => void): () => void {\n this.watchers.add(cb);\n return () => this.watchers.delete(cb);\n }\n}\n\nconst FROZEN_EMPTY: Readonly<Record<string, unknown>> = Object.freeze({});\n\nfunction deepFreeze<T>(obj: T): T {\n /* v8 ignore start -- defensive: callers (and the recursion guard below) only pass unfrozen objects */\n if (obj === null || typeof obj !== 'object') return obj;\n if (Object.isFrozen(obj)) return obj;\n /* v8 ignore stop */\n for (const key of Object.keys(obj as object)) {\n const v = (obj as Record<string, unknown>)[key];\n if (v !== null && typeof v === 'object' && !Object.isFrozen(v)) {\n deepFreeze(v);\n }\n }\n return Object.freeze(obj);\n}\n", "/**\n * provider-config-watcher \u2014 live hot-reload of provider credentials.\n *\n * Provider API keys are read from `config.json` once at boot and baked into\n * the constructed `Provider`. When a user adds/removes/replaces a key in\n * another process (a second terminal running `wstack auth`, or either WebUI\n * provider panel), a *running* session would otherwise keep using the\n * boot-time key until restart.\n *\n * This watcher closes that gap: it tails the global `config.json`, re-reads\n * and decrypts its `providers` map on change, and hands a fresh snapshot to\n * the caller, who updates in-memory config and rebuilds the active provider.\n *\n * It mirrors {@link module:hq/auth-store.watchHqAuthFile}:\n * - Watches the *directory* (not the file) so atomic tmp+rename writes\n * (see {@link module:utils/atomic-write.atomicWrite}) surface reliably.\n * - Debounces events (most writers do a tmp+rename dance emitting several).\n * - Reads leniently \u2014 a missing/corrupt/torn file yields a warning, never a\n * throw, and the watcher stays live for the next valid write.\n *\n * A `lastSerialized` guard suppresses the callback when the on-disk providers\n * map is byte-for-byte identical to the last one we surfaced. This makes the\n * watcher a no-op for self-induced writes (e.g. the same process's `/model`\n * switch or a WebUI key save), so there is no rebuild storm.\n *\n * `fs.watch` is best-effort across platforms; on some network filesystems\n * events may not fire and the operator must restart to pick up changes.\n *\n * @module storage/provider-config-watcher\n */\nimport * as syncFs from 'node:fs';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\n\nimport { decryptConfigSecrets } from '../security/config-secrets.js';\nimport type { ProviderConfig } from '../types/config.js';\nimport type { SecretVault } from '../types/secret-vault.js';\n\n/**\n * A decrypted snapshot of the credential-bearing slice of `config.json`.\n * `providers` is always present (empty object when the file has none);\n * `apiKey`/`baseUrl` are the top-level fallbacks a provider inherits when its\n * saved entry omits them.\n */\nexport interface ProviderConfigSnapshot {\n providers: Record<string, ProviderConfig>;\n apiKey?: string;\n baseUrl?: string;\n /** Display language (Config.uiLocale), surfaced so a cross-process change\n * (e.g. the desktop shell) propagates to running webui instances. */\n uiLocale?: string;\n}\n\nexport interface WatchProviderConfigOptions {\n /** Surface non-fatal read/parse/decrypt issues. */\n warn?: (msg: string) => void;\n /** Coalesce bursts of fs events (default 200ms). */\n debounceMs?: number;\n}\n\n/**\n * Read and decrypt the credential slice of a config file. Returns `undefined`\n * when the file is missing or unparseable (the `warn` callback carries the\n * detail); never throws for routine I/O.\n */\nasync function readProviderSnapshot(\n configPath: string,\n vault: SecretVault,\n warn?: (msg: string) => void,\n): Promise<ProviderConfigSnapshot | undefined> {\n let raw: string;\n try {\n raw = await fs.readFile(configPath, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n warn?.(`Could not read ${configPath}: ${(err as Error).message}`);\n }\n return undefined;\n }\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch (err) {\n // A torn read mid-rename can surface as invalid JSON; treat as transient.\n warn?.(`Config at ${configPath} is not valid JSON: ${(err as Error).message}`);\n return undefined;\n }\n const decrypted = decryptConfigSecrets(parsed, vault, warn ? { warn } : {}) as {\n providers?: Record<string, ProviderConfig>;\n apiKey?: string;\n baseUrl?: string;\n uiLocale?: string;\n };\n const snapshot: ProviderConfigSnapshot = {\n providers: decrypted.providers ?? {},\n };\n if (typeof decrypted.apiKey === 'string') snapshot.apiKey = decrypted.apiKey;\n if (typeof decrypted.baseUrl === 'string') snapshot.baseUrl = decrypted.baseUrl;\n if (typeof decrypted.uiLocale === 'string' && decrypted.uiLocale)\n snapshot.uiLocale = decrypted.uiLocale;\n return snapshot;\n}\n\n/** Stable serialization for the no-op guard (key order independent). */\nfunction serializeSnapshot(s: ProviderConfigSnapshot): string {\n return JSON.stringify({\n providers: s.providers,\n apiKey: s.apiKey ?? null,\n baseUrl: s.baseUrl ?? null,\n uiLocale: s.uiLocale ?? null,\n });\n}\n\n/**\n * Watch `configPath` for credential changes and invoke `onChange` with a\n * freshly-decrypted snapshot whenever the `providers`/`apiKey`/`baseUrl` slice\n * actually changes. Returns a `close()` that stops watching.\n *\n * The initial on-disk state is read once to seed the no-op guard but does NOT\n * fire `onChange` \u2014 only subsequent changes do.\n */\nexport function watchProviderConfig(\n configPath: string,\n vault: SecretVault,\n onChange: (snapshot: ProviderConfigSnapshot) => void,\n opts: WatchProviderConfigOptions = {},\n): { close: () => void } {\n const debounceMs = opts.debounceMs ?? 200;\n const warn = opts.warn;\n const base = path.basename(configPath);\n let timer: ReturnType<typeof setTimeout> | undefined;\n let closed = false;\n let lastSerialized: string | undefined;\n\n // Seed the guard with current on-disk content so the first *change* fires\n // rather than re-applying what we already have.\n void readProviderSnapshot(configPath, vault, warn).then((seed) => {\n if (!closed && seed && lastSerialized === undefined) {\n lastSerialized = serializeSnapshot(seed);\n }\n });\n\n let watcher: syncFs.FSWatcher;\n try {\n // Watch the directory (not the file) so atomic-rename events surface\n // reliably across platforms.\n watcher = syncFs.watch(path.dirname(configPath), { recursive: false });\n } catch (err) {\n warn?.(`Provider config watcher could not start: ${(err as Error).message}`);\n return { close: () => {} };\n }\n\n const trigger = (): void => {\n if (closed) return;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = undefined;\n void readProviderSnapshot(configPath, vault, warn).then(\n (next) => {\n if (closed || !next) return;\n const serialized = serializeSnapshot(next);\n if (serialized === lastSerialized) return; // no-op / self-write\n lastSerialized = serialized;\n onChange(next);\n },\n () => {\n // readProviderSnapshot never rejects for routine I/O \u2014 guard only.\n },\n );\n }, debounceMs);\n };\n\n watcher.on('change', (eventType: string, filename: string | Buffer | null) => {\n const name = typeof filename === 'string' ? filename : '';\n if (eventType === 'rename' || eventType === 'change') {\n if (!name || name === base) trigger();\n }\n });\n\n watcher.on('error', (err: Error) => {\n warn?.(`Provider config watcher error: ${err.message}`);\n });\n\n return {\n close: () => {\n closed = true;\n if (timer) clearTimeout(timer);\n watcher.close();\n },\n };\n}\n", "import type { SecretVault } from '../types/secret-vault.js';\n\n/**\n * Config-walking secret helpers, split out of secret-vault.ts so that\n * consumers which only need the walk logic (config loader, provider-config\n * watcher) don't pull the AES-GCM vault implementation \u2014 and its node:crypto\n * imports \u2014 into their bundle chunk. Everything here operates purely through\n * the `SecretVault` interface.\n */\n\n/**\n * Walk a Config-shaped object and decrypt any apiKey-like fields in place,\n * returning a new object. Used by the config loader so the rest of the\n * system never has to know about the wire format.\n *\n * @param warn \u2014 callback for decryption warnings. Defaults to `console.warn`\n * for backward compatibility; pass `logger.warn` when a structured logger\n * is available (preferred in long-running/server contexts).\n */\nexport function decryptConfigSecrets<T>(\n cfg: T,\n vault: SecretVault,\n opts?: { warn?: (msg: string) => void },\n): T {\n const warn = opts?.warn ?? ((msg: string) => console.warn(msg));\n // A single corrupted/malformed encrypted field should not kill the entire\n // config load. Swallow per-field decrypt errors (zero the field so callers\n // see \"missing key\" instead of holding ciphertext) and surface a warning.\n return walk(cfg, vault, (v, key) => {\n try {\n return vault.decrypt(v);\n } catch (err) {\n warn(\n `[secret-vault] Failed to decrypt \"${key}\": ${err instanceof Error ? err.message : err}`,\n );\n return '';\n }\n });\n}\n\nexport function encryptConfigSecrets<T>(\n cfg: T,\n vault: SecretVault,\n _opts?: { warn?: (msg: string) => void },\n): T {\n return walk(cfg, vault, (v) => vault.encrypt(v));\n}\n\nfunction walk<T>(node: T, vault: SecretVault, transform: (s: string, key: string) => string): T {\n if (node === null || node === undefined) return node;\n if (typeof node !== 'object') return node;\n if (Array.isArray(node)) {\n return node.map((item) => walk(item, vault, transform)) as never as T;\n }\n const out: Record<string, unknown> = Object.create(null);\n for (const [k, v] of Object.entries(node as Record<string, unknown>)) {\n if (typeof v === 'string' && isSecretField(k)) {\n out[k] = transform(v, k);\n } else if (typeof v === 'object' && v !== null) {\n out[k] = walk(v, vault, transform);\n } else {\n out[k] = v;\n }\n }\n return out as T;\n}\n\n/**\n * A key is treated as secret-bearing if its name (case-insensitive) contains\n * one of these tokens. Captures common variants like `apiKey`, `authToken`,\n * `refreshToken`, `sessionKey`, `password`, `client_secret`, `bearer`, etc.\n * Use a named field with `isSecret: false` annotation if you must opt out \u2014\n * see `NON_SECRET_OVERRIDES` below.\n */\nconst SECRET_KEY_PATTERN =\n /(?:apikey|api_key|authtoken|auth_token|bearer|secret|password|passwd|pwd|refreshtoken|refresh_token|sessionkey|session_key|access[_-]?token|private[_-]?key)/i;\n\n// Field names that contain the literal substring \"key\" but are not secrets.\n// Keep this list short; the substring rule itself is intentionally narrow.\nconst NON_SECRET_OVERRIDES = new Set(['publickey', 'public_key']);\n\nexport function isSecretField(name: string): boolean {\n const lc = name.toLowerCase();\n if (NON_SECRET_OVERRIDES.has(lc)) return false;\n return SECRET_KEY_PATTERN.test(lc);\n}\n", "import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { EventBus } from '../kernel/events.js';\nimport { decryptConfigSecrets } from '../security/config-secrets.js';\nimport {\n type Config,\n type ConfigLoader,\n DEFAULT_TUI_THINKING_WORD,\n type SyncConfig,\n} from '../types/config.js';\nimport {\n DEFAULT_CONTEXT_WINDOW_MODE_ID,\n isContextWindowModeId,\n listContextWindowModes,\n} from '../types/context-window.js';\nimport {\n DEFAULT_AUTONOMY_CONFIG,\n DEFAULT_CIRCUIT_BREAKER_CONFIG,\n DEFAULT_CONTEXT_CONFIG,\n DEFAULT_SESSION_LOGGING_CONFIG,\n DEFAULT_TOOLS_CONFIG,\n} from '../types/default-config.js';\nimport { ConfigError, ERROR_CODES } from '../types/errors.js';\nimport type { SecretVault } from '../types/secret-vault.js';\nimport { atomicWrite, withFileLock } from '../utils/atomic-write.js';\nimport { type DeepMergeOptions, deepMerge as deepMergeCore } from '../utils/deep-merge.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport { safeParse } from '../utils/safe-json.js';\nimport type { WstackPaths } from '../utils/wstack-paths.js';\n\n/**\n * Surface the OS error code (EACCES, ENOSPC, \u2026) alongside the message in\n * storage.* event payloads. Codes are stable and locale-independent, so\n * they are what dashboards and alerts key on; the message is supplementary.\n */\nfunction storageErrorString(err: unknown): string {\n if (err instanceof Error) {\n const code = (err as NodeJS.ErrnoException).code;\n return code ? `${code}: ${err.message}` : err.message;\n }\n /* v8 ignore next -- defensive: callers only pass fs Error instances */\n return String(err);\n}\n\n/**\n * Defaults express *behavior*, not identity. Provider and model are NOT\n * hardcoded \u2014 they must be resolved at runtime from config + env + the\n * ModelsRegistry. A bare Config returned by this loader will throw when\n * the agent tries to construct a provider, with a message that points\n * users at `wstack init`.\n */\nconst BEHAVIOR_DEFAULTS: Omit<Config, 'provider' | 'model'> = {\n version: 1,\n context: {\n mode: DEFAULT_CONTEXT_WINDOW_MODE_ID,\n warnThreshold: 0.6,\n softThreshold: 0.75,\n hardThreshold: 0.9,\n autoCompact: true,\n preserveK: DEFAULT_CONTEXT_CONFIG.preserveK,\n eliseThreshold: DEFAULT_CONTEXT_CONFIG.eliseThreshold,\n strategy: 'hybrid',\n },\n tools: {\n defaultExecutionStrategy: DEFAULT_TOOLS_CONFIG.defaultExecutionStrategy,\n maxIterations: DEFAULT_TOOLS_CONFIG.maxIterations,\n iterationTimeoutMs: DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,\n maxToolTimeoutMs: DEFAULT_TOOLS_CONFIG.maxToolTimeoutMs,\n sessionTimeoutMs: DEFAULT_TOOLS_CONFIG.sessionTimeoutMs,\n perIterationOutputCapBytes: DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,\n descriptionMode: DEFAULT_TOOLS_CONFIG.descriptionMode,\n disabledTools: DEFAULT_TOOLS_CONFIG.disabledTools as string[],\n autoExtendLimit: DEFAULT_TOOLS_CONFIG.autoExtendLimit,\n restrictToProjectRoot: DEFAULT_TOOLS_CONFIG.restrictToProjectRoot,\n loopDetection: DEFAULT_TOOLS_CONFIG.loopDetection,\n },\n log: { level: 'info' },\n features: {\n mcp: true,\n plugins: true,\n memory: true,\n modelsRegistry: true,\n skills: true,\n prompts: true,\n tokenSavingMode: 'off',\n allowOutsideProjectRoot: true,\n },\n superMemory: {\n enabled: true,\n storage: {\n projectLocal: true,\n directory: '.wrongstack/memories',\n },\n inject: {\n turnContext: true,\n toolResults: true,\n maxHintsPerTool: 4,\n maxCharsPerTool: 1200,\n maxTurnMemories: 8,\n maxCharsPerTurn: 2400,\n minScore: 0.65,\n repeatCooldownMs: 30 * 60_000,\n },\n hygiene: {\n autoAfterSession: true,\n autoOnFileChange: true,\n retentionDays: 90,\n archiveLowConfidenceAfterDays: 30,\n },\n embeddings: {\n enabled: false,\n },\n },\n skills: { readClaudeSkills: true },\n mcpServers: {},\n fallbackAuto: true,\n maxConcurrent: 4,\n // YOLO on by default: auto-approve non-denied tool calls without a per-call\n // prompt. Users\n // who want the per-call prompts set `yolo: false`. Existing installs whose\n // config lacks this key pick it up via fillMissingDefaults; an explicit\n // `false` is preserved by the deep-merge (opt-out respected).\n yolo: true,\n nextPrediction: false,\n hints: true,\n debugStream: false,\n configScope: 'global',\n indexing: {\n onSessionStart: true,\n onEdit: true,\n watchExternal: true,\n debounceMs: 400,\n },\n session: { ...DEFAULT_SESSION_LOGGING_CONFIG },\n autonomy: {\n // 'auto' by default: the agent self-drives (picks the top next-step after\n // each turn). This is a startup default, not a runtime flip \u2014 the\n // autoProceedMaxIterations cap + autoProceedDelayMs cooldown + Ctrl+C /\n // [GOAL_COMPLETE] stops remain in force. Explicit 'off'\n // in a config file is preserved (opt-out respected).\n defaultMode: 'auto',\n autoProceedDelayMs: DEFAULT_AUTONOMY_CONFIG.autoProceedDelayMs,\n autoProceedMaxIterations: 50,\n autonomyNextPrompt: 'auto {{suggestion}}',\n terminalTitleAnimation: true,\n // Mirrored from the top-level yolo default so the autonomy subsystem\n // (which reads autonomy.yolo) stays consistent with config.yolo.\n yolo: true,\n streamFleet: true,\n chime: false,\n confirmExit: true,\n mouseMode: false,\n enhance: true,\n enhanceDelayMs: 60_000,\n enhanceLanguage: 'original',\n statuslineMode: 'detailed',\n thinkingWord: DEFAULT_TUI_THINKING_WORD,\n },\n circuitBreaker: { ...DEFAULT_CIRCUIT_BREAKER_CONFIG },\n modelRuntime: {\n // `effort` is intentionally undefined by default. Leaving it unset lets\n // each model use its provider-recommended reasoning effort (or none at\n // all) instead of forcing an opinionated value that may be unsupported,\n // silently omitted, and surfaced as a per-request warning. Users who\n // want a specific effort can opt in via `/settings` or the WebUI panel.\n reasoning: { mode: 'auto' },\n cache: {},\n },\n};\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction cloneJsonValue<T>(value: T): T {\n return structuredClone(value);\n}\n\nfunction fillMissingDefaults(\n target: Record<string, unknown>,\n defaults: Record<string, unknown>,\n): { value: Record<string, unknown>; changed: boolean } {\n const value = cloneJsonValue(target);\n const changed = fillMissingDefaultsInPlace(value, defaults);\n return { value, changed };\n}\n\nfunction fillMissingDefaultsInPlace(\n target: Record<string, unknown>,\n defaults: Record<string, unknown>,\n): boolean {\n let changed = false;\n for (const [key, defaultValue] of Object.entries(defaults)) {\n if (!Object.hasOwn(target, key)) {\n target[key] = cloneJsonValue(defaultValue);\n changed = true;\n continue;\n }\n const current = target[key];\n if (isPlainRecord(current) && isPlainRecord(defaultValue)) {\n changed = fillMissingDefaultsInPlace(current, defaultValue) || changed;\n }\n }\n return changed;\n}\n\n/** Parse a boolean-ish env var: \"0\"/\"false\"/\"no\"/\"off\" \u2192 false, anything else \u2192 true. */\nfunction envBool(v: string): boolean {\n return !/^(0|false|no|off)$/i.test(v.trim());\n}\n\nfunction envBoolOptional(v: string | undefined): boolean {\n return v !== undefined && envBool(v);\n}\n\nconst LOG_LEVELS = new Set<Config['log']['level']>(['error', 'warn', 'info', 'debug', 'trace']);\n\nfunction envLogLevel(v: string): Config['log']['level'] {\n return LOG_LEVELS.has(v as Config['log']['level']) ? (v as Config['log']['level']) : 'info';\n}\n\nconst ENV_MAP: Record<string, (cfg: PartialConfig, val: string) => void> = {\n WRONGSTACK_PROVIDER: (c, v) => {\n c.provider = v;\n if (c._envSource === undefined) c._envSource = new Set();\n c._envSource.add('provider');\n },\n WRONGSTACK_MODEL: (c, v) => {\n c.model = v;\n if (c._envSource === undefined) c._envSource = new Set();\n c._envSource.add('model');\n },\n WRONGSTACK_API_KEY: (c, v) => {\n c.apiKey = v;\n if (c._envSource === undefined) c._envSource = new Set();\n c._envSource.add('apiKey');\n },\n WRONGSTACK_BASE_URL: (c, v) => {\n c.baseUrl = v;\n if (c._envSource === undefined) c._envSource = new Set();\n c._envSource.add('baseUrl');\n },\n WRONGSTACK_LOG_LEVEL: (c, v) => {\n /* v8 ignore next -- defensive: config defaults always seed c.log before env handlers run */\n if (!c.log) c.log = { level: 'info' };\n c.log.level = envLogLevel(v);\n },\n WRONGSTACK_INDEX_ON_START: (c, v) => {\n c.indexing = { ...defaultIndexing, ...c.indexing, onSessionStart: envBool(v) };\n },\n WRONGSTACK_INDEX_ON_EDIT: (c, v) => {\n c.indexing = { ...defaultIndexing, ...c.indexing, onEdit: envBool(v) };\n },\n WRONGSTACK_INDEX_WATCH: (c, v) => {\n c.indexing = { ...defaultIndexing, ...c.indexing, watchExternal: envBool(v) };\n },\n};\n\nconst defaultIndexing = {\n onSessionStart: true,\n onEdit: true,\n watchExternal: true,\n debounceMs: 400,\n} as const;\n\ntype PartialConfig = Partial<Config> & {\n providers?: Record<\n string,\n { apiKey?: string | undefined; baseUrl?: string | undefined; type?: string | undefined }\n >;\n /** Fields that came from environment variables \u2014 must not be persisted. */\n _envSource?: Set<string> | undefined;\n};\n\n/**\n * Top-level config keys a REPO-COMMITTED `<project>/.wrongstack/config.json`\n * (the `inProjectConfig` layer) IS permitted to set. The in-project config\n * is attacker-controllable (it ships inside a cloned/pulled repository), so\n * every other field is denied by default. Anything not in this list is\n * stripped by `stripUnsafeInProjectFields()` before the merge.\n *\n * Why an allow-list and not a deny-list? A deny-list of N known-bad keys is\n * structurally incomplete: any new field added to `Config` without a matching\n * edit to the deny list silently becomes attacker-controllable, and the next\n * field that carries an executable string or a credential immediately turns\n * `<project>/.wrongstack/config.json` into an RCE / secret-exfiltration\n * vector the moment someone clones a malicious repo. An allow-list inverts\n * that \u2014 new fields are denied by default and must be explicitly added, so a\n * forgotten update is a safe default instead of an unsafe one.\n *\n * Each entry below is a benign user-preference that a project author may\n * legitimately want to pin for everyone who works in the repo:\n *\n * - `version` \u2014 schema marker required for any config merge.\n * - `model` \u2014 model id (also settable via env / CLI).\n * - `cwd` \u2014 working-directory hint (UX, not a permission).\n * - `context` \u2014 compaction thresholds, mode, preserveK.\n * - `tools` \u2014 iteration / timeouts / restrictToProjectRoot.\n * - `features` \u2014 feature toggles (display-only side effects).\n * - `autonomy` \u2014 autoProceedDelayMs, thinkingWord.\n * - `indexing` \u2014 onSessionStart / onEdit / debounceMs.\n * - `session` \u2014 audit level + sampling.\n * - `log` \u2014 log level.\n * - `launch` \u2014 saved launch prefs.\n * - `nextPrediction` \u2014 toggle `/next` after-turn suggestions.\n * - `hints` \u2014 toggle startup hints.\n * - `debugStream` \u2014 verbose SSE dump (noisy, not security-sensitive).\n * - `configScope` \u2014 where settings persist.\n * - `maxConcurrent` \u2014 fleet concurrency limit.\n * - `fallbackModels` \u2014 model references tried on 429/5xx.\n * - `fallbackProfiles` \u2014 named fallback chains.\n * - `favoriteModels` \u2014 model references preferred for display/routing.\n * - `favoriteModelsOnly` \u2014 restrict auto-derived chains to favorite models.\n * - `fallbackAuto` \u2014 derived-fallback toggle.\n * - `models` \u2014 custom model definitions (data, not code).\n * - `modelMatrix` \u2014 per-task model matrix.\n * - `circuitBreaker` \u2014 process circuit-breaker config (process gating).\n * - `adaptiveConcurrency` \u2014 adaptive concurrency controller.\n * - `modelRuntime` \u2014 runtime reasoning/cache/parameters.\n * - `superMemory` \u2014 benign local memory storage/injection/hygiene knobs.\n *\n * Fields deliberately NOT in the allow-list (and therefore always stripped\n * from `<project>/.wrongstack/config.json`) \u2014 see `KNOWN_DENIED_IN_PROJECT`\n * below for the reason each is unsafe.\n */\nconst IN_PROJECT_ALLOWED_KEYS: ReadonlySet<string> = new Set([\n 'version',\n 'model',\n 'cwd',\n 'context',\n 'tools',\n 'features',\n 'superMemory',\n 'skills',\n 'autonomy',\n 'indexing',\n 'session',\n 'log',\n 'launch',\n 'nextPrediction',\n 'hints',\n 'debugStream',\n 'configScope',\n 'maxConcurrent',\n 'uiLocale',\n 'fallbackModels',\n 'fallbackProfiles',\n 'favoriteModels',\n 'favoriteModelsOnly',\n 'fallbackAuto',\n 'models',\n 'modelMatrix',\n 'circuitBreaker',\n 'adaptiveConcurrency',\n 'modelRuntime',\n]);\n\n/**\n * Top-level config keys that exist on `Config` but MUST NEVER be settable\n * from a repo-committed `<project>/.wrongstack/config.json`. Each entry pairs\n * the field name with the specific way a malicious repo would abuse it. This\n * list is documentation + exhaustiveness checking; the runtime enforcement\n * is the *allow-list* above (anything not in the allow-list is stripped).\n *\n * - `provider` \u2014 set provider id to a custom / evil implementation \u2192\n * intercepts every prompt and response.\n * - `apiKey` \u2014 overrides the user's API key with attacker-controlled\n * value, exfiltrating prompts to the attacker.\n * - `baseUrl` \u2014 redirects the provider endpoint so the user's real\n * decrypted API key is sent to the attacker's server.\n * - `providers` \u2014 per-provider `apiKey`/`baseUrl`/`oauthConfig` map,\n * same endpoint-redirect + secret-exfiltration vector.\n * - `mcpServers` \u2014 arbitrary `command` + `args` + `env` spawned at boot.\n * - `hooks` \u2014 shell command arrays attached to lifecycle events.\n * - `plugins` \u2014 npm package names dynamically loaded into the agent\n * process at boot.\n * - `sync` \u2014 carries `githubToken` (credential) and the repo\n * the user's sync push targets.\n * - `yolo` \u2014 flips off every permission confirmation prompt so a\n * malicious agent turn can run `bash` / `write` /\n * `install` without user approval.\n * - `extensions` \u2014 per-plugin namespaced config; the LSP plugin's\n * `servers[].command` is spawned on autoStart, and\n * arbitrary plugin configs can carry their own\n * credential / command fields \u2192 RCE / secret exposure.\n * - `hq` \u2014 carries `token` (HQ client credential) and `url`\n * (HQ endpoint, similar to `baseUrl`).\n * - `git` \u2014 carries `identity` (commit author/committer env\n * injection); a malicious repo could spoof who appears\n * to have authored the user's commits.\n */\nconst KNOWN_DENIED_IN_PROJECT: ReadonlyArray<{ key: string; reason: string }> = [\n { key: 'provider', reason: 'Provider id override; can intercept prompts/responses.' },\n { key: 'apiKey', reason: 'Overrides user API key; exfiltrates prompts.' },\n { key: 'baseUrl', reason: 'Redirects provider endpoint; leaks real API key.' },\n { key: 'providers', reason: 'Per-provider apiKey/baseUrl/oauthConfig; same redirect/exfil.' },\n { key: 'mcpServers', reason: 'Arbitrary command/args/env spawned at boot (RCE).' },\n { key: 'hooks', reason: 'Shell command arrays on lifecycle events (RCE).' },\n { key: 'plugins', reason: 'Dynamic npm package load at boot (RCE).' },\n { key: 'sync', reason: 'Carries githubToken credential and target repo.' },\n { key: 'yolo', reason: 'Disables all permission confirmation prompts.' },\n { key: 'extensions', reason: 'Per-plugin config can carry command/credential fields.' },\n { key: 'hq', reason: 'Carries HQ client token credential and endpoint URL.' },\n { key: 'acp', reason: 'Per-agent ACP command/args/env override \u2192 arbitrary command exec (RCE).' },\n {\n key: 'fleet',\n reason:\n 'Fleet supervision knobs: a repo-committed config could enable autonomous subagent spawning/steering/termination and mailbox traffic on the victim machine.',\n },\n {\n key: 'git',\n reason:\n 'Carries git.identity (GIT_AUTHOR_*/GIT_COMMITTER_* injection): a repo-committed config could spoof the author identity written into the victim\\'s commit history (impersonation).',\n },\n];\n\n/**\n * Every top-level key that exists on the `Config` interface. This is the\n * *ground truth* used by `assertInProjectAllowListComplete()` to detect when\n * a new field has been added to `Config` without a corresponding decision\n * about whether it is safe for an attacker-controllable source to set it.\n *\n * Each entry must appear in EXACTLY ONE of:\n * - `IN_PROJECT_ALLOWED_KEYS` \u2014 explicitly safe for in-project config\n * - `KNOWN_DENIED_IN_PROJECT` \u2014 explicitly documented as unsafe\n *\n * The drift-check function below throws at runtime / test time when this\n * invariant is violated, so a forgotten update fails loudly instead of\n * silently widening the attack surface.\n */\nconst KNOWN_CONFIG_TOP_LEVEL_KEYS: ReadonlySet<string> = new Set([\n 'version',\n 'provider',\n 'model',\n 'apiKey',\n 'baseUrl',\n 'maxConcurrent',\n 'uiLocale',\n 'providers',\n 'models',\n 'modelMatrix',\n 'context',\n 'tools',\n 'mcpServers',\n 'fallbackModels',\n 'fallbackProfiles',\n 'favoriteModels',\n 'favoriteModelsOnly',\n 'fallbackAuto',\n 'hooks',\n 'plugins',\n 'log',\n 'features',\n 'superMemory',\n 'skills',\n 'yolo',\n 'nextPrediction',\n 'cwd',\n 'autonomy',\n 'hints',\n 'debugStream',\n 'configScope',\n 'indexing',\n 'circuitBreaker',\n 'adaptiveConcurrency',\n 'launch',\n 'session',\n 'modelRuntime',\n 'hq',\n 'sync',\n 'extensions',\n 'acp',\n 'fleet',\n 'git',\n]);\n\n/**\n * Assert that the allow-list and deny-list together cover every top-level\n * field of `Config`. Throws on drift so the failure is loud at test time and\n * at first boot, not a silent widening of the attack surface. Exported so\n * tests (and any consumer building tooling on top of this) can call it\n * explicitly; `stripUnsafeInProjectFields()` also calls it lazily on its\n * first invocation so the guarantee is structural, not test-only.\n *\n * The check is two-sided:\n * 1. Every key in `KNOWN_CONFIG_TOP_LEVEL_KEYS` is either allowed or\n * explicitly documented as denied (catches: \"added a new field but\n * forgot to decide\").\n * 2. Every entry in `KNOWN_DENIED_IN_PROJECT` actually exists on Config\n * (catches: \"left a stale denied-field entry behind after a rename\").\n * 3. The two lists are disjoint (catches: \"put the same field in both\n * lists; allow-list silently wins and the deny docs lie\").\n */\nexport function assertInProjectAllowListComplete(): void {\n const missingFromBoth: string[] = [];\n for (const key of KNOWN_CONFIG_TOP_LEVEL_KEYS) {\n if (IN_PROJECT_ALLOWED_KEYS.has(key)) continue;\n const denied = KNOWN_DENIED_IN_PROJECT.find((d) => d.key === key);\n if (!denied) missingFromBoth.push(key);\n }\n const staleDenials = KNOWN_DENIED_IN_PROJECT.filter(\n (d) => !KNOWN_CONFIG_TOP_LEVEL_KEYS.has(d.key),\n ).map((d) => d.key);\n const duplicate = KNOWN_DENIED_IN_PROJECT.filter((d) => IN_PROJECT_ALLOWED_KEYS.has(d.key)).map(\n (d) => d.key,\n );\n\n const problems: string[] = [];\n if (missingFromBoth.length > 0) {\n problems.push(\n `new Config field(s) not classified as allowed or denied for in-project config: ` +\n missingFromBoth.join(', ') +\n '. Add each to IN_PROJECT_ALLOWED_KEYS (if safe) or KNOWN_DENIED_IN_PROJECT (with a reason).',\n );\n }\n if (staleDenials.length > 0) {\n problems.push(\n `KNOWN_DENIED_IN_PROJECT references keys that no longer exist on Config: ` +\n staleDenials.join(', ') +\n '. Remove them or restore the field on Config.',\n );\n }\n if (duplicate.length > 0) {\n problems.push(\n `field(s) appear in BOTH IN_PROJECT_ALLOWED_KEYS and KNOWN_DENIED_IN_PROJECT: ` +\n duplicate.join(', ') +\n '. The allow-list wins at runtime; remove from one of the two.',\n );\n }\n if (problems.length > 0) {\n throw new Error(\n `stripUnsafeInProjectFields drift check failed:\\n - ${problems.join('\\n - ')}`,\n );\n }\n}\n\nlet driftChecked = false;\n\n/**\n * Remove forbidden top-level keys from a repo-committed in-project config\n * before it is merged. Returns a new object; the original is not mutated.\n * Emits a warning (and a `config.read` failure-style event) naming the\n * stripped keys so the behavior is observable rather than silent.\n *\n * On first invocation, runs `assertInProjectAllowListComplete()` to verify\n * the allow-list + deny-list together still cover every top-level field of\n * `Config`. The check is idempotent and the result is memoized so the cost\n * is paid at most once per process. The assertion throws on drift, which\n * surfaces the issue at boot in production and at first test invocation in\n * CI \u2014 both observable, never silent.\n */\nexport function stripUnsafeInProjectFields(\n inProject: PartialConfig,\n sourcePath: string,\n warn: (msg: string) => void = (msg) => console.warn(msg),\n): PartialConfig {\n if (!driftChecked) {\n assertInProjectAllowListComplete();\n driftChecked = true;\n }\n const stripped: string[] = [];\n const out: PartialConfig = {};\n for (const [k, v] of Object.entries(inProject)) {\n if (IN_PROJECT_ALLOWED_KEYS.has(k)) {\n (out as Record<string, unknown>)[k] = v;\n continue;\n }\n stripped.push(k);\n }\n\n // Nested strip: `tools` is allow-listed (it carries benign limits), but\n // `tools.exec.allow` EXPANDS what the agent may execute \u2014 never honor that\n // from an attacker-controllable repo config. Remove it while preserving\n // `tools.exec.deny` (removing commands only narrows, so it is always safe).\n //\n // `tools.exec.danger.bypass` is the same threat model: a bypass list\n // weakens the heuristic danger gate on a per-rule basis. A malicious\n // repo that auto-loaded its own bypass rules could silently disarm\n // safety checks for anyone who clones it. Same strip semantics.\n //\n // Clone the affected objects so the caller's input is not mutated.\n const outTools = (out as Record<string, unknown>)['tools'];\n if (outTools && typeof outTools === 'object') {\n const execCfg = (outTools as Record<string, unknown>)['exec'];\n if (execCfg && typeof execCfg === 'object') {\n const hasAllow = 'allow' in (execCfg as Record<string, unknown>);\n const hasDanger = 'danger' in (execCfg as Record<string, unknown>);\n if (hasAllow || hasDanger) {\n const clonedExec: Record<string, unknown> = { ...(execCfg as Record<string, unknown>) };\n if (hasAllow) {\n delete clonedExec['allow'];\n stripped.push('tools.exec.allow');\n }\n if (hasDanger) {\n // Strip the whole `danger` object \u2014 `ExecDangerConfig` only\n // has `bypass` today, and bypass is the unsafe field, so\n // stripping the parent is equivalent and forward-compat with\n // any future fields added under `danger` that we don't yet\n // know about. Better to be conservative: anything new under\n // `danger` from a repo config is rejected until it gets an\n // explicit allow decision.\n delete clonedExec['danger'];\n stripped.push('tools.exec.danger');\n }\n (out as Record<string, unknown>)['tools'] = {\n ...(outTools as Record<string, unknown>),\n exec: clonedExec,\n };\n }\n }\n }\n\n // Nested strip: `skills` is allow-listed (benign prefs), but `skills.extraDirs`\n // points the loader at ARBITRARY directories to scan and inject into the\n // prompt \u2014 never honor that from an attacker-controllable repo config\n // (prompt-injection / read-into-prompt vector). `readClaudeSkills` and `mode`\n // are safe prefs and survive. `skills.registryUrl` is also stripped: it points\n // the skill-search HTTP client at an arbitrary URL whose parsed response flows\n // into the prompt (SSRF + prompt-injection vector). Safe prefs\n // (`readClaudeSkills`, `mode`, `eagerMaxChars`) survive.\n const outSkills = (out as Record<string, unknown>)['skills'];\n if (outSkills && typeof outSkills === 'object') {\n const skillsRec = outSkills as Record<string, unknown>;\n const needsClone = 'extraDirs' in skillsRec || 'registryUrl' in skillsRec;\n if (needsClone) {\n const clonedSkills = { ...skillsRec };\n if ('extraDirs' in clonedSkills) {\n delete clonedSkills['extraDirs'];\n stripped.push('skills.extraDirs');\n }\n if ('registryUrl' in clonedSkills) {\n delete clonedSkills['registryUrl'];\n stripped.push('skills.registryUrl');\n }\n (out as Record<string, unknown>)['skills'] = clonedSkills;\n }\n }\n\n // `superMemory` is benign except for `storage.directory`: that value is a\n // filesystem write destination and accepts absolute paths in trusted user\n // config. A repo-committed config must not redirect memory logs outside the\n // project, so only the destination override is stripped.\n const outSuperMemory = (out as Record<string, unknown>)['superMemory'];\n if (outSuperMemory && typeof outSuperMemory === 'object') {\n const storage = (outSuperMemory as Record<string, unknown>)['storage'];\n if (storage && typeof storage === 'object' && 'directory' in (storage as Record<string, unknown>)) {\n const clonedStorage = { ...(storage as Record<string, unknown>) };\n delete clonedStorage['directory'];\n (out as Record<string, unknown>)['superMemory'] = {\n ...(outSuperMemory as Record<string, unknown>),\n storage: clonedStorage,\n };\n stripped.push('superMemory.storage.directory');\n }\n }\n\n if (stripped.length > 0) {\n warn(\n JSON.stringify({\n level: 'warn',\n event: 'config.in_project_unsafe_fields_ignored',\n path: sourcePath,\n ignoredKeys: stripped,\n message:\n `Ignored ${stripped.length} field(s) from the repo-committed config ` +\n `\"${sourcePath}\": ${stripped.join(', ')}. ` +\n `Only a small allow-list of benign preferences (model, context, tools limits, ` +\n `features, \u2026) may be set by <project>/.wrongstack/config.json. ` +\n `Everything else must live in your personal ~/.wrongstack/config.json.`,\n timestamp: new Date().toISOString(),\n }),\n );\n }\n return out;\n}\n\n/**\n * Compare two absolute filesystem paths for identity. Normalizes separators and\n * `.`/`..` segments via `path.resolve`, and folds case on win32 (and darwin,\n * whose default APFS/HFS+ volumes are case-insensitive) so the same file\n * reached through differently-cased drive/dir spellings still compares equal \u2014\n * the same Windows path-casing hazard that bit the core token symbols.\n */\nfunction samePath(a: string, b: string): boolean {\n let ra = path.resolve(a);\n let rb = path.resolve(b);\n if (process.platform === 'win32' || process.platform === 'darwin') {\n ra = ra.toLowerCase();\n rb = rb.toLowerCase();\n }\n return ra === rb;\n}\n\n/**\n * Config-layer deep merge \u2014 delegates to the shared utility with\n * `arrayMode: 'concat-primitives'` and optional debug logging for\n * non-primitive array replacements.\n */\nfunction deepMerge<T, TPatch extends object>(base: T, patch: TPatch): T {\n const opts: DeepMergeOptions = { arrayMode: 'concat-primitives' };\n if (envBoolOptional(process.env.WRONGSTACK_DEBUG_CONFIG)) {\n opts.onNonPrimitiveArrayReplace = (key, existingLen, patchLen) => {\n console.warn(\n `[config] Non-primitive array for \"${key}\" replaced (global + local config merge). ` +\n `Global entries: ${existingLen}, local entries: ${patchLen}.`,\n );\n };\n }\n return deepMergeCore(\n base as Record<string, unknown>,\n patch as Record<string, unknown>,\n opts,\n ) as T;\n}\n\n/**\n * A single config source. Higher priority wins in merges.\n * Sources are applied in priority order (lowest first), so a source\n * with priority=10 overrides one with priority=1.\n */\nexport interface ConfigSource {\n /** Unique name for debugging and error messages. */\n name: string;\n /** Lower numbers merge first, higher numbers override lower. Default: 50. */\n priority?: number | undefined;\n /**\n * Read the raw config patch. Return an empty object if unavailable.\n * Errors are surfaced but do not abort loading \u2014 the source is skipped.\n */\n read(): Promise<Partial<Config>>;\n}\n\nexport interface ConfigLoaderOptions {\n paths: WstackPaths;\n strict?: boolean | undefined;\n vault?: SecretVault | undefined;\n /** Extra sources merged after the built-in layers. */\n sources?: ConfigSource[] | undefined;\n events?: EventBus;\n traceId?: string;\n}\n\ninterface MemoizedConfigSource {\n mtimeMs: number | null;\n value: PartialConfig;\n}\n\nexport class DefaultConfigLoader implements ConfigLoader {\n private readonly paths: WstackPaths;\n private readonly strict: boolean;\n private readonly vault: SecretVault | undefined;\n private readonly extraSources: ConfigSource[];\n private readonly events: EventBus | undefined;\n private readonly traceId: string | undefined;\n private readonly jsonCache = new Map<string, MemoizedConfigSource>();\n\n constructor(opts: ConfigLoaderOptions) {\n this.paths = opts.paths;\n this.strict = opts.strict ?? false;\n this.vault = opts.vault;\n this.extraSources = opts.sources ?? [];\n this.events = opts.events;\n this.traceId = opts.traceId;\n }\n\n async load(\n opts: { cliFlags?: Partial<Config> | undefined; cwd?: string | undefined; skipIdentityValidation?: boolean } = {},\n ): Promise<Config> {\n let cfg: PartialConfig = { ...BEHAVIOR_DEFAULTS } as PartialConfig;\n\n // Materialize behavior/settings defaults into the trusted global config\n // before env vars or CLI flags are applied, so first boot creates a real\n // ~/.wrongstack/config.json without persisting ephemeral overrides.\n await this.ensureGlobalDefaults();\n\n // Layer 2, 3 & 3b: global + project-local + in-project config \u2014 read in parallel.\n // inProjectConfig (<project>/.wrongstack/config.json) merges AFTER\n // projectLocalConfig so it takes priority (user-intended > auto-cached).\n //\n // When the project root *is* the user's home (e.g. launching from `~`),\n // `<projectRoot>/.wrongstack/config.json` resolves to the very same file as\n // the trusted global config. Reading it again as the untrusted in-project\n // layer would strip `provider`/`apiKey`/\u2026 from the user's *own* file and\n // emit a spurious `config.in_project_unsafe_fields_ignored` warning \u2014 even\n // though the trusted global layer already merged those fields. Skip the\n // in-project read entirely on collision so trust isn't applied to a file\n // the user fully controls.\n const inProjectCollides =\n samePath(this.paths.inProjectConfig, this.paths.globalConfig) ||\n samePath(this.paths.inProjectConfig, this.paths.projectLocalConfig);\n const [global, local, inProject] = await Promise.all([\n this.readJson(this.paths.globalConfig),\n this.readJson(this.paths.projectLocalConfig),\n inProjectCollides\n ? Promise.resolve({} as PartialConfig)\n : this.readJson(this.paths.inProjectConfig),\n ]);\n cfg = deepMerge(cfg, global);\n cfg = deepMerge(cfg, local);\n // The in-project config is repo-committed and therefore attacker-\n // controllable. Strip credential/endpoint/code-execution fields before\n // merging so a malicious repo cannot redirect the provider endpoint\n // (API-key exfiltration) or auto-run an MCP server / hook (RCE) on launch.\n cfg = deepMerge(cfg, stripUnsafeInProjectFields(inProject, this.paths.inProjectConfig));\n\n // Layer 4: env vars\n for (const [key, fn] of Object.entries(ENV_MAP)) {\n const v = process.env[key];\n if (v) fn(cfg, v);\n }\n\n // Layer 5: extra sources \u2014 sorted by priority (lowest first).\n // When priorities tie, sort by name for deterministic order.\n const sorted = [...this.extraSources].sort((a, b) => {\n const pd = (a.priority ?? 50) - (b.priority ?? 50);\n if (pd !== 0) return pd;\n return a.name.localeCompare(b.name);\n });\n for (const src of sorted) {\n try {\n const patch = await src.read();\n if (patch && Object.keys(patch).length > 0) {\n cfg = deepMerge(cfg, patch);\n }\n } catch (err) {\n // Best-effort: skip failing sources so one bad source doesn't block boot.\n console.warn(\n JSON.stringify({\n level: 'warn',\n event: 'config.source_load_failed',\n source: src.name,\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }),\n );\n }\n }\n\n // Layer 6: CLI flags\n if (opts.cliFlags) {\n cfg = deepMerge(cfg, opts.cliFlags);\n }\n\n // Decrypt apiKey-like fields if a vault is configured.\n if (this.vault) {\n cfg = decryptConfigSecrets(cfg, this.vault);\n }\n\n // Multi-key resolution: when a provider has `apiKeys[]` configured,\n // mirror the active entry into `apiKey` so downstream construction\n // code (provider registry, wire adapters) needs no changes. Honors\n // `activeKey` (by label), else falls back to the first entry. A\n // pre-existing `apiKey` set by env/CLI flags wins so an explicit\n // override still beats the saved list.\n if (cfg.providers) {\n for (const pcfg of Object.values(cfg.providers)) {\n if (!pcfg || typeof pcfg !== 'object') continue;\n const rawKeys = (pcfg as { apiKeys?: unknown | undefined }).apiKeys;\n if (!Array.isArray(rawKeys) || rawKeys.length === 0) continue;\n // Each apiKeys entry came from arbitrary JSON. Filter to entries\n // that actually have a string apiKey + label so a malformed array\n // (null entry, missing field) doesn't crash the .find / chosen.apiKey\n // path below.\n const keys = rawKeys.filter(\n (k): k is { label: string; apiKey: string } =>\n !!k &&\n typeof k === 'object' &&\n typeof (k as { label?: unknown | undefined }).label === 'string' &&\n typeof (k as { apiKey?: unknown | undefined }).apiKey === 'string',\n );\n if (keys.length === 0) continue;\n const existing = (pcfg as { apiKey?: string | undefined }).apiKey;\n if (existing && existing.length > 0) continue;\n const activeLabel = (pcfg as { activeKey?: string | undefined }).activeKey;\n const chosen = activeLabel\n ? (keys.find((k) => k.label === activeLabel) ?? keys[0])\n : keys[0];\n if (chosen?.apiKey) {\n (pcfg as { apiKey?: string | undefined }).apiKey = chosen.apiKey;\n }\n }\n }\n\n this.validateBehavior(cfg);\n if (this.strict && !opts.skipIdentityValidation) {\n this.validateIdentity(cfg);\n }\n // In strict mode, validateIdentity has confirmed provider/model are set;\n // it's safe to assert the full Config contract. In non-strict mode the\n // caller (e.g. early-boot wizard) accepts a Partial and constructs the\n // provider later, so we deliberately return without the cast.\n return Object.freeze(cfg) as Config;\n }\n\n private async ensureGlobalDefaults(): Promise<void> {\n const fp = this.paths.globalConfig;\n const t0 = Date.now();\n try {\n await withFileLock(fp, async () => {\n let parsed: Record<string, unknown>;\n try {\n const raw = await fs.readFile(fp, 'utf8');\n const result = safeParse<unknown>(raw);\n if (!result.ok || !isPlainRecord(result.value)) {\n // readJson() below owns the user-visible parse warning. Do not\n // overwrite a malformed config while trying to seed defaults.\n return;\n }\n parsed = result.value;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n this.events?.emit('storage.error', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'ensure_defaults',\n outcome: 'failure',\n error: storageErrorString(err),\n recoverable: false,\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n console.warn(\n JSON.stringify({\n level: 'warn',\n event: 'config.defaults_read_failed',\n path: fp,\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }),\n );\n return;\n }\n parsed = {};\n }\n\n const { value, changed } = fillMissingDefaults(\n parsed,\n BEHAVIOR_DEFAULTS as Record<string, unknown>,\n );\n if (!changed) return;\n\n await atomicWrite(fp, JSON.stringify(value, null, 2), { mode: 0o600 });\n this.events?.emit('storage.write', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'ensure_defaults',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n });\n } catch (err) {\n this.events?.emit('storage.error', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'ensure_defaults',\n outcome: 'failure',\n error: storageErrorString(err),\n recoverable: false,\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n console.warn(\n JSON.stringify({\n level: 'warn',\n event: 'config.defaults_write_failed',\n path: fp,\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }),\n );\n }\n }\n\n /**\n * Persist a sync config to ~/.wrongstack/sync.json, with the token encrypted\n * by the vault (if provided). The file is isolated from the main config\n * hierarchy to prevent accidental commits.\n */\n async persistSyncConfig(cfg: SyncConfig): Promise<void> {\n let toWrite = { ...cfg };\n if (this.vault && toWrite.githubToken && !toWrite.githubToken.startsWith('enc:')) {\n // Re-encrypt if plaintext (e.g. came from in-memory configStore update\n // rather than direct /sync enable call). Idempotent for already-encrypted.\n toWrite = { ...toWrite, githubToken: this.vault.encrypt(toWrite.githubToken) };\n }\n const fp = this.paths.syncConfig;\n const t0 = Date.now();\n try {\n await atomicWrite(fp, JSON.stringify(toWrite, null, 2), { mode: 0o600 });\n this.events?.emit('storage.write', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'persist_sync',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n } catch (err) {\n this.events?.emit('storage.error', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'persist_sync',\n outcome: 'failure',\n error: storageErrorString(err),\n recoverable: false,\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n throw err;\n }\n }\n\n /**\n * Read ~/.wrongstack/sync.json (encrypted GitHub token storage) and decrypt\n * the token if a vault is available. Returns null if the file doesn't exist.\n * This is separate from main config loading because sync.json is intentionally\n * isolated \u2014 it should never be part of project-local or env-driven config.\n */\n async loadSyncConfig(): Promise<SyncConfig | null> {\n const fp = this.paths.syncConfig;\n const t0 = Date.now();\n try {\n const raw = await fs.readFile(fp, 'utf8');\n const parsed = safeParse<SyncConfig>(raw);\n if (!parsed.ok || !parsed.value) {\n this.events?.emit('storage.read', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'load_sync',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'parse error or empty file',\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return null;\n }\n\n // Decrypt the token if vault is available (field name matches secret pattern)\n if (this.vault) {\n const decrypted = decryptConfigSecrets({ sync: parsed.value } as PartialConfig, this.vault);\n const result = (decrypted as { sync: SyncConfig }).sync ?? null;\n this.events?.emit('storage.read', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'load_sync',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return result;\n }\n this.events?.emit('storage.read', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'load_sync',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return parsed.value;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n // Non-ENOENT failures (EACCES, ENOSPC, etc.) \u2014 emit storage.read failure, then return null\n this.events?.emit('storage.read', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'load_sync',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: storageErrorString(err),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n console.warn(\n JSON.stringify({\n level: 'warn',\n event: 'config.sync_load_failed',\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }),\n );\n return null;\n }\n }\n\n private async readJson(file: string): Promise<PartialConfig> {\n const t0 = Date.now();\n let mtimeMs: number | null = null;\n try {\n const stat = await fs.stat(file);\n mtimeMs = stat.mtimeMs;\n const cached = this.jsonCache.get(file);\n if (cached && cached.mtimeMs === mtimeMs) {\n return structuredClone(cached.value);\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n this.jsonCache.set(file, { mtimeMs: null, value: {} });\n return {};\n }\n this.events?.emit('storage.read', {\n sessionId: '~config~',\n store: 'config',\n filePath: file,\n operation: 'read_json',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: storageErrorString(err),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n console.warn(\n JSON.stringify({\n level: 'warn',\n event: 'config.read_failed',\n path: file,\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }),\n );\n return {};\n }\n\n let raw: string;\n try {\n raw = await fs.readFile(file, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n this.events?.emit('storage.read', {\n sessionId: '~config~',\n store: 'config',\n filePath: file,\n operation: 'read_json',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: storageErrorString(err),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n console.warn(\n JSON.stringify({\n level: 'warn',\n event: 'config.read_failed',\n path: file,\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }),\n );\n }\n this.jsonCache.set(file, { mtimeMs: null, value: {} });\n return {};\n }\n const parsed = safeParse<PartialConfig>(raw);\n if (!parsed.ok || !parsed.value) {\n this.events?.emit('storage.read', {\n sessionId: '~config~',\n store: 'config',\n filePath: file,\n operation: 'read_json',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'parse error or empty file',\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n console.warn(\n JSON.stringify({\n level: 'warn',\n event: 'config.parse_failed',\n path: file,\n message: 'invalid JSON \u2014 falling back to defaults for this layer',\n timestamp: new Date().toISOString(),\n }),\n );\n return {};\n }\n this.jsonCache.set(file, { mtimeMs, value: structuredClone(parsed.value) });\n return parsed.value;\n }\n\n private validateBehavior(cfg: PartialConfig): void {\n /* v8 ignore start -- defensive: config defaults always seed version:1 before validation */\n if (cfg.version === undefined)\n throw new ConfigError({\n message: 'Config: missing version field',\n code: ERROR_CODES.CONFIG_INVALID,\n context: { field: 'version' },\n });\n /* v8 ignore stop */\n if (cfg.version !== 1)\n throw new ConfigError({\n message: `Config: unsupported version ${cfg.version}`,\n code: ERROR_CODES.CONFIG_INVALID,\n context: { field: 'version', actual: cfg.version },\n });\n const c = cfg.context;\n if (!c)\n throw new ConfigError({\n message: 'Config: missing context section',\n code: ERROR_CODES.CONFIG_INVALID,\n context: { field: 'context' },\n });\n // A user-edited config.json can land strings here (\"0.6\") and slip past\n // truthiness checks; the `>=` comparison then coerces silently and the\n // threshold ordering check passes for nonsense values. Validate types\n // explicitly so misconfigs surface here, not as confusing failures deep\n // in the auto-compaction logic.\n const fields: Array<keyof typeof c> = ['warnThreshold', 'softThreshold', 'hardThreshold'];\n for (const f of fields) {\n const v = c[f];\n if (typeof v !== 'number' || !Number.isFinite(v)) {\n throw new ConfigError({\n message: `Config: context.${String(f)} must be a finite number (got ${typeof v})`,\n code: ERROR_CODES.CONFIG_INVALID,\n context: { field: `context.${String(f)}`, actualType: typeof v },\n });\n }\n }\n if (c.warnThreshold >= c.softThreshold || c.softThreshold >= c.hardThreshold) {\n throw new ConfigError({\n message: 'Config: context thresholds must satisfy warn < soft < hard',\n code: ERROR_CODES.CONFIG_INVALID,\n context: { warn: c.warnThreshold, soft: c.softThreshold, hard: c.hardThreshold },\n });\n }\n if (c.mode !== undefined && !isContextWindowModeId(c.mode)) {\n // An unknown mode (typo or value from an older/renamed scheme) should not\n // brick the CLI \u2014 unlike the numeric thresholds above there is a safe\n // default. Warn and fall back rather than throwing.\n const known = listContextWindowModes()\n .map((m) => m.id)\n .join(', ');\n console.warn(\n `[config] Ignoring unknown context.mode \"${c.mode}\" (expected one of: ${known}); ` +\n `falling back to \"${DEFAULT_CONTEXT_WINDOW_MODE_ID}\".`,\n );\n c.mode = DEFAULT_CONTEXT_WINDOW_MODE_ID;\n }\n }\n\n private validateIdentity(cfg: PartialConfig): void {\n if (!cfg.provider) {\n throw new ConfigError({\n message: 'Config: no provider configured. Run `wstack init` or set WRONGSTACK_PROVIDER.',\n code: ERROR_CODES.CONFIG_INVALID,\n context: { field: 'provider' },\n });\n }\n if (!cfg.model) {\n throw new ConfigError({\n message: 'Config: no model configured. Run `wstack init` or set WRONGSTACK_MODEL.',\n code: ERROR_CODES.CONFIG_INVALID,\n context: { field: 'model' },\n });\n }\n }\n}\n", "import type { ContextWindowModeId } from './context-window.js';\nimport type { ConfiguredHook, HookEvent } from './hooks.js';\nimport type { WireFamily } from './models-registry.js';\nimport type { CacheTtl, Capabilities, ReasoningEffort } from './provider.js';\nimport type { Permission } from './tool.js';\n\n/**\n * Runtime reasoning controls the user can set per-session/project. Mapped into\n * the provider `Request.reasoning` field by the model-runtime request\n * middleware, gated by the active model's `reasoningConfig` capabilities so\n * unsupported values are omitted (and warned) instead of triggering provider\n * 400s. See `resolveReasoningForRequest()` in packages/core.\n */\nexport interface ModelRuntimeReasoningConfig {\n /**\n * Whether to send explicit reasoning enable/disable.\n * - 'auto' \u2192 do not send explicit fields; provider/model default wins\n * - 'on' \u2192 send `reasoning.enabled = true`\n * - 'off' \u2192 send `reasoning.enabled = false` only when the model supports disable\n */\n mode?: 'auto' | 'on' | 'off' | undefined;\n /** Reasoning effort. Only sent when the model advertises `effortSupported`. */\n effort?: ReasoningEffort | undefined;\n /** Preserve thinking across turns. Only sent when `preserveThinking !== 'unsupported'`. */\n preserve?: boolean | undefined;\n}\n\n/**\n * Runtime prompt-cache controls mapped into `Request.cache`. Currently only the\n * Anthropic TTL toggle (5m vs 1h) is exposed; other providers ignore it.\n */\nexport interface ModelRuntimeCacheConfig {\n ttl?: CacheTtl | undefined;\n}\n\n/**\n * Shared runtime controls applied to every provider request, regardless of host\n * (REPL / TUI / WebUI). The CLI installs a single request-pipeline middleware\n * that reads these and mutates the outgoing `Request`.\n */\nexport interface ModelRuntimeConfig {\n reasoning?: ModelRuntimeReasoningConfig | undefined;\n cache?: ModelRuntimeCacheConfig | undefined;\n /**\n * Generic generation parameters mapped directly onto `Request` fields.\n * Only sent when the active model's `Capabilities` advertise support.\n */\n parameters?: ModelRuntimeParametersConfig | undefined;\n}\n\n/**\n * Generic generation parameters the user can set per-session / per-project.\n * Each field maps to a `Request` field of the same name and is gated by the\n * corresponding `Capabilities` flag so unsupported models don't receive\n * parameters they'd reject.\n */\nexport interface ModelRuntimeParametersConfig {\n /** Top-K sampling (Anthropic, Gemini). Gated by `capabilities.topK`. */\n topK?: number | undefined;\n /** Frequency penalty (OpenAI, Gemini). Gated by `capabilities.frequencyPenalty`. */\n frequencyPenalty?: number | undefined;\n /** Presence penalty (OpenAI, Gemini). Gated by `capabilities.presencePenalty`. */\n presencePenalty?: number | undefined;\n /** Random seed (OpenAI, Gemini). Gated by `capabilities.seed`. */\n seed?: number | undefined;\n /** End-user identifier for abuse monitoring. */\n user?: string | undefined;\n /** Log probabilities (OpenAI, Gemini). Gated by `capabilities.logprobs`. */\n logprobs?: boolean | undefined;\n /** Number of top logprobs to return (OpenAI). Only when `logprobs` is true. */\n topLogprobs?: number | undefined;\n}\n\n/**\n * HQ client connection settings. Same-machine clients can auto-discover the\n * local HQ auth file; remote clients use this config-backed URL/token pair.\n */\nexport interface HqClientConfig {\n /** Enable HQ publishing. Env WRONGSTACK_HQ_ENABLED still overrides at runtime. */\n enabled?: boolean | undefined;\n /** HQ HTTP base URL, e.g. http://host:3499. */\n url?: string | undefined;\n /** Client token for /ws/client. Stored encrypted by SecretVault when persisted. */\n token?: string | undefined;\n /** Optional HQ data dir for same-machine auth.json discovery. */\n dataDir?: string | undefined;\n /** Send raw content previews to HQ instead of redacted previews. */\n rawContent?: boolean | undefined;\n /** Override project display name in HQ. */\n projectAlias?: string | undefined;\n}\n\n/**\n * Token-saving mode tier levels. Controls how aggressively the system prompt\n * is compacted to reduce per-request token consumption.\n *\n * - 'off' \u2014 Full prompt, all tools, complete guidance (no reduction)\n * - 'minimal' \u2014 TIER1 tools only (~10), stripped guidance (~3-4k tokens saved)\n * - 'light' \u2014 Core + memory tools (~14), common patterns, minimal guidance\n * - 'medium' \u2014 Most development tools (~24), some guidance (default when `true`)\n * - 'aggressive' \u2014 Maximum savings before tools become unusable (~4-5k tokens saved)\n */\nexport type TokenSavingTier = 'off' | 'minimal' | 'light' | 'medium' | 'aggressive';\n\n/**\n * Normalize a TokenSavingTier value, handling backward-compatible boolean inputs.\n * - `true` \u2192 'medium' (existing behavior)\n * - `false` \u2192 'off'\n * - string values are returned as-is after validation\n * - `undefined` \u2192 'off'\n */\nexport function normalizeTokenSavingTier(val?: TokenSavingTier | boolean): TokenSavingTier {\n if (val === undefined) return 'off';\n if (typeof val === 'boolean') return val ? 'medium' : 'off';\n const validTiers = new Set<TokenSavingTier>(['off', 'minimal', 'light', 'medium', 'aggressive']);\n return validTiers.has(val) ? val : 'off';\n}\n\nexport const DEFAULT_TUI_THINKING_WORD = 'thinking';\nexport const MAX_TUI_THINKING_WORD_LENGTH = 16;\n\n/**\n * Normalize the configurable statusline word shown while the TUI is working.\n * The value must be a single short word; invalid values fall back to the default.\n */\nexport function normalizeTuiThinkingWord(value: unknown): string {\n if (typeof value !== 'string') return DEFAULT_TUI_THINKING_WORD;\n const word = value.trim();\n if (word.length === 0 || word.length > MAX_TUI_THINKING_WORD_LENGTH) {\n return DEFAULT_TUI_THINKING_WORD;\n }\n if (!/^[\\p{L}\\p{N}_-]+$/u.test(word)) return DEFAULT_TUI_THINKING_WORD;\n return word;\n}\n\nexport interface ContextConfig {\n /** Context-window policy mode. Controls compaction thresholds and preservation depth. */\n mode?: ContextWindowModeId | undefined;\n warnThreshold: number;\n softThreshold: number;\n hardThreshold: number;\n /** Enable automatic compaction when thresholds are crossed (default: true). */\n autoCompact?: boolean | undefined;\n /**\n * Model used for LLM-assisted summarization in IntelligentCompactor.\n * Falls back to the main model when omitted.\n */\n summarizerModel?: string | undefined;\n /**\n * Override the effective context window size (in tokens). Use this when\n * you want the compactor to trigger earlier than the provider's actual\n * maxContext. Defaults to the provider's reported maxContext.\n */\n effectiveMaxContext?: number | undefined;\n maxSessionTokens?: number | undefined;\n maxDailyTokens?: number | undefined;\n preserveK: number;\n eliseThreshold: number;\n /** Compactor strategy: 'hybrid' (default, fast rules), 'intelligent' (LLM summarization), 'selective' (LLM-driven selection). */\n strategy?: 'hybrid' | 'intelligent' | 'selective' | undefined;\n /** Enable LLM-driven selective compaction (default: false for backward compat). */\n llmSelector?: boolean | undefined;\n}\n\n/**\n * Runtime configuration for the process circuit breaker (the one owned by the\n * ProcessRegistry that gates `bash`/`exec`). Toggle via `/settings breaker`.\n *\n * The breaker itself is a low-level primitive (`packages/tools/.../circuit-breaker.ts`)\n * that is on by default; this section controls whether the registry actually\n * participates in it and how it auto-recovers.\n */\nexport interface CircuitBreakerRuntimeConfig {\n /**\n * Enable circuit-breaker protection. When false (the default), the breaker\n * is bypassed \u2014 `bash`/`exec` calls always proceed regardless of failure\n * history. When true, the breaker trips on repeated failures / slow calls /\n * bursts and blocks further calls until it recovers.\n */\n enabled?: boolean | undefined;\n /**\n * When the breaker trips, automatically kill all tracked processes AND\n * reset the breaker to closed after this delay (ms). 0 = disabled (manual\n * recovery only via `/kill reset`). Only effective when `enabled` is true.\n * While armed, the statusline shows a live countdown to the kill/reset.\n */\n autoKillResetMs?: number | undefined;\n}\n\n/**\n * Adaptive concurrency controller configuration. When enabled, the controller\n * automatically adjusts `maxConcurrent` based on rate-limit (429) errors:\n * - On 429: halves `maxConcurrent` (floor at 1)\n * - On sustained success (no 429 for `recoveryIntervalMs`): increases `maxConcurrent` by 1\n */\nexport interface AdaptiveConcurrencyConfig {\n /** Enable adaptive concurrency. Default: false (disabled). */\n enabled?: boolean | undefined;\n /**\n * Minimum concurrency floor. The controller never drops below this.\n * Default: 1.\n */\n minConcurrent?: number | undefined;\n /**\n * Maximum concurrency ceiling. The controller never exceeds this.\n * Default: 16 (matches MultiAgentCoordinator default).\n */\n maxConcurrent?: number | undefined;\n /**\n * Multiplicative decrease factor when a 429 is hit.\n * `newConcurrency = floor(currentConcurrency * decreaseFactor)`.\n * Default: 0.5 (halves concurrency).\n */\n decreaseFactor?: number | undefined;\n /**\n * Number of consecutive successful requests before increasing concurrency by 1.\n * Default: 10.\n */\n successThreshold?: number | undefined;\n /**\n * How often (ms) to check for recovery and bump concurrency.\n * Default: 30_000 (30 seconds).\n */\n recoveryIntervalMs?: number | undefined;\n}\n\nexport interface ToolsConfig {\n defaultExecutionStrategy: 'parallel' | 'sequential' | 'smart';\n maxIterations: number;\n iterationTimeoutMs: number;\n /** Hard upper bound for a single tool call timeout. Defaults to 5 minutes. */\n maxToolTimeoutMs?: number | undefined;\n sessionTimeoutMs: number;\n perIterationOutputCapBytes: number;\n /**\n * Per-tool prose budget for the tool's top-level description and usage hint.\n * Missing entries default to \"extend\".\n */\n descriptionMode?: ToolDescriptionModeConfig | undefined;\n /**\n * Per-tool on-screen result rendering mode (terminal / WebUI / TUI).\n * Missing entries default to \"extend\". Independent of `descriptionMode`:\n * `/tool <name> result simple` toggles this without touching the\n * LLM-side description length.\n */\n resultRenderMode?: ToolResultRenderModeConfig | undefined;\n /**\n * Tool names to disable. Disabled tools are excluded from the tool registry\n * (`ToolRegistry.list()` / `get()`), so they do NOT appear in the system\n * prompt's \"## Tool usage\" block \u2014 reducing per-request token consumption.\n * Override per-session with `/tool enable <name>` or re-enable all via\n * `/tool enable-all`.\n */\n disabledTools?: string[] | undefined;\n /**\n * When true (default), the agent automatically extends its iteration\n * limit by 100 when hit. Set to false to require user confirmation.\n */\n autoExtendLimit?: boolean | undefined;\n /**\n * When true, file tools (read/write/edit/grep/glob/install) are confined to\n * the project root and `set_working_dir` may not leave it. Default: false \u2014\n * tools may access paths outside the project root, still subject to each\n * tool's permission tier (writes/edits prompt for confirmation). Toggle via\n * `/settings` (\"Filesystem access\").\n */\n restrictToProjectRoot?: boolean | undefined;\n /**\n * Per-command policy for the `exec` tool's allowlist. The tool ships a\n * curated default allowlist of dev/build commands; this extends or trims it.\n *\n * SECURITY: `allow` EXPANDS what the agent may execute, so it is honored only\n * from TRUSTED config (`~/.wrongstack/config.json`) \u2014 the config loader\n * strips `tools.exec.allow` from the untrusted, repo-committed\n * `<project>/.wrongstack/config.json`. `deny` only ever REMOVES commands, so\n * it is honored from any source.\n */\n exec?: ExecToolConfig | undefined;\n /**\n * Agent-loop repetition detector tuning. The detector watches two signals:\n * consecutive effectively-identical iterations (same tool-name set + inputs\n * + text) and per-call repeats (the same tool invoked with identical\n * arguments N times within a sliding window, even when interleaved with\n * other calls). In the default `steer-then-cut` mode the first detection\n * folds a corrective note into the conversation and lets the run continue;\n * only persistent repetition cuts the turn. Omitted fields use built-in\n * defaults (see DEFAULT_TOOLS_CONFIG.loopDetection).\n */\n loopDetection?: LoopDetectionConfig | undefined;\n}\n\n/** Tuning for the agent-loop repetition detector (`tools.loopDetection`). */\nexport interface LoopDetectionConfig {\n /**\n * `steer-then-cut` (default): inject a corrective note at the steer\n * threshold, cut the turn only if repetition persists to the cut threshold.\n * `cut`: legacy behavior \u2014 hard-stop at the steer threshold, per-call\n * detector disabled. `off`: disable loop detection entirely.\n */\n mode?: 'steer-then-cut' | 'cut' | 'off' | undefined;\n /** Consecutive identical iterations before the detector acts (default 3, min 2). */\n steerThreshold?: number | undefined;\n /**\n * Consecutive identical iterations at which the turn is cut in\n * `steer-then-cut` mode (default steerThreshold + 2, min steerThreshold + 1).\n */\n cutThreshold?: number | undefined;\n /** Sliding window of recent tool calls for per-call repeat detection (default 12, min 4). */\n windowSize?: number | undefined;\n /**\n * Identical (name + canonicalized args) calls within the window that\n * trigger a steer note (default 4, min 2).\n */\n callRepeatThreshold?: number | undefined;\n}\n\n/** Allow/deny extension of the `exec` tool's built-in command allowlist. */\nexport interface ExecToolConfig {\n /**\n * Extra command names to add to the allowlist (e.g. `[\"make\", \"dotnet\"]`).\n * Trusted sources only \u2014 stripped from in-project repo config.\n */\n allow?: string[] | undefined;\n /**\n * Command names to remove from the allowlist. Honored from any source \u2014\n * removing a command can only narrow what runs, so it is always safe.\n */\n deny?: string[] | undefined;\n /**\n * Per-rule bypass for the heuristic danger detector. Each entry is a\n * stable `matchedRule` id (e.g. `rm-recursive`, `git-push-force`); a\n * matched rule whose id is in this list is suppressed.\n *\n * Use case: a project that legitimately runs `rm -rf ./build` on every\n * CI run can add `\"rm-recursive\"` to bypass so the detector stops\n * emitting banners for that one rule \u2014 without disabling it for every\n * other `rm -rf` invocation.\n *\n * **Trusted sources only.** Bypassing a danger rule means the user\n * agreed to a specific destructive pattern; in-project repo config\n * could otherwise be used to silently opt everyone in. The boot path\n * strips this field from `<project>/.wrongstack/config.json` the\n * same way it strips `allow`.\n */\n danger?: ExecDangerConfig | undefined;\n}\n\nexport interface ExecDangerConfig {\n /**\n * List of danger rule ids to skip. Each id corresponds to a rule in\n * `@wrongstack/tools/src/_danger-detect.ts` (e.g. `rm-recursive`,\n * `git-push-force`, `inline-eval`, `sudo`). Unknown ids are ignored\n * (forward-compat: a rule added in a future version can be referenced\n * before the user upgrades).\n */\n bypass?: string[] | undefined;\n}\n\nexport type ToolDescriptionMode = 'extend' | 'simple';\nexport type ToolDescriptionModeConfig = Record<string, ToolDescriptionMode | undefined>;\n\n/**\n * Per-tool on-screen result rendering mode. Independent of\n * {@link ToolDescriptionMode}: `descriptionMode` controls the prose the\n * model sees in the system prompt, `resultRenderMode` controls how the\n * tool's RESULT is printed to the user (terminal / WebUI / TUI).\n *\n * - `simple` \u2014 meta only (filename, line count, exit code). Body is hidden\n * by default; the user can still expand on demand where the renderer\n * supports it.\n * - `extend` \u2014 full preview, up to 10 lines for read-like tools.\n *\n * The two modes are toggled independently via `/tool <name> desc simple`\n * and `/tool <name> result simple`. The legacy `/tool <name> simple`\n * command sets BOTH at once for backward compatibility.\n */\nexport type ToolResultRenderMode = 'extend' | 'simple';\nexport type ToolResultRenderModeConfig = Record<string, ToolResultRenderMode | undefined>;\n\nexport interface ProviderApiKey {\n /** Short human-readable label (e.g. \"personal\", \"work\", \"rate-limit-backup\"). */\n label: string;\n /**\n * The key itself. The field name contains `apiKey` so the secret-vault\n * walker will encrypt it on write and decrypt it on read.\n */\n apiKey: string;\n /** ISO-8601 timestamp the key was added. */\n createdAt: string;\n /**\n * How this credential was obtained.\n * - `api_key` \u2014 manually pasted API key (default)\n * - `oauth` \u2014 OAuth 2.0 device-code / authorization-code flow\n * - `session_token` \u2014 extracted from browser session (ChatGPT web, etc.)\n */\n authMethod?: 'api_key' | 'oauth' | 'session_token' | undefined;\n /** ISO-8601 expiry. When set, the token manager will refresh before this time. */\n expiresAt?: string | undefined;\n /**\n * OAuth refresh token. Stored encrypted by the secret-vault walker because\n * the field name contains `Token` (case-insensitive match by vault).\n */\n refreshToken?: string | undefined;\n /** Token type as returned by the OAuth endpoint (e.g. \"bearer\"). */\n tokenType?: string | undefined;\n /** OAuth scope string (e.g. \"openai.models.read openai.models.use\"). */\n scope?: string | undefined;\n /**\n * ChatGPT account id, extracted from the OAuth access-token JWT\n * (`https://api.openai.com/auth`.chatgpt_account_id). Sent as the\n * `chatgpt-account-id` header by the `openai-codex` wire family. Cached\n * here for display/diagnostics; the provider re-derives it from the live\n * token at request time so it can never go stale after a refresh.\n */\n accountId?: string | undefined;\n}\n\nexport interface ProviderConfig {\n type: string;\n /**\n * Legacy single-key field. Still honored as a read fallback when `apiKeys`\n * is empty (for configs not yet migrated to multi-key format). After key\n * management operations (`writeKeysBack`), this field is **cleared** to\n * prevent accidental serialization of the plaintext key. Consumers that\n * need the active API key should use `resolveActiveApiKey()` (cli) or\n * resolve from `apiKeys[]` directly \u2014 never read `cfg.apiKey` in new code.\n */\n apiKey?: string | undefined;\n /** Multiple keys for the same provider \u2014 pick one with `activeKey`. */\n apiKeys?: ProviderApiKey[] | undefined;\n /** Label of the entry in `apiKeys` to use. Defaults to the first one. */\n activeKey?: string | undefined;\n baseUrl?: string | undefined;\n headers?: Record<string, string>;\n model?: string | undefined;\n quirks?: Record<string, unknown>;\n capabilities?: Record<string, unknown>;\n /**\n * Optional wire-family override. When present, the provider can be\n * constructed without consulting the models.dev catalog \u2014 useful for\n * self-hosted endpoints, internal proxies, or for working offline.\n */\n family?: WireFamily | undefined;\n /** Custom env var names to probe when `apiKey` is missing. */\n envVars?: string[] | undefined;\n /** Optional list of models the user wants visible for this provider. */\n models?: string[] | undefined;\n /**\n * Fetch this provider's model list + per-model capabilities from its\n * `{baseUrl}/models` endpoint at startup and inject them into the catalog.\n * For openai-compatible gateways/proxies (omniroute, LiteLLM, vLLM, \u2026) that\n * expose rich metadata there. Defaults on for presets that set it (omniroute).\n * Discovery is best-effort: a down server or missing key is a no-op.\n */\n autoDiscoverModels?: boolean | undefined;\n /**\n * Provider-relative custom model definitions (maps modelId \u2192 definition).\n * Each entry adds/overrides a model for this provider with optional\n * capability overrides. The model id is the key, not a fully qualified id.\n */\n customModels?: Record<string, CustomModelDefinition>;\n /**\n * Per-provider OAuth configuration. When present, `wstack auth login <id>`\n * uses this instead of prompting for a raw API key. Set by the catalog or\n * by the user via `/settings`.\n */\n oauthConfig?:\n | {\n /** OAuth client id registered with the provider. */\n clientId?: string | undefined;\n /** Device authorization endpoint (RFC 8628). */\n deviceCodeEndpoint?: string | undefined;\n /** Token endpoint for code exchange and refresh. */\n tokenEndpoint?: string | undefined;\n /** Authorization server URL shown to the user for opening in browser. */\n authorizationEndpoint?: string | undefined;\n /** Default OAuth scopes to request. */\n scopes?: string[] | undefined;\n }\n | undefined;\n}\n\n/**\n * One entry in the per-task model matrix. Pins a catalog role, a phase, or\n * the `*` default to a specific model (and, optionally, a specific provider).\n * Resolved at subagent-spawn time so e.g. `security-scanner` can run a\n * different model than `documentation` while the leader stays on its own.\n */\nexport interface ModelMatrixEntry {\n /** Provider registry id (e.g. \"anthropic\", \"minimax\", \"zai\"). When omitted,\n * the leader's provider is used with this entry's model. */\n provider?: string | undefined;\n /** Model id to run for the matched role/phase/default. */\n model?: string | undefined;\n /**\n * Runtime request overrides for subagents matched by this entry. This is\n * intentionally scoped to subagents: leader requests keep using top-level\n * `Config.modelRuntime`, while a role/phase can opt into its own reasoning\n * effort, cache TTL, or gated generation parameters.\n */\n modelRuntime?: ModelRuntimeConfig | undefined;\n /**\n * Named fallback profile to use for the matched role/phase/default. When\n * `model` is omitted, the first model in the profile becomes the primary and\n * the remaining entries become that subagent's fallback chain.\n */\n fallbackProfile?: string | undefined;\n}\n\nexport interface MCPServerConfig {\n /** Human-readable description shown in `wstack mcp list`. */\n description?: string | undefined;\n name: string;\n transport: 'stdio' | 'sse' | 'streamable-http';\n command?: string | undefined;\n args?: string[] | undefined;\n env?: Record<string, string>;\n url?: string | undefined;\n headers?: Record<string, string>;\n enabled?: boolean | undefined;\n allowedTools?: string[] | undefined;\n permission?: Permission | undefined;\n startupTimeoutMs?: number | undefined;\n requestTimeoutMs?: number | undefined;\n /**\n * Lazy connect: when true, the server process is NOT spawned at boot. Its\n * tools are registered from a cached manifest (discovered on the first ever\n * connect) and the server only spawns when one of its tools is actually\n * called, then auto-sleeps after an idle period. Default (false/undefined) =\n * eager connect at boot.\n */\n lazy?: boolean | undefined;\n /**\n * Allowlist of environment variable names to forward from the parent process\n * to this MCP server's child process. The values are resolved from\n * `process.env` at spawn time, NOT stored in the config file.\n *\n * Why this exists: WrongStack's `buildChildEnv()` security filter scrubs\n * env vars whose names look like secrets (TOKEN, SECRET, AUTH, KEY, ...)\n * from all child processes \u2014 this prevents a compromised MCP server from\n * exfiltrating provider API keys. But most MCP servers (GitHub, Slack,\n * Brave Search, ...) need their own API tokens from the environment.\n * `passthroughEnv` is the explicit bypass: only vars listed here survive\n * the filter, and they go through the `extra` path (unfiltered merge).\n *\n * Built-in presets declare their required env vars here so they work\n * out of the box when the user has the corresponding env vars exported\n * in their shell. Users can also add entries for custom servers.\n *\n * Example: passthroughEnv: ['GITHUB_PERSONAL_ACCESS_TOKEN', 'GITHUB_TOKEN']\n */\n passthroughEnv?: string[] | undefined;\n}\n\nexport interface LogConfig {\n level: 'error' | 'warn' | 'info' | 'debug' | 'trace';\n file?: string | undefined;\n}\n\nexport interface PluginConfig {\n name: string;\n enabled?: boolean | undefined;\n options?: Record<string, unknown>;\n}\n\n/**\n * Optional subsystems that the CLI can boot without. The core flow\n * (provider + agent loop + bundled tools + session) always works; these\n * just add capabilities. `--no-features` flips all of these off, which\n * is the minimum viable WrongStack: a single provider, a fixed config,\n * no network calls at startup.\n */\nexport interface FeaturesConfig {\n /** Load MCP servers declared in `mcpServers`. */\n mcp: boolean;\n /** Load + initialise npm plugins declared in `plugins`. */\n plugins: boolean;\n /** Register `remember` / `forget` tools backed by memory store. */\n memory: boolean;\n /**\n * Automatically consolidate session learnings into long-term memory\n * after each completed run. The agent extracts key facts, conventions,\n * and decisions via a lightweight LLM call and persists them.\n * Enabled by default when `memory` is on; set to false to opt out.\n */\n memoryConsolidation?: boolean | undefined;\n /** Fetch the models.dev catalog at startup. When false, the provider\n * must declare its `family` explicitly in `providers[<id>]`. */\n modelsRegistry: boolean;\n /** Discover + load skills from disk. */\n skills: boolean;\n /**\n * Enable the prompt library (`/prompt`, `/prompts`, `/prompt-gen`, the WebUI\n * modal and the bundled 168-prompt dataset). Defaults to on; set to false to\n * disable the subsystem entirely (the loader is withheld so every surface\n * reports it unavailable).\n */\n prompts?: boolean | undefined;\n /**\n * Token-saving mode tier. Controls how aggressively the system prompt\n * is compacted to reduce per-request token consumption.\n *\n * - 'off' \u2014 Full prompt, all tools, complete guidance\n * - 'minimal' \u2014 TIER1 tools only, stripped guidance (~3-4k tokens saved)\n * - 'light' \u2014 Core + memory tools, common patterns, minimal guidance\n * - 'medium' \u2014 Most development tools, some guidance\n * - 'aggressive' \u2014 Maximum savings before tools become unusable (~4-5k tokens)\n *\n * Boolean values are accepted for backward compatibility:\n * - `true` \u2192 'medium'\n * - `false` \u2192 'off'\n *\n * Enable via CLI: `--token-saving-tier <level>` or `--token-saving-mode` (maps to 'medium').\n * Configure via: `features.tokenSavingMode: \"minimal\"` in config.\n */\n tokenSavingMode?: TokenSavingTier | boolean | undefined;\n /**\n * Enable the autonomous-coordination toolkit (AutonomousCoordinator +\n * KnowledgeGraph + ConsensusProtocol + TaskAuctioneer + ChangeManager +\n * TaskDAG). When true (the default), the TUI boot wires the coordinator\n * lazily on the first Director spawn. When false, the coordinator is\n * never constructed and the `/coordinator` slash command reports it\n * unavailable \u2014 reducing the coordination domain's runtime surface for\n * users who only use the simpler Director/Fleet path.\n */\n autonomousCoordination?: boolean | undefined;\n /**\n * Allow tools to read/write paths outside the project root directory.\n * When true (default), tools can access any path on the filesystem.\n * When false, tools are restricted to the project root directory.\n */\n allowOutsideProjectRoot?: boolean | undefined;\n /**\n * Auto-bootstrap the mailbox HTTP bridge from any WrongStack surface\n * (REPL/TUI/WebUI/eternal). When 'auto' (the default), the first\n * surface to come up for a given project joins or spawns the bridge\n * so external agents can connect without the user running\n * `wstack mailbox serve` themselves. 'off' disables this \u2014 operators\n * must start the bridge explicitly (e.g. via the `/mailbox-serve`\n * slash command or the standalone `wstack mailbox serve` subcommand).\n * The per-project lock + token-persistence model means a second\n * surface on the same project joins the first's bridge rather than\n * spawning a duplicate.\n */\n mailboxBridge?: 'auto' | 'off' | undefined;\n}\n\nexport interface SuperMemoryConfig {\n /** Enable the project-local Super Memory backend. Default: true. */\n enabled?: boolean | undefined;\n storage?: {\n /** Store memory inside the project under a gitignored directory. Default: true. */\n projectLocal?: boolean | undefined;\n /** Project-relative directory. Default: \".wrongstack/memories\". */\n directory?: string | undefined;\n } | undefined;\n inject?: {\n /** Add relevant memory to ordinary turn-level context. Default: true. */\n turnContext?: boolean | undefined;\n /** Add relevant memory to read/tree/grep/bash/edit tool results. Default: true. */\n toolResults?: boolean | undefined;\n /** Maximum hints appended to a single tool result. Default: 4. */\n maxHintsPerTool?: number | undefined;\n /** Maximum characters appended to a single tool result. Default: 1200. */\n maxCharsPerTool?: number | undefined;\n /** Maximum memories appended to ordinary turn context. Default: 8. */\n maxTurnMemories?: number | undefined;\n /** Maximum characters appended to ordinary turn context. Default: 2400. */\n maxCharsPerTurn?: number | undefined;\n /** Minimum retrieval score for ordinary hints. Default: 0.65. */\n minScore?: number | undefined;\n /** Cooldown before the same memory can be injected again. Default: 30 minutes. */\n repeatCooldownMs?: number | undefined;\n triggers?: Partial<Record<\n 'read' | 'tree' | 'grep' | 'glob' | 'codebase_search' | 'bash' | 'write' | 'edit' | 'patch',\n boolean\n >> | undefined;\n } | undefined;\n hygiene?: {\n /** Run hygiene after successful sessions. Default: true. */\n autoAfterSession?: boolean | undefined;\n /** Re-check anchored memories when files are edited. Default: true. */\n autoOnFileChange?: boolean | undefined;\n /** Archive stale/low-value memories after this many days. Default: 90. */\n retentionDays?: number | undefined;\n /** Archive low-confidence memories after this many days. Default: 30. */\n archiveLowConfidenceAfterDays?: number | undefined;\n } | undefined;\n embeddings?: {\n /** Optional future semantic layer. Disabled by default and never required. */\n enabled?: boolean | undefined;\n } | undefined;\n}\n\nexport interface AutonomyConfig {\n /** Default autonomy mode at startup. Default: \"auto\". */\n defaultMode?: 'off' | 'suggest' | 'auto' | undefined;\n /** ms to wait before auto-proceeding in 'auto' mode. Default: 45000. */\n autoProceedDelayMs?: number | undefined;\n /** Maximum consecutive auto-proceed turns before pausing. 0 = unlimited. Default: 50. */\n autoProceedMaxIterations?: number | undefined;\n /** Template used for YOLO+auto suggestions. Must include {{suggestion}}. */\n autonomyNextPrompt?: string | undefined;\n /** Animate the terminal/window title while the agent is active. Default: true. */\n terminalTitleAnimation?: boolean | undefined;\n /** Persisted YOLO preference mirrored into top-level config.yolo at runtime. Default: false. */\n yolo?: boolean | undefined;\n /** Stream fleet/subagent output into the main TUI chat. Default: true. */\n streamFleet?: boolean | undefined;\n /** Ring terminal bell when an agent run completes. Default: false. */\n chime?: boolean | undefined;\n /** Ask for confirmation before interrupt/exit. Default: true. */\n confirmExit?: boolean | undefined;\n /** Terminal mouse tracking preference. Default: false. */\n mouseMode?: boolean | undefined;\n /** Enable prompt refinement before sending. Default: true. */\n enhance?: boolean | undefined;\n /** Prompt-refinement preview countdown in ms. Default: 60000. */\n enhanceDelayMs?: number | undefined;\n /** Prompt-refinement language mode. Default: \"original\". */\n enhanceLanguage?: 'original' | 'english' | undefined;\n /** TUI statusline density. Default: \"detailed\". */\n statuslineMode?: 'minimum' | 'detailed' | 'no-color' | undefined;\n /** Single short word shown in the TUI rainbow working-state chip. Default: \"thinking\". */\n thinkingWord?: string | undefined;\n /**\n * Persist the TUI prompt input history to disk per project so Up/Down\n * navigation recalls prompts across sessions. Secrets are scrubbed before\n * they reach disk. Default: enabled, 100 entries.\n */\n inputHistory?: InputHistoryConfig | undefined;\n}\n\n/**\n * Per-project TUI input history persistence options. Lives under\n * `config.autonomy.inputHistory` because the TUI-specific knobs on Config\n * are grouped there.\n */\nexport interface InputHistoryConfig {\n /** Persist history to ~/.wrongstack/projects/<slug>/input-history.json. Default: true. */\n enabled?: boolean | undefined;\n /** Max entries kept on disk (and in memory). Default: 100. */\n maxEntries?: number | undefined;\n}\n\n/**\n * Automatic codebase symbol-index maintenance. Keeps the `codebase-search`\n * index (SQLite, `~/.wrongstack/projects/<hash>/codebase-index/index.db`) fresh\n * without the user having to call `codebase-index` by hand.\n */\nexport interface IndexingConfig {\n /** Run a blocking incremental index at session start (with a visible summary). Default: true. */\n onSessionStart: boolean;\n /** Reindex files the agent writes/edits via tools, in the background. Default: true. */\n onEdit: boolean;\n /** Watch the project root for external editor changes and reindex them. Default: true. */\n watchExternal: boolean;\n /** Debounce window (ms) coalescing rapid edits to the same file. Default: 400. */\n debounceMs: number;\n /**\n * Watchdog timeout (ms) for a full index run. A run exceeding this is\n * aborted (so it can never wedge the indexing mutex or freeze the terminal)\n * and counts toward the indexing circuit breaker. Default: 240000.\n */\n indexTimeoutMs?: number | undefined;\n}\n\n/**\n * Saved launch preferences \u2014 restored on next boot so the pre-launch prompt\n * can offer a one-line \"Continue with last settings? [Y/n]\" instead of\n * re-asking every question from scratch.\n */\nexport interface LaunchConfig {\n /** Interactive mode: 'tui' (Ink TUI) or 'repl' (readline REPL). */\n mode?: 'tui' | 'repl' | undefined;\n /** Start with Director mode on (fleet manifest + multi-agent orchestration). */\n director?: boolean | undefined;\n /**\n * Launch-time autonomy mode (binary choice from pre-launch prompt).\n * 'off' = stops after each turn; 'auto' = self-driving.\n * Distinct from `AutonomyConfig.defaultMode` which also supports 'suggest'.\n */\n autonomy?: 'off' | 'auto' | undefined;\n}\n\n/**\n * Controls how much detail is persisted to the per-session JSONL log\n * (`~/.wrongstack/projects/<hash>/sessions/<date>/sess_<ULID>.jsonl`).\n */\nexport interface SessionLoggingConfig {\n /**\n * How much detail to write to the persistent session log.\n *\n * - \"minimal\" \u2192 Only events required for resume/rewind/recovery\n * - \"standard\" \u2192 (default) + high-value lightweight audit events\n * (compaction, tool timing, retries, errors, etc.)\n * - \"full\" \u2192 Also persist full request payloads (very large).\n * Consider enabling a separate replay log instead.\n */\n auditLevel?: 'minimal' | 'standard' | 'full' | undefined;\n\n /**\n * Sampling configuration for high-volume events (especially relevant at\n * `auditLevel: \"full\"`).\n */\n sampling?: {\n /** Controls sampling of `tool_progress` events. */\n toolProgress?: {\n /**\n * Sample rate for noisy progress events (`log`, `partial_output`).\n * - 1 = no sampling (every message is logged)\n * - 8 = default (first message + every 8th)\n */\n sampleRate?: number | undefined;\n };\n };\n}\n\nexport type SyncCategory = 'settings' | 'skills' | 'prompts' | 'memory' | 'history';\n\nexport interface SyncConfig {\n enabled: boolean;\n repo: string;\n /** GitHub token (fine-grained PAT). Encrypted at rest via SecretVault. */\n githubToken: string;\n categories: SyncCategory[];\n lastSyncedAt?: string | undefined;\n}\n\n/**\n * Per-model capability overrides the user can define in their config.\n * Used to add models not in the models.dev catalog, or override catalog\n * facts when the real backend differs (e.g. local Ollama models, proxies).\n */\nexport interface CustomModelDefinition {\n /** Provider this model belongs to. Defaults to the owning ProviderConfig. */\n provider?: string | undefined;\n /** Optional display name. */\n name?: string | undefined;\n /** Capability overrides \u2014 only specified fields are overlaid. */\n capabilities?: Partial<Capabilities> | undefined;\n /**\n * Max output tokens. If not specified, the provider family default\n * or catalog entry is used.\n */\n maxOutput?: number | undefined;\n}\n\n/**\n * Skill subsystem configuration. All fields optional; the subsystem itself is\n * gated by `features.skills`. Honored from the user's `~/.wrongstack/config.json`;\n * in the repo-committed in-project config the `extraDirs` field is stripped\n * (arbitrary directories are a prompt-injection vector) \u2014 only `readClaudeSkills`\n * and `mode` survive there.\n */\nexport interface SkillsConfig {\n /**\n * Read skills from foreign coding-agent directories (`<project>/.claude/skills`\n * and `~/.claude/skills`). Default `true`. Lets Claude Code / Codex / Gemini /\n * `asm` / `gh skill` skills be used without copying them.\n */\n readClaudeSkills?: boolean | undefined;\n /**\n * Scan OTHER coding agents' skill directories (`~/.codex/skills`,\n * `~/.cursor/skills`, `~/.agents/skills`, `~/.qwen/skills`,\n * `~/.trae/skills`, \u2026 + their `<project>/.<tool>/\u2026` equivalents). Default\n * `true` (all known tools); pass a tool-id list to restrict, or `false` to\n * disable. Non-existent dirs are skipped. Unknown ids in the list (likely\n * typos) are dropped and surfaced via a config warning.\n */\n foreignSources?: boolean | string[] | undefined;\n /**\n * How skill bodies reach the system prompt.\n * - `'eager'` (default): inject every discovered skill body into the prompt.\n * - `'progressive'`: inject only the metadata manifest; the agent loads a\n * skill body on demand via the `skill` tool (the agentskills.io model).\n */\n mode?: 'eager' | 'progressive' | undefined;\n /**\n * Extra skill directories to scan (lowest priority, after the `.claude`\n * layers). Honored only from the user config; stripped from in-project config.\n */\n extraDirs?: string[] | undefined;\n /**\n * In eager mode, the maximum total chars of skill bodies injected into the\n * prompt (highest-priority skills first; the rest are listed as a manifest the\n * agent loads via the `skill` tool). Bounds prompt cost when many skills are\n * discovered. Default 24000 (~6k tokens). Set very high to disable. Ignored in\n * progressive mode (which injects only the manifest anyway).\n */\n eagerMaxChars?: number | undefined;\n /**\n * Base URL of the skill registry used by `/skill-search` and\n * `/skill-install <registry>:<id>`. Default `https://skills.sh` (the open\n * marketplace backed by mastra-ai/skills-api). Honored only from the user\n * config; stripped from in-project config (a repo-committed override would be\n * an SSRF / prompt-injection vector \u2014 the registry response is parsed into the\n * prompt). Set to a self-hosted skills-api instance to use a private catalog.\n */\n registryUrl?: string | undefined;\n}\n\n/**\n * Fleet peer-awareness + supervision settings. All sub-features are\n * enabled-by-default with conservative throttles; each has its own kill\n * switch. See `FleetSupervisor` (coordination/fleet-supervisor.ts) for the\n * supervisor semantics.\n */\nexport interface FleetConfig {\n /** Subagent process/registry lifecycle after it is no longer doing work. */\n lifecycle?:\n | {\n /**\n * Remove a spawned or between-task subagent after this much idle time.\n * This is separate from the in-task activity watchdog. Default 30000.\n */\n idleTimeoutMs?: number | undefined;\n /**\n * Retire a subagent as soon as its final task result is delivered and\n * no queued task reused it in the same dispatch cycle. Default true.\n */\n retireOnTaskComplete?: boolean | undefined;\n }\n | undefined;\n /** Fleet-wide hard ceilings. In-flight work may finish; new spawns are refused at the cap. */\n budget?:\n | {\n /** Maximum subagents spawned during one Director lifetime. Default 64 in CLI. */\n maxSpawns?: number | undefined;\n /** Maximum cumulative input+output tokens across all fleet subagents. */\n maxTokens?: number | undefined;\n /** Maximum cumulative estimated USD cost across all fleet subagents. */\n maxCostUsd?: number | undefined;\n }\n | undefined;\n /** Periodic \"[FLEET PULSE]\" peer-status digest folded into each agent's context. */\n pulse?:\n | {\n /** Default true. */\n enabled?: boolean | undefined;\n /** Inject at most every N agent iterations. Default 5. */\n everyNIterations?: number | undefined;\n /** Hard cap on digest characters. Default 900. */\n maxChars?: number | undefined;\n /** Max peers listed per digest. Default 15. */\n maxAgents?: number | undefined;\n }\n | undefined;\n /** Broadcast `type:'status'` mails on meaningful subagent transitions. */\n statusBroadcasts?:\n | {\n /** Default true. */\n enabled?: boolean | undefined;\n /** Min interval between broadcasts about the same subagent. Default 15000. */\n minIntervalMsPerAgent?: number | undefined;\n /** Global cap on broadcasts per minute (excess dropped + counted). Default 20. */\n globalPerMinuteCap?: number | undefined;\n }\n | undefined;\n /**\n * Per-subagent git-worktree isolation for Director fleets. The default is\n * `auto`: mutating/build-capable subagents run in isolated checkouts and are\n * squash-merged back on success; read-only review agents usually stay on the\n * shared checkout. Set `enabled:false` or `mode:'off'` when a workflow cannot\n * use worktrees.\n */\n worktrees?:\n | {\n /** Kill switch. Default true. */\n enabled?: boolean | undefined;\n /**\n * `auto` (default): isolate only side-effectful subagents.\n * `required`: side-effectful subagents must get a worktree or fail.\n * `off`: never allocate worktrees.\n */\n mode?: 'auto' | 'required' | 'off' | undefined;\n /**\n * Merge successful task branches back into the base checkout. Default\n * true. When false, successful worktrees are committed and kept for\n * manual `/worktree merge`.\n */\n autoMerge?: boolean | undefined;\n /** Keep failed/timeout worktrees when they contain changes. Default true. */\n keepFailed?: boolean | undefined;\n }\n | undefined;\n /** Brain-gated fleet supervisor (rebalance/steer/spawn-helper). */\n supervisor?: FleetSupervisorConfig | undefined;\n}\n\n/** Config surface for the brain-gated FleetSupervisor. */\nexport interface FleetSupervisorConfig {\n /** Kill switch. Default true (active whenever a Director is running). */\n enabled?: boolean | undefined;\n /** Evaluation tick. Default 20000. */\n intervalMs?: number | undefined;\n /** Per-(signal,subject) re-engagement cooldown. Default 120000. */\n cooldownMs?: number | undefined;\n /** Hard cap on interventions touching one subagent per run. Default 3. */\n maxInterventionsPerSubagent?: number | undefined;\n /** Pending task pinned to a busy worker longer than this \u2192 starvation signal. Default 60000. */\n pinnedWaitMs?: number | undefined;\n /** \u2265 this many pending tasks pinned to one worker (with an idle sibling) \u2192 overload signal. Default 2. */\n overloadPinnedThreshold?: number | undefined;\n /** pending > backlogFactor \u00D7 live workers (sustained) \u2192 spawn-helper signal. Default 2. */\n backlogFactor?: number | undefined;\n /** Running subagent with no observable fleet activity for this long \u2192 stuck signal. Default 180000. */\n stuckMs?: number | undefined;\n /** Consecutive failed/timeout results from one subagent \u2192 failure-streak signal. Default 2. */\n failureStreak?: number | undefined;\n /** Allow the supervisor to spawn helper subagents. Default true. */\n allowSpawn?: boolean | undefined;\n /** Allow the supervisor to terminate subagents (highest risk). Default false. */\n allowTerminate?: boolean | undefined;\n}\n\n/** Git behavior overrides for agent-run git commands. See `Config.git`. */\nexport interface GitBehaviorConfig {\n /**\n * Commit identity injected as `GIT_AUTHOR_NAME/EMAIL` +\n * `GIT_COMMITTER_NAME/EMAIL` into every child process. Either field may be\n * set alone; the missing one falls back to git's own config.\n */\n identity?: {\n name?: string | undefined;\n email?: string | undefined;\n } | undefined;\n}\n\nexport interface Config {\n version: 1;\n provider: string;\n model: string;\n apiKey?: string | undefined;\n baseUrl?: string | undefined;\n /**\n * Maximum number of subagent tasks the fleet coordinator dispatches\n * simultaneously. Extra tasks queue until a slot frees. Default: 4.\n * Overridden by WRONGSTACK_MAX_CONCURRENT env var and --max-concurrent\n * CLI flag. Change at runtime with /fleet concurrency <n>.\n */\n maxConcurrent?: number | undefined;\n /**\n * Display language for the UI chrome (WebUI + desktop shell). A BCP-47-ish\n * code from SUPPORTED_LOCALES (en/tr/de/fr/it/es/pt-BR). Persisted here so a\n * change in one surface propagates to all others via the shared machine\n * config; each surface may keep a local cache for instant reactivity. When\n * unset, surfaces fall back to their own browser/system detection.\n */\n uiLocale?: string | undefined;\n providers?: Record<string, ProviderConfig>;\n /**\n * Top-level custom models (maps modelId \u2192 definition). Merged with\n * per-provider `customModels` at resolution time. The key is the\n * model id \u2014 not a fully qualified name. When the same model id\n * appears in both places, the top-level one wins.\n */\n models?: Record<string, CustomModelDefinition>;\n /**\n * Per-task model matrix. Keys are catalog roles (e.g. \"security-scanner\"),\n * phase names (e.g. \"review\"), or the `*` default. Resolution precedence at\n * subagent spawn: exact role \u2192 the role's phase \u2192 `*` \u2192 leader model. Set via\n * the `/setmodel` slash command; persisted to ~/.wrongstack/config.json.\n */\n modelMatrix?: Record<string, ModelMatrixEntry>;\n /**\n * User-curated model references shown/prioritized by model commands and used\n * by smart fallback derivation. Entries are `model`, `provider/model`, or\n * `provider model`.\n */\n favoriteModels?: string[] | undefined;\n /**\n * When true, auto-derived fallback chains are restricted to `favoriteModels`.\n * Explicit fallback profiles/chains are always honored as written.\n */\n favoriteModelsOnly?: boolean | undefined;\n context: ContextConfig;\n tools: ToolsConfig;\n mcpServers?: Record<string, MCPServerConfig>;\n /**\n * Per-agent ACP invocation overrides, keyed by catalog agent id\n * (`claude-code`, `codex-cli`, `gemini-cli`, \u2026). Lets a user correct an\n * agent's ACP entry command \u2014 e.g. point `claude-code` at the right\n * adapter \u2014 without a code change. Consumed by `/acp`, `/ensemble`, and\n * `wstack acp`. SECURITY: this is an arbitrary-command exec surface, so it\n * is in the in-project config DENY list \u2014 only honoured from the user's\n * `~/.wrongstack/config.json`, never from a repo-committed config.\n */\n acp?: {\n agents?: Record<string, { command: string; args?: string[]; env?: Record<string, string> }>;\n };\n /**\n * Ordered list of fallback model references tried, in order, when the\n * primary model is overloaded (HTTP 429/529/5xx) and its own retries are\n * exhausted. Each entry is a model reference: a bare model id (same\n * provider), `provider/model`, or `provider model`. After a fallback hop,\n * the primary is retried only after its cooldown expires. See\n * `createFallbackModelExtension`.\n */\n fallbackModels?: string[] | undefined;\n /**\n * Named fallback chains. A profile's first entry can be used as a primary\n * model by `/setmodel`, while the whole ordered list is used for failover.\n */\n fallbackProfiles?: Record<string, string[]> | undefined;\n /**\n * When `true` (the default) and `fallbackModels` is empty, a fallback chain\n * is derived automatically from the other keyed providers/models so 429s\n * recover out of the box. Set `false` to disable the smart default and only\n * use an explicit `fallbackModels` list. Toggle via `/fallback auto on|off`.\n */\n fallbackAuto?: boolean | undefined;\n /**\n * Lifecycle command/HTTP hooks, keyed by event. Commands receive HookInput\n * JSON on stdin; HTTP hooks receive the same object as a POST body. A typed\n * outcome can allow, deny, or mutate. `policy: true` enforcement hooks remain\n * active under `--no-hooks`; ordinary automation is disabled.\n */\n hooks?: Partial<Record<HookEvent, ConfiguredHook[]>>;\n plugins?: (string | PluginConfig)[] | undefined;\n log: LogConfig;\n features: FeaturesConfig;\n /** Project-local structured memory, graph-ready anchors, retrieval, and hygiene. */\n superMemory?: SuperMemoryConfig | undefined;\n /** Skill subsystem options (readClaudeSkills / mode / extraDirs). */\n skills?: SkillsConfig | undefined;\n yolo?: boolean | undefined;\n /** When true, show lightweight LLM-predicted next steps after each turn (/next). */\n nextPrediction?: boolean | undefined;\n cwd?: string | undefined;\n /** Autonomy mode configuration (auto-proceed delay, etc.). */\n autonomy?: AutonomyConfig | undefined;\n /** Show rotating launch hints on startup. Default: true. Set to false to suppress. */\n hints?: boolean | undefined;\n /** Raw SSE stream debugging \u2014 hex-dump every byte received from providers to stderr. */\n debugStream?: boolean | undefined;\n /**\n * Where settings are persisted. 'global' \u2192 ~/.wrongstack/config.json\n * (default). 'project' \u2192 <project>/.wrongstack/config.json.\n * When 'project', safe settings are saved per-project.\n */\n configScope?: 'global' | 'project' | undefined;\n /** Automatic codebase symbol-index maintenance (session-start + live updates). */\n indexing?: IndexingConfig | undefined;\n /**\n * Process circuit-breaker protection (gates `bash`/`exec` on repeated\n * failures). Default off \u2014 toggle with `/settings breaker on|off`.\n */\n circuitBreaker?: CircuitBreakerRuntimeConfig | undefined;\n /**\n * Adaptive concurrency controller \u2014 automatically adjusts `maxConcurrent` based on\n * rate-limit (429) errors. On 429: decreases concurrency. On sustained success:\n * gradually increases concurrency back up. Default off.\n */\n adaptiveConcurrency?: AdaptiveConcurrencyConfig | undefined;\n /** Saved launch preferences \u2014 restored on next boot for one-line confirmation. */\n launch?: LaunchConfig | undefined;\n\n /**\n * Session logging & audit configuration.\n * Controls what gets written to the persistent JSONL transcript.\n */\n session?: SessionLoggingConfig | undefined;\n /**\n * Runtime reasoning / cache controls applied to every provider request\n * (REPL/TUI/WebUI). Mapped into `Request.reasoning` and `Request.cache` by a\n * single request-pipeline middleware, gated by the active model's\n * capabilities. See `ModelRuntimeConfig`.\n */\n modelRuntime?: ModelRuntimeConfig | undefined;\n /** HQ client publishing settings, used by CLI/REPL/TUI/WebUI consistently. */\n hq?: HqClientConfig | undefined;\n /**\n * Fleet awareness + supervision settings (peer-status pulse digests,\n * status-broadcast mails, and the brain-gated FleetSupervisor). SECURITY:\n * in the in-project config DENY list \u2014 a repo-committed config must not be\n * able to enable autonomous spawning/steering or mailbox traffic. Only\n * honoured from the user's `~/.wrongstack/config.json`.\n */\n fleet?: FleetConfig | undefined;\n /**\n * Cloud sync configuration. Stored separately in sync.json to avoid\n * accidentally committing the GitHub token to project configs.\n */\n sync?: SyncConfig | undefined;\n /**\n * Git behavior overrides for agent-run git commands.\n *\n * `identity` sets the commit author/committer used by every git process\n * WrongStack spawns (git tool, bash/exec shells, worktree manager,\n * plugins) via the `GIT_AUTHOR_*` / `GIT_COMMITTER_*` env vars. It never\n * touches the repo's or the user's `git config`, so commits made outside\n * WrongStack keep their normal identity. Unset \u2192 git's own config applies\n * (today's behavior). Manage at runtime with `/gitid`.\n *\n * SECURITY: in the in-project config DENY list \u2014 a repo-committed config\n * must not be able to spoof the identity written into the user's commit\n * history. Only honoured from the user's `~/.wrongstack/config.json`.\n */\n git?: GitBehaviorConfig | undefined;\n /**\n * Per-plugin namespaced config sections. Each plugin reads its own\n * subtree via `ConfigStore.getExtension(pluginName)`. Plugins should\n * declare a `configSchema` so the loader validates this section\n * automatically before `setup()` runs.\n *\n * Example:\n * extensions: {\n * 'wstack-auth': { tokenUrl: 'https://...', refreshBefore: 300 },\n * 'wstack-metrics': { sink: 'prometheus', port: 9090 },\n * }\n */\n extensions?: Record<string, Record<string, unknown>>;\n}\n\nexport interface ConfigLoader {\n load(opts?: {\n cliFlags?: Partial<Config> | undefined;\n cwd?: string | undefined;\n }): Promise<Config>;\n /** Load and decrypt the sync config from ~/.wrongstack/sync.json. */\n loadSyncConfig(): Promise<SyncConfig | null>;\n /** Persist sync config to ~/.wrongstack/sync.json with encrypted token. */\n persistSyncConfig(cfg: SyncConfig): Promise<void>;\n}\n\n/**\n * Subscribable view over Config. Plugins and CLI subsystems use this instead\n * of holding a frozen Config reference, so they can react to runtime updates\n * (e.g. `/model` switching the active provider, secrets rotation, dynamic\n * extension reload).\n *\n * The store enforces immutability \u2014 `get()` always returns a frozen object.\n * Updates happen through `update(partial)`, which produces a new Config\n * (structurally cloned, then frozen) and notifies watchers.\n */\nexport interface ConfigStore {\n get(): Readonly<Config>;\n /**\n * Get a typed top-level section. Convenience for consumers that only\n * care about one slice (e.g. `tools` or `context`).\n */\n getSection<K extends keyof Config>(key: K): Readonly<Config[K]>;\n /**\n * Return the extension namespace for `pluginName`, or an empty record\n * when none is configured. The returned object is frozen.\n */\n getExtension(pluginName: string): Readonly<Record<string, unknown>>;\n /**\n * Apply a partial update. Returns the new Config. Watchers are notified\n * synchronously after the update completes. Throws if the result fails\n * any registered invariants (currently: version must stay 1).\n */\n update(partial: Partial<Config>): Readonly<Config>;\n /** Subscribe to changes. Returns an unsubscribe function. */\n watch(cb: (next: Readonly<Config>, prev: Readonly<Config>) => void): () => void;\n}\n", "import { expectDefined } from '../utils/expect-defined.js';\nexport type ContextWindowModeId = 'balanced' | 'frugal' | 'deep' | 'archival';\n\nexport type ContextWindowAggressiveOn = 'hard' | 'soft' | 'warn';\n\nexport interface ContextWindowThresholds {\n warn: number;\n soft: number;\n hard: number;\n}\n\nexport interface ContextWindowMode {\n id: ContextWindowModeId;\n name: string;\n description: string;\n thresholds: ContextWindowThresholds;\n aggressiveOn: ContextWindowAggressiveOn;\n preserveK: number;\n eliseThreshold: number;\n targetLoad: number;\n}\n\nexport interface ContextWindowPolicy extends ContextWindowMode {}\n\nexport interface ContextWindowConfigLike {\n mode?: ContextWindowModeId | string | undefined;\n warnThreshold?: number | undefined;\n softThreshold?: number | undefined;\n hardThreshold?: number | undefined;\n preserveK?: number | undefined;\n eliseThreshold?: number | undefined;\n}\n\nexport const DEFAULT_CONTEXT_WINDOW_MODE_ID: ContextWindowModeId = 'balanced';\n\nexport const CONTEXT_WINDOW_MODES: readonly ContextWindowMode[] = Object.freeze([\n {\n id: 'balanced',\n name: 'Balanced',\n description: 'Default rolling compaction: recent work stays verbatim, old tool output is trimmed.',\n thresholds: { warn: 0.6, soft: 0.75, hard: 0.9 },\n aggressiveOn: 'soft',\n preserveK: 10,\n eliseThreshold: 2000,\n targetLoad: 0.65,\n },\n {\n id: 'frugal',\n name: 'Frugal',\n description: 'Token-saver mode: compacts early and keeps a tighter verbatim tail.',\n thresholds: { warn: 0.45, soft: 0.6, hard: 0.75 },\n aggressiveOn: 'warn',\n preserveK: 6,\n eliseThreshold: 700,\n targetLoad: 0.5,\n },\n {\n id: 'deep',\n name: 'Deep',\n description: 'Long-reasoning mode: delays compaction and keeps more recent turns intact.',\n thresholds: { warn: 0.72, soft: 0.86, hard: 0.96 },\n aggressiveOn: 'hard',\n preserveK: 18,\n eliseThreshold: 5000,\n targetLoad: 0.78,\n },\n {\n id: 'archival',\n name: 'Archival',\n description: 'Decision-preserving mode: compacts steadily while keeping summaries prominent.',\n thresholds: { warn: 0.55, soft: 0.7, hard: 0.84 },\n aggressiveOn: 'soft',\n preserveK: 8,\n eliseThreshold: 1200,\n targetLoad: 0.58,\n },\n]);\n\nexport function listContextWindowModes(): ContextWindowMode[] {\n return CONTEXT_WINDOW_MODES.map((m) => ({ ...m, thresholds: { ...m.thresholds } }));\n}\n\nexport function getContextWindowMode(id: string | null | undefined): ContextWindowMode | null {\n if (!id) return null;\n const mode = CONTEXT_WINDOW_MODES.find((m) => m.id === id);\n return mode ? { ...mode, thresholds: { ...mode.thresholds } } : null;\n}\n\nexport function isContextWindowModeId(id: string): id is ContextWindowModeId {\n return CONTEXT_WINDOW_MODES.some((m) => m.id === id);\n}\n\nexport function resolveContextWindowPolicy(\n config: ContextWindowConfigLike = {},\n overrideMode?: string | null | undefined,\n): ContextWindowPolicy {\n const requested = overrideMode ?? config.mode ?? DEFAULT_CONTEXT_WINDOW_MODE_ID;\n const mode = getContextWindowMode(requested) ?? expectDefined(getContextWindowMode(DEFAULT_CONTEXT_WINDOW_MODE_ID));\n\n return {\n ...mode,\n thresholds: {\n warn: config.warnThreshold ?? mode.thresholds.warn,\n soft: config.softThreshold ?? mode.thresholds.soft,\n hard: config.hardThreshold ?? mode.thresholds.hard,\n },\n preserveK: config.preserveK ?? mode.preserveK,\n eliseThreshold: config.eliseThreshold ?? mode.eliseThreshold,\n };\n}\n\nexport function formatContextWindowModeList(activeId?: string | null): string {\n return CONTEXT_WINDOW_MODES.map((m) => {\n const marker = m.id === activeId ? '*' : ' ';\n return `${marker} ${m.id.padEnd(9)} ${m.name} - ${m.description}`;\n }).join('\\n');\n}\n", "/**\n * Shared configuration constants used across execution, storage, CLI, and WebUI.\n * Centralized here to avoid cross-domain import cycles.\n */\n\n/** Default tools config \u2014 mirrors values baked into BEHAVIOR_DEFAULTS. */\nexport const DEFAULT_TOOLS_CONFIG = Object.freeze({\n defaultExecutionStrategy: 'smart',\n maxIterations: 100,\n iterationTimeoutMs: 300_000,\n maxToolTimeoutMs: 300_000,\n sessionTimeoutMs: 1_800_000,\n perIterationOutputCapBytes: 100_000,\n descriptionMode: Object.freeze({}) as Record<string, 'extend' | 'simple' | undefined>,\n disabledTools: Object.freeze([]) as readonly string[],\n autoExtendLimit: true,\n restrictToProjectRoot: false,\n loopDetection: Object.freeze({\n mode: 'steer-then-cut',\n steerThreshold: 3,\n cutThreshold: 5,\n windowSize: 12,\n callRepeatThreshold: 4,\n }) as Readonly<{\n mode: 'steer-then-cut' | 'cut' | 'off';\n steerThreshold: number;\n cutThreshold: number;\n windowSize: number;\n callRepeatThreshold: number;\n }>,\n});\n\n/** Default context config \u2014 mirrors BEHAVIOR_DEFAULTS.context. */\nexport const DEFAULT_CONTEXT_CONFIG = Object.freeze({\n preserveK: 10,\n eliseThreshold: 2000,\n});\n\n/** Default autonomy config \u2014 auto-proceed delay etc. */\nexport const DEFAULT_AUTONOMY_CONFIG = Object.freeze({\n autoProceedDelayMs: 45_000,\n});\n\n/**\n * Default process circuit-breaker config. Protection is OFF by default \u2014 the\n * breaker only gates `bash`/`exec` once the user opts in via `/settings breaker on`.\n * The auto kill/reset delay is only consulted when protection is enabled.\n */\nexport const DEFAULT_CIRCUIT_BREAKER_CONFIG = Object.freeze({\n enabled: false,\n autoKillResetMs: 60_000,\n});\n\n/** Default session logging / audit configuration. */\nexport const DEFAULT_SESSION_LOGGING_CONFIG = Object.freeze({\n auditLevel: 'standard' as const,\n sampling: {\n toolProgress: {\n sampleRate: 8,\n },\n },\n});\n\n/** Default retention window for local session pruning. */\nexport const DEFAULT_SESSION_PRUNE_DAYS = 30;\n", "/**\n * L2-D: Config version migration framework. Pure functions, decoupled\n * from disk/CLI \u2014 caller passes a parsed JSON object and gets back the\n * up-to-date `Config` shape (or a structured error explaining why\n * migration failed).\n *\n * Migrations are registered as `{ from, to, migrate }` triples and run\n * sequentially. Each migration is independently testable. Adding a new\n * version means appending one migration; existing user configs are\n * upgraded in place at load time.\n */\n\nexport interface MigrationContext {\n /**\n * Original on-disk version of the input. Migrations may use this to\n * decide between in-place patches and rewrites.\n */\n fromVersion: number;\n /**\n * Set when the migration writes back to disk. Callers persist the\n * migrated config when this is true so the user doesn't see the same\n * migration banner on every boot.\n */\n shouldPersist: boolean;\n}\n\nexport interface ConfigMigration {\n /** Version of the input this migration accepts. */\n from: number;\n /** Version of the output it produces. */\n to: number;\n /** Pure transform \u2014 no I/O. */\n migrate(input: Record<string, unknown>, ctx: MigrationContext): Record<string, unknown>;\n /** Optional human-readable description for migration logs / banners. */\n describe?: string | undefined;\n}\n\nexport interface MigrationResult {\n /** Final config (still typed as `unknown`-keyed \u2014 caller validates). */\n config: Record<string, unknown>;\n /** Ordered list of `from\u2192to` versions that ran. */\n applied: string[];\n /** True when at least one migration produced changes worth persisting. */\n shouldPersist: boolean;\n}\n\nexport class ConfigMigrationError extends Error {\n readonly fromVersion: number;\n readonly targetVersion: number;\n readonly missingStep: number | null;\n\n constructor(opts: {\n message: string;\n fromVersion: number;\n targetVersion: number;\n missingStep: number | null;\n }) {\n super(opts.message);\n this.name = 'ConfigMigrationError';\n this.fromVersion = opts.fromVersion;\n this.targetVersion = opts.targetVersion;\n this.missingStep = opts.missingStep;\n }\n}\n\n/**\n * Run registered migrations until the input reaches `targetVersion`.\n *\n * Resolution rules:\n * 1. If `input.version === targetVersion`, no migrations run; `shouldPersist`\n * is false.\n * 2. Otherwise walk the migration chain from `input.version` upward,\n * picking the migration whose `from` matches the current version.\n * 3. Stop when `current.version === targetVersion`.\n * 4. If no migration matches at some point, throw `ConfigMigrationError`\n * with the missing step recorded for diagnostics.\n *\n * Migrations may be downward (e.g. for staged rollouts), but `targetVersion`\n * must be reachable strictly via the registered chain \u2014 there's no implicit\n * \"skip\" or transitive resolution.\n */\nexport function runConfigMigrations(\n input: Record<string, unknown>,\n targetVersion: number,\n migrations: readonly ConfigMigration[],\n): MigrationResult {\n const initial = typeof input['version'] === 'number' ? (input['version'] as number) : 1;\n let current: Record<string, unknown> = { ...input };\n let currentVersion = initial;\n const applied: string[] = [];\n let shouldPersist = false;\n\n let guard = 0;\n while (currentVersion !== targetVersion) {\n if (++guard > 100) {\n throw new ConfigMigrationError({\n message: `Config migration looped past 100 steps (from v${initial} toward v${targetVersion})`,\n fromVersion: initial,\n targetVersion,\n missingStep: currentVersion,\n });\n }\n const step = migrations.find((m) => m.from === currentVersion);\n if (!step) {\n throw new ConfigMigrationError({\n message: `No migration registered from config v${currentVersion} (target v${targetVersion}). Update the framework or revert the config file.`,\n fromVersion: initial,\n targetVersion,\n missingStep: currentVersion,\n });\n }\n const ctx: MigrationContext = { fromVersion: currentVersion, shouldPersist: false };\n const next = step.migrate(current, ctx);\n // Ensure the migration set the new version. Be tolerant: if it didn't,\n // patch it in so the chain doesn't infinite-loop on author oversight.\n if (typeof next['version'] !== 'number' || next['version'] !== step.to) {\n next['version'] = step.to;\n }\n current = next;\n currentVersion = step.to;\n applied.push(`v${step.from}\u2192v${step.to}`);\n shouldPersist = shouldPersist || ctx.shouldPersist || step.from < step.to;\n }\n return { config: current, applied, shouldPersist };\n}\n\n/**\n * Default empty migration registry. Real migrations are appended as new\n * Config versions are introduced. Example (when v2 lands):\n *\n * export const CONFIG_MIGRATIONS: readonly ConfigMigration[] = [\n * {\n * from: 1, to: 2, describe: 'rename `apiKey` \u2192 `auth.apiKey`',\n * migrate(cfg) {\n * const apiKey = cfg.apiKey;\n * delete cfg.apiKey;\n * return { ...cfg, auth: { ...(cfg.auth ?? {}), apiKey } };\n * },\n * },\n * ];\n */\nexport const DEFAULT_CONFIG_MIGRATIONS: readonly ConfigMigration[] = [];\n", "import * as fsp from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport type { SessionStore } from '../types/session.js';\nimport { ensureDir } from '../utils/atomic-write.js';\n\n/**\n * Per-project lockfile used for crash detection. The CLI writes one of\n * these alongside the session JSONLs (`<projectSessions>/active.json`)\n * when an interactive run starts, and deletes it on clean exit. If we\n * find one on the next launch whose owning PID is dead (or whose host\n * doesn't match), we know the previous run was killed mid-flight and\n * the session it was writing to is a recovery candidate.\n *\n * The lockfile is intentionally per-project (already isolated by\n * `wpaths.projectSessions`), so two TUIs in two different repos do not\n * fight each other.\n */\nexport interface RecoveryLockOptions {\n /** Directory the lockfile lives in. Usually `wpaths.projectSessions`. */\n dir: string;\n /** This process's PID. Default: `process.pid`. */\n pid?: number | undefined;\n /** Hostname recorded for the lock. Default: `os.hostname()`. */\n hostname?: string | undefined;\n /** Locks older than this are considered orphaned (disk wiped, etc.). Default 24h. */\n maxAgeMs?: number | undefined;\n /** Used to check whether the abandoned session was actually closed cleanly. */\n sessionStore?: SessionStore | undefined;\n /**\n * Override the PID-liveness probe. Default: `process.kill(pid, 0)` \u2014\n * succeeds (or throws EPERM) when the PID is alive, throws ESRCH when\n * it is gone. Tests inject a deterministic stub.\n */\n isPidAlive?: (((pid: number) => boolean)) | undefined;\n}\n\nexport interface AbandonedSession {\n sessionId: string;\n pid: number;\n startedAt: string;\n /** Lockfile age in ms at the time of the check. */\n ageMs: number;\n /** Number of messages already on disk for this session. */\n messageCount: number;\n}\n\ninterface LockFile {\n v: 1;\n sessionId: string;\n pid: number;\n hostname: string;\n startedAt: string;\n}\n\nconst LOCK_FILE = 'active.json';\nconst DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1000;\n\nexport class RecoveryLock {\n private readonly file: string;\n private readonly pid: number;\n private readonly hostname: string;\n private readonly maxAgeMs: number;\n private readonly sessionStore?: SessionStore | undefined;\n private readonly probe: (pid: number) => boolean;\n\n constructor(opts: RecoveryLockOptions) {\n this.file = path.join(opts.dir, LOCK_FILE);\n this.pid = opts.pid ?? process.pid;\n this.hostname = opts.hostname ?? os.hostname();\n this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;\n this.sessionStore = opts.sessionStore;\n this.probe = opts.isPidAlive ?? defaultIsPidAlive;\n }\n\n /**\n * Examine the lockfile and decide whether it represents an abandoned\n * session. Returns `null` if the file is missing, points to a live\n * instance, references a clean-closed session, is too old, or is\n * malformed. Otherwise returns enough detail to prompt the user.\n *\n * Important: this is a read-only check. We never delete an active\n * lock from here \u2014 if another wstack instance is alive, the caller\n * should bail or run with a fresh session instead.\n */\n async checkAbandoned(): Promise<AbandonedSession | null> {\n const lock = await this.readLock();\n if (!lock) return null;\n\n const ageMs = Date.now() - new Date(lock.startedAt).getTime();\n if (Number.isNaN(ageMs) || ageMs < 0) {\n // Clock skew or corrupted timestamp \u2014 treat as orphan.\n return null;\n }\n if (ageMs > this.maxAgeMs) return null;\n\n // PID liveness only meaningful on the same host. Different host\n // means we can't probe \u2014 assume abandoned (the other machine's\n // wstack can't be holding *our* sessions dir unless it was\n // shared via network mount, in which case the user is on their\n // own).\n if (lock.hostname === this.hostname && this.probe(lock.pid)) {\n // Another wstack on this box is actively writing here.\n return null;\n }\n\n let messageCount = 0;\n if (this.sessionStore) {\n try {\n const data = await this.sessionStore.load(lock.sessionId);\n // Closed means the LAST session_end is not followed by further\n // conversation activity. Legacy /save wrote mid-stream session_end\n // markers \u2014 `some()` would treat a session that crashed AFTER such a\n // marker as cleanly closed and silently skip recovery.\n const lastEnd = data.events.findLastIndex((e) => e.type === 'session_end');\n const closed =\n lastEnd >= 0 &&\n !data.events\n .slice(lastEnd + 1)\n .some(\n (e) =>\n e.type === 'user_input' ||\n e.type === 'llm_response' ||\n e.type === 'in_flight_start',\n );\n if (closed) return null;\n messageCount = data.messages.length;\n } catch {\n // Lock points to a session that doesn't exist on disk (deleted\n // out from under us). Nothing to recover.\n return null;\n }\n }\n\n return {\n sessionId: lock.sessionId,\n pid: lock.pid,\n startedAt: lock.startedAt,\n ageMs,\n messageCount,\n };\n }\n\n /**\n * Claim the lock for the given session. Uses exclusive-create (`O_EXCL`)\n * to detect whether another process acquired the lock between our\n * `checkAbandoned()` call and now. If the file already exists, it means\n * another process won the race and we throw instead of silently\n * overwriting their recovery record.\n *\n * The caller MUST have already called `checkAbandoned()` and handled its\n * null return before calling this.\n */\n async write(sessionId: string): Promise<void> {\n await ensureDir(path.dirname(this.file));\n const lock: LockFile = {\n v: 1,\n sessionId,\n pid: this.pid,\n hostname: this.hostname,\n startedAt: new Date().toISOString(),\n };\n // O_EXCL: atomic create \u2014 fails with EEXIST if another process wrote\n // the file between our checkAbandoned() and this write. This prevents\n // two processes that scanned the same stale lock from both believing\n // they hold it. The atomicWrite approach (temp+rename) would silently\n // replace on POSIX, hiding the race.\n try {\n await fsp.writeFile(this.file, JSON.stringify(lock), { flag: 'wx', mode: 0o600 });\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'EEXIST') {\n throw new Error(`Recovery lock already held by another process`);\n }\n /* v8 ignore next -- defensive: an unexpected (non-EEXIST) write failure is rethrown */\n throw err;\n }\n }\n\n /**\n * Release the lock. Idempotent \u2014 silently succeeds if the file is\n * already gone (e.g. someone else cleared it, or the directory was\n * wiped).\n */\n async clear(): Promise<void> {\n try {\n await fsp.unlink(this.file);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') return;\n /* v8 ignore next -- defensive: an unexpected (non-ENOENT) unlink failure is rethrown */\n throw err;\n }\n }\n\n private async readLock(): Promise<LockFile | null> {\n let raw: string;\n try {\n raw = await fsp.readFile(this.file, 'utf8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') return null;\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (!isLockFile(parsed)) return null;\n return parsed;\n } catch {\n return null;\n }\n }\n}\n\nfunction isLockFile(v: unknown): v is LockFile {\n if (typeof v !== 'object' || v === null) return false;\n const o = v as Record<string, unknown>;\n return (\n o['v'] === 1 &&\n typeof o['sessionId'] === 'string' &&\n typeof o['pid'] === 'number' &&\n typeof o['hostname'] === 'string' &&\n typeof o['startedAt'] === 'string'\n );\n}\n\n/**\n * Probe whether a process is alive without sending it a real signal.\n *\n * Unix: `process.kill(pid, 0)` succeeds for our own processes, throws\n * EPERM for others (still alive, just not ours), and throws ESRCH\n * when the PID is gone.\n * Windows (Node 22+): same call returns true if the process exists,\n * throws otherwise.\n */\nfunction defaultIsPidAlive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n /* v8 ignore next -- platform/permission-specific: EPERM means alive but owned by another user */\n if (code === 'EPERM') return true; // alive, but owned by someone else\n return false;\n }\n}\n", "import * as fs from 'node:fs/promises';\nimport { expectDefined } from '../utils/expect-defined.js';\nimport type { ContentBlock } from '../types/blocks.js';\nimport type {\n DefaultSessionReaderOptions,\n SessionExportOptions,\n SessionQuery,\n SessionReader,\n SessionSearchHit,\n SessionSearchQuery,\n SessionSummaryLite,\n} from '../types/session-reader.js';\nimport { compileUserRegex } from '../utils/regex-guard.js';\nimport { sessionScopedPath } from '../utils/session-scoped-path.js';\nimport type { SessionData, SessionEvent, SessionMetadata, SessionStore } from '../types/session.js';\n\n/**\n * L2-A: read-only view over a `SessionStore` with query, replay, search,\n * and export helpers. Implemented on top of the public `SessionStore`\n * surface so any concrete store can be inspected without re-implementation.\n */\nexport class DefaultSessionReader implements SessionReader {\n private readonly store: SessionStore;\n private readonly eventCache = new Map<string, SessionData>();\n private readonly eventCacheMtimes = new Map<string, number>();\n private static readonly EVENT_CACHE_MAX_ENTRIES = 32;\n\n constructor(opts: DefaultSessionReaderOptions) {\n this.store = opts.store;\n }\n\n private async loadCachedSessionData(sessionId: string): Promise<SessionData> {\n const storeWithPath = this.store as SessionStore & {\n dir?: string | undefined;\n clearLoadCache?: ((sessionId?: string | undefined) => void) | undefined;\n };\n const rootDir = storeWithPath.dir;\n if (!rootDir) {\n return await this.store.load(sessionId);\n }\n const sessionPath = sessionScopedPath(rootDir, sessionId, '.jsonl');\n let mtimeMs: number | null = null;\n try {\n const stat = await fs.stat(sessionPath);\n mtimeMs = stat.mtimeMs;\n } catch {\n this.eventCache.delete(sessionId);\n this.eventCacheMtimes.delete(sessionId);\n return await this.store.load(sessionId);\n }\n\n const cachedMtime = this.eventCacheMtimes.get(sessionId);\n const cachedData = this.eventCache.get(sessionId);\n if (cachedData && cachedMtime === mtimeMs) {\n this.eventCache.delete(sessionId);\n this.eventCacheMtimes.delete(sessionId);\n this.eventCache.set(sessionId, cachedData);\n this.eventCacheMtimes.set(sessionId, mtimeMs);\n return cachedData;\n }\n\n const data = await this.store.load(sessionId);\n this.eventCache.delete(sessionId);\n this.eventCacheMtimes.delete(sessionId);\n this.eventCache.set(sessionId, data);\n this.eventCacheMtimes.set(sessionId, mtimeMs);\n while (this.eventCache.size > DefaultSessionReader.EVENT_CACHE_MAX_ENTRIES) {\n const oldest = this.eventCache.keys().next().value;\n if (oldest === undefined) break;\n this.eventCache.delete(oldest);\n this.eventCacheMtimes.delete(oldest);\n }\n\n if (data.metadata.endedAt) {\n storeWithPath.clearLoadCache?.(sessionId);\n }\n\n return data;\n }\n\n async query(q: SessionQuery = {}): Promise<SessionSummaryLite[]> {\n // Prefer the store's filtered list when available \u2014 it pushes the\n // filter into the cached index instead of fetching 1000 + linear scan.\n const storeWithFilter = this.store as SessionStore & {\n listFiltered?: ((criteria: {\n since?: string | undefined;\n until?: string | undefined;\n provider?: string | undefined;\n model?: string | undefined;\n minTokens?: number | undefined;\n titleContains?: string | undefined;\n limit?: number | undefined;\n }) => Promise<import('../types/session.js').SessionSummary[]>) | undefined;\n };\n let raw: import('../types/session.js').SessionSummary[];\n if (typeof storeWithFilter.listFiltered === 'function') {\n raw = await storeWithFilter.listFiltered({\n since: q.since,\n until: q.until,\n provider: q.provider,\n model: q.model,\n minTokens: q.minTokens,\n titleContains: q.titleContains,\n limit: q.limit,\n });\n } else {\n const fetched = await this.store.list(q.limit ? Math.max(q.limit, 100) : 1000);\n const titleNeedle = q.titleContains?.toLowerCase();\n raw = fetched.filter((s) => {\n if (q.since && s.startedAt < q.since) return false;\n if (q.until && s.startedAt > q.until) return false;\n if (q.provider && s.provider !== q.provider) return false;\n if (q.model && s.model !== q.model) return false;\n if (q.minTokens !== undefined && s.tokenTotal < q.minTokens) return false;\n if (titleNeedle && !s.title.toLowerCase().includes(titleNeedle)) return false;\n return true;\n });\n }\n const out: SessionSummaryLite[] = raw.map((s) => ({\n id: s.id,\n title: s.title,\n startedAt: s.startedAt,\n provider: s.provider,\n model: s.model,\n tokenTotal: s.tokenTotal,\n }));\n return q.limit ? out.slice(0, q.limit) : out;\n }\n\n async *replay(sessionId: string): AsyncIterable<SessionEvent> {\n const data = await this.loadCachedSessionData(sessionId);\n for (const e of data.events) yield e;\n }\n\n async search(q: SessionSearchQuery, sessionId?: string | undefined, sessionQuery?: SessionQuery): Promise<SessionSearchHit[]> {\n const limit = q.limit ?? 100;\n const matcher = buildMatcher(q);\n const allowedTypes = q.types ? new Set(q.types) : null;\n\n // Filter sessions BEFORE scanning events \u2014 avoids touching the JSONL\n // for sessions that don't match the time/provider/model criteria.\n let ids: string[];\n if (sessionId) {\n ids = [sessionId];\n } else {\n // Prefer the store's filtered list when available \u2014 avoids fetching\n // 1000 sessions and linear-filtering in-process.\n const storeWithFilter = this.store as SessionStore & {\n listFiltered?: ((criteria: {\n since?: string | undefined;\n until?: string | undefined;\n provider?: string | undefined;\n model?: string | undefined;\n minTokens?: number | undefined;\n titleContains?: string | undefined;\n limit?: number | undefined;\n }) => Promise<import('../types/session.js').SessionSummary[]>) | undefined;\n };\n let sessions: import('../types/session.js').SessionSummary[];\n if (typeof storeWithFilter.listFiltered === 'function') {\n sessions = await storeWithFilter.listFiltered({\n since: sessionQuery?.since,\n until: sessionQuery?.until,\n provider: sessionQuery?.provider,\n model: sessionQuery?.model,\n minTokens: sessionQuery?.minTokens,\n titleContains: sessionQuery?.titleContains,\n limit: 1000,\n });\n } else {\n sessions = await this.store.list(1000);\n const titleNeedle = sessionQuery?.titleContains?.toLowerCase();\n sessions = sessions.filter((s) => {\n if (sessionQuery?.since && s.startedAt < sessionQuery.since) return false;\n if (sessionQuery?.until && s.startedAt > sessionQuery.until) return false;\n if (sessionQuery?.provider && s.provider !== sessionQuery.provider) return false;\n if (sessionQuery?.model && s.model !== sessionQuery.model) return false;\n if (sessionQuery?.minTokens !== undefined && s.tokenTotal < sessionQuery.minTokens) return false;\n if (titleNeedle && !s.title.toLowerCase().includes(titleNeedle)) return false;\n return true;\n });\n }\n ids = sessions.map((s) => s.id);\n }\n\n const hits: SessionSearchHit[] = [];\n\n // Fast path: when the underlying store supports streaming search,\n // walk each session's JSONL line-by-line and bail out the moment we\n // hit `limit`. This avoids reading + parsing the entire file (which\n // `load()` does) and never reuses `_loadCache`, so concurrent\n // analytics queries don't churn the writer-side cache.\n const streaming = this.store.searchEvents?.bind(this.store);\n if (streaming) {\n for (const id of ids) {\n const matched = await streaming(\n id,\n (ev) => {\n if (allowedTypes && !allowedTypes.has(ev.type)) return false;\n const text = eventText(ev);\n if (text === null) return false;\n return matcher(text) !== null;\n },\n { limit: limit - hits.length },\n );\n for (const m of matched) {\n const text = expectDefined(eventText(m.event));\n const hit = expectDefined(matcher(text));\n hits.push({\n sessionId: id,\n eventIndex: m.eventIndex,\n ts: m.ts,\n type: m.event.type,\n snippet: snippetOf(text, hit.start, hit.end),\n });\n if (hits.length >= limit) return hits;\n }\n }\n return hits;\n }\n\n // Fallback: stores that don't implement streaming. Loads the full\n // event stream per session \u2014 necessary for in-memory or non-file\n // stores that don't expose a streaming surface.\n for (const id of ids) {\n let data;\n try {\n data = await this.loadCachedSessionData(id);\n } catch {\n continue;\n }\n for (let i = 0; i < data.events.length; i++) {\n const ev = expectDefined(data.events[i]);\n if (allowedTypes && !allowedTypes.has(ev.type)) continue;\n const text = eventText(ev);\n if (text === null) continue;\n const hit = matcher(text);\n if (!hit) continue;\n hits.push({\n sessionId: id,\n eventIndex: i,\n ts: ev.ts,\n type: ev.type,\n snippet: snippetOf(text, hit.start, hit.end),\n });\n if (hits.length >= limit) return hits;\n }\n }\n return hits;\n }\n\n async export(sessionId: string, opts: SessionExportOptions): Promise<string> {\n const data = await this.loadCachedSessionData(sessionId);\n const includeTools = opts.includeTools ?? true;\n const includeDiagnostics = opts.includeDiagnostics ?? true;\n\n const filtered = data.events.filter((e) => {\n if (\n !includeTools &&\n (e.type === 'tool_use' ||\n e.type === 'tool_result' ||\n e.type === 'tool_call_start' ||\n e.type === 'tool_call_end')\n ) {\n return false;\n }\n if (\n !includeDiagnostics &&\n (e.type === 'error' || e.type === 'compaction' || e.type === 'message_truncated')\n ) {\n return false;\n }\n return true;\n });\n\n if (opts.format === 'json') {\n return JSON.stringify({ metadata: data.metadata, events: filtered }, null, 2);\n }\n if (opts.format === 'text') {\n return renderPlainText(data.metadata, filtered);\n }\n return renderMarkdown(data.metadata, filtered);\n }\n\n async metadata(sessionId: string): Promise<SessionMetadata> {\n const data = await this.loadCachedSessionData(sessionId);\n return data.metadata;\n }\n}\n\nfunction buildMatcher(\n q: SessionSearchQuery,\n): (text: string) => { start: number; end: number } | null {\n const ci = q.caseInsensitive ?? true;\n if (q.regex) {\n const flags = ci ? 'i' : '';\n const compiled = compileUserRegex(q.query, flags);\n if (!compiled.ok) {\n throw new Error(`Invalid search regex \"${q.query}\": ${compiled.reason}`);\n }\n const re = compiled.regex;\n return (text) => {\n const m = re.exec(text);\n return m ? { start: m.index, end: m.index + m[0].length } : null;\n };\n }\n const needle = ci ? q.query.toLowerCase() : q.query;\n return (text) => {\n const hay = ci ? text.toLowerCase() : text;\n const idx = hay.indexOf(needle);\n return idx === -1 ? null : { start: idx, end: idx + needle.length };\n };\n}\n\nfunction eventText(e: SessionEvent): string | null {\n switch (e.type) {\n case 'user_input':\n return contentToString(e.content);\n case 'llm_response':\n return contentToString(e.content);\n case 'tool_use':\n return `${e.name} ${JSON.stringify(e.input)}`;\n case 'tool_result':\n return typeof e.content === 'string' ? e.content : JSON.stringify(e.content);\n case 'error':\n return `${e.phase}: ${e.message}`;\n case 'session_start':\n case 'session_resumed':\n return `${e.model}/${e.provider}`;\n case 'task_created':\n case 'task_completed':\n return e.title;\n case 'task_failed':\n return `${e.title}: ${e.error}`;\n case 'skill_activated':\n case 'skill_deactivated':\n return e.skillName;\n default:\n return null;\n }\n}\n\nfunction contentToString(content: string | ContentBlock[]): string {\n if (typeof content === 'string') return content;\n return content\n .map((b) => {\n switch (b.type) {\n case 'text':\n return b.text;\n case 'tool_use':\n return `[tool_use:${b.name} ${JSON.stringify(b.input)}]`;\n case 'tool_result':\n return typeof b.content === 'string' ? b.content : JSON.stringify(b.content);\n default:\n return '';\n }\n })\n .join('\\n');\n}\n\nconst SNIPPET_RADIUS = 60;\n\nfunction snippetOf(text: string, start: number, end: number): string {\n const from = Math.max(0, start - SNIPPET_RADIUS);\n const to = Math.min(text.length, end + SNIPPET_RADIUS);\n const prefix = from > 0 ? '\u2026' : '';\n const suffix = to < text.length ? '\u2026' : '';\n return prefix + text.slice(from, to).replace(/\\s+/g, ' ').trim() + suffix;\n}\n\nfunction renderMarkdown(meta: SessionMetadata, events: SessionEvent[]): string {\n const lines: string[] = [];\n lines.push(`# Session ${meta.id}`);\n lines.push('');\n if (meta.model || meta.provider) {\n lines.push(`- **Model:** ${meta.provider ?? '?'}/${meta.model ?? '?'}`);\n }\n lines.push(`- **Started:** ${meta.startedAt}`);\n if (meta.endedAt) lines.push(`- **Ended:** ${meta.endedAt}`);\n lines.push('');\n lines.push('---');\n lines.push('');\n for (const e of events) {\n switch (e.type) {\n case 'user_input': {\n lines.push(`## User \u2014 ${e.ts}`);\n lines.push('');\n lines.push(contentToString(e.content));\n lines.push('');\n break;\n }\n case 'llm_response': {\n lines.push(`## Assistant \u2014 ${e.ts}`);\n lines.push('');\n lines.push(contentToString(e.content));\n if (e.stopReason && e.stopReason !== 'end_turn') {\n lines.push('');\n lines.push(`*stop: ${e.stopReason}*`);\n }\n lines.push('');\n break;\n }\n case 'tool_use': {\n lines.push(`### Tool call: \\`${e.name}\\``);\n lines.push('');\n lines.push('```json');\n lines.push(JSON.stringify(e.input, null, 2));\n lines.push('```');\n lines.push('');\n break;\n }\n case 'tool_result': {\n const body = typeof e.content === 'string' ? e.content : JSON.stringify(e.content, null, 2);\n lines.push(`### Tool result${e.isError ? ' (error)' : ''}`);\n lines.push('');\n lines.push('```');\n lines.push(body);\n lines.push('```');\n lines.push('');\n break;\n }\n case 'error': {\n lines.push(`> **Error** (${e.phase}): ${e.message}`);\n lines.push('');\n break;\n }\n case 'compaction': {\n lines.push(`> **Compaction**: ${e.before} \u2192 ${e.after} tokens`);\n lines.push('');\n break;\n }\n default:\n break;\n }\n }\n return lines.join('\\n');\n}\n\nfunction renderPlainText(meta: SessionMetadata, events: SessionEvent[]): string {\n const lines: string[] = [];\n lines.push(\n `Session ${meta.id} \u2014 ${meta.provider ?? '?'}/${meta.model ?? '?'} \u2014 started ${meta.startedAt}`,\n );\n lines.push(''.padEnd(72, '-'));\n for (const e of events) {\n switch (e.type) {\n case 'user_input':\n lines.push(`[${e.ts}] USER`);\n lines.push(contentToString(e.content));\n lines.push('');\n break;\n case 'llm_response':\n lines.push(`[${e.ts}] ASSISTANT`);\n lines.push(contentToString(e.content));\n lines.push('');\n break;\n case 'tool_use':\n lines.push(`[${e.ts}] TOOL_USE ${e.name} ${JSON.stringify(e.input)}`);\n break;\n case 'tool_result':\n lines.push(\n `[${e.ts}] TOOL_RESULT${e.isError ? ' (error)' : ''} ${\n typeof e.content === 'string' ? e.content : JSON.stringify(e.content)\n }`,\n );\n break;\n case 'error':\n lines.push(`[${e.ts}] ERROR (${e.phase}): ${e.message}`);\n break;\n default:\n break;\n }\n }\n return lines.join('\\n');\n}\n", "import { expectDefined } from '../utils/expect-defined.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs/promises';\nimport { sessionScopedPath } from '../utils/session-scoped-path.js';\nimport { atomicWrite, withFileLock } from '../utils/atomic-write.js';\nimport { WrongStackError, ERROR_CODES } from '../types/errors.js';\nimport type { EventBus } from '../kernel/events.js';\n/**\n * L2-B: AnnotationsStore \u2014 sidecar storage for collaboration annotations\n * (Phase 2 of idea #13 from IDEAS.md).\n *\n * Why a sidecar file, not the session JSONL?\n *\n * The session log is an event-sourced append-only journal\n * (`packages/core/src/types/session.ts` invariant: events are\n * append-only, with `truncateToCheckpoint` only as an explicit\n * rewind). Mixing in human-typed annotations would break that\n * invariant \u2014 the user's note about \"this rm looks dangerous\"\n * is not part of the agent's event history; it is meta-commentary\n * on the history.\n *\n * So we keep annotations in a sibling file: one JSON document per\n * session, resolved with `sessionScopedPath(sessionDir, sessionId,\n * '.annotations.json')`. The shape is a simple versioned array,\n * written atomically.\n *\n * Concurrency model:\n *\n * The store uses a per-session Promise chain to serialize writes.\n * Multiple annotators adding notes at the same time will queue,\n * not race. The atomic write itself is the second line of\n * defense (in case the chain is bypassed \u2014 e.g. two processes\n * pointing at the same dir).\n */\n\n/** Wire/storage shape for one annotation. */\nexport interface Annotation {\n /** Stable id (UUIDv4-ish). Referenced by resolve/delete. */\n id: string;\n /** Session this annotation belongs to. */\n sessionId: string;\n /** Index into the session event log the annotation refers to. */\n atEventIndex: number;\n /** Participant id of the annotator (matches WSCollabParticipantJoined.participantId). */\n authorId: string;\n /** Human-readable role label snapshot for display (e.g. \"annotator\"). */\n authorRole: 'annotator';\n /** The note itself. Trimmed, capped at `MAX_TEXT_LENGTH` on add. */\n text: string;\n /** ISO timestamp of creation. */\n createdAt: string;\n /** Resolved state. Annotations start unresolved. */\n resolved: boolean;\n /** ISO timestamp when resolved (if resolved). */\n resolvedAt?: string | undefined;\n /** Participant id of the resolver (if resolved). */\n resolvedBy?: string | undefined;\n}\n\ninterface AnnotationsFile {\n /** Bumped when the on-disk shape changes. v1 = initial release. */\n version: 1;\n annotations: Annotation[];\n}\n\n/** Bumped when the on-disk shape changes. Bump + migration on change. */\nconst FILE_VERSION = 1;\n/** Hard cap to keep a runaway annotator from writing megabytes. */\nconst MAX_TEXT_LENGTH = 2000;\n/** Hard cap on total annotations per session (oldest are evicted beyond this). */\nconst MAX_ANNOTATIONS = 1000;\n\nexport interface AnnotationsStoreOptions {\n /** Root sessions directory used with `sessionScopedPath(..., '.annotations.json')`. */\n dir: string;\n events?: EventBus;\n traceId?: string;\n /**\n * Override the max annotations per session (default 1000).\n * Exposed for tests to avoid 1001 sequential disk writes on every eviction test.\n */\n maxAnnotations?: number;\n}\n\nexport class AnnotationsStore {\n private readonly dir: string;\n private readonly events: EventBus | undefined;\n private readonly traceId: string | undefined;\n private readonly maxAnnotations: number;\n /** Per-session write queue. Created lazily on first add. */\n private readonly writeChains = new Map<string, Promise<void>>();\n\n constructor(opts: AnnotationsStoreOptions) {\n this.dir = opts.dir;\n this.events = opts.events;\n this.traceId = opts.traceId;\n this.maxAnnotations = opts.maxAnnotations ?? MAX_ANNOTATIONS;\n }\n\n // \u2500\u2500 Reads \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Return all annotations for `sessionId` in insertion order\n * (oldest first). Returns an empty array when no file exists\n * yet (the normal case for a fresh session) and also degrades\n * gracefully to `[]` on a read error (permissions, corruption) \u2014\n * the failure is still surfaced via a `storage.read` event so it\n * never silently hides I/O problems from observers.\n */\n async list(sessionId: string): Promise<Annotation[]> {\n const t0 = Date.now();\n const fp = this.filePath(sessionId);\n try {\n const file = await this.readFile(sessionId);\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.read', {\n sessionId,\n store: 'annotations',\n filePath: fp,\n operation: 'list',\n outcome: 'success',\n durationMs,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return file ? file.annotations : [];\n } catch (err) {\n this.events?.emit('storage.read', {\n sessionId,\n store: 'annotations',\n filePath: fp,\n operation: 'list',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: toErrorMessage(err),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return [];\n }\n }\n\n /**\n * Convenience: only unresolved annotations, newest first \u2014 the\n * common UI rendering for \"what still needs attention?\".\n */\n async listOpen(sessionId: string): Promise<Annotation[]> {\n const all = await this.list(sessionId);\n return all.filter((a) => !a.resolved).reverse();\n }\n\n // \u2500\u2500 Writes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Add a new annotation. Returns the persisted record (with id\n * and timestamps filled in). Throws when `text` is empty or\n * exceeds `MAX_TEXT_LENGTH`.\n */\n async add(input: {\n sessionId: string;\n atEventIndex: number;\n authorId: string;\n text: string;\n }): Promise<Annotation> {\n const text = input.text.trim();\n if (text.length === 0) {\n throw new WrongStackError({\n message: 'Annotation text must be non-empty',\n code: ERROR_CODES.VALIDATION_ERROR,\n subsystem: 'general',\n context: { field: 'text', sessionId: input.sessionId },\n });\n }\n if (text.length > MAX_TEXT_LENGTH) {\n throw new WrongStackError({\n message: `Annotation text exceeds ${MAX_TEXT_LENGTH} chars (got ${text.length})`,\n code: ERROR_CODES.VALIDATION_ERROR,\n subsystem: 'general',\n context: { field: 'text', maxLength: MAX_TEXT_LENGTH, actualLength: text.length },\n });\n }\n if (!Number.isInteger(input.atEventIndex) || input.atEventIndex < 0) {\n throw new WrongStackError({\n message: 'atEventIndex must be a non-negative integer',\n code: ERROR_CODES.VALIDATION_ERROR,\n subsystem: 'general',\n context: { field: 'atEventIndex', value: input.atEventIndex },\n });\n }\n const annotation: Annotation = {\n id: randomUUID(),\n sessionId: input.sessionId,\n atEventIndex: input.atEventIndex,\n authorId: input.authorId,\n authorRole: 'annotator',\n text,\n createdAt: new Date().toISOString(),\n resolved: false,\n };\n const fp = this.filePath(input.sessionId);\n const t0 = Date.now();\n try {\n await this.enqueue(input.sessionId, async () => {\n await withFileLock(fp, async () => {\n const all = await this.list(input.sessionId);\n all.push(annotation);\n // Evict oldest if we crossed the cap. Resolved first, then oldest.\n if (all.length > this.maxAnnotations) {\n const sorted = all\n .map((a, i) => ({ a, i }))\n .sort((x, y) => {\n // resolved=false wins (keep unresolved); among same resolved state, oldest first.\n /* v8 ignore next -- scale+tiebreak: needs >1000 annotations with mixed resolved states */\n if (x.a.resolved !== y.a.resolved) return x.a.resolved ? 1 : -1;\n return x.a.createdAt.localeCompare(y.a.createdAt);\n });\n const evictCount = all.length - this.maxAnnotations;\n const toEvict = new Set(sorted.slice(0, evictCount).map((s) => s.a.id));\n const kept = all.filter((a) => !toEvict.has(a.id));\n await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: kept });\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: input.sessionId,\n store: 'annotations',\n filePath: fp,\n operation: 'evict',\n outcome: 'success',\n durationMs,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n } else {\n await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: input.sessionId,\n store: 'annotations',\n filePath: fp,\n operation: 'add',\n outcome: 'success',\n durationMs,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n }\n });\n });\n return annotation;\n } catch (err) {\n this.events?.emit('storage.error', {\n sessionId: input.sessionId,\n store: 'annotations',\n filePath: fp,\n operation: 'add',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: false,\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n throw err;\n }\n }\n\n /**\n * Mark an annotation as resolved. Returns the updated record, or\n * `null` if no annotation with that id exists in this session.\n * Idempotent: resolving an already-resolved annotation refreshes\n * `resolvedAt` / `resolvedBy` to the latest call.\n */\n async resolve(input: {\n sessionId: string;\n annotationId: string;\n resolvedBy: string;\n }): Promise<Annotation | null> {\n let updated: Annotation | null = null;\n const fp = this.filePath(input.sessionId);\n const t0 = Date.now();\n try {\n await this.enqueue(input.sessionId, async () => {\n await withFileLock(fp, async () => {\n const all = await this.list(input.sessionId);\n const idx = all.findIndex((a) => a.id === input.annotationId);\n if (idx === -1) {\n updated = null;\n return;\n }\n const next: Annotation = {\n ...expectDefined(all[idx]),\n resolved: true,\n resolvedAt: new Date().toISOString(),\n resolvedBy: input.resolvedBy,\n };\n all[idx] = next;\n await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });\n updated = next;\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: input.sessionId,\n store: 'annotations',\n filePath: fp,\n operation: 'resolve',\n outcome: 'success',\n durationMs,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n });\n });\n return updated;\n } catch (err) {\n this.events?.emit('storage.error', {\n sessionId: input.sessionId,\n store: 'annotations',\n filePath: fp,\n operation: 'resolve',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: false,\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n throw err;\n }\n }\n\n // \u2500\u2500 Internals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private filePath(sessionId: string): string {\n // Containment-checked: date-sharded ids (\"2026-06-11/<base>\") are\n // legitimate; traversal is rejected. A plain slash ban here used to\n // throw for every modern session id, breaking annotations entirely.\n return sessionScopedPath(this.dir, sessionId, '.annotations.json');\n }\n\n private async readFile(sessionId: string): Promise<AnnotationsFile | null> {\n const fp = this.filePath(sessionId);\n let raw: string;\n try {\n raw = await fs.readFile(fp, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n // Non-ENOENT I/O errors (EACCES, ENOSPC): re-throw so callers emit\n // storage.error.\n throw err;\n }\n try {\n const parsed = JSON.parse(raw) as AnnotationsFile;\n if (parsed.version !== FILE_VERSION) {\n return { version: FILE_VERSION, annotations: [] };\n }\n return parsed;\n } catch {\n // JSON parse error (SyntaxError): treat as empty store \u2014 not an I/O failure.\n return null;\n }\n }\n\n private async writeFile(sessionId: string, file: AnnotationsFile): Promise<void> {\n const fp = this.filePath(sessionId);\n await atomicWrite(fp, JSON.stringify(file, null, 2));\n }\n\n /**\n * Serialize writes per-sessionId. We chain promises instead of\n * using a Mutex class so the contract is obvious from the\n * call-site: `enqueue(sid, fn)` runs `fn` after every prior\n * enqueue for `sid` has settled.\n */\n private enqueue(sessionId: string, fn: () => Promise<void>): Promise<void> {\n const prev = this.writeChains.get(sessionId) ?? Promise.resolve();\n const next = prev.then(fn, fn);\n // Keep the chain intact even when `fn` throws; failures\n // shouldn't break subsequent writes.\n this.writeChains.set(\n sessionId,\n next.catch(() => undefined),\n );\n return next;\n }\n}\n", "import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { atomicWrite, withFileLock } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport { sessionScopedPath } from '../utils/session-scoped-path.js';\nimport { hashRequest } from '../replay/hash.js';\nimport type { Request, Response } from '../types/provider.js';\nimport { safeParse } from '../utils/safe-json.js';\nimport type { EventBus } from '../kernel/events.js';\n\n/**\n * Surface the OS error code (EACCES, ENOSPC, \u2026) alongside the message in\n * storage.* event payloads. Codes are stable and locale-independent, so\n * they are what dashboards and alerts key on; the message is supplementary.\n */\nfunction storageErrorString(err: unknown): string {\n if (err instanceof Error) {\n const code = (err as NodeJS.ErrnoException).code;\n return code ? `${code}: ${err.message}` : err.message;\n }\n /* v8 ignore next -- defensive: fs/lock failures are always Error instances */\n return String(err);\n}\n\n/**\n * ReplayLogStore \u2014 sidecar store for deterministic-replay support\n * (idea #2 from IDEAS.md). One JSONL file per session, recording\n * every provider request/response pair so the same agent loop can\n * be re-run later with frozen API responses.\n *\n * Why a sidecar (not the session JSONL)?\n *\n * Same reason as `AnnotationsStore` \u2014 the session log is\n * event-sourced and append-only; a provider request payload can be\n * tens of kilobytes (especially with long conversation history),\n * and we want replay to be opt-in (recorded only when the user\n * runs with `--replay` or a future equivalent). Mixing it into\n * the event log would inflate every read for replay-irrelevant\n * paths.\n *\n * File layout: `sessionScopedPath(dir, sessionId, '.replay.jsonl')`,\n * one entry per line.\n * Each entry: `{ hash, ts, request, response }`. The `hash` is\n * computed via `hashRequest` so lookups are O(1) by hash.\n *\n * Concurrency: per-session write queue (same pattern as\n * `AnnotationsStore`). Reads are lock-free; the write chain makes\n * the append + rehash sequence atomic.\n */\nexport interface ReplayEntry {\n hash: string;\n ts: string;\n request: Request;\n response: Response;\n}\n\nconst FILE_VERSION = 1;\n\n/** Default cap on the number of entries per session. */\nconst DEFAULT_MAX_ENTRIES = 1000;\n\nexport interface ReplayLogStoreOptions {\n /** Root sessions directory used with `sessionScopedPath(..., '.replay.jsonl')`. */\n dir: string;\n /**\n * Cap on the number of entries per session. When a `record` would\n * push the file beyond this, the oldest entries are evicted (LRU\n * by insertion order). Set to `Infinity` to disable rotation.\n * Defaults to 1000 \u2014 a single LLM call averages ~5KB serialized\n * (messages + tools + response), so 1000 entries is ~5MB per\n * session which is a reasonable upper bound.\n */\n maxEntries?: number | undefined;\n events?: EventBus;\n traceId?: string;\n}\n\ninterface ReplayEntryLocation {\n entry?: ReplayEntry;\n offset: number;\n length: number;\n}\n\nexport class ReplayLogStore {\n private readonly dir: string;\n private readonly events: EventBus | undefined;\n private readonly traceId: string | undefined;\n private readonly writeChains = new Map<string, Promise<void>>();\n /** Per-session hash \u2192 on-disk location, with lazy entry hydration. */\n private readonly cache = new Map<string, Map<string, ReplayEntryLocation>>();\n /** Per-session entry count on disk, to detect when compaction is needed. */\n private readonly diskCount = new Map<string, number>();\n private readonly maxEntries: number;\n\n constructor(opts: ReplayLogStoreOptions) {\n this.dir = opts.dir;\n this.events = opts.events;\n this.traceId = opts.traceId;\n this.maxEntries = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;\n }\n\n // \u2500\u2500 Writes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Record a request/response pair. Idempotent on hash: a second\n * `record` for the same hash is a no-op (the existing entry wins).\n * Returns the hash.\n */\n async record(input: {\n sessionId: string;\n request: Request;\n response: Response;\n }): Promise<string> {\n const hash = hashRequest(input.request);\n const fp = this.filePath(input.sessionId);\n const t0 = Date.now();\n try {\n await this.enqueue(input.sessionId, async () => {\n await withFileLock(fp, async () => {\n // Dedup via the in-memory hash map \u2014 O(1) instead of re-reading\n // and re-parsing the whole JSONL just to run `entries.some(...)`.\n // `ensureCache` populates both the cache and `diskCount` from a\n // single read on first contact; subsequent records reuse it.\n const cache = await this.ensureCache(input.sessionId);\n if (cache.has(hash)) return; // already recorded\n\n const entry: ReplayEntry = {\n hash,\n ts: new Date().toISOString(),\n request: input.request,\n response: input.response,\n };\n\n const currentCount = this.diskCount.get(input.sessionId) ?? 0;\n const willEvict = currentCount + 1 > this.maxEntries;\n\n if (!willEvict) {\n // Common path (the first `maxEntries` writes per session, plus\n // any session that never hits the cap): a single O(1) append.\n // The previous implementation did a full readAll + full rewrite\n // (atomicWrite of the entire file) on every single record, which\n // was quadratic in session length \u2014 a 1000-call session rewrote\n // a multi-MB file 1000 times.\n const line = JSON.stringify(entry) + '\\n';\n let offset = 0;\n try {\n const stat = await fs.stat(fp);\n offset = stat.size;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;\n }\n await fs.appendFile(fp, line, 'utf8');\n cache.set(hash, { entry, offset, length: Buffer.byteLength(line, 'utf8') });\n this.diskCount.set(input.sessionId, currentCount + 1);\n this.events?.emit('storage.write', {\n sessionId: input.sessionId,\n store: 'replay',\n filePath: fp,\n operation: 'record',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return;\n }\n\n // Eviction path: we're at capacity, drop the oldest entry to make\n // room. This does require a full read + rewrite, but it fires at\n // most once per `maxEntries` writes (default 1000), not per write.\n const all = await this.readAll(input.sessionId);\n all.push(entry);\n const keep = all.slice(-this.maxEntries);\n const refreshed = new Map<string, ReplayEntryLocation>();\n let offset = 0;\n for (const e of keep) {\n const line = JSON.stringify(e) + '\\n';\n refreshed.set(e.hash, { entry: e, offset, length: Buffer.byteLength(line, 'utf8') });\n offset += Buffer.byteLength(line, 'utf8');\n }\n this.cache.set(input.sessionId, refreshed);\n this.diskCount.set(input.sessionId, keep.length);\n await this.writeAll(input.sessionId, keep, 'compact');\n });\n });\n return hash;\n } catch (err) {\n this.events?.emit('storage.error', {\n sessionId: input.sessionId,\n store: 'replay',\n filePath: fp,\n operation: 'record',\n outcome: 'failure',\n error: storageErrorString(err),\n recoverable: false,\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n throw err;\n }\n }\n\n // \u2500\u2500 Reads \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Look up an entry by hash. Returns `null` when the request has\n * not been recorded for this session. O(1) after the first call\n * per session (in-memory cache).\n */\n async lookup(sessionId: string, hash: string): Promise<ReplayEntry | null> {\n const fp = this.filePath(sessionId);\n const t0 = Date.now();\n try {\n const cache = await this.ensureCache(sessionId);\n const location = cache.get(hash);\n const entry = location ? await this.hydrateEntry(sessionId, hash, location) : null;\n this.events?.emit('storage.read', {\n sessionId,\n store: 'replay',\n filePath: fp,\n operation: 'lookup',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return entry;\n } catch (err) {\n this.events?.emit('storage.read', {\n sessionId,\n store: 'replay',\n filePath: fp,\n operation: 'lookup',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: storageErrorString(err),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n throw err;\n }\n }\n\n /** All recorded entries for a session, in insertion order. */\n async load(sessionId: string): Promise<ReplayEntry[]> {\n const fp = this.filePath(sessionId);\n const t0 = Date.now();\n try {\n const cache = await this.ensureCache(sessionId);\n const entries: ReplayEntry[] = [];\n for (const [hash, location] of cache) {\n entries.push(await this.hydrateEntry(sessionId, hash, location));\n }\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.read', {\n sessionId,\n store: 'replay',\n filePath: fp,\n operation: 'load',\n outcome: 'success',\n durationMs,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return entries;\n } catch (err) {\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.read', {\n sessionId,\n store: 'replay',\n filePath: fp,\n operation: 'load',\n outcome: 'failure',\n durationMs,\n error: storageErrorString(err),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n throw err;\n }\n }\n\n /**\n * List every session id that has a replay log in the store dir.\n * Returns an array of `{ sessionId, entryCount, path }` sorted\n * by sessionId for stable output. Used by `wstack replay --list`.\n */\n async list(): Promise<Array<{ sessionId: string; entryCount: number; path: string }>> {\n const out: Array<{ sessionId: string; entryCount: number; path: string }> = [];\n // Replay logs sit next to their session JSONL \u2014 flat at the root for\n // legacy/`record-<ts>` ids, inside a date-shard dir for modern ids.\n // Scan both levels; a root-only scan misses every sharded session.\n const scan = async (dir: string, prefix: string, depth: number): Promise<void> => {\n let entries: import('node:fs').Dirent[];\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch (err) {\n if (depth === 0 && (err as NodeJS.ErrnoException).code !== 'ENOENT') {\n // EACCES, ENOTDIR, etc. \u2014 log the real error so the operator can\n // diagnose a misconfiguration, but still return empty list so the\n // caller (slash command display) doesn't crash.\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'replay_log_store.list_readdir_failed',\n dir,\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }));\n }\n return;\n }\n for (const entry of entries) {\n if (entry.name.startsWith('.')) continue;\n if (entry.isDirectory()) {\n if (depth === 0) await scan(path.join(dir, entry.name), entry.name, depth + 1);\n continue;\n }\n if (!entry.isFile() || !entry.name.endsWith('.replay.jsonl')) continue;\n const base = entry.name.slice(0, -'.replay.jsonl'.length);\n const sessionId = prefix ? `${prefix}/${base}` : base;\n const fp = path.join(dir, entry.name);\n out.push({\n sessionId,\n entryCount: await this.countEntries(fp),\n path: fp,\n });\n }\n };\n await scan(this.dir, '', 0);\n return out.sort((a, b) => a.sessionId.localeCompare(b.sessionId));\n }\n\n // \u2500\u2500 Internals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private filePath(sessionId: string): string {\n // Containment-checked: date-sharded ids (\"2026-06-11/<base>\") are\n // legitimate; traversal is rejected. A plain slash ban would throw\n // the moment a real (sharded) session id is used for --replay.\n return sessionScopedPath(this.dir, sessionId, '.replay.jsonl');\n }\n\n private async countEntries(filePath: string): Promise<number> {\n // list() only needs a count, not provider request/response payloads. Count\n // non-empty JSONL rows in fixed-size chunks so large replay logs are not\n // parsed or materialized just to render `wstack replay --list`.\n const handle = await fs.open(filePath, 'r');\n const buffer = Buffer.allocUnsafe(64 * 1024);\n let count = 0;\n let hasNonWhitespace = false;\n try {\n while (true) {\n const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);\n if (bytesRead === 0) break;\n for (let i = 0; i < bytesRead; i++) {\n const ch = buffer[i]!;\n if (ch === 10) {\n if (hasNonWhitespace) count++;\n hasNonWhitespace = false;\n continue;\n }\n if (ch !== 13 && ch !== 32 && ch !== 9) {\n hasNonWhitespace = true;\n }\n }\n }\n } finally {\n await handle.close();\n }\n if (hasNonWhitespace) count++;\n return count;\n }\n\n private parseReplayLine(line: string): ReplayEntry | null {\n try {\n const parsed = safeParse<\n { version?: number | undefined; entry?: ReplayEntry | undefined } & ReplayEntry\n >(line);\n if (!parsed.ok || !parsed.value) return null;\n if ('entry' in parsed.value && parsed.value.entry) {\n return parsed.value.entry;\n }\n return parsed.value;\n } catch {\n return null;\n }\n }\n\n private async readAll(sessionId: string): Promise<ReplayEntry[]> {\n const fp = this.filePath(sessionId);\n try {\n const raw = await fs.readFile(fp, 'utf8');\n const out: ReplayEntry[] = [];\n for (const line of raw.split('\\n')) {\n if (!line.trim()) continue;\n const parsed = this.parseReplayLine(line);\n if (parsed) out.push(parsed);\n }\n return out;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw err;\n }\n }\n\n private async writeAll(\n sessionId: string,\n entries: ReplayEntry[],\n operation: 'record' | 'compact' = 'record',\n ): Promise<void> {\n const fp = this.filePath(sessionId);\n const t0 = Date.now();\n const body = entries.map((e) => JSON.stringify(e)).join('\\n') + (entries.length ? '\\n' : '');\n await atomicWrite(fp, body);\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId,\n store: 'replay',\n filePath: fp,\n operation,\n outcome: 'success',\n durationMs,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n // Drop the version-stamp comment at the top \u2014 v1 has no envelope,\n // but we keep a one-line marker for human readers / future tooling.\n // (The atomicWrite just wrote pure JSONL; that's correct for v1.)\n void FILE_VERSION;\n }\n\n private async ensureCache(sessionId: string): Promise<Map<string, ReplayEntryLocation>> {\n let cache = this.cache.get(sessionId);\n if (cache) return cache;\n\n const fp = this.filePath(sessionId);\n cache = new Map();\n try {\n const handle = await fs.open(fp, 'r');\n const CHUNK = 64 * 1024;\n const buffer = Buffer.alloc(CHUNK);\n let leftover = '';\n let leftoverOffset = 0;\n let fileOffset = 0;\n try {\n while (true) {\n const { bytesRead } = await handle.read(buffer, 0, CHUNK, fileOffset);\n if (bytesRead === 0) break;\n const chunk = buffer.subarray(0, bytesRead).toString('utf8');\n const text = leftover + chunk;\n let lineStart = leftoverOffset;\n let searchFrom = 0;\n while (true) {\n const newlineIndex = text.indexOf('\\n', searchFrom);\n if (newlineIndex === -1) break;\n const line = text.slice(searchFrom, newlineIndex);\n const lineLength = Buffer.byteLength(text.slice(searchFrom, newlineIndex + 1), 'utf8');\n if (line.trim()) {\n const entry = this.parseReplayLine(line);\n if (entry) {\n cache.set(entry.hash, { offset: lineStart, length: lineLength });\n }\n }\n lineStart += lineLength;\n searchFrom = newlineIndex + 1;\n }\n leftover = text.slice(searchFrom);\n leftoverOffset = lineStart;\n fileOffset += bytesRead;\n }\n if (leftover.trim()) {\n const entry = this.parseReplayLine(leftover);\n if (entry) {\n cache.set(entry.hash, { offset: leftoverOffset, length: Buffer.byteLength(leftover, 'utf8') });\n }\n }\n } finally {\n await handle.close();\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;\n }\n\n this.cache.set(sessionId, cache);\n this.diskCount.set(sessionId, cache.size);\n return cache;\n }\n\n private async hydrateEntry(\n sessionId: string,\n hash: string,\n location: ReplayEntryLocation,\n ): Promise<ReplayEntry> {\n if (location.entry) return location.entry;\n\n const fp = this.filePath(sessionId);\n const handle = await fs.open(fp, 'r');\n try {\n const buffer = Buffer.alloc(location.length);\n const { bytesRead } = await handle.read(buffer, 0, location.length, location.offset);\n const line = buffer.subarray(0, bytesRead).toString('utf8').trimEnd();\n const entry = this.parseReplayLine(line);\n if (!entry) {\n throw new Error(`Replay entry ${hash} is unreadable`);\n }\n location.entry = entry;\n return entry;\n } finally {\n await handle.close();\n }\n }\n\n private enqueue(sessionId: string, fn: () => Promise<void>): Promise<void> {\n const prev = this.writeChains.get(sessionId) ?? Promise.resolve();\n const next = prev.then(fn, fn);\n this.writeChains.set(\n sessionId,\n next.catch(() => undefined),\n );\n return next;\n }\n}\n", "import { createHash } from 'node:crypto';\nimport type { Request } from '../types/provider.js';\n\n/**\n * Idea #2 from IDEAS.md \u2014 Deterministic Replay.\n *\n * The hash function is the foundation of replay: given a `Request`,\n * produce a stable identifier so a recorded `Response` can be looked\n * up later when we want to \"re-run\" the same agent loop without\n * burning API credits.\n *\n * Stability rules:\n *\n * - All object keys are sorted recursively before stringification.\n * Without this, two semantically identical requests that differ\n * only in key insertion order would produce different hashes.\n * - We hash ONLY the fields that affect the response: `model`,\n * `system`, `messages`, `tools`, `maxTokens`, and the four\n * sampling knobs (`temperature`, `topP`, `stopSequences`,\n * `toolChoice`). Anything else on the `Request` (metadata,\n * future extensions) is ignored so replay stays forward-compat.\n * - We serialize to JSON. The `ContentBlock` and `Message` shapes\n * are pure data; this works as long as no `undefined` values\n * sneak in (those get dropped by `JSON.stringify`, which is\n * fine \u2014 the structural diff is what matters).\n *\n * The SHA-256 output is hex-encoded and prefixed with the algorithm\n * tag so a future migration to a different hash (e.g. blake3) is\n * trivial to detect.\n */\nexport function stableStringify(value: unknown): string {\n return JSON.stringify(sortKeys(value));\n}\n\nfunction sortKeys(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortKeys);\n if (value && typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(obj).sort()) {\n sorted[key] = sortKeys(obj[key]);\n }\n return sorted;\n }\n return value;\n}\n\nexport function hashRequest(request: Request): string {\n // Pick only the fields that affect the response. See stability rules.\n const payload = {\n model: request.model,\n system: request.system,\n messages: request.messages,\n tools: request.tools,\n maxTokens: request.maxTokens,\n temperature: request.temperature,\n topP: request.topP,\n stopSequences: request.stopSequences,\n toolChoice: request.toolChoice,\n };\n const json = stableStringify(payload);\n const digest = createHash('sha256').update(json, 'utf8').digest('hex');\n return `sha256:${digest}`;\n}\n", "import { expectDefined } from '../utils/expect-defined.js';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { SessionEvent } from '../types/session.js';\nimport { sessionScopedPath } from '../utils/session-scoped-path.js';\n/**\n * Idea #1 from IDEAS.md \u2014 Stateful Session Recovery.\n *\n * `SessionRecovery` is the read-side companion to the in-flight\n * marker mechanism. When the agent loop is running, it writes an\n * `in_flight_start` event at the current point in the log. On\n * clean shutdown, a matching `in_flight_end` follows. Provider and\n * tool events may legitimately appear after the start marker, so\n * recovery finds the latest lifecycle boundary rather than assuming\n * the marker is literally the last JSONL record.\n *\n * Detection is diagnostic only. Normal session resume reconstructs the\n * conversation by replaying persisted JSONL events; it must not blindly\n * re-execute tool calls after a crash. `recover()` exposes the tail after the\n * last checkpoint so callers can explain which persisted work was in flight.\n *\n * Concurrency: pure read; no writes. Safe to call from multiple\n * processes simultaneously.\n */\nexport interface StaleSession {\n sessionId: string;\n /** Path to the JSONL log. */\n path: string;\n /** Last event ts (the in_flight_start timestamp). */\n lastEventTs: string;\n /** Context the agent was working on when it died. */\n context: string;\n /** Total events in the log. */\n eventCount: number;\n}\n\nexport interface RecoveryPlan {\n sessionId: string;\n /** True if the session is stale (has a dangling in_flight_start). */\n stale: boolean;\n /** The last `checkpoint` event before the un-replayed work, or null. */\n lastCheckpoint: SessionEvent | null;\n /** All persisted events after the last checkpoint (diagnostic in-flight tail). */\n pendingEvents: SessionEvent[];\n /** The dangling in_flight_start event, if any. */\n inFlightStart: SessionEvent | null;\n /** Free-form context the agent was working on, if any. */\n context: string | null;\n}\n\n/**\n * Result of `SessionRecovery.recover(sessionId)`. Distinct from\n * `StaleSession`: a session is \"stale\" if its latest lifecycle\n * boundary is an open marker, but a \"recovery plan\" can also be generated for\n * clean sessions whose last checkpoint is older than the\n * conversation history (e.g. a user-initiated \"rewind to last\n * good state\" flow). This is a diagnostic plan, not authorization to replay\n * external side effects.\n */\nexport class SessionRecovery {\n /**\n * Scan a session log and return a `StaleSession` if and only if the newest\n * lifecycle boundary is an `in_flight_start` without a later\n * `in_flight_end`/`session_end`. Ordinary provider/tool events after the\n * marker do not make the session clean. Returns `null` when:\n * - the log does not exist;\n * - the log is empty;\n * - the latest lifecycle boundary is `in_flight_end` or `session_end`;\n * - there is no lifecycle boundary (legacy/pre-marker log).\n *\n * The reverse scanner is chunked and line-aware. It can cross arbitrarily\n * large JSONL records (for example a large tool result) without imposing a\n * fixed tail-size correctness limit. Clean logs normally return after the\n * first chunk; stale logs continue counting lines so `eventCount` remains\n * the documented total rather than a tail-only approximation.\n */\n async detectStale(sessionId: string): Promise<StaleSession | null> {\n const fp = this.filePath(sessionId);\n let stat;\n try {\n stat = await fs.stat(fp);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n /* v8 ignore next -- defensive: any other stat failure is also non-recoverable */\n return null;\n }\n if (stat.size === 0) return null;\n\n try {\n const scan = await scanLatestLifecycleBoundary(fp, stat.size);\n if (scan?.boundary.type !== 'in_flight_start') return null;\n return {\n sessionId,\n path: fp,\n lastEventTs: scan.boundary.ts,\n context: scan.boundary.context,\n eventCount: scan.eventCount,\n };\n /* v8 ignore start -- defensive: reverse scan failure after a successful stat is rare */\n } catch {\n return null;\n }\n /* v8 ignore stop */\n }\n\n /**\n * Generate a recovery plan for a session. The plan describes\n * the persisted tail after the last checkpoint, plus the dangling in-flight\n * marker if present. SessionStore.resume() independently reconstructs state\n * from the journal and does not re-run these events as commands.\n *\n * Returns a non-null plan for ANY session that has at least\n * one event after a checkpoint (or, for legacy sessions, at\n * least one event). Pure read; no mutation.\n */\n async recover(sessionId: string): Promise<RecoveryPlan | null> {\n const fp = this.filePath(sessionId);\n let raw: string;\n try {\n raw = await fs.readFile(fp, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n /* v8 ignore next -- defensive: any other read failure is also non-recoverable */\n return null;\n }\n const events: SessionEvent[] = [];\n for (const line of raw.split('\\n')) {\n if (!line.trim()) continue;\n try {\n events.push(JSON.parse(line) as SessionEvent);\n } catch {\n // skip corrupt lines\n }\n }\n if (events.length === 0) return null;\n // Find the last checkpoint.\n let lastCheckpoint: SessionEvent | null = null;\n let lastCheckpointIdx = -1;\n for (let i = 0; i < events.length; i++) {\n if (events[i]?.type === 'checkpoint') {\n lastCheckpoint = expectDefined(events[i]);\n lastCheckpointIdx = i;\n }\n }\n // Events after the last checkpoint = the diagnostic in-flight tail.\n const pendingEvents =\n lastCheckpointIdx >= 0 ? events.slice(lastCheckpointIdx + 1) : events;\n // The dangling in_flight_start, if it is the newest lifecycle boundary.\n // Provider/tool events may follow the marker and are still pending work.\n const latestBoundary = events.findLast(isLifecycleBoundary);\n const inFlightStart = latestBoundary?.type === 'in_flight_start' ? latestBoundary : null;\n const context = inFlightStart && inFlightStart.type === 'in_flight_start'\n ? inFlightStart.context\n : null;\n return {\n sessionId,\n stale: inFlightStart !== null,\n lastCheckpoint,\n pendingEvents,\n inFlightStart,\n context,\n };\n }\n\n /**\n * List every stale session in a directory. Returns an array\n * (possibly empty) sorted by `lastEventTs` descending \u2014 most\n * recent crash first.\n */\n async listResumable(): Promise<StaleSession[]> {\n const out: StaleSession[] = [];\n // Modern sessions live inside date-shard subdirectories\n // (\"2026-06-11/<base>.jsonl\"); legacy/flat sessions sit at the root.\n // Scan both \u2014 a root-only scan silently misses every modern crash.\n const collect = async (dir: string, prefix: string, depth: number): Promise<void> => {\n let entries: import('node:fs').Dirent[];\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n /* v8 ignore start -- defensive: the sessions dir (and its shards) are readable during a scan */\n } catch {\n return;\n }\n /* v8 ignore stop */\n for (const entry of entries) {\n if (entry.name.startsWith('.')) continue;\n if (\n entry.name === 'shared' ||\n entry.name === 'subagents' ||\n entry.name === 'attachments'\n )\n continue;\n if (entry.isDirectory()) {\n if (depth === 0) {\n await collect(path.join(dir, entry.name), entry.name, depth + 1);\n }\n continue;\n }\n if (!entry.isFile() || !entry.name.endsWith('.jsonl')) continue;\n if (entry.name === '_index.jsonl' || entry.name === '_mailbox.jsonl') continue;\n const base = entry.name.slice(0, -'.jsonl'.length);\n if (base.includes('.replay') || base.includes('.annotations') || base.includes('.audit'))\n continue;\n const sessionId = prefix ? `${prefix}/${base}` : base;\n const stale = await this.detectStale(sessionId);\n if (stale) out.push(stale);\n }\n };\n await collect(this.dir, '', 0);\n return out.sort((a, b) => b.lastEventTs.localeCompare(a.lastEventTs));\n }\n\n // \u2500\u2500 Internals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private filePath(sessionId: string): string {\n // Containment-checked: date-sharded ids (\"2026-06-11/<base>\") are\n // legitimate; traversal is rejected. Shared with the other per-session\n // sidecar stores so the contract can't drift.\n return sessionScopedPath(this.dir, sessionId, '.jsonl');\n }\n\n constructor(private readonly dir: string) {}\n}\n\ntype LifecycleBoundary = Extract<\n SessionEvent,\n { type: 'in_flight_start' | 'in_flight_end' | 'session_end' }\n>;\n\nfunction isLifecycleBoundary(event: SessionEvent): event is LifecycleBoundary {\n return (\n event.type === 'in_flight_start' ||\n event.type === 'in_flight_end' ||\n event.type === 'session_end'\n );\n}\n\nfunction parseLifecycleBoundary(line: Buffer): LifecycleBoundary | null {\n try {\n const parsed = JSON.parse(line.toString('utf8')) as SessionEvent;\n return isLifecycleBoundary(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nfunction hasNonWhitespace(line: Buffer): boolean {\n for (const byte of line) {\n if (byte !== 0x09 && byte !== 0x0a && byte !== 0x0d && byte !== 0x20) return true;\n }\n return false;\n}\n\n/**\n * Walk JSONL records newest-first without decoding partial UTF-8 chunks. A\n * record split across chunks is retained as bytes and decoded only after its\n * opening newline is found, so large/non-ASCII tool results cannot hide the\n * lifecycle marker immediately before them.\n */\nasync function scanLatestLifecycleBoundary(\n filePath: string,\n size: number,\n): Promise<{ boundary: LifecycleBoundary; eventCount: number } | null> {\n const CHUNK_SIZE = 64 * 1024;\n const handle = await fs.open(filePath, 'r');\n let position = size;\n let laterLineFragment = Buffer.alloc(0);\n let latestBoundary: LifecycleBoundary | null = null;\n let eventCount = 0;\n\n const observeLine = (line: Buffer): LifecycleBoundary | null => {\n if (!hasNonWhitespace(line)) return null;\n eventCount++;\n return parseLifecycleBoundary(line);\n };\n\n try {\n while (position > 0) {\n const length = Math.min(CHUNK_SIZE, position);\n position -= length;\n const chunk = Buffer.allocUnsafe(length);\n const { bytesRead } = await handle.read(chunk, 0, length, position);\n const data = Buffer.concat([chunk.subarray(0, bytesRead), laterLineFragment]);\n\n let lineEnd = data.length;\n for (let i = data.length - 1; i >= 0; i--) {\n if (data[i] !== 0x0a) continue;\n const boundary = observeLine(data.subarray(i + 1, lineEnd));\n if (!latestBoundary && boundary) latestBoundary = boundary;\n lineEnd = i;\n }\n laterLineFragment = data.subarray(0, lineEnd);\n\n // A clean boundary needs no exact event count because no StaleSession is\n // returned. The common clean path therefore remains a one-chunk read.\n if (latestBoundary && latestBoundary.type !== 'in_flight_start') {\n return { boundary: latestBoundary, eventCount };\n }\n }\n\n const boundary = observeLine(laterLineFragment);\n if (!latestBoundary && boundary) latestBoundary = boundary;\n return latestBoundary ? { boundary: latestBoundary, eventCount } : null;\n } finally {\n await handle.close();\n }\n}\n", "import { expectDefined } from '../utils/expect-defined.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport { createHash, randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs/promises';\nimport { withFileLock } from '../utils/atomic-write.js';\nimport { safeParse } from '../utils/safe-json.js';\nimport { sessionScopedPath } from '../utils/session-scoped-path.js';\nimport type { EventBus } from '../kernel/events.js';\n/**\n * ToolAuditLog \u2014 idea #9 from IDEAS.md.\n *\n * Tamper-evident audit trail for tool calls. Every tool_use /\n * tool_result pair is appended to a sidecar JSONL with a chained\n * SHA-256 \u2014 each entry's `prevHash` is the prior entry's `hash`,\n * so any post-hoc modification of a single line breaks the chain\n * from that point forward.\n *\n * Why a sidecar (not the session JSONL)?\n * Same reason as `AnnotationsStore` and `ReplayLogStore`: the\n * session log is an event-sourced journal. Mixing in a hash\n * chain would inflate every read and tightly couple the\n * integrity check to the event format. Sidecar keeps both\n * concerns orthogonal.\n *\n * What \"tamper-evident\" means here:\n * - The hash covers the full serialized entry: tool name, id,\n * input, output, timestamp, author. Changing any byte\n * changes the hash.\n * - The chain is sequential \u2014 a verifier walks the file in\n * order, recomputing each hash, and checks `prevHash`\n * matches the previous entry's `hash`.\n * - Any insertion, deletion, or modification of a single\n * entry surfaces as a \"chain broken at entry N\" verdict.\n *\n * What it does NOT defend against:\n * - An attacker who rewrites the whole file consistently.\n * For that you'd need an external anchor (signing key,\n * transparency log, etc.) \u2014 out of scope for Phase 1.\n * - The agent itself misbehaving; this is post-hoc audit, not\n * real-time enforcement. Use `PermissionPolicy` for that.\n *\n * File layout: `sessionScopedPath(dir, sessionId, '.audit.jsonl')`,\n * one entry per line. The chain starts with a `genesis` entry whose\n * `prevHash` is all zeros.\n */\nexport interface AuditEntry {\n /** Monotonic index (0-based). */\n index: number;\n /** UUID for cross-referencing with logs. */\n id: string;\n /** ISO timestamp. */\n ts: string;\n /** Hash of the previous entry (or all-zeros for the genesis entry). */\n prevHash: string;\n /** Hash of this entry's content (sha256 over the canonical JSON). */\n hash: string;\n toolName: string;\n toolUseId: string;\n input: unknown;\n output: unknown;\n isError: boolean;\n}\n\nconst GENESIS_PREV = '0'.repeat(64);\n\nexport type VerifyResult =\n | { ok: true; entries: number }\n | { ok: false; brokenAt: number; reason: string };\n\nexport interface ToolAuditLogOptions {\n /** Root sessions directory used with `sessionScopedPath(..., '.audit.jsonl')`. */\n dir: string;\n /**\n * Flush the file system cache to disk every N writes per session.\n * Default 100. Lower values = better crash durability, more I/O overhead.\n * Set to `Infinity` to disable periodic fsync (fastest, but highest data-loss risk).\n */\n fsyncEvery?: number | undefined;\n events?: EventBus;\n traceId?: string;\n}\n\n/** Default number of writes between fsync calls. */\nconst DEFAULT_FSYNC_EVERY = 100;\n\nexport class ToolAuditLog {\n private readonly dir: string;\n private readonly events: EventBus | undefined;\n private readonly traceId: string | undefined;\n /** In-memory cache of the last entry's hash (per session), to compute chains efficiently. */\n private readonly tailHash = new Map<string, string>();\n /** In-memory counter for entry indices \u2014 avoids re-reading the file on every write. */\n private readonly tailIndex = new Map<string, number>();\n /**\n * File mtime+size recorded after our last write, per session. Used to\n * detect cross-process writes (session handoff, recovery) that would\n * invalidate the in-memory tail cache: if the stat no longer matches\n * we re-read the file to re-establish the chain tip before appending.\n */\n private readonly tailStat = new Map<string, { mtimeMs: number; size: number }>();\n /** Tracks writes since last fsync, per session. */\n private readonly unSyncedWrites = new Map<string, number>();\n private readonly writeChains = new Map<string, Promise<void>>();\n private readonly fsyncEvery: number;\n\n constructor(opts: ToolAuditLogOptions) {\n this.dir = opts.dir;\n this.events = opts.events;\n this.traceId = opts.traceId;\n this.fsyncEvery = opts.fsyncEvery ?? DEFAULT_FSYNC_EVERY;\n }\n\n /**\n * Append a tool call/result pair to the chain. Returns the\n * resulting entry. Idempotency is not guaranteed \u2014 if you\n * record the same tool_use twice you get two entries. That's\n * intentional: the audit log is a record, not a cache.\n */\n async record(input: {\n sessionId: string;\n toolName: string;\n toolUseId: string;\n input: unknown;\n output: unknown;\n isError: boolean;\n }): Promise<AuditEntry> {\n let entry!: AuditEntry; // assigned inside the enqueue callback\n const fp = this.filePath(input.sessionId);\n const t0 = Date.now();\n try {\n await this.enqueue(input.sessionId, async () => {\n await withFileLock(fp, async () => {\n // Resolve the chain tip from the in-memory cache when possible,\n // re-reading the file only on first contact or when an external\n // writer (cross-process session handoff) changed it under us.\n // The previous implementation did `readAll` on every single\n // record just to find `entries.at(-1)` \u2014 a full parse of the\n // audit file per tool call.\n const tip = await this._resolveChainTip(input.sessionId, fp);\n const prevHash = tip.prevHash;\n const index = tip.nextIndex;\n\n const id = randomUUID();\n const ts = new Date().toISOString();\n const content = {\n id,\n ts,\n prevHash,\n toolName: input.toolName,\n toolUseId: input.toolUseId,\n input: input.input,\n output: input.output,\n isError: input.isError,\n index,\n };\n const hash = createHash('sha256').update(stableStringify(content), 'utf8').digest('hex');\n entry = {\n id,\n ts,\n prevHash,\n hash,\n toolName: input.toolName,\n toolUseId: input.toolUseId,\n input: input.input,\n output: input.output,\n isError: input.isError,\n index,\n };\n\n // True O(1) append \u2014 one line, no full-file rewrite. The previous\n // `writeAll(entries)` path re-serialized every entry on every\n // record; a 1000-call session rewrote a multi-MB audit file 1000\n // times (quadratic in session length). `withFileLock` above\n // guarantees no concurrent writer interleaves with our append.\n await fs.appendFile(fp, JSON.stringify(entry) + '\\n', 'utf8');\n\n // Refresh caches + fsync bookkeeping. We stat post-write so the\n // mtime+size tracker reflects the just-appended line \u2014 the next\n // record can trust the cache without re-reading.\n try {\n const st = await fs.stat(fp);\n this.tailStat.set(input.sessionId, { mtimeMs: st.mtimeMs, size: st.size });\n } catch {\n /* best-effort; next record will just re-read */\n }\n this.tailHash.set(input.sessionId, hash);\n this.tailIndex.set(input.sessionId, index + 1);\n await this._trackUnsynced(input.sessionId, fp);\n\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: input.sessionId,\n store: 'audit',\n filePath: fp,\n operation: 'record',\n outcome: 'success',\n durationMs,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n });\n });\n return entry;\n } catch (err) {\n this.events?.emit('storage.error', {\n sessionId: input.sessionId,\n store: 'audit',\n filePath: fp,\n operation: 'record',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: false,\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n throw err;\n }\n }\n\n /**\n * Resolve the chain tip (previous hash + next index) for an append.\n * Uses the in-memory `tailHash`/`tailIndex` cache when the file's\n * stat matches our last known write; falls back to a full read on\n * cache miss or when an external writer has extended the file.\n */\n private async _resolveChainTip(\n sessionId: string,\n fp: string,\n ): Promise<{ prevHash: string; nextIndex: number }> {\n const cachedHash = this.tailHash.get(sessionId);\n const cachedIndex = this.tailIndex.get(sessionId);\n const cachedStat = this.tailStat.get(sessionId);\n\n if (cachedHash !== undefined && cachedIndex !== undefined && cachedStat) {\n // Verify no other process appended since our last write. Stat is a\n // single inode lookup; the common case (same-process sequential\n // writes) returns immediately and we skip the full read.\n try {\n const st = await fs.stat(fp);\n if (st.mtimeMs === cachedStat.mtimeMs && st.size === cachedStat.size) {\n return { prevHash: cachedHash, nextIndex: cachedIndex };\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n // File was removed out from under us \u2014 reset and start a new chain.\n this.tailHash.delete(sessionId);\n this.tailIndex.delete(sessionId);\n this.tailStat.delete(sessionId);\n return { prevHash: GENESIS_PREV, nextIndex: 0 };\n }\n /* v8 ignore next -- defensive: a non-ENOENT stat failure during cache-hit is rare */\n throw err;\n }\n }\n\n // Cache miss or external write detected \u2014 read the true tail.\n const entries = await this.readAll(sessionId);\n const prev = entries.at(-1);\n const prevHash = prev?.hash ?? GENESIS_PREV;\n const nextIndex = prev ? prev.index + 1 : 0;\n this.tailHash.set(sessionId, prevHash);\n this.tailIndex.set(sessionId, nextIndex);\n try {\n const st = await fs.stat(fp);\n this.tailStat.set(sessionId, { mtimeMs: st.mtimeMs, size: st.size });\n } catch {\n /* leave cache empty; next record re-reads */\n }\n return { prevHash, nextIndex };\n }\n\n /**\n * Walk the chain and verify every entry's hash and prevHash.\n * Returns a structured verdict \u2014 never throws.\n */\n async verify(sessionId: string): Promise<VerifyResult> {\n const fp = this.filePath(sessionId);\n const t0 = Date.now();\n let entries: AuditEntry[];\n try {\n entries = await this.readAll(sessionId);\n } catch (err) {\n // The file exists but can't be read (permissions, corruption). We\n // can't verify it, so emit a read failure for observability and\n // degrade gracefully \u2014 this method's contract is \"never throws\".\n this.events?.emit('storage.read', {\n sessionId,\n store: 'audit',\n filePath: fp,\n operation: 'verify',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: toErrorMessage(err),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return { ok: true, entries: 0 };\n }\n\n // Walk the chain into a verdict, then emit a single storage.read whose\n // outcome reflects the verification result (a broken chain is a failure).\n const verdict = ((): VerifyResult => {\n if (entries.length === 0) return { ok: true, entries: 0 };\n // The first entry's prevHash must be the all-zeros genesis marker.\n if (entries[0]?.prevHash !== GENESIS_PREV) {\n return {\n ok: false,\n brokenAt: 0,\n reason: 'first entry is not the genesis (prevHash != 0\u20260)',\n };\n }\n let prevHash = GENESIS_PREV;\n for (let i = 0; i < entries.length; i++) {\n const e = expectDefined(entries[i]);\n if (e.prevHash !== prevHash) {\n return {\n ok: false,\n brokenAt: i,\n reason: `prevHash mismatch at entry ${i} (expected ${prevHash.slice(0, 8)}\u2026, got ${e.prevHash.slice(0, 8)}\u2026)`,\n };\n }\n // Recompute the hash from the entry's content (without the\n // `hash` field itself, which is what we are verifying).\n const content = {\n id: e.id,\n ts: e.ts,\n prevHash: e.prevHash,\n toolName: e.toolName,\n toolUseId: e.toolUseId,\n input: e.input,\n output: e.output,\n isError: e.isError,\n index: e.index,\n };\n const expectedHash = createHash('sha256')\n .update(stableStringify(content), 'utf8')\n .digest('hex');\n if (expectedHash !== e.hash) {\n return {\n ok: false,\n brokenAt: i,\n reason: `hash mismatch at entry ${i} (entry content was modified)`,\n };\n }\n prevHash = e.hash;\n }\n return { ok: true, entries: entries.length };\n })();\n\n this.events?.emit('storage.read', {\n sessionId,\n store: 'audit',\n filePath: fp,\n operation: 'verify',\n outcome: verdict.ok ? 'success' : 'failure',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return verdict;\n }\n\n /** All entries for a session, in insertion order. */\n async load(sessionId: string): Promise<AuditEntry[]> {\n const fp = this.filePath(sessionId);\n const t0 = Date.now();\n try {\n const entries = await this.readAll(sessionId);\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.read', {\n sessionId,\n store: 'audit',\n filePath: fp,\n operation: 'load',\n outcome: 'success',\n durationMs,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return entries;\n } catch (err) {\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.read', {\n sessionId,\n store: 'audit',\n filePath: fp,\n operation: 'load',\n outcome: 'failure',\n durationMs,\n error: toErrorMessage(err),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n throw err;\n }\n }\n\n // \u2500\u2500 Internals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private filePath(sessionId: string): string {\n // Containment-checked: date-sharded ids (\"2026-06-11/<base>\") are\n // legitimate; traversal is rejected. A plain slash ban would throw\n // for every modern session id the moment record() gets wired in.\n return sessionScopedPath(this.dir, sessionId, '.audit.jsonl');\n }\n\n private async readAll(sessionId: string): Promise<AuditEntry[]> {\n const fp = this.filePath(sessionId);\n try {\n const raw = await fs.readFile(fp, 'utf8');\n const out: AuditEntry[] = [];\n for (const line of raw.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const parsed = safeParse<AuditEntry>(line);\n if (parsed.ok && parsed.value) out.push(parsed.value);\n } catch {\n // Skip corrupt lines \u2014 audit data is meta, not fatal.\n }\n }\n return out;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return [];\n // Non-ENOENT errors (EACCES, ENOSPC, etc.) are real I/O failures \u2014\n // re-throw so callers (verify, load) can emit storage.error.\n throw err;\n }\n }\n\n /**\n * Tracks writes since last fsync and triggers periodic fsync.\n * Called after each O(1) append to maintain the same durability\n * guarantees as the old writeAll approach.\n */\n private async _trackUnsynced(sessionId: string, fp: string): Promise<void> {\n const count = (this.unSyncedWrites.get(sessionId) ?? 0) + 1;\n this.unSyncedWrites.set(sessionId, count);\n if (this.fsyncEvery !== Number.POSITIVE_INFINITY && count % this.fsyncEvery === 0) {\n await this.sync(sessionId, fp);\n }\n }\n\n /**\n * Explicitly sync the file to disk. Called automatically every\n * `fsyncEvery` writes, and available for callers who want to\n * force a sync before closing or during graceful shutdown.\n */\n async flush(sessionId: string): Promise<void> {\n await this.sync(sessionId, this.filePath(sessionId));\n }\n\n private async sync(sessionId: string, fp: string): Promise<void> {\n try {\n const fh = await fs.open(fp, 'r+');\n try {\n await fh.sync();\n } finally {\n await fh.close();\n }\n } catch {\n // fsync is best-effort; a failure here does not corrupt the chain.\n } finally {\n this.unSyncedWrites.set(sessionId, 0);\n }\n }\n\n private enqueue(sessionId: string, fn: () => Promise<void>): Promise<void> {\n const prev = this.writeChains.get(sessionId) ?? Promise.resolve();\n const next = prev.then(fn, fn);\n this.writeChains.set(\n sessionId,\n next.catch(() => undefined),\n );\n return next;\n }\n}\n\nfunction stableStringify(value: unknown): string {\n return JSON.stringify(sortKeys(value));\n}\n\nfunction sortKeys(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortKeys);\n if (value && typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(obj).sort()) {\n sorted[key] = sortKeys(obj[key]);\n }\n return sorted;\n }\n return value;\n}\n", "import type { SessionEvent } from '../types/session.js';\n\nexport interface QueryFilter {\n eventTypes?: string[] | undefined;\n toolNames?: string[] | undefined;\n timeRange?: { start: string; end: string } | undefined;\n}\n\nexport interface ToolInvocation {\n ts: string;\n name: string;\n input: unknown;\n output?: unknown | undefined;\n error?: string | undefined;\n durationMs: number;\n}\n\nexport interface SessionError {\n ts: string;\n phase: string;\n message: string;\n}\n\nexport interface ModeChange {\n ts: string;\n from: string;\n to: string;\n}\n\nexport interface TaskSummary {\n taskId: string;\n title: string;\n status: string;\n createdAt: string;\n completedAt?: string | undefined;\n}\n\nexport interface SessionAnalysis {\n sessionId: string;\n totalDuration: number;\n toolUsageCount: Record<string, number>;\n errorCount: number;\n modeChanges: ModeChange[];\n tasks: TaskSummary[];\n}\n\nexport class SessionAnalyzer {\n analyze(events: SessionEvent[]): SessionAnalysis {\n const toolUsageCount: Record<string, number> = {};\n const errors: SessionError[] = [];\n const modeChanges: ModeChange[] = [];\n const tasksById = new Map<string, TaskSummary>();\n let sessionId = '';\n\n for (const event of events) {\n // sessionId comes from session_start / session_resumed.\n if (event.type === 'session_start' || event.type === 'session_resumed') {\n if (!sessionId) sessionId = event.id;\n }\n if (event.type === 'tool_use') {\n toolUsageCount[event.name] = (toolUsageCount[event.name] ?? 0) + 1;\n }\n if (event.type === 'error') {\n errors.push({ ts: event.ts, phase: event.phase, message: event.message });\n }\n if (event.type === 'mode_changed') {\n modeChanges.push({ ts: event.ts, from: event.from, to: event.to });\n }\n if (event.type === 'task_created') {\n tasksById.set(event.taskId, {\n taskId: event.taskId,\n title: event.title,\n status: 'created',\n createdAt: event.ts,\n });\n }\n if (event.type === 'task_updated') {\n const t = tasksById.get(event.taskId);\n if (t) t.status = event.status;\n }\n if (event.type === 'task_completed') {\n const t = tasksById.get(event.taskId);\n if (t) {\n t.status = 'completed';\n t.completedAt = event.ts;\n } else {\n tasksById.set(event.taskId, {\n taskId: event.taskId,\n title: event.title,\n status: 'completed',\n createdAt: event.ts,\n completedAt: event.ts,\n });\n }\n }\n if (event.type === 'task_failed') {\n const t = tasksById.get(event.taskId);\n if (t) {\n t.status = 'failed';\n t.completedAt = event.ts;\n } else {\n tasksById.set(event.taskId, {\n taskId: event.taskId,\n title: event.title,\n status: 'failed',\n createdAt: event.ts,\n completedAt: event.ts,\n });\n }\n }\n }\n\n return {\n sessionId,\n totalDuration: this.calcDuration(events),\n toolUsageCount,\n errorCount: errors.length,\n modeChanges,\n tasks: Array.from(tasksById.values()),\n };\n }\n\n query(events: SessionEvent[], filter: QueryFilter): SessionEvent[] {\n return events.filter((e) => {\n if (filter.eventTypes?.length && !filter.eventTypes.includes(e.type)) return false;\n if (filter.toolNames?.length && e.type === 'tool_use') {\n const toolEvent = e as { type: 'tool_use'; name: string };\n if (!filter.toolNames.includes(toolEvent.name)) return false;\n }\n if (filter.timeRange) {\n const ts = new Date(e.ts).getTime();\n const start = new Date(filter.timeRange.start).getTime();\n const end = new Date(filter.timeRange.end).getTime();\n if (ts < start || ts > end) return false;\n }\n return true;\n });\n }\n\n private calcDuration(events: SessionEvent[]): number {\n if (events.length < 2) return 0;\n const firstEvent = events[0];\n const lastEvent = events[events.length - 1];\n /* v8 ignore next -- defensive: length>=2 guard above guarantees both ends exist */\n if (!firstEvent || !lastEvent) return 0;\n const first = new Date(firstEvent.ts).getTime();\n const last = new Date(lastEvent.ts).getTime();\n return last - first;\n }\n}\n", "/**\n * SessionRegistry \u2014 cross-process session and agent tracker.\n *\n * Each WrongStack process registers its session on start and updates its\n * status periodically. The registry is a single JSON file at\n * `~/.wrongstack/session-registry.json`. Entries are keyed by session ID.\n *\n * Because multiple processes may write concurrently, every write is an\n * atomic read-modify-write protected by a per-file advisory lock (flock on\n * Unix, exclusive open on Windows). Stale entries (process no longer alive)\n * are pruned on every read.\n *\n * @module session-registry\n */\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { randomUUID } from 'node:crypto';\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Live status of a single agent within a session. */\nexport type AgentLiveStatus =\n | 'idle'\n | 'running'\n | 'streaming'\n | 'waiting_user' // brain.ask_human, confirm prompt\n | 'error';\n\nexport interface AgentEntry {\n /** Unique agent id (ULID or UUID). */\n id: string;\n /** Human-readable label (e.g. \"leader\", \"bug-hunter #1\"). */\n name: string;\n /** UTC ISO timestamp when this agent/run started, when known. */\n startedAt?: string | undefined;\n status: AgentLiveStatus;\n /** Current tool name if running, undefined otherwise. */\n currentTool?: string | undefined;\n /** Iteration count so far. */\n iterations: number;\n /** Tool calls so far. */\n toolCalls: number;\n /** Cumulative cost in USD for this agent, when known. */\n costUsd?: number | undefined;\n /** Cumulative input tokens, when known. */\n tokensIn?: number | undefined;\n /** Cumulative output tokens, when known. */\n tokensOut?: number | undefined;\n /** Context window fill 0\u2013100 (may exceed 100 when over limit), when known. */\n ctxPct?: number | undefined;\n /** Model id this agent is running on, when known. */\n model?: string | undefined;\n /**\n * Tail of the assistant text currently being streamed (capped, throttled).\n * Lets a cross-process watcher see the response form in near-real-time\n * instead of waiting for the completed turn to land in the session log.\n */\n partialText?: string | undefined;\n /** UTC ISO timestamp of last activity. */\n lastActivityAt: string;\n}\n\nexport type SessionLiveStatus =\n | 'active' // process running, agents may be idle or busy\n | 'idle' // process running, no agent activity\n | 'closing' // session_end written, process shutting down\n | 'stale' // process no longer alive (pruned on next read)\n | 'lost'; // heartbeat timeout \u2014 process may still be alive but unreachable\n\nexport interface SessionRegistryEntry {\n sessionId: string;\n projectSlug: string;\n projectRoot: string;\n projectName: string;\n workingDir: string;\n /**\n * Which surface owns this session \u2014 `'tui'` / `'webui'` / `'cli'` (one-shot or\n * REPL). Lets cross-process consumers (e.g. the WebUI Fleet HQ office map) label\n * each live session by client kind. Optional for back-compat with older entries.\n */\n clientType?: 'tui' | 'webui' | 'cli' | 'repl' | string | undefined;\n /** Current git branch, if the project is a git repo. Detected at registration. */\n gitBranch?: string | undefined;\n status: SessionLiveStatus;\n pid: number;\n /** UTC ISO */\n startedAt: string;\n /** UTC ISO \u2014 updated on every heartbeat */\n lastHeartbeatAt: string;\n /** Count of tracked agents */\n agentCount: number;\n agents: AgentEntry[];\n}\n\n// \u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst REGISTRY_FILE = 'session-registry.json';\nconst HEARTBEAT_INTERVAL_MS = 5_000;\nconst STALE_TIMEOUT_MS = 30_000; // entry considered stale after 30s without heartbeat\n// A session that announced `closing` (heartbeat stopped) is dropped this long\n// after its last heartbeat, so the fleet view doesn't keep a dead client around.\nconst CLOSING_GRACE_MS = 15_000;\n// A session marked 'lost' (heartbeat timeout but process still alive or\n// recently dead) stays visible in the fleet view for this long so the user\n// can see \"what went down\" instead of a silent disappearance.\nconst LOST_GRACE_MS = 300_000; // 5 minutes\n// A held lock is released within milliseconds; anything older is a crashed\n// owner's leftover and is safe to break so writes never wedge permanently.\nconst STALE_LOCK_MS = 10_000;\nconst STALE_TMP_MS = 60_000;\nconst MAX_STALE_TMP_FILES = 20;\n\n// \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Parse registry file contents, tolerating corruption. A system crash can\n * leave the file zero-filled (NTFS journals the rename metadata but the data\n * blocks were never flushed \u2014 the file is its full size of NUL bytes), or a\n * torn write can leave invalid JSON. Treat anything unparsable \u2014 or parsable\n * but not a plain object \u2014 as an empty registry so the next write heals the\n * file instead of wedging every future write forever.\n */\nfunction parseRegistry(raw: string): Record<string, SessionRegistryEntry> {\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return parsed as Record<string, SessionRegistryEntry>;\n }\n } catch {\n /* corrupt file \u2014 fall through to empty */\n }\n return {};\n}\n\n// \u2500\u2500 Registry class \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class SessionRegistry {\n private readonly filePath: string;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private currentSessionId: string | null = null;\n /**\n * Last full entry this process registered. Kept so the heartbeat can\n * re-create our entry if it ever goes missing \u2014 e.g. our initial register()\n * write was dropped (a wedged lock), the file was reset, or we were pruned.\n */\n private lastEntry: SessionRegistryEntry | null = null;\n\n constructor(globalRoot: string) {\n this.filePath = path.join(globalRoot, REGISTRY_FILE);\n }\n\n // \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Register the current session. Call once on session start.\n * Starts the heartbeat timer.\n */\n async register(\n entry: Omit<SessionRegistryEntry, 'status' | 'lastHeartbeatAt' | 'agentCount' | 'agents'> & {\n agents?: AgentEntry[] | undefined;\n },\n ): Promise<void> {\n // Safe to call again on a project switch: the WebUI re-roots in place and\n // creates a fresh session id pointing at the new project. Clear the prior\n // heartbeat timer (otherwise each switch leaks a timer that keeps writing).\n // A process owns exactly one entry, so the same-pid dedup below drops our\n // own previous entry \u2014 the registry never carries a phantom session still\n // pointing at the old project's root/workingDir.\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n this.currentSessionId = entry.sessionId;\n const full: SessionRegistryEntry = {\n ...entry,\n status: 'active',\n lastHeartbeatAt: new Date().toISOString(),\n agentCount: entry.agents?.length ?? 0,\n agents: entry.agents ?? [],\n };\n this.lastEntry = full;\n await this.atomicUpdate((registry) => {\n // Prune dead entries that haven't heartbeated recently.\n // A just-created entry has no heartbeat yet \u2014 don't prune it.\n const now = Date.now();\n for (const [id, existing] of Object.entries(registry)) {\n if (existing.pid === entry.pid) {\n // Our own process owns exactly one entry. When re-registering under\n // a new session id (project switch re-roots in place), drop the\n // stale same-pid entry so it doesn't linger pointing at the old\n // project's root/workingDir.\n if (id !== entry.sessionId) delete registry[id];\n continue;\n }\n const heartbeatAge = now - new Date(existing.lastHeartbeatAt).getTime();\n if (heartbeatAge > STALE_TIMEOUT_MS && !pidAlive(existing.pid)) {\n delete registry[id];\n }\n }\n registry[entry.sessionId] = full;\n });\n\n // Start heartbeat\n /* v8 ignore start -- 5s heartbeat timer fires only in a live process, not under test */\n this.heartbeatTimer = setInterval(() => {\n void this.heartbeat();\n }, HEARTBEAT_INTERVAL_MS);\n /* v8 ignore stop */\n if (this.heartbeatTimer.unref) this.heartbeatTimer.unref();\n }\n\n /**\n * Update agent status for the current session. Call on every\n * significant status change (agent start, tool start, user wait, error).\n */\n async updateAgents(agents: AgentEntry[]): Promise<void> {\n if (!this.currentSessionId) return;\n // Derive session status from the agent collective.\n const hasRunning = agents.some((a) => a.status === 'running' || a.status === 'streaming');\n const hasWaiting = agents.some((a) => a.status === 'waiting_user');\n const hasError = agents.some((a) => a.status === 'error');\n const status: SessionLiveStatus = hasRunning || hasWaiting || hasError ? 'active' : 'idle';\n const nowIso = new Date().toISOString();\n\n // Keep the cached entry current so a heartbeat re-insert carries live agents.\n if (this.lastEntry) {\n this.lastEntry.agents = agents;\n this.lastEntry.agentCount = agents.length;\n this.lastEntry.status = status;\n this.lastEntry.lastHeartbeatAt = nowIso;\n }\n\n await this.atomicUpdate((registry) => {\n let entry = registry[this.currentSessionId!];\n if (!entry) {\n // Our entry vanished (dropped write / reset / pruned) \u2014 re-create it.\n /* v8 ignore next -- unreachable: register() sets lastEntry before any updateAgents */\n if (!this.lastEntry) return;\n entry = { ...this.lastEntry };\n registry[this.currentSessionId!] = entry;\n }\n entry.agents = agents;\n entry.agentCount = agents.length;\n entry.status = status;\n entry.lastHeartbeatAt = nowIso;\n });\n }\n\n /**\n * Mark the session as closing. Called during shutdown.\n * Stops the heartbeat timer.\n */\n async markClosing(): Promise<void> {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n if (!this.currentSessionId) return;\n await this.atomicUpdate((registry) => {\n const entry = registry[this.currentSessionId!];\n if (!entry) return;\n entry.status = 'closing';\n entry.lastHeartbeatAt = new Date().toISOString();\n });\n }\n\n /**\n * Remove the current session from the registry. Call on clean exit.\n */\n async unregister(): Promise<void> {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n if (!this.currentSessionId) return;\n const sid = this.currentSessionId;\n this.currentSessionId = null;\n await this.atomicUpdate((registry) => {\n delete registry[sid];\n });\n }\n\n /**\n * List all non-stale sessions. Prunes stale entries automatically.\n */\n async list(): Promise<SessionRegistryEntry[]> {\n const registry = await this.readAndPrune();\n return Object.values(registry);\n }\n\n /**\n * Get a single session entry by ID. Returns undefined if not found or stale.\n */\n async get(sessionId: string): Promise<SessionRegistryEntry | undefined> {\n const registry = await this.readAndPrune();\n return registry[sessionId];\n }\n\n /**\n * List all sessions for a specific project (by slug).\n */\n async listByProject(projectSlug: string): Promise<SessionRegistryEntry[]> {\n const all = await this.list();\n return all.filter((e) => e.projectSlug === projectSlug);\n }\n\n /**\n * Return the registry file path. Useful for WebUI to watch/read.\n */\n get registryPath(): string {\n return this.filePath;\n }\n\n // \u2500\u2500 Internal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async heartbeat(): Promise<void> {\n if (!this.currentSessionId) return;\n try {\n const sessionId = this.currentSessionId;\n const nowIso = new Date().toISOString();\n await this.atomicUpdate((registry) => {\n const entry = registry[sessionId];\n if (entry) {\n entry.lastHeartbeatAt = nowIso;\n // Status bound: if closing, don't revert\n if (entry.status !== 'closing') {\n const hasRunning = (entry.agents ?? []).some(\n (a) => a.status === 'running' || a.status === 'streaming',\n );\n entry.status = hasRunning ? 'active' : 'idle';\n }\n return;\n }\n if (this.lastEntry) {\n // Our entry is gone (initial register() dropped on a wedged lock, file\n // reset, or pruned). Re-create it through the locked path so a process\n // that booted into a broken registry still shows up once it heals.\n registry[sessionId] = { ...this.lastEntry, lastHeartbeatAt: nowIso };\n }\n });\n } catch {\n // Best-effort heartbeat \u2014 never throw\n }\n }\n\n private async readAndPrune(): Promise<Record<string, SessionRegistryEntry>> {\n try {\n const raw = await fs.readFile(this.filePath, 'utf8');\n const registry = parseRegistry(raw);\n const now = Date.now();\n let pruned = false;\n\n for (const [id, entry] of Object.entries(registry)) {\n const heartbeatAge = now - new Date(entry.lastHeartbeatAt).getTime();\n // Cleanly-closed session: drop after a short grace (handles both a fully\n // exited process and one still alive but done) so no dead client lingers.\n if (entry.status === 'closing' && heartbeatAge > CLOSING_GRACE_MS) {\n delete registry[id];\n pruned = true;\n continue;\n }\n // Lost session: heartbeat timed out but process may still be alive.\n // Instead of disappearing instantly (which makes fleet views flicker),\n // mark as 'lost' and keep visible for LOST_GRACE_MS so the user can\n // see what went down.\n if (heartbeatAge > STALE_TIMEOUT_MS) {\n const alive = pidAlive(entry.pid);\n if (alive) {\n // Process alive but not heartbeating \u2192 mark lost (stays visible)\n if (entry.status !== 'lost' && entry.status !== 'closing' && entry.status !== 'stale') {\n entry.status = 'lost';\n pruned = true;\n }\n // Keep lost entries around for the grace period\n if (entry.status === 'lost' && heartbeatAge > STALE_TIMEOUT_MS + LOST_GRACE_MS) {\n delete registry[id];\n pruned = true;\n }\n } else {\n // Process dead \u2192 stale, then prune after 5 min (existing behavior)\n entry.status = 'stale';\n const startedAge = now - new Date(entry.startedAt).getTime();\n if (startedAge > 5 * 60_000) {\n delete registry[id];\n pruned = true;\n }\n }\n }\n }\n\n if (pruned) {\n /* v8 ignore start -- best-effort prune write; the .catch only fires on a write failure */\n await this.writeAtomic(registry).catch(() => undefined);\n /* v8 ignore stop */\n }\n\n return registry;\n } catch {\n return {};\n }\n }\n\n private async atomicUpdate(\n fn: (registry: Record<string, SessionRegistryEntry>) => void,\n ): Promise<void> {\n const lockPath = `${this.filePath}.lock`;\n const maxRetries = 8;\n const retryDelayMs = 20;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n // Ensure directory exists\n await fs.mkdir(path.dirname(this.filePath), { recursive: true });\n\n // Acquire exclusive lock via O_CREAT | O_EXCL\n let lockHandle = await fs.open(lockPath, 'wx').catch(() => null);\n if (!lockHandle) {\n // Lock contended. A crashed process can leave its lock file behind\n // forever (the `finally` unlink never ran), which would wedge EVERY\n // future write \u2014 the registry silently stops updating. Break the lock\n // when its owner pid is dead or it has been held implausibly long\n // (legit holds are sub-millisecond), then retry the open once.\n if (await this.breakStaleLock(lockPath)) {\n /* v8 ignore start -- retry-open after breaking a stale lock; .catch only fires on contention */\n lockHandle = await fs.open(lockPath, 'wx').catch(() => null);\n /* v8 ignore stop */\n }\n if (!lockHandle) {\n await new Promise((r) => setTimeout(r, retryDelayMs * (attempt + 1)));\n continue;\n }\n }\n\n try {\n // Stamp the owner pid so other processes can detect a stale lock.\n /* v8 ignore start -- best-effort owner-pid stamp; .catch only fires on a write failure */\n await lockHandle.writeFile(String(process.pid)).catch(() => undefined);\n /* v8 ignore stop */\n const raw = await fs.readFile(this.filePath, 'utf8').catch(() => '{}');\n // Corruption-tolerant: a crash-zeroed or torn file must not abort\n // the write \u2014 starting from {} rewrites a healthy registry.\n const registry = parseRegistry(raw);\n fn(registry);\n await this.writeAtomicLocked(registry);\n return; // success\n } finally {\n await lockHandle.close();\n /* v8 ignore start -- best-effort lock cleanup in finally; .catch only fires if the lock vanished */\n await fs.unlink(lockPath).catch(() => undefined);\n /* v8 ignore stop */\n }\n } catch {\n // Best-effort \u2014 never throw from registry writes\n /* v8 ignore next -- defensive: a registry write failure must never propagate */\n return;\n }\n }\n // All retries exhausted \u2014 registry update dropped (non-critical)\n }\n\n /**\n * Break a contended lock if it is stale: the recorded owner pid is no longer\n * alive, or the lock is older than {@link STALE_LOCK_MS}. Returns true when the\n * lock was removed (caller should retry acquisition). Best-effort and\n * race-tolerant \u2014 a fresh lock (age ~0, live owner) is never broken, so the\n * common concurrent case self-heals on the next heartbeat.\n */\n private async breakStaleLock(lockPath: string): Promise<boolean> {\n try {\n const [stat, content] = await Promise.all([\n fs.stat(lockPath),\n /* v8 ignore start -- best-effort lock-content read; .catch only fires if the lock vanished */\n fs.readFile(lockPath, 'utf8').catch(() => ''),\n /* v8 ignore stop */\n ]);\n const ageMs = Date.now() - stat.mtimeMs;\n const ownerPid = Number.parseInt(content.trim(), 10);\n const ownerDead =\n Number.isInteger(ownerPid) && ownerPid > 0 && ownerPid !== process.pid && !pidAlive(ownerPid);\n if (ownerDead || ageMs > STALE_LOCK_MS) {\n /* v8 ignore start -- best-effort stale-lock removal; .catch only fires if the lock vanished */\n await fs.unlink(lockPath).catch(() => undefined);\n /* v8 ignore stop */\n return true;\n }\n return false;\n } catch {\n // stat failed \u2192 the lock vanished underneath us; let the caller retry.\n /* v8 ignore next -- defensive: a vanished lock between stat and read is fine */\n return true;\n }\n }\n\n private async writeAtomicLocked(registry: Record<string, SessionRegistryEntry>): Promise<void> {\n await this.pruneStaleTempFiles();\n await this.writeAtomicFile(registry);\n }\n\n /** Legacy write without lock \u2014 used by heartbeat for performance. */\n private async writeAtomic(registry: Record<string, SessionRegistryEntry>): Promise<void> {\n await this.pruneStaleTempFiles();\n await this.writeAtomicFile(registry);\n }\n\n private async writeAtomicFile(registry: Record<string, SessionRegistryEntry>): Promise<void> {\n const tmp = path.join(\n path.dirname(this.filePath),\n `.${path.basename(this.filePath)}.${randomUUID().slice(0, 8)}.tmp`,\n );\n try {\n // Write + fsync BEFORE the rename: without the fsync, a system crash\n // can journal the rename metadata while the data blocks were never\n // flushed, leaving a zero-filled (all-NUL) registry file on reboot.\n const handle = await fs.open(tmp, 'w');\n try {\n await handle.writeFile(JSON.stringify(registry, null, 2), 'utf8');\n await handle.sync().catch(() => undefined);\n } finally {\n await handle.close();\n }\n await fs.rename(tmp, this.filePath);\n } catch (err) {\n /* v8 ignore start -- rename-failure cleanup: best-effort tmp unlink + rethrow (atomicUpdate swallows it) */\n await fs.unlink(tmp).catch(() => undefined);\n throw err;\n /* v8 ignore stop */\n }\n }\n\n private async pruneStaleTempFiles(): Promise<void> {\n try {\n const dir = path.dirname(this.filePath);\n const base = path.basename(this.filePath);\n const now = Date.now();\n const stale: Array<{ name: string; mtimeMs: number }> = [];\n\n for (const name of await fs.readdir(dir)) {\n const isTemp =\n (name.startsWith(`${base}.`) || name.startsWith(`.${base}.`)) && name.endsWith('.tmp');\n if (!isTemp) continue;\n /* v8 ignore start -- best-effort temp stat; .catch(null)+continue only fire when the temp vanished */\n const stat = await fs.stat(path.join(dir, name)).catch(() => null);\n if (!stat) continue;\n /* v8 ignore stop */\n if (now - stat.mtimeMs > STALE_TMP_MS) stale.push({ name, mtimeMs: stat.mtimeMs });\n }\n\n stale.sort((a, b) => b.mtimeMs - a.mtimeMs);\n await Promise.all(\n stale.slice(MAX_STALE_TMP_FILES).map(async ({ name }) => {\n /* v8 ignore start -- best-effort temp removal; .catch only fires if the temp vanished */\n await fs.unlink(path.join(dir, name)).catch(() => undefined);\n /* v8 ignore stop */\n }),\n );\n } catch {\n // best-effort cleanup must not block registry heartbeats\n }\n }\n}\n\n/** Singleton \u2014 created once per process. */\nlet _instance: SessionRegistry | null = null;\n\nexport function getSessionRegistry(globalRoot?: string): SessionRegistry {\n if (!_instance && globalRoot) {\n _instance = new SessionRegistry(globalRoot);\n }\n if (!_instance) {\n /* v8 ignore next -- the singleton is initialized by the first call in every test/process */\n throw new Error('SessionRegistry not initialized. Call getSessionRegistry(globalRoot) first.');\n }\n return _instance;\n}\n\nexport function hasSessionRegistry(): boolean {\n return _instance !== null;\n}\n", "/**\n * AgentStatusTracker \u2014 subscribes to EventBus events and keeps the\n * SessionRegistry updated with live agent status.\n *\n * Created once per process during boot. Listens for:\n * - agent.run.started / agent.run.completed \u2192 agent status changes\n * - tool.started / tool.executed \u2192 current tool tracking\n * - brain.ask_human \u2192 waiting_user status\n * - fleet events (subagent spawn/start/done) \u2192 agent entries\n *\n * @module agent-status-tracker\n */\nimport type { EventBus } from './kernel/events.js';\nimport type {\n AgentEntry,\n AgentLiveStatus,\n SessionRegistry,\n} from './session-registry.js';\n\n/** A finished (idle/error) subagent older than this is reaped from the fleet view. */\nconst AGENT_REAP_MS = 30_000;\n/** How often the reaper sweeps for finished subagents. */\nconst AGENT_SWEEP_INTERVAL_MS = 10_000;\n/** Max chars of streamed assistant text kept in the registry (the live tail). */\nconst PARTIAL_TEXT_CAP = 1200;\n/** Min gap between registry flushes triggered purely by streamed text. */\nconst PARTIAL_FLUSH_THROTTLE_MS = 300;\n\nfunction clampPct(pct: number): number {\n if (!Number.isFinite(pct)) return 0;\n return Math.max(0, Math.min(100, pct));\n}\n\nexport interface AgentStatusTrackerOptions {\n events: EventBus;\n registry: SessionRegistry;\n /** Session id whose live agent state is mirrored by this tracker. */\n sessionId?: string | (() => string | undefined) | undefined;\n /** Leader agent name shown in the registry. Default: \"leader\". */\n leaderName?: string | undefined;\n /**\n * Best-effort callback fired after each registry write settles. Used to nudge\n * local WebUI servers (FleetNotifier) so cross-process status reaches the map\n * without waiting on their file-watch/poll. Never block or throw.\n */\n onUpdate?: (() => void) | undefined;\n}\n\nexport class AgentStatusTracker {\n private readonly events: EventBus;\n private readonly registry: SessionRegistry;\n private readonly sessionId: string | (() => string | undefined) | undefined;\n private readonly leaderName: string;\n\n // Live agent map: agentId \u2192 AgentEntry\n private agents = new Map<string, AgentEntry>();\n // Last full agent list flushed (leader + subagents). Lets external consumers\n // read the current state synchronously without re-deriving it.\n private lastAgents: AgentEntry[] = [];\n\n // Leader tracking\n private leaderStatus: AgentLiveStatus = 'idle';\n private leaderCurrentTool: string | undefined;\n private leaderIterations = 0;\n private leaderToolCalls = 0;\n private leaderCostUsd = 0;\n private leaderTokensIn = 0;\n private leaderTokensOut = 0;\n private leaderCtxPct: number | undefined;\n private leaderModel: string | undefined;\n private leaderPartialText = '';\n private leaderStartedAt: string | undefined;\n\n private unsubscribers: Array<() => void> = [];\n private readonly onUpdate: (() => void) | undefined;\n private sweepTimer: ReturnType<typeof setInterval> | null = null;\n private partialTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(opts: AgentStatusTrackerOptions) {\n this.events = opts.events;\n this.registry = opts.registry;\n this.sessionId = opts.sessionId;\n this.leaderName = opts.leaderName ?? 'leader';\n this.onUpdate = opts.onUpdate;\n }\n\n /** Current full agent list (leader + subagents) as of the last flush. */\n getAgents(): AgentEntry[] {\n return this.lastAgents.length > 0 ? [...this.lastAgents] : [];\n }\n\n start(): void {\n const on = (\n pattern: string,\n fn: (event: string, payload: unknown) => void,\n ): (() => void) =>\n this.events.onPattern(pattern, (event, payload) => {\n if (!this.acceptsSession(payload)) return;\n fn(event, payload);\n });\n\n // Leader events\n this.unsubscribers.push(\n on('agent.run.started', (_event, payload) => {\n const p = payload as { at?: string; model?: string; ctx?: unknown } | undefined;\n this.markLeaderStarted(p?.at);\n this.captureLeaderContext(p?.ctx);\n if (p?.model) this.leaderModel = p.model;\n this.leaderStatus = 'running';\n this.leaderIterations++;\n this.flush();\n }),\n );\n\n // Capture the leader's model + context fill from each iteration's context.\n this.unsubscribers.push(\n on('iteration.started', (_e, payload) => {\n const p = payload as { ctx?: unknown; index?: number } | undefined;\n const ctx = p?.ctx;\n this.markLeaderStarted();\n this.leaderStatus = 'running';\n if (typeof p?.index === 'number') {\n this.leaderIterations = Math.max(this.leaderIterations, p.index + 1);\n }\n if (!ctx) {\n this.flush();\n return;\n }\n this.captureLeaderContext(ctx);\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('agent.run.completed', (_event, payload) => {\n const p = payload as { status?: string; ctx?: unknown } | undefined;\n this.captureLeaderContext(p?.ctx);\n this.leaderStatus = p?.status === 'failed' ? 'error' : 'idle';\n this.leaderCurrentTool = undefined;\n this.leaderPartialText = '';\n if (this.leaderStatus === 'idle') this.leaderStartedAt = undefined;\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('agent.run.error', (_event, payload) => {\n const p = payload as { ctx?: unknown } | undefined;\n this.captureLeaderContext(p?.ctx);\n this.leaderStatus = 'error';\n this.leaderCurrentTool = undefined;\n this.leaderPartialText = '';\n this.flush();\n }),\n );\n\n // Tool events \u2014 track current tool\n this.unsubscribers.push(\n on('tool.started', (_event, payload) => {\n const p = payload as { name?: string } | undefined;\n if (p?.name) {\n this.markLeaderStarted();\n this.leaderCurrentTool = p.name;\n this.leaderToolCalls++;\n }\n this.leaderStatus = 'running';\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('tool.executed', () => {\n this.leaderCurrentTool = undefined;\n this.flush();\n }),\n );\n\n // Brain ask_human \u2192 waiting for user input\n this.unsubscribers.push(\n on('brain.ask_human', () => {\n this.markLeaderStarted();\n this.leaderStatus = 'waiting_user';\n this.flush();\n }),\n );\n\n // Streaming events\n this.unsubscribers.push(\n on('llm.stream_started', () => {\n this.markLeaderStarted();\n this.leaderStatus = 'streaming';\n // A new response is starting \u2014 drop the previous turn's live tail.\n this.leaderPartialText = '';\n this.flush();\n }),\n );\n\n // Live assistant text \u2014 accumulate the streamed tail so a cross-process\n // watcher sees the response form. Flushed on a throttle, NOT per token\n // (text_delta fires once per chunk \u2192 would thrash the shared registry).\n this.unsubscribers.push(\n on('provider.text_delta', (_e, payload) => {\n const p = payload as { text?: string; ctx?: unknown } | undefined;\n const text = p?.text;\n if (!text) return;\n this.markLeaderStarted();\n this.captureLeaderContext(p?.ctx);\n this.leaderStatus = 'streaming';\n const next = this.leaderPartialText + text;\n this.leaderPartialText =\n next.length > PARTIAL_TEXT_CAP ? next.slice(next.length - PARTIAL_TEXT_CAP) : next;\n this.schedulePartialFlush();\n }),\n );\n\n this.unsubscribers.push(\n on('provider.response', (_e, payload) => {\n const p = payload as { ctx?: unknown } | undefined;\n this.captureLeaderContext(p?.ctx);\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('provider.fallback', (_e, payload) => {\n const p = payload as { to?: { providerId?: string; model?: string } } | undefined;\n if (p?.to?.model) {\n this.leaderModel = p.to.providerId\n ? `${p.to.providerId}/${p.to.model}`\n : p.to.model;\n this.flush();\n }\n }),\n );\n\n this.unsubscribers.push(\n on('ctx.pct', (_e, payload) => {\n const p = payload as { load?: number } | undefined;\n if (typeof p?.load === 'number' && Number.isFinite(p.load)) {\n this.leaderCtxPct = clampPct(Math.round(p.load * 100));\n this.flush();\n }\n }),\n );\n\n // Leader token + cost accounting (per provider call \u2014 accumulate).\n this.unsubscribers.push(\n on('token.accounted', (_e, payload) => {\n const p = payload as\n | { usage?: { input?: number; output?: number }; cost?: { total?: number } }\n | undefined;\n if (!p) return;\n this.leaderTokensIn += p.usage?.input ?? 0;\n this.leaderTokensOut += p.usage?.output ?? 0;\n this.leaderCostUsd += p.cost?.total ?? 0;\n this.flush();\n }),\n );\n\n // \u2500\u2500 Subagent tracking \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // These are the real fleet lifecycle events (emitted by MultiAgentHost /\n // the subagent runner / director). The previous code listened to a\n // `fleet.subagent.*` namespace that nothing emits, so subagents never\n // reached the registry \u2014 only the leader showed up.\n const touch = (id: string): AgentEntry => {\n let entry = this.agents.get(id);\n if (!entry) {\n const now = new Date().toISOString();\n entry = { id, name: id, status: 'idle', iterations: 0, toolCalls: 0, startedAt: now, lastActivityAt: now };\n this.agents.set(id, entry);\n }\n entry.lastActivityAt = new Date().toISOString();\n return entry;\n };\n\n this.unsubscribers.push(\n on('subagent.spawned', (_e, payload) => {\n const p = payload as { subagentId?: string; name?: string; model?: string } | undefined;\n if (!p?.subagentId) return;\n const entry = touch(p.subagentId);\n entry.name = p.name?.trim() || entry.name;\n if (p.model) entry.model = p.model;\n /* v8 ignore next -- touch() always sets startedAt on creation */\n if (!entry.startedAt) entry.startedAt = new Date().toISOString();\n entry.status = 'running';\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('subagent.ctx_pct', (_e, payload) => {\n const p = payload as { subagentId?: string; load?: number } | undefined;\n if (!p?.subagentId) return;\n const entry = touch(p.subagentId);\n if (typeof p.load === 'number') entry.ctxPct = clampPct(Math.round(p.load * 100));\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('subagent.task_started', (_e, payload) => {\n const p = payload as { subagentId?: string } | undefined;\n if (!p?.subagentId) return;\n const entry = touch(p.subagentId);\n entry.status = 'running';\n /* v8 ignore next -- touch() always sets startedAt on creation */\n if (!entry.startedAt) entry.startedAt = new Date().toISOString();\n entry.iterations++;\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('subagent.tool_executed', (_e, payload) => {\n const p = payload as { subagentId?: string; name?: string } | undefined;\n if (!p?.subagentId) return;\n const entry = touch(p.subagentId);\n entry.status = 'running';\n /* v8 ignore next -- touch() always sets startedAt on creation */\n if (!entry.startedAt) entry.startedAt = new Date().toISOString();\n entry.currentTool = p.name;\n entry.toolCalls++;\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('subagent.iteration_summary', (_e, payload) => {\n const p = payload as\n | {\n subagentId?: string;\n iteration?: number;\n toolCalls?: number;\n currentTool?: string;\n costUsd?: number;\n partialText?: string;\n }\n | undefined;\n if (!p?.subagentId) return;\n const entry = touch(p.subagentId);\n entry.status = 'running';\n /* v8 ignore next -- touch() always sets startedAt on creation */\n if (!entry.startedAt) entry.startedAt = new Date().toISOString();\n if (typeof p.iteration === 'number') entry.iterations = p.iteration;\n if (typeof p.toolCalls === 'number') entry.toolCalls = p.toolCalls;\n if (typeof p.costUsd === 'number') entry.costUsd = p.costUsd;\n if (p.currentTool) entry.currentTool = p.currentTool;\n // Live streamed tail of THIS subagent's current response (the runner\n // already accumulates it) \u2014 capped to the same budget as the leader.\n if (typeof p.partialText === 'string') {\n entry.partialText =\n p.partialText.length > PARTIAL_TEXT_CAP\n ? p.partialText.slice(p.partialText.length - PARTIAL_TEXT_CAP)\n : p.partialText;\n }\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('subagent.task_completed', (_e, payload) => {\n const p = payload as\n | { subagentId?: string; status?: string; iterations?: number; toolCalls?: number }\n | undefined;\n if (!p?.subagentId) return;\n // Only update an agent we already know \u2014 a completion for an unseen\n // agent isn't worth materialising.\n const entry = this.agents.get(p.subagentId);\n if (!entry) return;\n entry.status = p.status === 'failed' || p.status === 'timeout' ? 'error' : 'idle';\n entry.currentTool = undefined;\n entry.partialText = undefined;\n if (typeof p.iterations === 'number') entry.iterations = p.iterations;\n if (typeof p.toolCalls === 'number') entry.toolCalls = p.toolCalls;\n entry.lastActivityAt = new Date().toISOString();\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('subagent.stopped', (_e, payload) => {\n const p = payload as { subagentId?: string } | undefined;\n if (!p?.subagentId) return;\n if (this.agents.delete(p.subagentId)) this.flush();\n }),\n );\n this.unsubscribers.push(\n on('subagent.removed', (_e, payload) => {\n const p = payload as { subagentId?: string } | undefined;\n if (!p?.subagentId) return;\n if (this.agents.delete(p.subagentId)) this.flush();\n }),\n );\n\n // Reap finished subagents so the fleet view doesn't fill with dead/idle\n // desks. The leader is synthesised in flush() (never in `this.agents`), so\n // it is never reaped \u2014 it represents the live session.\n this.sweepTimer = setInterval(() => this.sweep(), AGENT_SWEEP_INTERVAL_MS);\n if (typeof this.sweepTimer.unref === 'function') this.sweepTimer.unref();\n }\n\n stop(): void {\n for (const unsub of this.unsubscribers) {\n try { unsub(); } catch { /* ignore */ }\n }\n this.unsubscribers = [];\n if (this.sweepTimer) {\n clearInterval(this.sweepTimer);\n this.sweepTimer = null;\n }\n if (this.partialTimer) {\n clearTimeout(this.partialTimer);\n this.partialTimer = null;\n }\n }\n\n /**\n * Coalesce streamed-text flushes: at most one registry write per\n * {@link PARTIAL_FLUSH_THROTTLE_MS} while text streams in, so per-token\n * deltas never thrash the cross-process registry file.\n */\n private schedulePartialFlush(): void {\n if (this.partialTimer) return;\n this.partialTimer = setTimeout(() => {\n this.partialTimer = null;\n this.flush();\n }, PARTIAL_FLUSH_THROTTLE_MS);\n if (typeof this.partialTimer.unref === 'function') this.partialTimer.unref();\n }\n\n /**\n * Remove subagents that have been finished (idle/error) for longer than\n * {@link AGENT_REAP_MS}. Running / streaming / waiting_user agents are kept\n * regardless of age \u2014 only *not-working* agents are reaped.\n */\n private sweep(): void {\n const now = Date.now();\n let removed = false;\n for (const [id, a] of this.agents) {\n const finished = a.status !== 'running' && a.status !== 'streaming' && a.status !== 'waiting_user';\n const age = now - Date.parse(a.lastActivityAt);\n if (finished && Number.isFinite(age) && age > AGENT_REAP_MS) {\n this.agents.delete(id);\n removed = true;\n }\n }\n if (removed) this.flush();\n }\n\n private flush(): void {\n const leaderEntry: AgentEntry = {\n id: 'leader',\n name: this.leaderName,\n startedAt: this.leaderStartedAt,\n status: this.leaderStatus,\n currentTool: this.leaderCurrentTool,\n iterations: this.leaderIterations,\n toolCalls: this.leaderToolCalls,\n costUsd: this.leaderCostUsd,\n tokensIn: this.leaderTokensIn,\n tokensOut: this.leaderTokensOut,\n ctxPct: this.leaderCtxPct,\n model: this.leaderModel,\n partialText: this.leaderPartialText || undefined,\n lastActivityAt: new Date().toISOString(),\n };\n\n const allAgents = [leaderEntry, ...this.agents.values()];\n this.lastAgents = allAgents;\n // Broadcast the fresh agent list on the local bus so in-process consumers\n // (e.g. the HQ session-telemetry bridge) can build snapshots without\n // re-reading the shared registry file. Best-effort, never throws here.\n try {\n this.events.emit('session.agents_updated', {\n sessionId: this.currentSessionId(),\n agents: allAgents,\n });\n } catch {\n /* best-effort */\n }\n // Nudge local WebUIs only AFTER the write settles, so they re-read fresh\n // data. Best-effort \u2014 never let a notifier failure surface here.\n this.registry\n .updateAgents(allAgents)\n .then(() => {\n try {\n this.onUpdate?.();\n } catch {\n /* best-effort */\n }\n })\n .catch(() => undefined);\n }\n\n private currentSessionId(): string | undefined {\n return typeof this.sessionId === 'function' ? this.sessionId() : this.sessionId;\n }\n\n private acceptsSession(payload: unknown): boolean {\n const expected = this.currentSessionId();\n if (!expected) return true;\n if (typeof payload !== 'object' || payload === null) return true;\n const actual = (payload as { sessionId?: unknown }).sessionId;\n return typeof actual !== 'string' || actual.length === 0 || actual === expected;\n }\n\n private markLeaderStarted(startedAt?: string): void {\n if (\n this.leaderStartedAt &&\n (this.leaderStatus === 'running' ||\n this.leaderStatus === 'streaming' ||\n this.leaderStatus === 'waiting_user')\n ) {\n return;\n }\n this.leaderStartedAt = startedAt ?? new Date().toISOString();\n }\n\n private captureLeaderContext(ctx: unknown): void {\n if (typeof ctx !== 'object' || ctx === null) return;\n const c = ctx as {\n model?: unknown;\n lastRequestTokens?: unknown;\n meta?: Record<string, unknown> | undefined;\n provider?: { capabilities?: { maxContext?: unknown } | undefined } | undefined;\n };\n if (typeof c.model === 'string' && c.model.length > 0) this.leaderModel = c.model;\n\n const metaLimit = c.meta?.['effectiveMaxContext'];\n const providerMax = c.provider?.capabilities?.maxContext;\n const maxContext =\n typeof metaLimit === 'number' && metaLimit > 0\n ? metaLimit\n : typeof providerMax === 'number' && providerMax > 0\n ? providerMax\n : undefined;\n if (\n typeof c.lastRequestTokens === 'number' &&\n c.lastRequestTokens > 0 &&\n maxContext !== undefined\n ) {\n this.leaderCtxPct = clampPct(Math.round((c.lastRequestTokens / maxContext) * 100));\n }\n }\n}\n", "/**\n * FleetNotifier \u2014 push-on-write nudge to local WebUI servers.\n *\n * The cross-process source of truth is the file-based SessionRegistry, which\n * every WebUI server already watches (`fs.watch`, ~150ms) and polls (5s). This\n * notifier is a best-effort *latency* optimisation on top: after a process\n * writes its session/agent status to the registry, it sends a tiny\n * fire-and-forget `POST /api/fleet/ping` to every WebUI running against the\n * same project (discovered from `~/.wrongstack/webui-instances.json`). The\n * WebUI responds by re-broadcasting the (already-fresh) registry immediately \u2014\n * so a TUI/REPL agent's activity reaches the Fleet HQ map in ~milliseconds\n * instead of waiting on the watch/poll.\n *\n * Design notes:\n * - **Never the source of truth.** If no WebUI is running, the file system +\n * watch/poll still carry everything. Every failure here is swallowed.\n * - **Coalesced.** Bursts of events (tool calls, deltas) collapse into one POST\n * per ~50ms so we never hammer the WebUI per token.\n * - **Discovery cached** for a couple seconds to avoid a disk read per event.\n * - **Loopback only / self-excluded.** Targets the local instances file; a\n * `0.0.0.0`/`::` bind is dialled on `127.0.0.1`, and the caller's own pid is\n * skipped so a WebUI never pings itself.\n *\n * @module fleet-notifier\n */\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\n\nconst INSTANCES_FILE = 'webui-instances.json';\nconst DISCOVERY_TTL_MS = 2_500;\nconst COALESCE_MS = 50;\nconst POST_TIMEOUT_MS = 500;\n\ninterface InstanceRecordLike {\n pid: number;\n httpPort: number;\n host: string;\n projectRoot: string;\n}\n\nfunction pidAlive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException).code !== 'ESRCH';\n }\n}\n\n/** Normalise a project root for cross-process comparison (case-insensitive on Windows). */\nfunction normRoot(root: string): string {\n const resolved = path.resolve(root);\n return process.platform === 'win32' ? resolved.toLowerCase() : resolved;\n}\n\nexport interface FleetNotifierOptions {\n /** WrongStack global dir (`~/.wrongstack`) holding `webui-instances.json`. */\n baseDir: string;\n /** This process's project root \u2014 only WebUIs on the same project are pinged. */\n projectRoot: string;\n /** This process's pid, so a WebUI never pings itself. */\n selfPid?: number | undefined;\n /** Injectable POST for tests. Defaults to a timed `fetch`. */\n post?: ((url: string) => Promise<void>) | undefined;\n}\n\nexport class FleetNotifier {\n private readonly baseDir: string;\n private readonly projectRoot: string;\n private readonly selfPid: number;\n private readonly doPost: (url: string) => Promise<void>;\n\n private cache: { at: number; urls: string[] } | null = null;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private disposed = false;\n\n constructor(opts: FleetNotifierOptions) {\n this.baseDir = opts.baseDir;\n this.projectRoot = normRoot(opts.projectRoot);\n this.selfPid = opts.selfPid ?? process.pid;\n this.doPost = opts.post ?? defaultPost;\n }\n\n /** Coalesced, best-effort nudge. Safe to call on every status change. */\n notify(): void {\n if (this.disposed || this.timer) return;\n this.timer = setTimeout(() => {\n this.timer = null;\n void this.flush();\n }, COALESCE_MS);\n /* v8 ignore next -- timer.unref is always a function on Node Timeout objects */\n if (typeof this.timer.unref === 'function') this.timer.unref();\n }\n\n /** Resolve same-project WebUI ping URLs (cached briefly). Exposed for tests. */\n async endpoints(): Promise<string[]> {\n const now = Date.now();\n if (this.cache && now - this.cache.at < DISCOVERY_TTL_MS) return this.cache.urls;\n const urls = await this.discover();\n this.cache = { at: now, urls };\n return urls;\n }\n\n dispose(): void {\n this.disposed = true;\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n }\n\n private async flush(): Promise<void> {\n const urls = await this.endpoints();\n await Promise.all(urls.map((u) => this.doPost(u).catch(() => undefined)));\n }\n\n private async discover(): Promise<string[]> {\n try {\n const raw = await fs.readFile(path.join(this.baseDir, INSTANCES_FILE), 'utf8');\n const data = JSON.parse(raw) as { instances?: InstanceRecordLike[] };\n const list = Array.isArray(data?.instances) ? data.instances : [];\n return list\n .filter((i) => i && typeof i.httpPort === 'number')\n .filter((i) => i.pid !== this.selfPid)\n .filter((i) => normRoot(i.projectRoot) === this.projectRoot)\n .filter((i) => pidAlive(i.pid))\n .map((i) => {\n const host = i.host === '0.0.0.0' || i.host === '::' || !i.host ? '127.0.0.1' : i.host;\n return `http://${host}:${i.httpPort}/api/fleet/ping`;\n });\n } catch {\n // Missing/corrupt instances file \u2192 no WebUIs to notify.\n return [];\n }\n }\n}\n\nasync function defaultPost(url: string): Promise<void> {\n await fetch(url, {\n method: 'POST',\n signal: AbortSignal.timeout(POST_TIMEOUT_MS),\n });\n}\n", "import { expectDefined } from '../utils/expect-defined.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { CheckpointInfo, RewindResult, RewindResultExtended, SessionRewinder } from '../types/session-rewinder.js';\nimport type { SessionEvent, FileSnapshot } from '../types/session.js';\nimport { atomicWrite } from '../utils/atomic-write.js';\nimport { SessionError, ERROR_CODES } from '../types/errors.js';\nimport { sessionScopedPath } from '../utils/session-scoped-path.js';\n\nexport interface SessionRewinderOptions {\n sessionsDir: string;\n /** The project root directory; used to validate rewind targets stay inside it. */\n projectRoot: string;\n}\n\n/**\n * Rewind engine that reads session JSONL files and reverts file system\n * changes to any previous checkpoint.\n */\nexport class DefaultSessionRewinder implements SessionRewinder {\n constructor(private readonly sessionsDir: string, private readonly projectRoot: string) {}\n\n private sessionFile(sessionId: string): string {\n return sessionScopedPath(this.sessionsDir, sessionId, '.jsonl');\n }\n\n async listCheckpoints(sessionId: string): Promise<CheckpointInfo[]> {\n const file = this.sessionFile(sessionId);\n const raw = await fsp.readFile(file, 'utf8');\n const events = parseEvents(raw);\n\n // Build a map of promptIndex -> file snapshot count\n const fileCountMap = new Map<number, number>();\n for (const event of events) {\n if (event.type === 'file_snapshot') {\n const e = event as { promptIndex: number; files: FileSnapshot[] };\n fileCountMap.set(e.promptIndex, (fileCountMap.get(e.promptIndex) ?? 0) + e.files.length);\n }\n }\n\n const checkpoints: CheckpointInfo[] = [];\n for (const event of events) {\n if (event.type === 'checkpoint') {\n const e = event as { promptIndex: number; promptPreview: string; ts: string };\n checkpoints.push({\n promptIndex: e.promptIndex,\n promptPreview: e.promptPreview,\n ts: e.ts,\n fileCount: fileCountMap.get(e.promptIndex) ?? 0,\n });\n }\n }\n\n return checkpoints;\n }\n\n async rewindToCheckpoint(\n sessionId: string,\n checkpointIndex: number,\n ): Promise<RewindResultExtended> {\n const file = this.sessionFile(sessionId);\n const raw = await fsp.readFile(file, 'utf8');\n const events = parseEvents(raw);\n\n let targetIdx = -1;\n for (let i = 0; i < events.length; i++) {\n const event = expectDefined(events[i]);\n if (event.type === 'checkpoint') {\n const checkpointEvent = event as { promptIndex: number };\n if (checkpointEvent.promptIndex === checkpointIndex) {\n targetIdx = i;\n break;\n }\n }\n }\n\n if (targetIdx === -1) {\n throw new SessionError({\n message: `Checkpoint ${checkpointIndex} not found`,\n code: ERROR_CODES.SESSION_NOT_FOUND,\n context: { checkpointIndex },\n });\n }\n\n const snapshotsToRevert: Array<{ promptIndex: number; files: FileSnapshot[] }> = [];\n for (let i = targetIdx + 1; i < events.length; i++) {\n const event = expectDefined(events[i]);\n if (event.type === 'checkpoint') {\n break;\n }\n if (event.type === 'file_snapshot') {\n const snapshotEvent = event as { promptIndex: number; files: FileSnapshot[] };\n if (snapshotEvent.promptIndex >= checkpointIndex) {\n snapshotsToRevert.push({ promptIndex: snapshotEvent.promptIndex, files: snapshotEvent.files });\n }\n }\n }\n\n const result = await revertSnapshots(snapshotsToRevert, this.projectRoot);\n const removedEvents = events.length - targetIdx - 1;\n return { ...result, toPromptIndex: checkpointIndex, removedEvents };\n }\n\n async rewindLastN(sessionId: string, n: number): Promise<RewindResultExtended> {\n const file = this.sessionFile(sessionId);\n const raw = await fsp.readFile(file, 'utf8');\n const events = parseEvents(raw);\n\n const checkpoints: Array<{ promptIndex: number; ts: string }> = [];\n for (const event of events) {\n if (event.type === 'checkpoint') {\n checkpoints.push({ promptIndex: event.promptIndex, ts: event.ts });\n }\n }\n\n if (checkpoints.length === 0) {\n return { revertedFiles: [], errors: [], toPromptIndex: 0, removedEvents: 0 };\n }\n\n checkpoints.sort((a, b) => b.promptIndex - a.promptIndex);\n const targetIndex = checkpoints[n]?.promptIndex ?? 0;\n\n const snapshotsToRevert: Array<{ promptIndex: number; files: FileSnapshot[] }> = [];\n let shouldRevert = false;\n\n for (const event of events) {\n if (event.type === 'checkpoint' && event.promptIndex === targetIndex) {\n shouldRevert = true;\n continue;\n }\n if (shouldRevert && event.type === 'file_snapshot') {\n snapshotsToRevert.push({ promptIndex: event.promptIndex, files: event.files });\n }\n }\n\n const result = await revertSnapshots(snapshotsToRevert, this.projectRoot);\n return { ...result, toPromptIndex: targetIndex, removedEvents: snapshotsToRevert.length };\n }\n\n async rewindToStart(sessionId: string): Promise<RewindResultExtended> {\n const file = this.sessionFile(sessionId);\n const raw = await fsp.readFile(file, 'utf8');\n const events = parseEvents(raw);\n\n const allSnapshots: Array<{ promptIndex: number; files: FileSnapshot[] }> = [];\n for (const event of events) {\n if (event.type === 'file_snapshot') {\n allSnapshots.push({ promptIndex: event.promptIndex, files: event.files });\n }\n }\n\n if (allSnapshots.length === 0) {\n return { revertedFiles: [], errors: [], toPromptIndex: 0, removedEvents: 0 };\n }\n\n const result = await revertSnapshots(allSnapshots, this.projectRoot);\n return { ...result, toPromptIndex: 0, removedEvents: allSnapshots.length };\n }\n}\n\nfunction parseEvents(raw: string): SessionEvent[] {\n const lines = raw.split('\\n').filter((l) => l.trim());\n const events: SessionEvent[] = [];\n\n for (const line of lines) {\n try {\n const parsed = JSON.parse(line);\n if (\n parsed !== null &&\n typeof parsed === 'object' &&\n typeof (parsed as { type?: unknown | undefined }).type === 'string' &&\n typeof (parsed as { ts?: unknown | undefined }).ts === 'string'\n ) {\n events.push(parsed as SessionEvent);\n }\n } catch {\n // skip malformed\n }\n }\n\n return events;\n}\n\nasync function revertSnapshots(\n snapshots: Array<{ promptIndex: number; files: FileSnapshot[] }>,\n projectRoot: string,\n): Promise<RewindResult> {\n const revertedFiles: string[] = [];\n const errors: string[] = [];\n\n // Undo is the inverse of execution order. Reverse both event order and the\n // per-event file list so repeated edits of the same path restore the oldest\n // `before` content rather than stopping at an intermediate version.\n for (const snapshot of [...snapshots].reverse()) {\n for (const file of [...snapshot.files].reverse()) {\n try {\n // Guard: ensure the target path resolves inside the project root.\n // Without this, a maliciously recorded path (e.g., via path traversal\n // in a tool call that wasn't caught) could cause rewind to write\n // to arbitrary locations.\n const absPath = path.resolve(file.path);\n const root = path.resolve(projectRoot);\n const rel = path.relative(root, absPath);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n errors.push(`${file.path}: path resolves outside project root \u2014 skipping`);\n continue;\n }\n\n if (file.action === 'deleted') {\n // File was deleted \u2014 restore it from before\n if (file.before !== null) {\n // atomicWrite: torn restore would leave the user with a frankenstein file.\n await atomicWrite(file.path, file.before, { mode: 0o644 });\n revertedFiles.push(file.path);\n }\n } else if (file.action === 'created') {\n // File was created \u2014 delete it\n await fsp.unlink(file.path);\n revertedFiles.push(file.path);\n } else if (file.action === 'modified') {\n // File was modified \u2014 restore before content\n if (file.before !== null) {\n // atomicWrite: torn restore would leave the user with a frankenstein file.\n await atomicWrite(file.path, file.before, { mode: 0o644 });\n revertedFiles.push(file.path);\n }\n }\n } catch (err) {\n errors.push(`${file.path}: ${toErrorMessage(err)}`);\n }\n }\n }\n\n return { revertedFiles, errors };\n}\n", "import * as fsp from 'node:fs/promises';\nimport type { EventBus } from '../kernel/events.js';\nimport type { TodoItem } from '../core/context.js';\nimport type { ConversationState } from '../core/conversation-state.js';\nimport { atomicWrite } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/error.js';\n\n/**\n * On-disk checkpoint for `ctx.todos`. Written atomically every time the\n * todo list changes, read once on session resume. This is the missing\n * piece that lets `wstack resume <id>` rehydrate where the previous run\n * stopped instead of starting with an empty board.\n *\n * Schema is intentionally small \u2014 a single JSON object so a future\n * format bump is easy. The `version` field is the only contract; the\n * shape under `todos` mirrors `TodoItem` so reading is a straight assign.\n */\nexport interface TodosCheckpointFile {\n version: 1;\n sessionId: string;\n updatedAt: string;\n todos: TodoItem[];\n}\n\nexport type TodosCheckpointDetach = () => Promise<void>;\n\n/** Read a checkpoint from disk. Returns null when the file doesn't\n * exist or is corrupt \u2014 callers treat both cases as \"no prior state\".\n */\nexport async function loadTodosCheckpoint(\n filePath: string,\n events?: EventBus,\n traceId?: string,\n): Promise<TodoItem[] | null> {\n const t0 = Date.now();\n let raw: string;\n try {\n raw = await fsp.readFile(filePath, 'utf8');\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: traceId ?? '~boot~',\n store: 'todos',\n filePath,\n operation: 'load',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: true,\n });\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as TodosCheckpointFile;\n if (parsed?.version !== 1 || !Array.isArray(parsed.todos)) {\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'todos',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'invalid_schema',\n ...(traceId !== undefined && { traceId }),\n });\n return null;\n }\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'todos',\n filePath,\n operation: 'load',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(traceId !== undefined && { traceId }),\n });\n return parsed.todos.filter(\n (t): t is TodoItem =>\n !!t &&\n typeof t.id === 'string' &&\n typeof t.content === 'string' &&\n typeof t.status === 'string' &&\n (t.activeForm === undefined || typeof t.activeForm === 'string'),\n );\n } catch {\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'todos',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'parse_failed',\n ...(traceId !== undefined && { traceId }),\n });\n return null;\n }\n}\n\n/** Write the checkpoint atomically. Best-effort: a write failure is\n * logged but does not throw \u2014 losing one checkpoint shouldn't bring\n * down the agent run.\n */\nexport async function saveTodosCheckpoint(\n filePath: string,\n sessionId: string,\n todos: readonly TodoItem[],\n events?: EventBus,\n traceId?: string,\n): Promise<void> {\n const t0 = Date.now();\n const payload: TodosCheckpointFile = {\n version: 1,\n sessionId,\n updatedAt: new Date().toISOString(),\n todos: [...todos],\n };\n try {\n await atomicWrite(filePath, JSON.stringify(payload, null, 2), { mode: 0o600 });\n events?.emit('storage.write', {\n sessionId: traceId ?? sessionId,\n store: 'todos',\n filePath,\n operation: 'save',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(traceId !== undefined && { traceId }),\n });\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: traceId ?? sessionId,\n store: 'todos',\n filePath,\n operation: 'save',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: false,\n });\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'todos_checkpoint.save_failed',\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }));\n }\n}\n\n/**\n * Subscribe a `ConversationState` so every `todos_replaced` mutation\n * triggers an atomic write to disk. Returns the unsubscribe function.\n *\n * Writes are debounced by 150ms so a flurry of edits (e.g. the LLM\n * marking three items done in the same tool call) coalesces into one\n * disk hit.\n */\nexport function attachTodosCheckpoint(\n state: ConversationState,\n filePath: string,\n sessionId: string,\n events?: EventBus,\n traceId?: string,\n): TodosCheckpointDetach {\n let timer: NodeJS.Timeout | null = null;\n let pending: readonly TodoItem[] | null = null;\n let writeChain: Promise<void> = Promise.resolve();\n\n const enqueueWrite = (todos: readonly TodoItem[]) => {\n writeChain = writeChain\n .then(() => saveTodosCheckpoint(filePath, sessionId, todos, events, traceId))\n /* v8 ignore start -- defensive: saveTodosCheckpoint swallows its own errors and never rejects */\n .catch((err) => {\n // Log and keep the chain alive \u2014 a failed write must not\n // poison the chain and silently stop all subsequent writes.\n const msg = toErrorMessage(err);\n console.error(JSON.stringify({\n level: 'error',\n event: 'todos_checkpoint.write_chain_failed',\n sessionId,\n message: msg,\n timestamp: new Date().toISOString(),\n }));\n });\n /* v8 ignore stop */\n return writeChain;\n };\n\n const flush = () => {\n timer = null;\n if (pending) {\n const todos = pending;\n pending = null;\n return enqueueWrite(todos);\n }\n /* v8 ignore next -- defensive: flush is only invoked when a change is pending */\n return writeChain;\n };\n\n const unsubscribe = state.onChange((change) => {\n if (change.kind !== 'todos_replaced') return;\n pending = change.todos;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n void flush();\n }, 150);\n });\n return async () => {\n unsubscribe();\n if (timer) {\n clearTimeout(timer);\n // Flush any pending write before detach so callers can safely\n // unsubscribe at shutdown without losing the last update.\n await flush();\n } else {\n await writeChain;\n }\n };\n}\n", "import * as fsp from 'node:fs/promises';\nimport type { EventBus } from '../kernel/events.js';\nimport type { CompletedWorkEvidence } from '../types/context-evidence.js';\nimport { atomicWrite } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/error.js';\n\nexport interface CompletedWorkCheckpointFile {\n version: 1;\n sessionId: string;\n updatedAt: string;\n completedWork: CompletedWorkEvidence[];\n}\n\nexport async function loadCompletedWorkCheckpoint(\n filePath: string,\n events?: EventBus,\n traceId?: string,\n): Promise<CompletedWorkEvidence[] | null> {\n const t0 = Date.now();\n let raw: string;\n try {\n raw = await fsp.readFile(filePath, 'utf8');\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: traceId ?? '~boot~',\n store: 'completed-work',\n filePath,\n operation: 'load',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: true,\n });\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as CompletedWorkCheckpointFile;\n if (parsed?.version !== 1 || !Array.isArray(parsed.completedWork)) {\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'completed-work',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'invalid_schema',\n ...(traceId !== undefined && { traceId }),\n });\n return null;\n }\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'completed-work',\n filePath,\n operation: 'load',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(traceId !== undefined && { traceId }),\n });\n return parsed.completedWork.filter(isCompletedWorkEvidence);\n } catch {\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'completed-work',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'parse_failed',\n ...(traceId !== undefined && { traceId }),\n });\n return null;\n }\n}\n\nexport async function saveCompletedWorkCheckpoint(\n filePath: string,\n sessionId: string,\n completedWork: readonly CompletedWorkEvidence[],\n events?: EventBus,\n traceId?: string,\n): Promise<void> {\n const t0 = Date.now();\n const payload: CompletedWorkCheckpointFile = {\n version: 1,\n sessionId,\n updatedAt: new Date().toISOString(),\n completedWork: [...completedWork],\n };\n try {\n await atomicWrite(filePath, JSON.stringify(payload, null, 2), { mode: 0o600 });\n events?.emit('storage.write', {\n sessionId: traceId ?? sessionId,\n store: 'completed-work',\n filePath,\n operation: 'save',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(traceId !== undefined && { traceId }),\n });\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: traceId ?? sessionId,\n store: 'completed-work',\n filePath,\n operation: 'save',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: false,\n });\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'completed_work_checkpoint.save_failed',\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }));\n }\n}\n\nfunction isCompletedWorkEvidence(value: unknown): value is CompletedWorkEvidence {\n if (!value || typeof value !== 'object') return false;\n const item = value as Partial<CompletedWorkEvidence>;\n return (\n typeof item.key === 'string' &&\n typeof item.source === 'string' &&\n ['todo', 'plan', 'task', 'verification', 'manual'].includes(item.source) &&\n typeof item.summary === 'string' &&\n typeof item.completedAt === 'number' &&\n (item.evidence === undefined || typeof item.evidence === 'string')\n );\n}\n", "import * as fsp from 'node:fs/promises';\nimport { randomUUID } from 'node:crypto';\nimport type { EventBus } from '../kernel/events.js';\nimport type { ConversationState } from '../core/conversation-state.js';\nimport { atomicWrite, withFileLock } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport { SessionError } from '../types/errors.js';\n\n/**\n * Plan items are the strategic counterpart to todos. Where `ctx.todos`\n * is the moment-to-moment task board the LLM mutates per-turn, a plan\n * captures the higher-level approach \u2014 the steps the user (or LLM)\n * laid out before any work began.\n *\n * Plans persist by default (per session) so a resumed session can show\n * \"you were on step 3 of 5\". Todos are derived/transient. Both can\n * coexist: think roadmap (plan) vs. sprint board (todos).\n */\nexport interface PlanItem {\n id: string;\n title: string;\n /** Optional longer-form context or rationale. */\n details?: string | undefined;\n status: 'open' | 'in_progress' | 'done';\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface PlanFile {\n version: 1;\n sessionId: string;\n title?: string | undefined;\n updatedAt: string;\n items: PlanItem[];\n}\n\nexport async function loadPlan(filePath: string, events?: EventBus): Promise<PlanFile | null> {\n const t0 = Date.now();\n let raw: string;\n try {\n raw = await fsp.readFile(filePath, 'utf8');\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: '~boot~',\n store: 'plan',\n filePath,\n operation: 'load',\n error: toErrorMessage(err),\n recoverable: true,\n });\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as PlanFile;\n if (parsed?.version !== 1 || !Array.isArray(parsed.items)) {\n events?.emit('storage.read', {\n sessionId: '~boot~',\n store: 'plan',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'invalid_schema',\n });\n return null;\n }\n events?.emit('storage.read', {\n sessionId: '~boot~',\n store: 'plan',\n filePath,\n operation: 'load',\n outcome: 'success',\n durationMs: Date.now() - t0,\n });\n return parsed;\n } catch {\n events?.emit('storage.read', {\n sessionId: '~boot~',\n store: 'plan',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'parse_failed',\n });\n return null;\n }\n}\n\n/**\n * Persist a plan. Returns `true` on success, `false` if the write failed\n * (still emits `storage.error` + warns \u2014 it does NOT throw, so callers that\n * treat a lost plan-save as non-fatal keep working). `mutatePlan` inspects the\n * result and throws so the plan TOOL can report `ok:false` instead of falsely\n * claiming the plan was persisted.\n */\nexport async function savePlan(filePath: string, plan: PlanFile, events?: EventBus): Promise<boolean> {\n const t0 = Date.now();\n try {\n await atomicWrite(filePath, JSON.stringify(plan, null, 2), { mode: 0o600 });\n events?.emit('storage.write', {\n sessionId: '~boot~',\n store: 'plan',\n filePath,\n operation: 'save',\n outcome: 'success',\n durationMs: Date.now() - t0,\n });\n return true;\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: '~boot~',\n store: 'plan',\n filePath,\n operation: 'save',\n error: toErrorMessage(err),\n recoverable: false,\n });\n console.warn(\n '[plan-store] save failed:',\n toErrorMessage(err),\n );\n return false;\n }\n}\n\n/** Create a new PlanFile when none exists on disk. */\nexport function emptyPlan(sessionId: string, title?: string): PlanFile {\n return {\n version: 1,\n sessionId,\n title,\n updatedAt: new Date().toISOString(),\n items: [],\n };\n}\n\nexport function addPlanItem(\n plan: PlanFile,\n title: string,\n details?: string | undefined,\n): { plan: PlanFile; item: PlanItem } {\n const now = new Date().toISOString();\n const item: PlanItem = {\n id: `plan_${Date.now()}_${randomUUID().slice(0, 6)}`,\n title,\n details,\n status: 'open',\n createdAt: now,\n updatedAt: now,\n };\n return {\n plan: { ...plan, items: [...plan.items, item], updatedAt: now },\n item,\n };\n}\n\nexport function removePlanItem(plan: PlanFile, idOrIndex: string): PlanFile {\n const idx = matchIndex(plan, idOrIndex);\n if (idx === -1) return plan;\n return {\n ...plan,\n items: plan.items.filter((_, i) => i !== idx),\n updatedAt: new Date().toISOString(),\n };\n}\n\nexport function setPlanItemStatus(\n plan: PlanFile,\n idOrIndex: string,\n status: PlanItem['status'],\n): PlanFile {\n const idx = matchIndex(plan, idOrIndex);\n if (idx === -1) return plan;\n const now = new Date().toISOString();\n const items = plan.items.map((it, i) =>\n i === idx ? { ...it, status, updatedAt: now } : it,\n );\n return { ...plan, items, updatedAt: now };\n}\n\nexport function clearPlan(plan: PlanFile): PlanFile {\n return { ...plan, items: [], updatedAt: new Date().toISOString() };\n}\n\n/** Render the plan as a short markdown-ish string suitable for slash output. */\nexport function formatPlan(plan: PlanFile): string {\n if (plan.items.length === 0) return 'Plan is empty.';\n const lines: string[] = [];\n if (plan.title) lines.push(`# ${plan.title}`);\n plan.items.forEach((it, i) => {\n const mark = it.status === 'done' ? '[x]' : it.status === 'in_progress' ? '[~]' : '[ ]';\n lines.push(`${i + 1}. ${mark} ${it.title}`);\n if (it.details) {\n for (const line of it.details.split('\\n')) lines.push(` ${line}`);\n }\n });\n return lines.join('\\n');\n}\n\nfunction matchIndex(plan: PlanFile, idOrIndex: string): number {\n const asNum = Number.parseInt(idOrIndex, 10);\n if (!Number.isNaN(asNum) && asNum >= 1 && asNum <= plan.items.length) return asNum - 1;\n const byId = plan.items.findIndex((it) => it.id === idOrIndex);\n if (byId !== -1) return byId;\n const lower = idOrIndex.toLowerCase();\n return plan.items.findIndex((it) => it.title.toLowerCase().includes(lower));\n}\n\n/**\n * Promote a plan item to a set of todo items.\n * The plan item is marked 'in_progress' (if not already done) and its\n * title + details become the first todo; additional subtasks are appended.\n * Returns the derived todo list so the caller can pass it to `todoTool`\n * or `ctx.state.replaceTodos()`.\n */\nexport function deriveTodosFromPlanItem(\n plan: PlanFile,\n idOrIndex: string,\n subtasks?: string[] | undefined,\n): { plan: PlanFile; todos: Array<{ id: string; content: string; status: 'pending' | 'in_progress' | 'completed'; activeForm?: string | undefined; promotedFromPlan?: string | undefined }> } | null {\n const idx = matchIndex(plan, idOrIndex);\n if (idx === -1) return null;\n\n const item = plan.items[idx];\n /* v8 ignore next -- defensive: matchIndex returns a valid in-range index or -1 (handled above) */\n if (!item) return null;\n\n // Mark the plan item in_progress if it wasn't already done\n let updatedPlan = plan;\n if (item.status !== 'done') {\n updatedPlan = setPlanItemStatus(plan, idOrIndex, 'in_progress');\n }\n\n const todos: Array<{ id: string; content: string; status: 'pending' | 'in_progress' | 'completed'; activeForm?: string | undefined; promotedFromPlan?: string | undefined }> = [];\n\n // First todo from the plan item itself\n todos.push({\n id: `todo_${Date.now()}_plan`,\n content: item.title,\n status: 'in_progress',\n activeForm: item.title,\n promotedFromPlan: item.id,\n });\n\n // Optional subtasks\n if (subtasks && subtasks.length > 0) {\n for (const st of subtasks) {\n todos.push({\n id: `todo_${Date.now()}_${randomUUID().slice(0, 6)}`,\n content: st,\n status: 'pending',\n promotedFromPlan: item.id,\n });\n }\n }\n\n return { plan: updatedPlan, todos };\n}\n\n/**\n * Load, modify, and save the plan file under a file-level lock.\n * Prevents races from parallel tool invocations (e.g. batch_tool_use).\n */\nexport async function mutatePlan(\n filePath: string,\n sessionId: string,\n fn: (plan: PlanFile) => PlanFile | Promise<PlanFile>,\n): Promise<PlanFile> {\n return withFileLock(filePath, async () => {\n const plan = (await loadPlan(filePath)) ?? emptyPlan(sessionId);\n const updated = await fn(plan);\n const persisted = await savePlan(filePath, updated);\n if (!persisted) {\n throw new SessionError({\n message: `Failed to persist plan to ${filePath} \u2014 the change was NOT saved.`,\n code: 'SESSION_WRITE_FAILED',\n sessionId,\n context: { filePath, operation: 'mutatePlan' },\n });\n }\n return updated;\n });\n}\n\n/**\n * Optional: attach a state-listener so meta operations (storing a plan\n * id on ctx.meta) trigger a save. Currently a stub \u2014 plans don't live\n * on Context, but this keeps the API surface symmetric with the todos\n * checkpoint so future refactors can flip plans into Context if needed.\n */\nexport function attachPlanCheckpoint(\n _state: ConversationState,\n _filePath: string,\n _sessionId: string,\n): () => void {\n return () => undefined;\n}\n", "/**\n * Plan templates \u2014 pre-defined plan skeletons for common workflows.\n *\n * Templates are stored in-memory (no disk I/O). Users instantiate them\n * via `/plan template use <name>` or `planTool(action: 'template_use')`.\n * Each template is a function that returns an array of item titles, so\n * dynamic content (dates, project names) can be injected later.\n */\n\nexport interface PlanTemplate {\n name: string;\n description: string;\n category: 'development' | 'release' | 'maintenance' | 'infrastructure';\n items: Array<{\n title: string;\n details?: string | undefined;\n }>;\n}\n\nconst templates: Record<string, PlanTemplate> = {\n 'new-feature': {\n name: 'new-feature',\n description: 'Standard workflow for adding a new feature',\n category: 'development',\n items: [\n { title: 'Write specification / design doc', details: 'Define scope, acceptance criteria, edge cases' },\n { title: 'Set up feature branch', details: 'git checkout -b feature/...' },\n { title: 'Implement core logic', details: 'TDD preferred \u2014 write tests first' },\n { title: 'Add unit tests', details: '>= 80% coverage for new code' },\n { title: 'Add integration tests', details: 'End-to-end happy path + error paths' },\n { title: 'Update documentation', details: 'README, API docs, changelog' },\n { title: 'Code review', details: 'Self-review before requesting review' },\n { title: 'Merge and deploy', details: 'CI green, tag release' },\n ],\n },\n 'bug-fix': {\n name: 'bug-fix',\n description: 'Systematic approach to fixing bugs',\n category: 'maintenance',\n items: [\n { title: 'Reproduce the bug', details: 'Minimal reproduction case' },\n { title: 'Root cause analysis', details: 'Trace through logs, debugger' },\n { title: 'Write failing test', details: 'Test must fail before fix' },\n { title: 'Implement fix', details: 'Smallest possible change' },\n { title: 'Verify fix', details: 'Test passes, reproduction no longer fails' },\n { title: 'Regression test', details: 'Ensure no related tests broken' },\n { title: 'Document in changelog', details: 'Brief description + issue link' },\n ],\n },\n 'refactor': {\n name: 'refactor',\n description: 'Safe refactoring workflow',\n category: 'maintenance',\n items: [\n { title: 'Identify refactoring target', details: 'Code smell, performance bottleneck, or tech debt' },\n { title: 'Ensure test coverage', details: 'Existing tests must pass before and after' },\n { title: 'Write characterization tests', details: 'Capture current behavior if tests weak' },\n { title: 'Apply refactoring', details: 'Small steps, frequent commits' },\n { title: 'Run full test suite', details: 'All tests must pass' },\n { title: 'Performance check', details: 'Ensure no regression' },\n { title: 'Code review', details: 'Explain the why, not just the what' },\n ],\n },\n 'release': {\n name: 'release',\n description: 'Preparing a new release',\n category: 'release',\n items: [\n { title: 'Version bump', details: 'package.json, lockfiles, tags' },\n { title: 'Update changelog', details: 'All changes since last release' },\n { title: 'Run full test suite', details: 'Unit + integration + e2e' },\n { title: 'Build artifacts', details: 'Docker images, bundles, binaries' },\n { title: 'Staging smoke tests', details: 'Deploy to staging, verify' },\n { title: 'Production deploy', details: 'Blue-green or canary' },\n { title: 'Post-deploy verification', details: 'Health checks, error rates' },\n { title: 'Announce release', details: 'Slack, email, GitHub release notes' },\n ],\n },\n 'security-audit': {\n name: 'security-audit',\n description: 'Security review and hardening',\n category: 'infrastructure',\n items: [\n { title: 'Dependency audit', details: 'npm audit, Snyk, Dependabot alerts' },\n { title: 'Secret scan', details: 'git-secrets, truffleHog, manual review' },\n { title: 'Access control review', details: 'IAM, roles, least privilege' },\n { title: 'Input validation audit', details: 'SQL injection, XSS, path traversal' },\n { title: 'Authentication review', details: 'Session management, MFA, password policy' },\n { title: 'Logging and monitoring', details: 'PII in logs, audit trails' },\n { title: 'Incident response plan', details: 'Runbooks, contacts, escalation' },\n ],\n },\n 'onboarding': {\n name: 'onboarding',\n description: 'New developer onboarding checklist',\n category: 'infrastructure',\n items: [\n { title: 'Repository access', details: 'GitHub/GitLab permissions' },\n { title: 'Local environment setup', details: 'Docker, dependencies, env files' },\n { title: 'Run tests locally', details: 'Verify green suite' },\n { title: 'Read architecture docs', details: 'ADR, README, onboarding guide' },\n { title: 'First commit', details: 'Docs fix or small improvement' },\n { title: 'Pair programming session', details: 'Walk through codebase with buddy' },\n { title: 'Deploy to staging', details: 'Verify CI/CD access' },\n ],\n },\n};\n\nexport function listPlanTemplates(): PlanTemplate[] {\n return Object.values(templates);\n}\n\nexport function getPlanTemplate(name: string): PlanTemplate | undefined {\n return templates[name];\n}\n\nexport function formatPlanTemplates(): string {\n const cats = new Map<PlanTemplate['category'], PlanTemplate[]>();\n for (const t of Object.values(templates)) {\n const arr = cats.get(t.category) ?? [];\n arr.push(t);\n cats.set(t.category, arr);\n }\n\n const lines: string[] = ['Available plan templates:'];\n for (const [cat, items] of cats) {\n lines.push(`\\n${cat}:`);\n for (const t of items) {\n lines.push(` ${t.name.padEnd(18)} \u2014 ${t.description}`);\n }\n }\n return lines.join('\\n');\n}\n", "import * as fsp from 'node:fs/promises';\nimport type { EventBus } from '../kernel/events.js';\nimport { atomicWrite, withFileLock } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport type { TaskItem } from '../utils/task-format.js';\nimport { SessionError } from '../types/errors.js';\n\n// ---------------------------------------------------------------------------\n// Task file persistence \u2014 one JSON file per session, resolved via\n// `sessionScopedPath(projectSessions, sessionId, '.tasks.json')`.\n//\n// Low-level load/save are exported for read-only consumers. Mutating callers\n// should use `mutateTasks` which wraps the entire read-modify-write cycle\n// under a file-level lock, preventing races from parallel tool invocations.\n// ---------------------------------------------------------------------------\n\nexport interface TaskFile {\n version: 1;\n sessionId: string;\n updatedAt: string;\n tasks: TaskItem[];\n}\n\nexport function emptyTaskFile(sessionId: string): TaskFile {\n return {\n version: 1,\n sessionId,\n updatedAt: new Date().toISOString(),\n tasks: [],\n };\n}\n\n/** Read the task file. Returns null when the file doesn't exist. */\nexport async function loadTasks(\n filePath: string,\n events?: EventBus,\n traceId?: string,\n): Promise<TaskFile | null> {\n const t0 = Date.now();\n let raw: string;\n try {\n raw = await fsp.readFile(filePath, 'utf8');\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: traceId ?? '~boot~',\n store: 'tasks',\n filePath,\n operation: 'load',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: true,\n });\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as TaskFile;\n if (parsed?.version !== 1 || !Array.isArray(parsed.tasks)) {\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'tasks',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'invalid_schema',\n ...(traceId !== undefined && { traceId }),\n });\n return null;\n }\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'tasks',\n filePath,\n operation: 'load',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(traceId !== undefined && { traceId }),\n });\n return parsed;\n } catch {\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'tasks',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'parse_failed',\n ...(traceId !== undefined && { traceId }),\n });\n return null;\n }\n}\n\n/**\n * Write the task file atomically. Prefer `mutateTasks` for read-modify-write\n * cycles \u2014 this low-level function does NOT acquire a lock.\n */\n/**\n * Persist the task file. Returns `true` on success, `false` if the write\n * failed (still emits `storage.error` + warns \u2014 does NOT throw). `mutateTasks`\n * inspects the result and throws so the task TOOL can report `ok:false`\n * instead of falsely claiming the tasks were saved.\n */\nexport async function saveTasks(\n filePath: string,\n tasks: TaskFile,\n events?: EventBus,\n traceId?: string,\n): Promise<boolean> {\n const t0 = Date.now();\n try {\n tasks.updatedAt = new Date().toISOString();\n await atomicWrite(filePath, JSON.stringify(tasks, null, 2), { mode: 0o600 });\n events?.emit('storage.write', {\n sessionId: traceId ?? '~boot~',\n store: 'tasks',\n filePath,\n operation: 'save',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(traceId !== undefined && { traceId }),\n });\n return true;\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: traceId ?? '~boot~',\n store: 'tasks',\n filePath,\n operation: 'save',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: false,\n ...(traceId !== undefined && { traceId }),\n });\n console.warn(\n '[task-store] save failed:',\n toErrorMessage(err),\n );\n return false;\n }\n}\n\n/**\n * Load, modify, and save the task file under a file-level lock.\n * `fn` receives the current TaskFile (or a fresh empty one) and must\n * return the mutated TaskFile (mutating in-place is fine \u2014 the returned\n * reference is what gets saved).\n *\n * This is the primary API for any code path that reads *and then writes*\n * the task file \u2014 it prevents races from parallel `batch_tool_use` calls.\n */\nexport async function mutateTasks(\n filePath: string,\n sessionId: string,\n fn: (file: TaskFile) => TaskFile | Promise<TaskFile>,\n events?: EventBus,\n traceId?: string,\n): Promise<TaskFile> {\n return withFileLock(filePath, async () => {\n const file = (await loadTasks(filePath, events, traceId)) ?? emptyTaskFile(sessionId);\n const updated = await fn(file);\n const persisted = await saveTasks(filePath, updated, events, traceId);\n if (!persisted) {\n throw new SessionError({\n message: `Failed to persist tasks to ${filePath} \u2014 the change was NOT saved.`,\n code: 'SESSION_WRITE_FAILED',\n sessionId,\n context: { filePath, operation: 'mutateTasks' },\n });\n }\n return updated;\n });\n}\n", "import * as fsp from 'node:fs/promises';\nimport { hostname } from 'node:os';\nimport { atomicWrite } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/error.js';\n\n/**\n * Director state checkpoint \u2014 written incrementally throughout a fleet\n * run so a crashed director can be inspected (and eventually resumed)\n * instead of leaving only a final `fleet.json` manifest after `shutdown()`.\n *\n * Schema is JSON-friendly and deliberately denormalized. Each mutation\n * triggers an atomic-write of the whole file \u2014 small payloads (typically\n * < 10 KB even with dozens of subagents) make this cheap.\n */\nexport interface DirectorSubagentState {\n id: string;\n name?: string | undefined;\n role?: string | undefined;\n provider?: string | undefined;\n model?: string | undefined;\n spawnedAt: string;\n}\n\nexport interface DirectorTaskState {\n taskId: string;\n subagentId?: string | undefined;\n description?: string | undefined;\n status: 'pending' | 'running' | 'completed' | 'failed' | 'stopped' | 'timeout';\n assignedAt?: string | undefined;\n completedAt?: string | undefined;\n iterations?: number | undefined;\n toolCalls?: number | undefined;\n durationMs?: number | undefined;\n error?: string | undefined;\n worktree?: DirectorTaskWorktreeState | undefined;\n}\n\nexport interface DirectorTaskWorktreeState {\n taskId: string;\n subagentId: string;\n handleId: string;\n dir: string;\n branch: string;\n baseBranch: string;\n status:\n | 'allocated'\n | 'fallback'\n | 'committed'\n | 'merged'\n | 'kept'\n | 'released'\n | 'conflict'\n | 'failed';\n commitSha?: string | undefined;\n conflictFiles?: string[] | undefined;\n error?: string | undefined;\n}\n\nexport interface DirectorStateSnapshot {\n version: 1;\n directorRunId: string;\n updatedAt: string;\n spawnCount: number;\n maxSpawns?: number | undefined;\n spawnDepth: number;\n maxSpawnDepth: number;\n directorBudget?:\n | {\n maxCostUsd?: number | undefined;\n maxTokens?: number | undefined;\n }\n | undefined;\n subagents: DirectorSubagentState[];\n tasks: DirectorTaskState[];\n /** Aggregated usage snapshot. Optional \u2014 populated by the Director on save when available. */\n usage?: unknown | undefined;\n}\n\nexport async function loadDirectorState(filePath: string): Promise<DirectorStateSnapshot | null> {\n let raw: string;\n try {\n raw = await fsp.readFile(filePath, 'utf8');\n } catch {\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as DirectorStateSnapshot;\n if (parsed?.version !== 1) return null;\n return parsed;\n } catch {\n return null;\n }\n}\n\n/**\n * Lock file entry written when a director starts. Prevents two directors\n * from resuming the same run \u2014 the second one sees the lock and refuses\n * rather than corrupting the checkpoint by writing concurrently.\n */\nexport interface DirectorStateLock {\n pid: number;\n hostname: string;\n startedAt: string;\n}\n\n/**\n * Write a lock file to claim this checkpoint. Returns false if the lock\n * is already held by a live process; returns true if the lock was acquired\n * (either the file didn't exist, or the previous holder is dead).\n */\nexport async function acquireDirectorStateLock(\n lockPath: string,\n processId = process.pid,\n): Promise<boolean> {\n let existing: string | undefined;\n try {\n existing = await fsp.readFile(lockPath, 'utf8');\n } catch {\n // No lock file \u2014 we're safe to claim\n }\n\n if (existing) {\n try {\n const lock = JSON.parse(existing) as DirectorStateLock;\n // Check if the process is still alive\n try {\n process.kill(lock.pid, 0);\n // Signal success means the process is alive \u2014 another director\n // owns this checkpoint. Refuse.\n return false;\n } catch {\n // ESRCH means the process is dead \u2014 stale lock. We'll overwrite.\n }\n } catch {\n // Malformed lock \u2014 treat as stale.\n }\n }\n\n const lock: DirectorStateLock = {\n pid: processId,\n hostname: hostname(),\n startedAt: new Date().toISOString(),\n };\n await atomicWrite(lockPath, JSON.stringify(lock), { mode: 0o600 });\n return true;\n}\n\n/**\n * Remove the lock file. Call this on graceful Director.shutdown() so the\n * next director run can claim the checkpoint without stale-lock checks.\n */\nexport async function releaseDirectorStateLock(lockPath: string): Promise<void> {\n try {\n await fsp.unlink(lockPath);\n } catch {\n // ignore\n }\n}\n\n/**\n * In-memory accumulator with atomic-write checkpoint. The Director keeps\n * an instance, mutates it on every spawn/assign/complete/fail event, and\n * the instance debounces writes so a burst of activity collapses into a\n * single disk hit.\n *\n * Supports crash recovery: use `loadDirectorState()` to read an existing\n * checkpoint, then call `DirectorStateCheckpoint.resume(snapshot)` to\n * re-attach to a fleet mid-flight. The lock mechanism ensures no two\n * directors can claim the same checkpoint.\n */\nexport class DirectorStateCheckpoint {\n private snapshot: DirectorStateSnapshot;\n private readonly filePath: string;\n private readonly lockPath: string;\n private timer: NodeJS.Timeout | null = null;\n private readonly debounceMs: number;\n private writing = false;\n private rewriteRequested = false;\n\n constructor(\n filePath: string,\n init: {\n directorRunId: string;\n maxSpawns?: number | undefined;\n spawnDepth: number;\n maxSpawnDepth: number;\n directorBudget?:\n | {\n maxCostUsd?: number | undefined;\n maxTokens?: number | undefined;\n }\n | undefined;\n },\n debounceMs = 250,\n ) {\n this.filePath = filePath;\n // Lock file lives alongside the checkpoint \u2014 `<path>.lock`\n this.lockPath = `${filePath}.lock`;\n this.debounceMs = debounceMs;\n this.snapshot = {\n version: 1,\n directorRunId: init.directorRunId,\n updatedAt: new Date().toISOString(),\n spawnCount: 0,\n maxSpawns: init.maxSpawns,\n spawnDepth: init.spawnDepth,\n maxSpawnDepth: init.maxSpawnDepth,\n directorBudget: init.directorBudget,\n subagents: [],\n tasks: [],\n };\n }\n\n /**\n * Attempt to acquire the lock for this checkpoint. Call this before\n * resuming a crashed director run. If it returns false, another\n * director process is still running this fleet \u2014 do not resume.\n */\n async acquireLock(): Promise<boolean> {\n return acquireDirectorStateLock(this.lockPath);\n }\n\n /**\n * Release the lock on graceful shutdown. Call `flush()` first to ensure\n * the final checkpoint state is on disk before removing the lock.\n * Without this, the next resume will see a stale-lock and refuse.\n */\n async releaseLock(): Promise<void> {\n return releaseDirectorStateLock(this.lockPath);\n }\n\n /**\n * Resume from a snapshot previously loaded via `loadDirectorState()`.\n * Use this when `--resume <runId>` is triggered \u2014 the snapshot has\n * the full fleet state (subagents, tasks) from before the crash; the\n * checkpoint continues from there.\n */\n resume(snapshot: DirectorStateSnapshot): void {\n this.snapshot = snapshot;\n }\n\n current(): DirectorStateSnapshot {\n return this.snapshot;\n }\n\n recordSpawn(sub: DirectorSubagentState, spawnCount: number): void {\n this.snapshot = {\n ...this.snapshot,\n spawnCount,\n subagents: [...this.snapshot.subagents.filter((s) => s.id !== sub.id), sub],\n };\n this.bumpUpdatedAt();\n this.schedule();\n }\n\n recordTaskAssigned(task: DirectorTaskState): void {\n const exists = this.snapshot.tasks.some((t) => t.taskId === task.taskId);\n this.snapshot = {\n ...this.snapshot,\n tasks: exists\n ? this.snapshot.tasks.map((t) => (t.taskId === task.taskId ? { ...t, ...task } : t))\n : [...this.snapshot.tasks, task],\n };\n this.bumpUpdatedAt();\n this.schedule();\n }\n\n recordTaskStatus(\n taskId: string,\n patch: Partial<DirectorTaskState> & { status: DirectorTaskState['status'] },\n ): void {\n this.snapshot = {\n ...this.snapshot,\n tasks: this.snapshot.tasks.map((t) => (t.taskId === taskId ? { ...t, ...patch } : t)),\n };\n this.bumpUpdatedAt();\n this.schedule();\n }\n\n recordTaskWorktree(taskId: string, worktree: DirectorTaskWorktreeState): void {\n const exists = this.snapshot.tasks.some((t) => t.taskId === taskId);\n this.snapshot = {\n ...this.snapshot,\n tasks: exists\n ? this.snapshot.tasks.map((t) => (t.taskId === taskId ? { ...t, worktree } : t))\n : [\n ...this.snapshot.tasks,\n {\n taskId,\n subagentId: worktree.subagentId,\n status: 'running',\n worktree,\n },\n ],\n };\n this.bumpUpdatedAt();\n this.schedule();\n }\n\n setUsage(usage: unknown): void {\n this.snapshot = { ...this.snapshot, usage };\n this.bumpUpdatedAt();\n this.schedule();\n }\n\n /** Force a synchronous flush \u2014 used by Director.shutdown(). */\n async flush(): Promise<void> {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n await this.persist();\n // If a rewrite was requested while we waited, persist() scheduled\n // a follow-up write. Loop until no more rewrites are requested so\n // shutdown doesn't return before the most recent state lands on disk.\n /* v8 ignore start -- concurrency-defensive: persist()'s finally clears the flag in single-threaded flow */\n while (this.rewriteRequested) {\n this.rewriteRequested = false;\n await this.persist();\n }\n /* v8 ignore stop */\n }\n\n private bumpUpdatedAt(): void {\n this.snapshot = { ...this.snapshot, updatedAt: new Date().toISOString() };\n }\n\n private schedule(): void {\n if (this.timer) return;\n this.timer = setTimeout(() => {\n this.timer = null;\n void this.persist();\n }, this.debounceMs);\n }\n\n private async persist(): Promise<void> {\n if (this.writing) {\n // A write is already in flight \u2014 defer to a follow-up flush so the\n // most recent state still lands. Without this guard, simultaneous\n // burst mutations can drop the latest snapshot if rename races.\n this.rewriteRequested = true;\n return;\n }\n this.writing = true;\n try {\n await atomicWrite(this.filePath, JSON.stringify(this.snapshot, null, 2), {\n mode: 0o600,\n });\n } catch (err) {\n console.warn(\n JSON.stringify({\n level: 'warn',\n event: 'director_state.checkpoint_write_failed',\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }),\n );\n } finally {\n this.writing = false;\n /* v8 ignore start -- concurrency-defensive: rewriteRequested is only set by an overlapping persist() */\n if (this.rewriteRequested) {\n this.rewriteRequested = false;\n this.schedule();\n }\n /* v8 ignore stop */\n }\n }\n}\n", "import * as fsp from 'node:fs/promises';\nimport type { EventBus } from '../kernel/events.js';\nimport { atomicWrite, withFileLock } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport { color } from '../utils/color.js';\nimport { resolveWstackPaths } from '../utils/wstack-paths.js';\nimport { FsError, ERROR_CODES } from '../types/errors.js';\n\n/**\n * Long-running autonomous mission. A goal survives across sessions and\n * drives the EternalAutonomyEngine \u2014 every iteration of the engine\n * consults the goal to choose what to do next.\n *\n * Storage: `~/.wrongstack/projects/<hash>/goal.json`. Persistent and\n * project-scoped on purpose: the goal belongs to the codebase, not the\n * REPL session.\n */\n\nexport interface JournalEntry {\n /** ISO timestamp of the iteration. */\n at: string;\n /** Sequential iteration counter (1-based, monotonically increasing). */\n iteration: number;\n /** Source that produced the action ('todo' | 'git' | 'brainstorm' | 'resume' | 'manual' | 'parallel'). */\n source: 'todo' | 'git' | 'brainstorm' | 'resume' | 'manual' | 'parallel';\n /** Short one-line description of what the iteration set out to do. */\n task: string;\n /** Outcome status. */\n status: 'success' | 'failure' | 'aborted' | 'skipped';\n /** Optional free-form note (error message, summary, etc.). */\n note?: string | undefined;\n /** Optional token usage delta for this iteration. */\n tokens?: { input: number; output: number } | undefined;\n /** Optional USD cost delta for this iteration (provider-estimated). */\n costUsd?: number | undefined;\n}\n\nexport interface GoalFile {\n version: 1;\n /** The raw mission statement as entered by the user. */\n goal: string;\n /**\n * LLM-refined version of the goal \u2014 unambiguous, with concrete\n * deliverables and acceptance criteria.\n */\n refinedGoal?: string | undefined;\n /**\n * Concrete, verifiable deliverables extracted from the refined goal.\n */\n deliverables?: string[] | undefined;\n /**\n * Estimated completion 0-100. Updated by the engine after each\n * iteration. Null means \"not yet assessed\".\n */\n progress?: number | undefined;\n /** Human-readable note explaining the current progress estimate. */\n progressNote?: string | undefined;\n /**\n * Time-series of progress measurements for trend analysis.\n * Last 200 entries retained. Use `recordProgress()` to append.\n */\n progressHistory?: ProgressSnapshot[] | undefined;\n /**\n * Computed trend from recent progress measurements.\n * 'accelerating' | 'steady' | 'stalling' | undefined.\n */\n progressTrend?: 'accelerating' | 'steady' | 'stalling' | undefined;\n /** When the goal was first set or last replaced. */\n setAt: string;\n /** Updated on every iteration completion. */\n lastActivityAt: string;\n /** Total iterations the engine has run against this goal (cumulative). */\n iterations: number;\n /** Engine lifecycle state \u2014 'running' means another process owns this goal. */\n engineState: 'idle' | 'running' | 'stopped';\n /**\n * Mission-level lifecycle.\n */\n goalState?: 'active' | 'paused' | 'completed' | 'abandoned' | undefined;\n /**\n * Per-todo attempt counter.\n */\n todoAttempts?: Record<string, number>;\n /** Bounded ring buffer of recent iterations (newest last). */\n journal: JournalEntry[];\n}\n\n/** Cap on persisted journal entries \u2014 older entries are evicted FIFO. */\nexport const MAX_JOURNAL_ENTRIES = 500;\n\n/**\n * Resolve the goal file path for a given project root.\n *\n * SINGLE canonical location: the per-project directory that\n * `resolveWstackPaths()` uses for everything else (sessions, memory, specs) \u2014\n * `~/.wrongstack/projects/<slug>/goal.json`. This is the same path the `/goal`\n * slash command writes via `opts.paths.projectGoal`, so every reader/writer\n * (the eternal/parallel autonomy engines, the CLI autonomy commands, the TUI\n * F9 panel, and `/goal` itself) now agree on one file.\n *\n * Previously this returned a SEPARATE hash-based dir (`projects/<hash>/`), which\n * disagreed with `/goal` and littered the home dir with thousands of stray\n * `<hash>/goal.json` directories that held nothing else.\n */\nexport function goalFilePath(projectRoot: string): string {\n return resolveWstackPaths({ projectRoot }).projectGoal;\n}\n\nexport async function loadGoal(filePath: string, events?: EventBus): Promise<GoalFile | null> {\n const t0 = Date.now();\n let raw: string;\n try {\n raw = await fsp.readFile(filePath, 'utf8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') {\n events?.emit('storage.read', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'load',\n outcome: 'success',\n durationMs: Date.now() - t0,\n });\n return null; // file doesn't exist \u2014 not an error\n }\n events?.emit('storage.error', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'load',\n error: toErrorMessage(err),\n recoverable: false,\n });\n throw err; // permission errors etc. should surface\n }\n try {\n const parsed = JSON.parse(raw) as GoalFile;\n if (parsed?.version !== 1 || typeof parsed.goal !== 'string' || !Array.isArray(parsed.journal)) {\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'goal_store.invalid_schema',\n path: filePath,\n message: 'invalid schema \u2014 consider deleting and re-creating',\n timestamp: new Date().toISOString(),\n }));\n events?.emit('storage.read', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'invalid_schema',\n });\n return null;\n }\n events?.emit('storage.read', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'load',\n outcome: 'success',\n durationMs: Date.now() - t0,\n });\n return parsed;\n } catch {\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'goal_store.parse_failed',\n path: filePath,\n message: 'JSON parse failed \u2014 consider deleting and re-creating',\n timestamp: new Date().toISOString(),\n }));\n events?.emit('storage.read', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'parse_failed',\n });\n return null;\n }\n}\n\nexport async function saveGoal(filePath: string, goal: GoalFile, events?: EventBus): Promise<void> {\n const t0 = Date.now();\n try {\n await atomicWrite(filePath, JSON.stringify(goal, null, 2), { mode: 0o600 });\n events?.emit('storage.write', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'save',\n outcome: 'success',\n durationMs: Date.now() - t0,\n });\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'save',\n error: toErrorMessage(err),\n recoverable: false,\n });\n throw new FsError({\n message: toErrorMessage(err),\n code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,\n path: filePath,\n cause: err,\n });\n }\n}\n\n/**\n * Atomically load, modify, and save a goal file under a file lock.\n * Prevents lost-update races when the autonomy engine and CLI /goal commands\n * write concurrently (both eternal and parallel engines may run simultaneously).\n *\n * `fn` receives the current GoalFile (or `null` if no goal exists yet)\n * and must return the updated GoalFile (or `null` to delete).\n */\nexport async function updateGoal(\n filePath: string,\n fn: (current: GoalFile | null) => GoalFile | null,\n events?: EventBus,\n): Promise<void> {\n const t0 = Date.now();\n await withFileLock(filePath, async () => {\n const current = await loadGoal(filePath, events);\n const next = fn(current);\n if (next) {\n await saveGoal(filePath, next, events);\n } else {\n try {\n await fsp.unlink(filePath);\n events?.emit('storage.write', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'delete',\n outcome: 'success',\n durationMs: Date.now() - t0,\n });\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'delete',\n error: toErrorMessage(err),\n recoverable: true,\n });\n // best-effort \u2014 file may not exist\n }\n }\n events?.emit('storage.write', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'update',\n outcome: 'success',\n durationMs: Date.now() - t0,\n });\n });\n}\n\nexport function emptyGoal(goal: string): GoalFile {\n const now = new Date().toISOString();\n return {\n version: 1,\n goal,\n setAt: now,\n lastActivityAt: now,\n iterations: 0,\n engineState: 'idle',\n goalState: 'active',\n todoAttempts: {},\n journal: [],\n };\n}\n\n/**\n * Set progress estimate on a goal. Returns a new GoalFile.\n * Clamps progress to 0-100.\n */\nexport function setProgress(\n goal: GoalFile,\n progress: number,\n note?: string,\n): GoalFile {\n const clamped = Math.min(100, Math.max(0, progress));\n return {\n ...goal,\n progress: clamped,\n progressNote: note ?? clamped + '% complete',\n };\n}\n\n/**\n * Append a journal entry, bumping iteration counters and trimming the\n * ring buffer. Returns a new GoalFile \u2014 does not mutate the argument.\n */\nexport function appendJournal(goal: GoalFile, entry: Omit<JournalEntry, 'iteration' | 'at'>): GoalFile {\n const iteration = goal.iterations + 1;\n const at = new Date().toISOString();\n const full: JournalEntry = { ...entry, iteration, at };\n const journal = [...goal.journal, full];\n const trimmed = journal.length > MAX_JOURNAL_ENTRIES\n ? journal.slice(journal.length - MAX_JOURNAL_ENTRIES)\n : journal;\n return {\n ...goal,\n iterations: iteration,\n lastActivityAt: at,\n journal: trimmed,\n };\n}\n\n/**\n * Aggregate cumulative cost + tokens across all journal entries.\n */\nexport function summarizeUsage(goal: GoalFile): {\n totalCostUsd: number;\n totalInputTokens: number;\n totalOutputTokens: number;\n iterationsWithUsage: number;\n} {\n let totalCostUsd = 0;\n let totalInputTokens = 0;\n let totalOutputTokens = 0;\n let iterationsWithUsage = 0;\n for (const e of goal.journal) {\n if (typeof e.costUsd === 'number') totalCostUsd += e.costUsd;\n if (e.tokens) {\n totalInputTokens += e.tokens.input;\n totalOutputTokens += e.tokens.output;\n }\n if (typeof e.costUsd === 'number' || e.tokens) iterationsWithUsage++;\n }\n return { totalCostUsd, totalInputTokens, totalOutputTokens, iterationsWithUsage };\n}\n\nconst DOLLAR = '\\u0024';\n\n/** Format the goal + recent journal as a human-readable status block. */\nexport function formatGoal(goal: GoalFile, journalLimit = 10): string {\n const lines: string[] = [];\n\n // Header \u2014 show refined goal, with original as annotation if different\n const displayGoal = goal.refinedGoal || goal.goal;\n lines.push(color.bold('Goal') + ': ' + displayGoal);\n if (goal.refinedGoal && goal.refinedGoal !== goal.goal) {\n const snippet = goal.goal.length > 60 ? goal.goal.slice(0, 60) + '\u2026' : goal.goal;\n lines.push(color.dim(' (original: \"' + snippet + '\")'));\n }\n\n // Progress bar (20-segment)\n if (typeof goal.progress === 'number') {\n const pct = Math.min(100, Math.max(0, Math.round(goal.progress)));\n const filled = Math.round(pct / 5);\n const empty = 20 - filled;\n const bar = color.green('\u2588'.repeat(filled)) + color.dim('\u2591'.repeat(empty));\n lines.push('Progress: ' + bar + ' ' + color.bold(pct + '%'));\n if (goal.progressNote) {\n lines.push(' ' + color.dim(goal.progressNote));\n }\n // Trend indicator\n if (goal.progressTrend) {\n const trendIcon = goal.progressTrend === 'accelerating' ? '\uD83D\uDE80'\n : goal.progressTrend === 'stalling' ? '\u26A0\uFE0F'\n : '\u27A1\uFE0F';\n lines.push(' Trend: ' + trendIcon + ' ' + goal.progressTrend);\n }\n }\n\n // Deliverables checklist\n if (goal.deliverables && goal.deliverables.length > 0) {\n lines.push('');\n lines.push(color.bold('Deliverables:'));\n for (const d of goal.deliverables) {\n const done = /^\\[[x\u2713]\\]|\u2705|\\(done\\)/i.test(d);\n const marker = done ? color.green('\u2713') : color.dim('\u25CB');\n lines.push(' ' + marker + ' ' + d);\n }\n }\n\n lines.push('');\n lines.push('Set: ' + goal.setAt);\n lines.push('Last activity: ' + goal.lastActivityAt);\n lines.push('Iterations: ' + goal.iterations);\n const stateLabel = goal.goalState ?? 'active';\n lines.push('State: ' + stateLabel + (goal.iterations > 0 ? ' (iteration #' + goal.iterations + ')' : ''));\n lines.push('Engine: ' + goal.engineState);\n const usage = summarizeUsage(goal);\n if (usage.iterationsWithUsage > 0) {\n const spent = 'Spent: ' + DOLLAR + usage.totalCostUsd.toFixed(4)\n + ' (in ' + usage.totalInputTokens + ' / out ' + usage.totalOutputTokens\n + ' tokens across ' + usage.iterationsWithUsage + ' iterations)';\n lines.push(spent);\n }\n if (goal.journal.length > 0) {\n lines.push('');\n lines.push('Recent journal (last ' + Math.min(journalLimit, goal.journal.length) + '):');\n const tail = goal.journal.slice(-journalLimit);\n for (const e of tail) {\n const mark = e.status === 'success' ? '\u2713' : e.status === 'failure' ? '\u2717' : e.status === 'aborted' ? '\u2298' : '\u00B7';\n const note = e.note ? ' \u2014 ' + e.note : '';\n const cost = typeof e.costUsd === 'number' ? ' (' + DOLLAR + e.costUsd.toFixed(4) + ')' : '';\n lines.push(' #' + e.iteration + ' ' + mark + ' [' + e.source + '] ' + e.task + cost + note);\n }\n }\n return lines.join('\\n');\n}\n\n/** A single progress measurement at a point in time. */\nexport interface ProgressSnapshot {\n at: string;\n progress: number;\n note?: string | undefined;\n}\n\n/**\n * Parse [PROGRESS: N%] from agent final text.\n * Supports formats:\n * [PROGRESS: 45%]\n * [PROGRESS: 45%] \u2014 3/5 deliverables done\n * [progress: 100%]\n * Returns null if no match.\n */\nexport function parseProgressFromText(text: string): { progress: number; note?: string } | null {\n const re = /\\[progress:\\s*(\\d{1,3})%\\]\\s*(?:[\u2014-]\\s*(.+))?/i;\n const m = text.match(re);\n if (!m) return null;\n // Regex match guarantees capture group 1 exists, but use ?? fallback to\n // satisfy noUncheckedIndexedAccess without a non-null assertion.\n const progress = Math.min(100, Math.max(0, Number.parseInt(m[1] ?? '0', 10)));\n const note = m[2]?.trim() || undefined;\n return note === undefined ? { progress } : { progress, note };\n}\n\n/**\n * Record a progress measurement. Returns a new GoalFile with:\n * - progress + progressNote updated\n * - progressHistory appended (last 200 entries kept)\n * - progress trend computed (accelerating/steady/stalling)\n */\nexport function recordProgress(\n goal: GoalFile,\n progress: number,\n note?: string,\n): GoalFile {\n const clamped = Math.min(100, Math.max(0, progress));\n const history = [...(goal.progressHistory ?? []), { at: new Date().toISOString(), progress: clamped, note }];\n // Keep last 200 snapshots\n const trimmed = history.length > 200 ? history.slice(-200) : history;\n\n return {\n ...goal,\n progress: clamped,\n progressNote: note ?? `${clamped}% complete`,\n progressHistory: trimmed,\n progressTrend: computeTrend(trimmed),\n };\n}\n\n/** Max progress history entries to retain. */\nexport const MAX_PROGRESS_HISTORY = 200;\n\nfunction computeTrend(history: ProgressSnapshot[]): 'accelerating' | 'steady' | 'stalling' | undefined {\n if (history.length < 3) return undefined;\n const recent = history.slice(-5);\n const deltas: number[] = [];\n for (let i = 1; i < recent.length; i++) {\n deltas.push((recent[i]?.progress ?? 0) - (recent[i - 1]?.progress ?? 0));\n }\n /* v8 ignore next -- unreachable: history.length>=3 guard above guarantees >=2 deltas */\n if (deltas.length < 2) return undefined;\n const avgDelta = deltas.reduce((a, b) => a + b, 0) / deltas.length;\n if (avgDelta > 2) return 'accelerating';\n if (avgDelta < -1) return 'stalling';\n return 'steady';\n}\n", "import { createHash } from 'node:crypto';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { PromptEntry, PromptVariable } from '../types/prompt.js';\nimport { atomicWrite, ensureDir } from '../utils/atomic-write.js';\nimport { slugify } from '../utils/slug.js';\nimport { ulid } from '../utils/ulid.js';\nimport type { WstackPaths } from '../utils/wstack-paths.js';\n\nexport type { PromptEntry, PromptSource, PromptVariable } from '../types/prompt.js';\n\nexport interface PromptStore {\n list(): Promise<PromptEntry[]>;\n get(id: string): Promise<PromptEntry | null>;\n save(entry: PromptEntry): Promise<void>;\n delete(id: string): Promise<boolean>;\n find(query: string): Promise<PromptEntry[]>;\n}\n\n/** Current on-disk schema version. */\nconst SCHEMA_VERSION = 2;\n\ninterface RawPromptFile {\n version: number;\n entry: unknown;\n}\n\n/** sha256 of a prompt's content \u2014 used for builtin/synced integrity checks. */\nexport function promptChecksum(content: string): string {\n return createHash('sha256').update(content, 'utf8').digest('hex');\n}\n\n/**\n * Upgrade any persisted prompt record (v1 or v2) to a fully-populated v2\n * `PromptEntry`. Pure \u2014 does not touch disk. v1 records (only\n * `id/title/content/tags/createdAt/updatedAt`) get sensible defaults:\n * slug from title, empty description, `uncategorized` category, `user` source.\n */\nexport function migratePromptEntry(raw: unknown): PromptEntry | null {\n if (!raw || typeof raw !== 'object') return null;\n const r = raw as Record<string, unknown>;\n if (typeof r['id'] !== 'string' || typeof r['title'] !== 'string') return null;\n\n const title = r['title'];\n const content = typeof r['content'] === 'string' ? r['content'] : '';\n const now = typeof r['createdAt'] === 'string' ? r['createdAt'] : new Date(0).toISOString();\n const tags = Array.isArray(r['tags'])\n ? (r['tags'].filter((t) => typeof t === 'string') as string[])\n : [];\n\n return {\n id: r['id'],\n slug: typeof r['slug'] === 'string' && r['slug'].length > 0 ? r['slug'] : slugify(title),\n title,\n description: typeof r['description'] === 'string' ? r['description'] : '',\n content,\n category:\n typeof r['category'] === 'string' && r['category'].length > 0\n ? r['category']\n : 'uncategorized',\n tags,\n source: isPromptSource(r['source']) ? r['source'] : 'user',\n favorite: r['favorite'] === true,\n variables: normalizeVariables(r['variables']),\n author: typeof r['author'] === 'string' ? r['author'] : undefined,\n version: typeof r['version'] === 'string' ? r['version'] : undefined,\n license: typeof r['license'] === 'string' ? r['license'] : undefined,\n checksum: typeof r['checksum'] === 'string' ? r['checksum'] : undefined,\n forkedFrom: typeof r['forkedFrom'] === 'string' ? r['forkedFrom'] : undefined,\n createdAt: now,\n updatedAt: typeof r['updatedAt'] === 'string' ? r['updatedAt'] : now,\n };\n}\n\nfunction isPromptSource(v: unknown): v is PromptEntry['source'] {\n return v === 'builtin' || v === 'user' || v === 'project' || v === 'synced';\n}\n\nfunction normalizeVariables(v: unknown): PromptVariable[] | undefined {\n if (!Array.isArray(v)) return undefined;\n const out: PromptVariable[] = [];\n for (const item of v) {\n if (\n item &&\n typeof item === 'object' &&\n typeof (item as Record<string, unknown>)['name'] === 'string'\n ) {\n const o = item as Record<string, unknown>;\n const enumVals =\n Array.isArray(o['enum']) && o['enum'].every((x) => typeof x === 'string')\n ? (o['enum'] as string[])\n : undefined;\n out.push({\n name: o['name'] as string,\n description: typeof o['description'] === 'string' ? o['description'] : undefined,\n default: typeof o['default'] === 'string' ? o['default'] : undefined,\n required: o['required'] === true ? true : undefined,\n enum: enumVals && enumVals.length > 0 ? enumVals : undefined,\n multiline: o['multiline'] === true ? true : undefined,\n });\n }\n }\n return out.length > 0 ? out : undefined;\n}\n\n/**\n * DefaultPromptStore \u2014 file-per-prompt JSON in a single directory (the global\n * `~/.wrongstack/prompts` by default, or a layer dir the loader passes in).\n * Reads tolerate legacy v1 files via `migratePromptEntry`; writes always emit\n * the current v2 schema.\n */\nexport class DefaultPromptStore implements PromptStore {\n private readonly dir: string;\n\n constructor(pathsOrDir: WstackPaths | string) {\n this.dir = typeof pathsOrDir === 'string' ? pathsOrDir : pathsOrDir.globalPrompts;\n }\n\n async list(): Promise<PromptEntry[]> {\n await ensureDir(this.dir);\n const entries: PromptEntry[] = [];\n try {\n const files = await fs.readdir(this.dir);\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n try {\n const raw: RawPromptFile = JSON.parse(\n await fs.readFile(path.join(this.dir, file), 'utf8'),\n );\n const migrated = migratePromptEntry(raw.entry);\n if (migrated) entries.push(migrated);\n } catch {\n // skip corrupt files\n }\n }\n } catch {\n // dir doesn't exist yet\n }\n return entries.sort(\n (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),\n );\n }\n\n async get(id: string): Promise<PromptEntry | null> {\n const file = path.join(this.dir, `${id}.json`);\n try {\n const raw: RawPromptFile = JSON.parse(await fs.readFile(file, 'utf8'));\n return migratePromptEntry(raw.entry);\n } catch {\n return null;\n }\n }\n\n async save(entry: PromptEntry): Promise<void> {\n await ensureDir(this.dir);\n const file = path.join(this.dir, `${entry.id}.json`);\n const raw: RawPromptFile = { version: SCHEMA_VERSION, entry };\n await atomicWrite(file, JSON.stringify(raw, null, 2));\n }\n\n async delete(id: string): Promise<boolean> {\n const file = path.join(this.dir, `${id}.json`);\n try {\n await fs.unlink(file);\n return true;\n } catch {\n return false;\n }\n }\n\n async find(query: string): Promise<PromptEntry[]> {\n const all = await this.list();\n const lower = query.toLowerCase();\n return all.filter(\n (e) =>\n e.title.toLowerCase().includes(lower) ||\n e.description.toLowerCase().includes(lower) ||\n e.content.toLowerCase().includes(lower) ||\n e.category.toLowerCase().includes(lower) ||\n e.tags.some((t) => t.toLowerCase().includes(lower)),\n );\n }\n\n /** Create a new v2 entry and return it. Does NOT persist \u2014 call save() afterwards. */\n createNew(\n title: string,\n content: string,\n tags: string[] = [],\n extra: Partial<\n Omit<PromptEntry, 'id' | 'title' | 'content' | 'tags' | 'createdAt' | 'updatedAt'>\n > = {},\n ): PromptEntry {\n const now = new Date().toISOString();\n return {\n id: ulid(),\n slug: extra.slug && extra.slug.length > 0 ? extra.slug : slugify(title),\n title,\n description: extra.description ?? '',\n content,\n category: extra.category ?? 'uncategorized',\n tags,\n source: extra.source ?? 'user',\n favorite: extra.favorite ?? false,\n variables: extra.variables,\n author: extra.author,\n version: extra.version,\n license: extra.license,\n checksum: extra.checksum,\n forkedFrom: extra.forkedFrom,\n createdAt: now,\n updatedAt: now,\n };\n }\n}\n", "import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { atomicWrite, ensureDir } from '../utils/atomic-write.js';\n\nexport interface PromptUsage {\n count: number;\n lastUsedAt: string;\n}\n\ninterface RawUsageFile {\n version: 1;\n usage: Record<string, PromptUsage>;\n}\n\n/**\n * Tracks how often each prompt is inserted, keyed by slug, in a single JSON\n * file (`~/.wrongstack/prompt-usage.json`). Kept SEPARATE from the prompt\n * entries so usage can be recorded for read-only builtin prompts without\n * copy-on-writing them into the user layer. Surfaces \"recent / most-used\"\n * views and a gentle search-ranking boost.\n */\nexport class PromptUsageStore {\n constructor(private readonly file: string) {}\n\n async load(): Promise<Record<string, PromptUsage>> {\n try {\n const raw: RawUsageFile = JSON.parse(await fs.readFile(this.file, 'utf8'));\n if (raw && typeof raw === 'object' && raw.usage && typeof raw.usage === 'object') {\n return raw.usage;\n }\n } catch {\n // missing or corrupt \u2192 empty\n }\n return {};\n }\n\n async record(slug: string, at: string = new Date().toISOString()): Promise<PromptUsage> {\n const usage = await this.load();\n const prev = usage[slug];\n const next: PromptUsage = { count: (prev?.count ?? 0) + 1, lastUsedAt: at };\n usage[slug] = next;\n await ensureDir(path.dirname(this.file));\n await atomicWrite(this.file, JSON.stringify({ version: 1, usage } satisfies RawUsageFile, null, 2));\n return next;\n }\n\n async get(slug: string): Promise<PromptUsage | undefined> {\n return (await this.load())[slug];\n }\n\n /** Slugs ordered by most-recently-used (then by count), capped at `limit`. */\n async recent(limit = 15): Promise<{ slug: string; usage: PromptUsage }[]> {\n const usage = await this.load();\n return Object.entries(usage)\n .map(([slug, u]) => ({ slug, usage: u }))\n .sort(\n (a, b) =>\n new Date(b.usage.lastUsedAt).getTime() - new Date(a.usage.lastUsedAt).getTime() ||\n b.usage.count - a.usage.count,\n )\n .slice(0, limit);\n }\n\n /** Slugs ordered by usage count (then recency), capped at `limit`. */\n async top(limit = 15): Promise<{ slug: string; usage: PromptUsage }[]> {\n const usage = await this.load();\n return Object.entries(usage)\n .map(([slug, u]) => ({ slug, usage: u }))\n .sort(\n (a, b) =>\n b.usage.count - a.usage.count ||\n new Date(b.usage.lastUsedAt).getTime() - new Date(a.usage.lastUsedAt).getTime(),\n )\n .slice(0, limit);\n }\n}\n", "import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { atomicWrite, ensureDir } from '../utils/atomic-write.js';\nimport type { SecretScrubber } from '../types/secret-scrubber.js';\n\n/**\n * On-disk shape of the per-project TUI input history file. Newest entry\n * first. `version` is forward-compat \u2014 a future schema bump can migrate.\n */\ninterface InputHistoryFile {\n version: 1;\n updatedAt: string;\n entries: string[];\n}\n\nexport const INPUT_HISTORY_DEFAULT_MAX = 100;\n\n/**\n * Per-project TUI prompt input history.\n *\n * Backed by a single JSON file at `~/.wrongstack/projects/<slug>/input-\n * history.json`. Entries are newest-first, capped at `maxEntries`. Before\n * any entry is written to disk it is passed through a `SecretScrubber` and\n * dropped if it still contains a redacted marker \u2014 leaking a pasted API\n * key into a history file would be a real incident, so the scrubber is\n * mandatory, not optional.\n *\n * The store is intentionally small and synchronous-ish (load once at TUI\n * boot, save debounced on change). It mirrors PromptUsageStore's shape so\n * the two read as a family.\n */\nexport class InputHistoryStore {\n /**\n * @param file Absolute path to the per-project input-history.json.\n * @param scrubber SecretScrubber used to filter secrets before write.\n * @param maxEntries Cap on the number of entries persisted. Default 100.\n */\n constructor(\n private readonly file: string,\n private readonly scrubber: SecretScrubber,\n private readonly maxEntries: number = INPUT_HISTORY_DEFAULT_MAX,\n ) {}\n\n /**\n * Load the persisted entries. Missing or corrupt file \u2192 empty list\n * (never throws \u2014 history is best-effort).\n */\n async load(): Promise<string[]> {\n try {\n const raw: InputHistoryFile = JSON.parse(await fs.readFile(this.file, 'utf8'));\n if (\n raw &&\n typeof raw === 'object' &&\n Array.isArray(raw.entries) &&\n raw.entries.every((e) => typeof e === 'string')\n ) {\n return raw.entries.slice(0, this.maxEntries);\n }\n } catch {\n // missing or corrupt \u2192 empty\n }\n return [];\n }\n\n /**\n * Persist `entries` to disk after scrubbing each one. Entries that still\n * contain a redaction marker AFTER scrubbing are dropped \u2014 a half-scrubbed\n * prompt like \"export API_KEY=***REDACTED***\" is useless as history and\n * keeping it risks confusing the agent. Dedup is the caller's job (the\n * reducer already dedups); this method only scrubs, caps, and writes.\n */\n async save(entries: string[]): Promise<void> {\n const cleaned = this.scrubAndFilter(entries);\n await ensureDir(path.dirname(this.file));\n const payload: InputHistoryFile = {\n version: 1,\n updatedAt: new Date().toISOString(),\n entries: cleaned,\n };\n await atomicWrite(this.file, JSON.stringify(payload, null, 2));\n }\n\n /** Truncate the file to an empty entry list (used by /clear). */\n async clear(): Promise<void> {\n await ensureDir(path.dirname(this.file));\n const payload: InputHistoryFile = { version: 1, updatedAt: new Date().toISOString(), entries: [] };\n await atomicWrite(this.file, JSON.stringify(payload, null, 2));\n }\n\n /**\n * Run each entry through the scrubber and drop any that still contain a\n * redaction marker. Internal so the test suite can pin the policy.\n */\n private scrubAndFilter(entries: string[]): string[] {\n const out: string[] = [];\n for (const entry of entries) {\n const scrubbed = this.scrubber.scrub(entry);\n if (this.looksRedacted(scrubbed)) continue;\n out.push(scrubbed);\n }\n return out.slice(0, this.maxEntries);\n }\n\n /**\n * Heuristic: did the scrubber actually redact anything? DefaultSecretScrubber\n * replaces matches with \"***REDACTED***\", so a residual marker means a secret\n * was detected. Subclasses of SecretScrubber that use a different sentinel\n * can be accommodated by widening this check in the future; for now the\n * marker check covers the shipped scrubber.\n */\n private looksRedacted(text: string): boolean {\n return text.includes('***REDACTED***');\n }\n}\n", "import { expectDefined } from '../utils/expect-defined.js';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { createHash } from 'node:crypto';\nimport type { WstackPaths } from '../utils/wstack-paths.js';\nimport type { SyncCategory, SyncConfig } from '../types/config.js';\nimport { FsError, WrongStackError, ERROR_CODES } from '../types/errors.js';\nexport const ALL_SYNC_CATEGORIES: SyncCategory[] = ['settings', 'skills', 'prompts', 'memory', 'history'];\n\nexport interface SyncResult {\n ok: boolean;\n action: 'push' | 'pull';\n categories: SyncCategory[];\n committedAt?: string | undefined;\n message: string;\n}\n\ninterface SyncStateFile {\n version: 1;\n sha: string;\n lastSyncedAt: string;\n localRev: string;\n}\n\n/**\n * CloudSync \u2014 push/pull user-selected ~/.wrongstack categories to a\n * private GitHub repo. No git CLI needed; uses GitHub REST API via fetch.\n * The token is stored encrypted via SecretVault (field named `githubToken`\n * so the vault walker picks it up automatically).\n */\nexport class CloudSync {\n private readonly statePath: string;\n private state: SyncStateFile | null = null;\n\n constructor(\n private readonly paths: WstackPaths,\n private readonly getConfig: () => SyncConfig | null,\n private readonly setConfig: (c: SyncConfig) => Promise<void>,\n ) {\n this.statePath = path.join(paths.globalRoot, 'sync-state.json');\n }\n\n // \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async status(): Promise<string> {\n const cfg = this.getConfig();\n if (!cfg?.enabled) {\n return 'CloudSync: disabled. Run `/sync enable` to activate.';\n }\n const last = this.state?.lastSyncedAt;\n const since = last ? timeAgo(last) : 'never';\n return [\n `CloudSync: enabled`,\n ` repo: ${cfg.repo}`,\n ` categories: ${cfg.categories.join(', ')}`,\n ` last sync: ${since}`,\n ].join('\\n');\n }\n\n async enable(_repo: string, _categories: SyncCategory[]): Promise<string> {\n // Persisted by the slash command via configStore.update.\n return 'Enable via /sync enable.';\n }\n\n async disable(): Promise<string> {\n const cfg = this.getConfig();\n if (!cfg) return 'CloudSync is not configured.';\n const next = { ...cfg, enabled: false };\n await this.setConfig(next);\n return 'CloudSync disabled. Local data kept.';\n }\n\n async push(token: string): Promise<SyncResult> {\n const cfg = this.getConfig();\n if (!cfg?.enabled) return { ok: false, action: 'push', categories: [], message: 'Not enabled.' };\n\n const parts = cfg.repo.split('/');\n const owner = expectDefined(parts[0]);\n const repoName = expectDefined(parts[1]);\n const branch = 'main';\n const baseTreeSha = this.state?.sha;\n\n const { treeEntries, rev } = await this.buildLocalTree(cfg.categories);\n const newTreeSha = await this.createGitTree(token, owner, repoName, treeEntries, baseTreeSha);\n\n const commitSha = await this.createCommit(\n token, owner, repoName, newTreeSha,\n baseTreeSha,\n `Sync ${cfg.categories.join(', ')} \u2014 ${new Date().toISOString()}`,\n );\n\n try {\n await this.updateRef(token, owner, repoName, branch, commitSha);\n } catch (err) {\n // 422 = not a fast forward \u2014 remote branch moved. Fetch latest SHA and retry.\n if (err instanceof Error && err.message.includes('422')) {\n const remote = await this.getRef(token, owner, repoName, branch);\n const currentSha = remote.object.sha;\n const rebasedCommitSha = await this.createCommit(\n token, owner, repoName, newTreeSha,\n currentSha,\n `Sync ${cfg.categories.join(', ')} \u2014 ${new Date().toISOString()}`,\n );\n await this.updateRef(token, owner, repoName, branch, rebasedCommitSha);\n } else {\n throw err;\n }\n }\n\n const syncState: SyncStateFile = {\n version: 1,\n sha: commitSha,\n lastSyncedAt: new Date().toISOString(),\n localRev: rev,\n };\n await fs.writeFile(this.statePath, JSON.stringify(syncState, null, 2), 'utf8');\n this.state = syncState;\n\n return {\n ok: true,\n action: 'push',\n categories: cfg.categories,\n committedAt: commitSha,\n message: `Pushed ${cfg.categories.join(', ')} to ${cfg.repo}. Commit: ${commitSha.slice(0, 7)}`,\n };\n }\n\n async pull(token: string): Promise<SyncResult> {\n const cfg = this.getConfig();\n if (!cfg?.enabled) return { ok: false, action: 'pull', categories: [], message: 'Not enabled.' };\n\n const pullParts = cfg.repo.split('/');\n const owner = expectDefined(pullParts[0]);\n const repoName = expectDefined(pullParts[1]);\n\n const branchData = await this.getRef(token, owner, repoName, 'main');\n const currentSha = branchData.object.sha;\n\n const commitData = await this.getCommit(token, owner, repoName, currentSha);\n const treeSha = commitData.tree.sha;\n\n const treeEntries = await this.getTreeEntries(token, owner, repoName, treeSha);\n\n for (const entry of treeEntries) {\n if (entry.type !== 'blob') continue;\n\n // Paths look like \"data/{category}/...\" \u2014 extract the category\n const segments = entry.path.split('/');\n if (segments[0] !== 'data' || !segments[1]) continue;\n const cat = segments[1] as SyncCategory;\n if (!['settings', 'skills', 'prompts', 'memory', 'history'].includes(cat)) continue;\n\n const localPath = this.categoryToPath(cat);\n if (!localPath) continue;\n\n // Reconstruct relative path under the category dir. Remote trees are\n // untrusted input: a compromised sync repo could contain paths like\n // data/skills/../../config.json. Keep every write inside the selected\n // category root, and only allow subpaths for directory-backed categories.\n const rel = segments.slice(2).join('/');\n const destPath = resolvePulledCategoryPath(cat, localPath, rel, entry.path);\n\n const blobData = await this.getBlob(token, owner, repoName, entry.sha);\n await fs.mkdir(path.dirname(destPath), { recursive: true });\n await fs.writeFile(destPath, Buffer.from(blobData, 'base64'));\n }\n\n const localRev = await this.hashLocalCategories(cfg.categories);\n const syncState: SyncStateFile = {\n version: 1,\n sha: currentSha,\n lastSyncedAt: new Date().toISOString(),\n localRev,\n };\n await fs.writeFile(this.statePath, JSON.stringify(syncState, null, 2), 'utf8');\n this.state = syncState;\n\n return {\n ok: true,\n action: 'pull',\n categories: cfg.categories,\n committedAt: currentSha,\n message: `Pulled ${cfg.categories.join(', ')} from ${cfg.repo}. Commit: ${currentSha.slice(0, 7)}`,\n };\n }\n\n async hasLocalChanges(): Promise<boolean> {\n if (!this.state) return true;\n const cfg = this.getConfig();\n if (!cfg) return true;\n const current = await this.hashLocalCategories(cfg.categories);\n return current !== this.state.localRev;\n }\n\n async loadState(): Promise<void> {\n try {\n const raw = await fs.readFile(this.statePath, 'utf8');\n this.state = JSON.parse(raw) as SyncStateFile;\n } catch {\n this.state = null;\n }\n }\n\n // \u2500\u2500 GitHub API helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async githubFetch(\n token: string,\n owner: string,\n repo: string,\n method: 'GET' | 'POST' | 'PUT' | 'PATCH',\n pathSegment: string,\n body?: unknown | undefined,\n ): Promise<unknown> {\n const url = `https://api.github.com/repos/${owner}/${repo}${pathSegment}`;\n const init: RequestInit = {\n signal: AbortSignal.timeout(15_000),\n method,\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n 'Content-Type': 'application/json',\n },\n };\n if (body !== undefined) init.body = JSON.stringify(body);\n const res = await fetch(url, init);\n\n if (!res.ok) {\n const errText = await res.text();\n throw new WrongStackError({\n message: `GitHub API ${method} ${pathSegment} failed (${res.status}): ${errText}`,\n code: ERROR_CODES.UNKNOWN,\n subsystem: 'general',\n context: { method, pathSegment, status: res.status, repo: `${owner}/${repo}` },\n });\n }\n\n const text = await res.text();\n return text ? JSON.parse(text) : {};\n }\n\n private async getRef(token: string, owner: string, repo: string, ref: string) {\n return (await this.githubFetch(token, owner, repo, 'GET', `/git/refs/heads/${ref}`)) as {\n object: { sha: string };\n };\n }\n\n private async updateRef(token: string, owner: string, repo: string, ref: string, sha: string) {\n await this.githubFetch(token, owner, repo, 'PATCH', `/git/refs/heads/${ref}`, {\n sha,\n force: false,\n });\n }\n\n private async getCommit(token: string, owner: string, repo: string, sha: string) {\n return (await this.githubFetch(token, owner, repo, 'GET', `/git/commits/${sha}`)) as {\n tree: { sha: string };\n message: string;\n };\n }\n\n private async getTreeEntries(token: string, owner: string, repo: string, treeSha: string) {\n return (await this.githubFetch(token, owner, repo, 'GET', `/git/trees/${treeSha}?recursive=1`)) as Array<{\n path: string;\n sha: string;\n type: 'blob' | 'tree';\n }>;\n }\n\n private async createCommit(\n token: string, owner: string, repo: string,\n treeSha: string, parentSha?: string | undefined, message = 'sync',\n ) {\n const body: Record<string, unknown> = { message, tree: treeSha };\n if (parentSha) body.parents = [parentSha];\n const result = (await this.githubFetch(token, owner, repo, 'POST', '/git/commits', body)) as { sha: string };\n return result.sha;\n }\n\n private async createGitTree(\n token: string, owner: string, repo: string,\n entries: Array<{ path: string; content: string; mode: string }>,\n baseTreeSha?: string | undefined,\n ): Promise<string> {\n const tree = entries.map((e) => ({\n path: e.path,\n mode: e.mode,\n type: 'blob',\n content: e.content,\n }));\n const body: Record<string, unknown> = { tree };\n if (baseTreeSha) body.base_tree = baseTreeSha;\n const result = (await this.githubFetch(token, owner, repo, 'POST', '/git/trees', body)) as { sha: string };\n return result.sha;\n }\n\n private async getBlob(token: string, owner: string, repo: string, sha: string): Promise<string> {\n const result = (await this.githubFetch(token, owner, repo, 'GET', `/git/blobs/${sha}`)) as { content: string };\n return result.content;\n }\n\n // \u2500\u2500 Local file helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async buildLocalTree(categories: SyncCategory[]): Promise<{\n treeEntries: Array<{ path: string; content: string; mode: string }>;\n rev: string;\n }> {\n const entries: Array<{ path: string; content: string; mode: string }> = [];\n const hashes: string[] = [];\n\n for (const cat of categories) {\n const localPath = this.categoryToPath(cat);\n if (!localPath) continue;\n try {\n const stat = await fs.stat(localPath);\n if (stat.isDirectory()) {\n const files = await this.walkDir(localPath, localPath);\n for (const file of files) {\n const content = await fs.readFile(file, 'utf8');\n const rel = path.relative(localPath, file).replace(/\\\\/g, '/');\n entries.push({ path: `data/${cat}/${rel}`, content, mode: '100644' });\n hashes.push(content);\n }\n } else {\n const content = await fs.readFile(localPath, 'utf8');\n entries.push({ path: `data/${cat}`, content, mode: '100644' });\n hashes.push(content);\n }\n } catch {\n // skip missing files/dirs\n }\n }\n\n const rev = createHash('sha256').update(hashes.join('')).digest('hex').slice(0, 12);\n return { treeEntries: entries, rev };\n }\n\n private async hashLocalCategories(categories: SyncCategory[]): Promise<string> {\n const hashes: string[] = [];\n for (const cat of categories) {\n const localPath = this.categoryToPath(cat);\n if (!localPath) continue;\n try {\n const stat = await fs.stat(localPath);\n if (stat.isDirectory()) {\n const files = await this.walkDir(localPath, localPath);\n for (const file of files) {\n const content = await fs.readFile(file);\n hashes.push(content.toString('base64') + file);\n }\n } else {\n const content = await fs.readFile(localPath);\n hashes.push(content.toString('base64') + localPath);\n }\n } catch {\n // skip\n }\n }\n return createHash('sha256').update(hashes.join('')).digest('hex').slice(0, 12);\n }\n\n private categoryToPath(cat: SyncCategory): string | null {\n switch (cat) {\n case 'settings': return this.paths.globalConfig;\n case 'skills': return this.paths.globalSkills;\n case 'prompts': return this.paths.globalPrompts;\n case 'memory': return this.paths.globalMemory;\n case 'history': return this.paths.historyFile;\n /* v8 ignore next -- unreachable: SyncCategory is exhaustively matched above */\n default: return null;\n }\n }\n\n private async walkDir(dir: string, base: string): Promise<string[]> {\n const results: string[] = [];\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...(await this.walkDir(full, base)));\n } else {\n results.push(full);\n }\n }\n return results;\n }\n}\n\nfunction resolvePulledCategoryPath(\n cat: SyncCategory,\n localPath: string,\n rel: string,\n remotePath: string,\n): string {\n const directoryBacked = cat === 'skills' || cat === 'prompts';\n if (!directoryBacked) {\n if (rel) throw new FsError({\n message: `Refusing nested CloudSync path for file category: ${remotePath}`,\n code: ERROR_CODES.FS_DELETE_FAILED,\n path: remotePath,\n context: { reason: 'nested_file_category', category: cat },\n });\n return localPath;\n }\n\n if (!rel) return localPath;\n const normalizedRel = path.normalize(rel);\n const traversesUp = normalizedRel === '..' || normalizedRel.startsWith(`..${path.sep}`);\n if (path.isAbsolute(normalizedRel) || traversesUp) {\n throw new FsError({\n message: `Refusing CloudSync path traversal: ${remotePath}`,\n code: ERROR_CODES.FS_DELETE_FAILED,\n path: remotePath,\n context: { reason: 'path_traversal', normalizedRel },\n });\n }\n\n const dest = path.resolve(localPath, normalizedRel);\n const root = path.resolve(localPath);\n const relative = path.relative(root, dest);\n /* v8 ignore start -- unreachable: the normalizedRel '..' guard above already rejects traversal */\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n throw new FsError({\n message: `Refusing CloudSync path outside category root: ${remotePath}`,\n code: ERROR_CODES.FS_DELETE_FAILED,\n path: remotePath,\n context: { reason: 'outside_category_root', category: cat },\n });\n }\n /* v8 ignore stop */\n return dest;\n}\n\nfunction timeAgo(iso: string): string {\n const diff = Date.now() - new Date(iso).getTime();\n const mins = Math.floor(diff / 60_000);\n if (mins < 1) return 'just now';\n if (mins < 60) return `${mins}m ago`;\n const hrs = Math.floor(mins / 60);\n if (hrs < 24) return `${hrs}h ago`;\n const days = Math.floor(hrs / 24);\n return `${days}d ago`;\n}\n", "import type { SessionEvent, SessionWriter } from '../types/session.js';\n\nexport type AuditLevel = 'minimal' | 'standard' | 'full';\n\n/**\n * Configuration for sampling high-volume events inside the bridge.\n * This allows callers (CLI, TUI, WebUI, plugins) to tune how aggressively\n * noisy events like tool progress are persisted.\n */\nexport interface ToolProgressSamplingOptions {\n /**\n * How often to persist 'log' and 'partial_output' progress events.\n * - 1 = every message (no sampling)\n * - 8 = keep the first message + every 8th after that (default)\n */\n sampleRate?: number | undefined;\n}\n\nexport interface SessionSamplingOptions {\n /** Controls sampling behavior for `tool_progress` events (only relevant at auditLevel 'full'). */\n toolProgress?: ToolProgressSamplingOptions | undefined;\n}\n\nexport interface SessionEventBridgeOptions {\n /** Sampling rules for high-volume audit events. */\n sampling?: SessionSamplingOptions | undefined;\n}\n\n/**\n * Small, safe helper that wraps a SessionWriter and enforces the\n * configured auditLevel.\n *\n * All appends are best-effort. Failures are swallowed (with optional\n * diagnostics) so they never crash the agent loop.\n */\nexport interface SessionEventBridge {\n /** Append an event if allowed by the current audit level. */\n append(event: SessionEvent): Promise<void>;\n /** Batch-append events allowed by the current audit level. */\n appendBatch(events: SessionEvent[]): Promise<void>;\n\n /** Current audit level. Reflects the latest {@link setAuditLevel} value. */\n readonly level: AuditLevel;\n\n /**\n * Change the audit level on a live bridge. Subsequent appends are filtered\n * by the new level \u2014 used by the TUI `/settings` picker to apply an\n * `auditLevel` change to the running session without a restart.\n */\n setAuditLevel(level: AuditLevel): void;\n\n /** Returns true if an event of this type should be written at the current level. */\n allows(type: SessionEvent['type']): boolean;\n}\n\n/** Core events that are always written regardless of auditLevel. */\nconst CORE_RECONSTRUCT_EVENTS = new Set<SessionEvent['type']>([\n 'session_start',\n 'session_resumed',\n 'session_forked',\n 'user_input',\n 'llm_response',\n 'tool_result',\n 'context_snapshot',\n 'checkpoint',\n 'file_snapshot',\n 'file_observation',\n 'rewound',\n 'in_flight_start',\n 'in_flight_end',\n 'session_end',\n]);\n\n/**\n * Events that are considered \"standard\" audit detail.\n * These are lightweight and high-value for forensics.\n */\nconst STANDARD_AUDIT_EVENTS = new Set<SessionEvent['type']>([\n 'llm_request',\n 'tool_use',\n 'tool_call_start',\n 'tool_call_end',\n 'compaction',\n 'error',\n 'message_truncated',\n 'provider_retry',\n 'provider_error',\n 'skill_activated',\n 'skill_deactivated',\n]);\n\n/**\n * Events that are only allowed at 'full' audit level because they can be\n * very high volume (e.g. streaming tool output).\n */\nconst FULL_ONLY_EVENTS = new Set<SessionEvent['type']>([\n 'tool_progress',\n]);\n\n/**\n * \"full\" level allows everything (including potentially heavy events\n * that plugins or future code may emit).\n */\nfunction isAllowed(type: SessionEvent['type'], level: AuditLevel): boolean {\n if (CORE_RECONSTRUCT_EVENTS.has(type)) return true;\n if (level === 'minimal') return false;\n\n if (STANDARD_AUDIT_EVENTS.has(type)) return true;\n if (level === 'standard') return false;\n\n // 'full' level events (high volume)\n if (FULL_ONLY_EVENTS.has(type)) {\n return level === 'full';\n }\n\n // 'full' \u2014 allow everything else\n return true;\n}\n\n/**\n * Create a safe, audit-level-aware bridge around a SessionWriter.\n *\n * The bridge can also apply sampling for high-volume events (e.g. `tool_progress`)\n * when `auditLevel` is set to `'full'`.\n *\n * @example\n * const bridge = createSessionEventBridge(sessionWriter, 'full', {\n * sampling: {\n * toolProgress: { sampleRate: 5 } // more aggressive sampling\n * }\n * });\n */\nexport function createSessionEventBridge(\n writer:\n | SessionWriter\n | (() => SessionWriter | undefined | null)\n | undefined\n | null,\n level: AuditLevel = 'standard',\n options: SessionEventBridgeOptions = {},\n): SessionEventBridge {\n // Mutable so setAuditLevel() can re-tune a live bridge in place.\n let currentLevel: AuditLevel = level ?? 'standard';\n\n // Accept either a writer instance or a getter. A getter lets long-lived\n // hosts (CLI/TUI/WebUI) resolve the CURRENT writer on every append \u2014 when\n // the user resumes another session mid-run, audit events follow the swap\n // instead of being silently dropped into the old, closed writer.\n const resolveWriter: () => SessionWriter | undefined | null =\n typeof writer === 'function' ? writer : () => writer;\n\n // Internal sampling state for high-volume events (e.g. tool_progress).\n // Keyed by tool call id (or name as fallback) to keep sampling per-call.\n const progressCounters = new Map<string, number>();\n\n const toolProgressConfig = options.sampling?.toolProgress ?? {};\n const TOOL_PROGRESS_SAMPLE_RATE = toolProgressConfig.sampleRate ?? 8;\n\n /**\n * Decide whether a high-volume event should be sampled in.\n * Currently only implements sampling for 'tool_progress'.\n */\n function shouldSample(event: SessionEvent): boolean {\n if (event.type !== 'tool_progress') return true;\n\n const progEvent = event as Extract<SessionEvent, { type: 'tool_progress' }>;\n const innerType = progEvent.event?.type;\n\n // Always let through high-signal structured events\n if (innerType === 'warning' || innerType === 'metric' || innerType === 'file_changed') {\n return true;\n }\n\n // Sample noisy text streams (log / partial_output)\n if (innerType === 'log' || innerType === 'partial_output') {\n const key = progEvent.id || progEvent.name;\n const count = (progressCounters.get(key) || 0) + 1;\n progressCounters.set(key, count);\n\n // Always keep the first message + every Nth after that\n return count === 1 || (count % TOOL_PROGRESS_SAMPLE_RATE === 0);\n }\n\n return true;\n }\n\n return {\n get level() {\n return currentLevel;\n },\n\n setAuditLevel(next) {\n currentLevel = next ?? 'standard';\n },\n\n allows(type) {\n return isAllowed(type, currentLevel);\n },\n\n async append(event) {\n const target = resolveWriter();\n if (!target) return;\n if (!isAllowed(event.type, currentLevel)) return;\n\n // Apply sampling for high-volume events (only at 'full' level)\n if (!shouldSample(event)) return;\n\n try {\n await target.append(event);\n } catch (_err) {\n // Best-effort: never let session logging break the agent.\n // The existing FileSessionWriter already does throttled warnings,\n // but we keep this wrapper silent by default to avoid log spam.\n // Callers that care can listen to EventBus 'session.damaged' etc.\n }\n },\n\n async appendBatch(events) {\n const target = resolveWriter();\n if (!target || events.length === 0) return;\n const allowed = events.filter(\n (e) => isAllowed(e.type, currentLevel) && shouldSample(e),\n );\n if (allowed.length === 0) return;\n try {\n await target.appendBatch(allowed);\n } catch {\n // best-effort \u2014 same contract as append()\n }\n },\n };\n}\n\n/** Convenience re-export of the allowed core set for tests/docs. */\nexport { CORE_RECONSTRUCT_EVENTS, STANDARD_AUDIT_EVENTS };\n\n/**\n * Safely extract the auditLevel from a (possibly partial) Config object.\n * Falls back to 'standard' if not present or invalid.\n */\nexport function resolveAuditLevel(\n cfg?: { session?: { auditLevel?: AuditLevel | undefined } | undefined } | null,\n): AuditLevel {\n const raw = cfg?.session?.auditLevel;\n if (raw === 'minimal' || raw === 'standard' || raw === 'full') {\n return raw;\n }\n return 'standard';\n}\n\n/**\n * Fully resolves the session logging configuration with sensible defaults.\n * This is the recommended way for the CLI / TUI / WebUI to read session config.\n */\nexport function resolveSessionLoggingConfig(\n cfg?: {\n session?: {\n auditLevel?: AuditLevel | undefined;\n sampling?: {\n toolProgress?: { sampleRate?: number | undefined };\n };\n };\n } | null,\n): {\n auditLevel: AuditLevel;\n sampling: {\n toolProgress: { sampleRate: number };\n };\n} {\n const session = cfg?.session ?? {};\n\n const auditLevel = resolveAuditLevel(cfg);\n\n const toolProgressSampleRate =\n session.sampling?.toolProgress?.sampleRate ?? 8;\n\n return {\n auditLevel,\n sampling: {\n toolProgress: {\n sampleRate: Math.max(1, Math.floor(toolProgressSampleRate)),\n },\n },\n };\n}\n"],
|
|
5
|
-
"mappings": ";AAAA,SAAS,cAAAA,mBAAkB;AAC3B,SAAS,wBAAqC;AAC9C,YAAYC,UAAS;AACrB,YAAYC,WAAU;AACtB,SAAS,uBAAuB;;;ACJhC,SAAS,eAAAC,oBAAmB;AAC5B,YAAY,QAAQ;AACpB,YAAYC,WAAU;;;ACwBtB,IAAM,eAAe,oBAAI,IAAY;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAMD,IAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,YAAY,MAAuB;AAC1C,QAAM,QAAQ,KAAK,YAAY;AAC/B,aAAW,KAAK,mBAAmB;AACjC,QAAI,MAAM,SAAS,CAAC,EAAG,QAAO;AAAA,EAChC;AAGA,MAAI,wBAAwB,KAAK,KAAK,EAAG,QAAO;AAChD,MAAI,eAAe,KAAK,KAAK,EAAG,QAAO;AACvC,MAAI,kBAAkB,KAAK,KAAK,EAAG,QAAO;AAC1C,MAAI,kBAAkB,KAAK,KAAK,MAAM,SAAS,WAAW,KAAK,KAAK,GAAG;AAGrE,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAcA,SAAS,2BAA2B,OAAwB;AAG1D,SAAO,+CAA+C,KAAK,KAAK;AAClE;AAMA,IAAM,8BACJ;AACF,IAAM,iCACJ;AAQK,SAAS,oBAAoB,OAAuB;AACzD,QAAM,SAAS,MAAM,MAAM,KAAK,EAAE,OAAO,OAAO;AAChD,QAAM,OAAiB,CAAC;AACxB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,MAAM,OAAO,CAAC;AACpB,QAAI,+BAA+B,KAAK,GAAG,EAAG;AAC9C,QAAI,4BAA4B,KAAK,GAAG,GAAG;AACzC;AACA;AAAA,IACF;AACA,SAAK,KAAK,GAAG;AAAA,EACf;AACA,SAAO,KAAK,KAAK,GAAG;AACtB;AAsBA,IAAI,cAAkC;AAyB/B,SAAS,cAAc,iBAAoE;AAChG,QAAM,OACJ,OAAO,oBAAoB,WACvB,EAAE,WAAW,gBAAgB,IAC5B,mBAAmB,CAAC;AAS3B,QAAM,SAAS,OAAO,OAAO,QAAQ,KAAK,kCAAkC;AAC5E,QAAM,eAAe,OAAO,OAAO,QAAQ,KAAK,iCAAiC;AACjF,QAAM,cAAe,UAAU,QAAQ,IAAI,kCAAkC,MAAM,OAC7E,gBAAgB,QAAQ,IAAI,iCAAiC,MAAM;AACzE,MAAI,eAAe,CAAC,QAAQ,IAAI,IAAI,GAAG;AACrC,YAAQ;AAAA,MACN;AAAA,IAGF;AAAA,EACF;AACA,QAAM,MAAyB,CAAC;AAUhC,QAAM,mBAAmB,QAAQ,IAAI,+BAA+B,MAAM;AAE1E,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,QAAQ,GAAG,GAAG;AAChD,QAAI,MAAM,OAAW;AACrB,QAAI,qBAAqB,MAAM,cAAc,MAAM,iCAAkC;AACrF,QAAI,aAAa;AACf,UAAI,CAAC,IAAI;AACT;AAAA,IACF;AACA,UAAM,QAAQ,EAAE,YAAY;AAI5B,QAAI,2BAA2B,CAAC,EAAG;AAGnC,QAAI,aAAa,IAAI,KAAK,GAAG;AAC3B,UAAI,CAAC,IAAI;AACT;AAAA,IACF;AAEA,QAAI,YAAY,KAAK,EAAG;AAKxB,QAAI,UAAU,gBAAgB;AAC5B,YAAM,YAAY,oBAAoB,CAAC;AACvC,UAAI,UAAW,KAAI,CAAC,IAAI;AACxB;AAAA,IACF;AAGA,QACE,MAAM,WAAW,OAAO,KACxB,MAAM,WAAW,MAAM,KACvB,MAAM,WAAW,OAAO,KACxB,MAAM,WAAW,OAAO,KACxB,MAAM,WAAW,MAAM,KACvB,MAAM,WAAW,IAAI,KACrB,MAAM,WAAW,MAAM;AAAA;AAAA;AAAA;AAAA,IAKvB,MAAM,WAAW,aAAa,KAC9B,UAAU,YACV,UAAU,YACV,UAAU,SACV;AACA,UAAI,CAAC,IAAI;AAAA,IACX;AAAA,EACF;AAKA,MAAI,aAAa;AACf,QAAI,YAAY,MAAM;AACpB,UAAI,iBAAiB,IAAI,YAAY;AACrC,UAAI,oBAAoB,IAAI,YAAY;AAAA,IAC1C;AACA,QAAI,YAAY,OAAO;AACrB,UAAI,kBAAkB,IAAI,YAAY;AACtC,UAAI,qBAAqB,IAAI,YAAY;AAAA,IAC3C;AAAA,EACF;AAQA,MAAI,KAAK,OAAO;AACd,WAAO,OAAO,KAAK,KAAK,KAAK;AAAA,EAC/B;AAEA,MAAI,KAAK,UAAW,KAAI,uBAAuB,IAAI,KAAK;AACxD,SAAO;AACT;;;ACtRA,IAAM,YAAY,MAAe,OAAO,YAAY,eAAe,CAAC,CAAC,QAAQ;AAItE,SAAS,cAAuB;AACrC,SAAO,UAAU,KAAK,QAAQ,QAAQ,OAAO,KAAK;AACpD;;;ACxBA,IAAM,aAAa,MAAe;AAChC,MAAI,QAAQ,QAAQ,IAAI,QAAQ,EAAG,QAAO;AAC1C,MAAI,QAAQ,QAAQ,IAAI,WAAW,EAAG,QAAO;AAC7C,SAAO,YAAY;AACrB;AAEA,SAAS,QAAQ,OAAoC;AACnD,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,MAAM,KAAK,MAAM,GAAI,QAAO;AAChC,SAAO,CAAC,sBAAsB,KAAK,MAAM,KAAK,CAAC;AACjD;AAEA,IAAM,QAAQ,WAAW;AAEzB,IAAM,OACJ,CAACC,OAAc,UACf,CAAC,MACC,QAAQ,QAAQA,KAAI,IAAI,CAAC,QAAQ,KAAK,MAAM;AAEzC,IAAM,QAAQ;AAAA,EACnB,OAAO,KAAK,KAAK,GAAG;AAAA,EACpB,MAAM,KAAK,KAAK,IAAI;AAAA,EACpB,KAAK,KAAK,KAAK,IAAI;AAAA,EACnB,QAAQ,KAAK,KAAK,IAAI;AAAA,EACtB,WAAW,KAAK,KAAK,IAAI;AAAA,EACzB,KAAK,KAAK,MAAM,IAAI;AAAA,EACpB,OAAO,KAAK,MAAM,IAAI;AAAA,EACtB,QAAQ,KAAK,MAAM,IAAI;AAAA,EACvB,MAAM,KAAK,MAAM,IAAI;AAAA,EACrB,SAAS,KAAK,MAAM,IAAI;AAAA,EACxB,MAAM,KAAK,MAAM,IAAI;AAAA,EACrB,MAAM,KAAK,MAAM,IAAI;AAAA,EACrB,OAAO,KAAK,YAAY,IAAI;AAAA,EAC5B,MAAM,KAAK,YAAY,IAAI;AAAA,EAC3B,OAAO,KAAK,MAAM,IAAI;AAAA,EACtB,SAAS,KAAK,MAAM,IAAI;AAC1B;;;ACtBO,IAAM,uBAAuB,oBAAI,IAAI;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAOM,SAAS,iBAAiB,GAAuB;AACtD,SAAO,EAAE,MAAM,CAAC,MAAM,MAAM,QAAS,OAAO,MAAM,YAAY,OAAO,MAAM,UAAW;AACxF;AA6EO,SAAS,UACd,MACA,OACA,UAA4B,CAAC,GACpB;AACT,QAAM;AAAA,IACJ,qBAAqB;AAAA,IACrB,YAAY;AAAA,IACZ,eAAe;AAAA,IACf;AAAA,EACF,IAAI;AAGJ,MAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC7C,WAAO,uBAAuB,iBAAiB,QAAQ;AAAA,EACzD;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO,uBAAuB,iBAAiB,QAAQ;AAAA,EACzD;AAIA,MAAI,MAAM,QAAQ,IAAI,KAAK,MAAM,QAAQ,KAAK,GAAG;AAC/C,QACE,cAAc,uBACd,iBAAiB,IAAI,KACrB,iBAAiB,KAAK,GACtB;AACA,aAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC,CAAC;AAAA,IACzC;AACA,WAAO,uBAAuB,iBAAiB,QAAQ;AAAA,EACzD;AAGA,MAAI,MAAM,QAAQ,IAAI,KAAK,MAAM,QAAQ,KAAK,GAAG;AAC/C,WAAO,uBAAuB,iBAAiB,QAAQ;AAAA,EACzD;AAGA,QAAM,UAAU;AAChB,QAAM,WAAW;AACjB,QAAM,MAA+B,EAAE,GAAG,QAAQ;AAElD,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,QAAQ,GAAG;AAC7C,QAAI,gBAAgB,qBAAqB,IAAI,CAAC,EAAG;AAEjD,UAAM,WAAW,IAAI,CAAC;AACtB,QACE,MAAM,QACN,OAAO,MAAM,YACb,CAAC,MAAM,QAAQ,CAAC,KAChB,aAAa,QACb,OAAO,aAAa,YACpB,CAAC,MAAM,QAAQ,QAAQ,GACvB;AAEA,UAAI,CAAC,IAAI,UAAU,UAAU,GAAG,OAAO;AAAA,IACzC,WAAW,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,QAAQ,GAAG;AAMtD,UAAI,8BAA8B,CAAC,iBAAiB,CAAC,GAAG;AACtD,mCAA2B,GAAG,SAAS,QAAQ,EAAE,MAAM;AAAA,MACzD;AACA,UAAI,CAAC,IAAI,UAAU,UAAU,GAAG,OAAO;AAAA,IACzC,WAAW,MAAM,QAAW;AAG1B,UACE,8BACA,MAAM,QAAQ,CAAC,KACf,CAAC,iBAAiB,CAAC,GACnB;AACA,cAAM,cAAc,MAAM,QAAQ,QAAQ,IAAI,SAAS,SAAS;AAChE,mCAA2B,GAAG,aAAa,EAAE,MAAM;AAAA,MACrD;AACA,UAAI,CAAC,IAAI;AAAA,IACX;AAAA,EAIF;AAEA,SAAO;AACT;;;AChMO,SAAS,eAAe,KAAsB;AACnD,SAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;;;ACFO,SAAS,cAAiB,OAA6B,OAAmB;AAC/E,MAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,UAAM,MAAM,IAAI,MAAM,QAAQ,YAAY,KAAK,mBAAmB,8BAA8B;AAChG,QAAI,OAAO;AACX,UAAM;AAAA,EACR;AACA,SAAO;AACT;;;ACaO,SAAS,uBAAuB,UAA0C;AAC/E,QAAM,kBAA4B,CAAC;AACnC,QAAM,qBAA+B,CAAC;AACtC,MAAI,kBAAkB;AACtB,MAAI,UAAU;AACd,QAAM,MAAiB,CAAC;AAExB,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAM,WAAW,cAAc,SAAS,CAAC,CAAC;AAC1C,QAAI,MAAM;AAEV,QAAI,WAAW,GAAG,GAAG;AACnB,YAAM,UAAU,cAAc,SAAS,IAAI,CAAC,CAAC;AAC7C,YAAM,WAAW,WAAW,KAAK,CAAC,WAAW;AAC3C,cAAM,OAAuB,CAAC;AAC9B,mBAAW,SAAS,QAAQ;AAC1B,cAAI,MAAM,SAAS,cAAc,CAAC,QAAQ,IAAI,MAAM,EAAE,GAAG;AACvD,4BAAgB,KAAK,MAAM,EAAE;AAC7B,sBAAU;AACV;AAAA,UACF;AACA,eAAK,KAAK,KAAK;AAAA,QACjB;AACA,eAAO;AAAA,MACT,CAAC;AACD,YAAM,YAAY;AAAA,IACpB;AAEA,QAAI,cAAc,GAAG,GAAG;AACtB,YAAM,UAAU,WAAW,IAAI,IAAI,SAAS,CAAC,CAAC;AAC9C,YAAM,WAAW,WAAW,KAAK,CAAC,WAAW;AAC3C,cAAM,OAAuB,CAAC;AAC9B,mBAAW,SAAS,QAAQ;AAC1B,cAAI,MAAM,SAAS,iBAAiB,CAAC,QAAQ,IAAI,MAAM,WAAW,GAAG;AACnE,+BAAmB,KAAK,MAAM,WAAW;AACzC,sBAAU;AACV;AAAA,UACF;AACA,eAAK,KAAK,KAAK;AAAA,QACjB;AACA,eAAO;AAAA,MACT,CAAC;AACD,YAAM,YAAY;AAAA,IACpB;AAEA,QAAI,eAAe,GAAG,GAAG;AACvB;AACA,gBAAU;AACV;AAAA,IACF;AACA,QAAI,KAAK,GAAG;AAAA,EACd;AAEA,SAAO;AAAA,IACL,UAAU,UAAU,MAAM;AAAA,IAC1B,QAAQ,EAAE,SAAS,iBAAiB,oBAAoB,gBAAgB;AAAA,EAC1E;AACF;AAEA,SAAS,WAAW,KAAmC;AACrD,SAAO,cAAc,GAAG,EAAE,KAAK,CAAC,MAAyB,EAAE,SAAS,UAAU;AAChF;AAEA,SAAS,cAAc,KAAmC;AACxD,SAAO,cAAc,GAAG,EAAE,KAAK,CAAC,MAA4B,EAAE,SAAS,aAAa;AACtF;AAEA,SAAS,WAAW,KAAuC;AACzD,QAAM,MAAM,oBAAI,IAAY;AAC5B,MAAI,KAAK,SAAS,YAAa,QAAO;AACtC,aAAW,SAAS,cAAc,GAAG,GAAG;AACtC,QAAI,MAAM,SAAS,WAAY,KAAI,IAAI,MAAM,EAAE;AAAA,EACjD;AACA,SAAO;AACT;AAEA,SAAS,cAAc,KAAuC;AAC5D,QAAM,MAAM,oBAAI,IAAY;AAC5B,MAAI,KAAK,SAAS,OAAQ,QAAO;AACjC,aAAW,SAAS,cAAc,GAAG,GAAG;AACtC,QAAI,MAAM,SAAS,cAAe,KAAI,IAAI,MAAM,WAAW;AAAA,EAC7D;AACA,SAAO;AACT;AAEA,SAAS,cAAc,KAA0C;AAC/D,SAAO,OAAO,MAAM,QAAQ,IAAI,OAAO,IAAI,IAAI,UAAU,CAAC;AAC5D;AAEA,SAAS,WACP,KACA,IACgB;AAChB,MAAI,CAAC,MAAM,QAAQ,IAAI,OAAO,EAAG,QAAO;AACxC,QAAM,OAAO,GAAG,IAAI,OAAO;AAC3B,MAAI,KAAK,WAAW,IAAI,QAAQ,UAAU,KAAK,MAAM,CAAC,GAAG,QAAQ,MAAM,IAAI,QAAQ,GAAG,CAAC,GAAG;AACxF,WAAO;AAAA,EACT;AACA,SAAO,EAAE,GAAG,KAAK,SAAS,KAAK;AACjC;AAEA,SAAS,eAAe,KAAuB;AAC7C,MAAI,OAAO,IAAI,YAAY,SAAU,QAAO,IAAI,QAAQ,KAAK,EAAE,WAAW;AAC1E,SAAO,IAAI,QAAQ,WAAW;AAChC;;;ACpHA,IAAM,kBAAkB;AAGxB,IAAM,qBAA4C;AAAA,EAChD;AAAA;AAAA,EACA;AAAA;AACF;AAYO,SAAS,iBAAiB,SAAiB,OAA4C;AAC5F,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO,EAAE,IAAI,OAAO,QAAQ,2BAA2B;AAAA,EACzD;AACA,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO,EAAE,IAAI,OAAO,QAAQ,mBAAmB;AAAA,EACjD;AACA,MAAI,QAAQ,SAAS,iBAAiB;AACpC,WAAO,EAAE,IAAI,OAAO,QAAQ,mBAAmB,eAAe,cAAc;AAAA,EAC9E;AACA,aAAW,MAAM,oBAAoB;AACnC,QAAI,GAAG,KAAK,OAAO,GAAG;AACpB,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,QACE;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AACA,MAAI;AACF,WAAO,EAAE,IAAI,MAAM,OAAO,IAAI,OAAO,SAAS,KAAK,EAAE;AAAA,EACvD,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ,eAAe,QAAQ,IAAI,UAAU;AAAA,IAC/C;AAAA,EACF;AACF;;;ACjDO,SAAS,UAAuB,OAAe,WAAW,KAA+B;AAC9F,MAAI,MAAM,SAAS,UAAU;AAC3B,WAAO,EAAE,IAAI,OAAO,OAAO,wBAAwB,QAAQ,UAAU;AAAA,EACvE;AACA,MAAI;AACF,WAAO,EAAE,IAAI,MAAM,OAAO,KAAK,MAAM,KAAK,EAAO;AAAA,EACnD,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO,eAAe,GAAG;AAAA,IAC3B;AAAA,EACF;AACF;;;ACpBA,YAAY,UAAU;AAcf,SAAS,kBAAkB,KAAa,WAAmB,QAAwB;AACxF,MAAI,CAAC,aAAa,UAAU,SAAS,IAAI,KAAK,UAAU,SAAS,IAAI,GAAG;AACtE,UAAM,QAAQ,SAAS;AAAA,EACzB;AACA,QAAM,WAAgB,aAAQ,KAAK,GAAG,SAAS,GAAG,MAAM,EAAE;AAC1D,QAAM,MAAW,cAAc,aAAQ,GAAG,GAAG,QAAQ;AACrD,MAAI,IAAI,WAAW,IAAI,KAAU,gBAAW,GAAG,GAAG;AAChD,UAAM,QAAQ,SAAS;AAAA,EACzB;AACA,SAAO;AACT;AAEA,SAAS,QAAQ,WAA4B;AAC3C,SAAO,IAAI,QAAQ;AAAA,IACjB,SAAS,sBAAsB,SAAS;AAAA,IACxC,MAAM,YAAY;AAAA,IAClB,MAAM;AAAA,IACN,SAAS,EAAE,QAAQ,iBAAiB;AAAA,EACtC,CAAC;AACH;;;ACtBO,SAAS,QAAQ,MAAc,WAAW,UAAU,SAAS,IAAY;AAC9E,SACE,KACG,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,YAAY,EAAE,EACtB,MAAM,GAAG,MAAM,EACf,QAAQ,QAAQ,EAAE,KAAK;AAE9B;;;ACpBA,SAAS,mBAAmB;AAK5B,IAAM,WAAW;AACjB,IAAM,eAAe,SAAS;AAC9B,IAAM,WAAW;AACjB,IAAM,aAAa;AAEnB,SAAS,WAAW,KAAa,KAAqB;AACpD,MAAI;AACJ,MAAI,MAAM;AACV,WAAS,IAAI,MAAM,GAAG,KAAK,GAAG,KAAK;AACjC,UAAM,MAAM;AACZ,UAAM,SAAS,GAAG,IAAI;AACtB,WAAO,MAAM,OAAO;AAAA,EACtB;AACA,SAAO;AACT;AAEA,SAAS,aAAa,KAAqB;AACzC,QAAM,QAAQ,YAAY,GAAG;AAC7B,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,WAAO,SAAU,MAAM,CAAC,IAAe,YAAY;AAAA,EACrD;AACA,SAAO;AACT;AASO,SAAS,KAAK,WAAmB,KAAK,IAAI,GAAW;AAC1D,SAAO,WAAW,UAAU,QAAQ,IAAI,aAAa,UAAU;AACjE;;;ACvCA,SAAS,kBAAkB;AAC3B,YAAY,QAAQ;AACpB,YAAYC,WAAU;AAkHf,SAAS,YAAY,SAAyB;AACnD,SAAO,WAAW,QAAQ,EAAE,OAAY,cAAQ,OAAO,CAAC,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AACrF;AAMO,SAAS,YAAY,SAAyB;AACnD,QAAM,OAAOC,SAAa,eAAS,OAAO,CAAC;AAC3C,QAAM,OAAO,WAAW,QAAQ,EAAE,OAAY,cAAQ,OAAO,CAAC,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,CAAC;AACxF,SAAO,GAAG,IAAI,IAAI,IAAI;AACxB;AAGA,SAASA,SAAQ,MAAsB;AACrC,SACE,KACG,YAAY,EAEZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,YAAY,EAAE,EACtB,MAAM,GAAG,EAAE,KAAK;AAEvB;AAqBO,SAAS,mBAA2B;AACzC,QAAM,UAAU,QAAQ,IAAI,iBAAiB;AAC7C,MAAI,WAAW,QAAQ,KAAK,EAAE,SAAS,EAAG,QAAY,cAAQ,OAAO;AACrE,SAAY,WAAQ,WAAQ,GAAG,aAAa;AAC9C;AAEO,SAAS,mBAAmB,MAAsC;AAGvE,QAAM,aACJ,KAAK,eAAe,KAAK,WAAgB,WAAK,KAAK,UAAU,aAAa,IAAI,iBAAiB;AAKjG,QAAM,UAAU,KAAK,YAAe,WAAQ;AAC5C,QAAM,OAAO,YAAY,KAAK,WAAW;AACzC,QAAM,OAAO,YAAY,KAAK,WAAW;AACzC,QAAM,aAAkB,WAAK,YAAY,YAAY,IAAI;AACzD,SAAO;AAAA,IACL;AAAA,IACA,aAAa,KAAK;AAAA,IAClB;AAAA,IACA,WAAW;AAAA,IACX,cAAmB,WAAK,YAAY,aAAa;AAAA,IACjD,YAAiB,WAAK,YAAY,MAAM;AAAA,IACxC,cAAmB,WAAK,YAAY,WAAW;AAAA,IAC/C,cAAmB,WAAK,YAAY,QAAQ;AAAA,IAC5C,oBAAyB,WAAK,SAAS,WAAW,QAAQ;AAAA,IAC1D,kBAAuB,WAAK,YAAY,aAAa;AAAA,IACrD,eAAoB,WAAK,YAAY,SAAS;AAAA,IAC9C,oBAAyB,WAAK,YAAY,cAAc;AAAA,IACxD,aAAkB,WAAK,YAAY,mBAAmB;AAAA,IACtD,UAAe,WAAK,YAAY,OAAO;AAAA,IACvC,aAAkB,WAAK,YAAY,SAAS,iBAAiB;AAAA,IAC7D,oBAAyB,WAAK,YAAY,SAAS,qBAAqB;AAAA,IACxE,aAAkB,WAAK,YAAY,SAAS;AAAA,IAC5C,SAAc,WAAK,YAAY,QAAQ,gBAAgB;AAAA,IACvD;AAAA,IACA,sBAA2B,WAAK,YAAY,gBAAgB;AAAA,IAC5D,eAAoB,WAAK,YAAY,WAAW;AAAA,IAChD,iBAAsB,WAAK,YAAY,UAAU;AAAA,IACjD,cAAmB,WAAK,YAAY,YAAY;AAAA,IAChD,aAAkB,WAAK,YAAY,WAAW;AAAA,IAC9C,oBAAyB,WAAK,YAAY,mBAAmB;AAAA,IAC7D,iBAAsB,WAAK,KAAK,aAAa,eAAe,aAAa;AAAA,IACzE,qBAA0B,WAAK,KAAK,aAAa,eAAe,WAAW;AAAA,IAC3E,iBAAsB,WAAK,KAAK,aAAa,eAAe,QAAQ;AAAA,IACpE,uBAA4B,WAAK,KAAK,aAAa,WAAW,QAAQ;AAAA,IACtE,kBAAuB,WAAK,KAAK,aAAa,eAAe,SAAS;AAAA,IACtE,uBAA4B,WAAK,KAAK,aAAa,eAAe,cAAc;AAAA,IAChF,qBAA0B,WAAK,KAAK,aAAa,eAAe,aAAa;AAAA,IAC7E,oBAAyB,WAAK,KAAK,aAAa,eAAe,WAAW;AAAA,IAC1E,aAAa;AAAA,IACb,aAAa;AAAA,IACb,aAAkB,WAAK,YAAY,WAAW;AAAA,IAC9C,qBAA0B,WAAK,YAAY,oBAAoB;AAAA,IAC/D,cAAmB,WAAK,YAAY,OAAO;AAAA,IAC3C,mBAAwB,WAAK,YAAY,aAAa;AAAA,IACtD,mBAAwB,WAAK,YAAY,kBAAkB;AAAA,IAC3D,aAAkB,WAAK,YAAY,WAAW;AAAA,IAC9C,kBAAuB,WAAK,YAAY,WAAW;AAAA,IACnD,kBAAuB,WAAK,YAAY,YAAY;AAAA,IACpD,YAAiB,WAAK,YAAY,WAAW;AAAA,IAC7C,eAAe,CAACC,iBAA6B,WAAK,YAAY,YAAYA,cAAa,aAAa;AAAA,EACtG;AACF;;;AC3MO,IAAM,cAAc;AAAA;AAAA,EAEzB,uBAAuB;AAAA,EACvB,sBAAsB;AAAA,EACtB,qBAAqB;AAAA,EACrB,0BAA0B;AAAA,EAC1B,uBAAuB;AAAA,EACvB,wBAAwB;AAAA,EACxB,2BAA2B;AAAA;AAAA,EAE3B,gBAAgB;AAAA,EAChB,wBAAwB;AAAA,EACxB,uBAAuB;AAAA,EACvB,cAAc;AAAA,EACd,oBAAoB;AAAA;AAAA,EAEpB,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,qBAAqB;AAAA,EACrB,yBAAyB;AAAA;AAAA,EAEzB,oBAAoB;AAAA,EACpB,qBAAqB;AAAA,EACrB,2BAA2B;AAAA;AAAA,EAE3B,uBAAuB;AAAA,EACvB,wBAAwB;AAAA,EACxB,eAAe;AAAA,EACf,kBAAkB;AAAA;AAAA,EAElB,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,sBAAsB;AAAA;AAAA,EAEtB,+BAA+B;AAAA,EAC/B,2BAA2B;AAAA,EAC3B,+BAA+B;AAAA,EAC/B,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,kBAAkB;AAAA;AAAA,EAElB,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,wBAAwB;AAAA;AAAA,EAExB,uBAAuB;AAAA,EACvB,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,eAAe;AAAA;AAAA,EAEf,kBAAkB;AAAA,EAClB,cAAc;AAAA,EACd,SAAS;AACX;AAwBO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,MAQT;AACD,UAAM,KAAK,SAAS,EAAE,OAAO,KAAK,MAAM,CAAC;AACzC,SAAK,OAAO;AACZ,SAAK,OAAO,KAAK;AACjB,SAAK,YAAY,KAAK;AACtB,SAAK,WAAW,KAAK,YAAY;AACjC,SAAK,cAAc,KAAK,eAAe;AACvC,SAAK,UAAU,KAAK;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAmB;AACjB,UAAM,MAAM,KAAK,UAAU,IAAI,cAAc,KAAK,OAAO,CAAC,KAAK;AAC/D,WAAO,GAAG,KAAK,IAAI,KAAK,KAAK,OAAO,GAAG,GAAG;AAAA,EAC5C;AACF;AAEA,SAAS,cAAc,KAAsC;AAC3D,QAAM,QAAQ,OAAO,QAAQ,GAAG,EAC7B,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,MAAM,MAAS,EACjC,MAAM,GAAG,CAAC,EACV,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,EAAE;AACtC,SAAO,MAAM,SAAS,IAAI,IAAI,MAAM,KAAK,GAAG,CAAC,MAAM;AACrD;AAyCO,IAAM,cAAN,cAA0B,gBAAgB;AAAA,EAC/C,YAAY,MAQT;AACD,UAAM;AAAA,MACJ,SAAS,KAAK;AAAA,MACd,MAAM,KAAK;AAAA,MACX,WAAW;AAAA,MACX,UAAU;AAAA,MACV,aAAa;AAAA,MACb,SAAS,KAAK;AAAA,MACd,OAAO,KAAK;AAAA,IACd,CAAC;AACD,SAAK,OAAO;AAAA,EACd;AACF;AAkFO,IAAM,eAAN,cAA2B,gBAAgB;AAAA,EACvC;AAAA,EAET,YAAY,MAMT;AACD,UAAM;AAAA,MACJ,SAAS,KAAK;AAAA,MACd,MAAM,KAAK;AAAA,MACX,WAAW;AAAA,MACX,UAAU,KAAK,SAAS,YAAY,uBAAuB,UAAU;AAAA,MACrE,aAAa,KAAK,SAAS,YAAY;AAAA,MACvC,SAAS,EAAE,WAAW,KAAK,WAAW,GAAG,KAAK,QAAQ;AAAA,MACtD,OAAO,KAAK;AAAA,IACd,CAAC;AACD,SAAK,OAAO;AACZ,SAAK,YAAY,KAAK;AAAA,EACxB;AACF;AAgCO,IAAM,UAAN,cAAsB,gBAAgB;AAAA,EAClC;AAAA,EAET,YAAY,MAST;AACD,UAAM;AAAA,MACJ,SAAS,KAAK;AAAA,MACd,MAAM,KAAK;AAAA,MACX,WAAW;AAAA,MACX,UAAU;AAAA,MACV,aAAa,KAAK,SAAS,YAAY;AAAA,MACvC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,KAAK,QAAQ;AAAA,MAC5C,OAAO,KAAK;AAAA,IACd,CAAC;AACD,SAAK,OAAO;AACZ,SAAK,OAAO,KAAK;AAAA,EACnB;AACF;;;AdhWA,eAAsB,YACpB,YACA,SACA,OAA2B,CAAC,GACb;AACf,QAAM,MAAW,cAAQ,UAAU;AACnC,QAAS,SAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACvC,QAAM,MAAW,WAAK,KAAK,IAAS,eAAS,UAAU,CAAC,IAAIC,aAAY,CAAC,EAAE,SAAS,KAAK,CAAC,MAAM;AAIhG,MAAI;AACF,QAAI,OAAO,YAAY,UAAU;AAC/B,YAAS,aAAU,KAAK,SAAS,EAAE,MAAM,MAAM,UAAU,KAAK,YAAY,OAAO,CAAC;AAAA,IACpF,OAAO;AACL,YAAS,aAAU,KAAK,SAAS,EAAE,MAAM,KAAK,CAAC;AAAA,IACjD;AACA,QAAI;AACF,YAAM,KAAK,MAAS,QAAK,KAAK,IAAI;AAClC,UAAI;AACF,cAAM,GAAG,KAAK;AAAA,MAChB,UAAE;AACA,cAAM,GAAG,MAAM;AAAA,MACjB;AAAA,IACF,QAAQ;AAAA,IAER;AAGA,QAAI;AACJ,QAAI;AACF,YAAMC,SAAO,MAAS,QAAK,UAAU;AACrC,aAAOA,OAAK,OAAO;AAAA,IACrB,QAAQ;AACN,aAAO,KAAK;AAAA,IACd;AACA,QAAI,SAAS,QAAW;AACtB,YAAS,SAAM,KAAK,IAAI;AAAA,IAC1B;AACA,UAAM,gBAAgB,KAAK,UAAU;AASrC,QAAI,SAAS,UAAa,QAAQ,aAAa,SAAS;AACtD,UAAI;AACF,cAAS,SAAM,YAAY,IAAI;AAAA,MACjC,QAAQ;AAAA,MAGR;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,QAAI;AACF,YAAS,UAAO,GAAG;AAAA,IACrB,QAAQ;AAAA,IAER;AACA,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,UAAU,KAA4B;AAC1D,QAAS,SAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACzC;AAEA,eAAsB,aACpB,YACA,IACA,OAAwB,CAAC,GACb;AACZ,QAAM,MAAW,cAAQ,UAAU;AACnC,QAAS,SAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACvC,QAAM,WAAgB,WAAK,KAAK,IAAS,eAAS,UAAU,CAAC,OAAO;AACpE,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,UAAU,KAAK,IAAI;AACzB,MAAI;AAEJ,aAAS;AACP,QAAI;AACF,eAAS,MAAS,QAAK,UAAU,IAAI;AACrC,YAAM,OAAO,UAAU,GAAG,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC,EAAE;AACrD;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,OAAQ,IAA8B;AAG5C,UAAI,SAAS,UAAU;AACrB,cAAS,SAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACvC;AAAA,MACF;AACA,UAAI,SAAS,SAAU,OAAM;AAC7B,UAAI;AACF,cAAMA,SAAO,MAAS,QAAK,QAAQ;AACnC,YAAI,KAAK,IAAI,IAAIA,OAAK,UAAU,SAAS;AACvC,gBAAS,UAAO,QAAQ;AACxB;AAAA,QACF;AAAA,MACF,QAAQ;AACN;AAAA,MACF;AACA,UAAI,KAAK,IAAI,IAAI,WAAW,WAAW;AACrC,cAAM,IAAI,QAAQ;AAAA,UAChB,SAAS,oCAAoC,UAAU;AAAA,UACvD,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS,EAAE,UAAU;AAAA,QACvB,CAAC;AAAA,MACH;AACA,YAAM,IAAI,QAAQ,CAACC,cAAY,WAAWA,WAAS,EAAE,CAAC;AAAA,IACxD;AAAA,EACF;AAEA,MAAI;AACF,WAAO,MAAM,GAAG;AAAA,EAClB,UAAE;AACA,QAAI;AACF,YAAM,QAAQ,MAAM;AAAA,IACtB,QAAQ;AAAA,IAER;AACA,QAAI;AACF,YAAS,UAAO,QAAQ;AAAA,IAC1B,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAMA,IAAM,yBAAyB,oBAAI,IAAI,CAAC,SAAS,SAAS,UAAU,WAAW,CAAC;AAEhF,eAAe,gBAAgB,MAAc,IAA2B;AACtE,MAAI,QAAQ,aAAa,SAAS;AAChC,UAAS,UAAO,MAAM,EAAE;AACxB;AAAA,EACF;AACA,QAAM,SAAS,CAAC,IAAI,IAAI,IAAI,KAAK,GAAG;AACpC,MAAI;AACJ,WAAS,IAAI,GAAG,KAAK,OAAO,QAAQ,KAAK;AACvC,QAAI;AACF,YAAS,UAAO,MAAM,EAAE;AACxB;AAAA,IACF,SAAS,KAAK;AACZ,gBAAU;AACV,YAAM,OAAQ,KAA+B;AAC7C,UAAI,CAAC,QAAQ,CAAC,uBAAuB,IAAI,IAAI,KAAK,MAAM,OAAO,QAAQ;AACrE,cAAM;AAAA,MACR;AACA,YAAM,IAAI,QAAQ,CAACA,cAAY,WAAWA,WAAS,OAAO,CAAC,CAAC,CAAC;AAAA,IAC/D;AAAA,EACF;AACA,QAAM;AACR;;;AehLA,SAAS,WAAW,WAAW,UAAU,iBAAiB;AAC1D,YAAY,SAAS;AACrB,YAAYC,WAAU;;;ACMf,SAAS,eAAe,SAA0C;AACvE,QAAM,OACJ,OAAO,YAAY,WACf,UACA,QACG,OAAO,CAAC,MAA2C,EAAE,SAAS,MAAM,EACpE,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,KAAK,GAAG;AACjB,UAAQ,QAAQ,oBAAoB,MAAM,GAAG,EAAE;AACjD;;;ADSO,IAAM,oBAAN,MAAM,mBAA2C;AAAA,EAoNtD,YACkB,IACR,QACS,WACA,MACA,QACjB,OAQI,CAAC,GACL,SACA;AAfgB;AACR;AACS;AACA;AACA;AAYjB,SAAK,UAAU,KAAK,WAAW;AAI/B,SAAK,eAAe,KAAK,MAAW,WAAK,KAAK,KAAK,GAAQ,eAAS,EAAE,CAAC,eAAe,IAAI;AAC1F,SAAK,WAAW,KAAK,YAAY;AACjC,SAAK,iBAAiB,KAAK;AAC3B,SAAK,gBAAgB,KAAK;AAC1B,SAAK,YAAY,KAAK;AACtB,SAAK,UAAU;AAAA,MACb;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA,OAAO,KAAK,SAAS;AAAA,MACrB,UAAU,KAAK,YAAY;AAAA,MAC3B,YAAY;AAAA,IACd;AAIA,SAAK,UAAU;AAAA,EACjB;AAAA,EArCkB;AAAA,EACR;AAAA,EACS;AAAA,EACA;AAAA,EACA;AAAA,EAxNX,SAAS;AAAA,EACT,eAAqC;AAAA,EACrC;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,WAAW;AAAA,EACF;AAAA,EACjB,IAAI,iBAAqC;AACvC,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,cAAoC;AAAA,EACpC,aAA4B;AAClC,QAAI,CAAC,KAAK,YAAa,MAAK,cAAc,KAAK,sBAAsB;AACrE,WAAO,KAAK;AAAA,EACd;AAAA,EACiB;AAAA,EACT,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,cAA8B,CAAC;AAAA,EAC/B,aAAmD;AAAA,EAC3D,OAAwB,oBAAoB;AAAA,EAC5C,OAAwB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAY7B,aAA4B,QAAQ,QAAQ;AAAA,EAC5C,aAA4B,QAAQ,QAAQ;AAAA;AAAA,EAG5C,aAAa,MAA6B;AAChD,UAAM,QAAQ,KAAK,WAAW,KAAK,MAAM,KAAK,OAAO,WAAW,MAAM,MAAM,CAAC;AAC7E,SAAK,aAAa,MAAM;AAAA,MACtB,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,gBAAwC,CAAC;AAAA,EACzC,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,UAAqC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUrC,WAAW,OAAmC;AACpD,UAAM,IAAI,KAAK;AACf,QAAI,MAAM,SAAS,oBAAoB;AACrC,aAAO;AAAA,QACL,GAAG;AAAA,QACH,UAAU,MAAM,SAAS,IAAI,CAAC,YAAY;AACxC,gBAAM,EAAE,YAAY,UAAU,GAAG,UAAU,IAAI;AAC/C,iBAAO;AAAA,YACL,GAAG;AAAA,YACH,SACE,OAAO,UAAU,YAAY,WACxB,GAAG,MAAM,UAAU,OAAO,KAAK,UAAU,UACzC,GAAG,YAAY,UAAU,OAAO,KAAK,UAAU;AAAA,UACxD;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AACA,QAAI,CAAC,EAAG,QAAO;AACf,QAAI,MAAM,SAAS,cAAc;AAC/B,aAAO;AAAA,QACL,GAAG;AAAA,QACH,SACE,OAAO,MAAM,YAAY,WAAW,EAAE,MAAM,MAAM,OAAO,IAAI,EAAE,YAAY,MAAM,OAAO;AAAA,MAC5F;AAAA,IACF;AACA,QAAI,MAAM,SAAS,gBAAgB;AACjC,aAAO,EAAE,GAAG,OAAO,SAAS,EAAE,YAAY,MAAM,OAAO,EAAE;AAAA,IAC3D;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,uBAKH,CAAC;AAAA;AAAA,EAEE,oBAAmC;AAAA;AAAA,EAEnC,eAAe,oBAAI,IAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO/B,uBAAuB,OAA2B;AACxD,QAAI,KAAK,OAAQ;AACjB,SAAK,KAAK,WAAW;AACrB,SAAK,kBAAkB,KAAK;AAC5B,SAAK,YAAY,KAAK,KAAK;AAC3B,QAAI,KAAK,YAAY,UAAU,mBAAkB,YAAY;AAC3D,UAAI,KAAK,YAAY;AACnB,qBAAa,KAAK,UAAU;AAC5B,aAAK,aAAa;AAAA,MACpB;AACA,WAAK,KAAK,YAAY,EAAE,MAAM,MAAM;AAAA,MAEpC,CAAC;AAAA,IACH,OAAO;AACL,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AAAA,EAEA,iBAAiB,OAKR;AACP,QAAI,KAAK,OAAQ;AACjB,QAAI,KAAK,sBAAsB,MAAM;AAInC,WAAK,qBAAqB,KAAK,KAAK;AACpC;AAAA,IACF;AAMA,UAAM,QAAsB;AAAA,MAC1B,MAAM;AAAA,MACN,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC3B,aAAa,KAAK;AAAA,MAClB,OAAO,CAAC,KAAK;AAAA,IACf;AACA,SAAK,uBAAuB,KAAK;AAAA,EACnC;AAAA,EAEA,sBAAsB,OAKb;AACP,QAAI,CAAC,MAAM,QAAQ,CAAC,iBAAiB,KAAK,MAAM,IAAI,KAAK,CAAC,OAAO,SAAS,MAAM,OAAO,EAAG;AAC1F,SAAK,uBAAuB;AAAA,MAC1B,MAAM;AAAA,MACN,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC3B,MAAM,MAAM;AAAA,MACZ,MAAM,MAAM,KAAK,YAAY;AAAA,MAC7B,SAAS,MAAM;AAAA,MACf,QAAQ,MAAM;AAAA,IAChB,CAAC;AAAA,EACH;AAAA,EAEA,iBAAiB,OAMR;AAEP,SAAK,OAAO;AAAA,MACV,MAAM;AAAA,MACN,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC3B,WAAW,MAAM;AAAA,MACjB,UAAU,MAAM;AAAA,MAChB,OAAO,MAAM;AAAA,MACb,SAAS,MAAM;AAAA,MACf,MAAM,MAAM;AAAA,IACd,CAAC,EAAE,MAAM,MAAM;AAAA,IAAoB,CAAC;AAAA,EACtC;AAAA,EA0CA,IAAI,kBAA4B;AAC9B,WAAO,MAAM,KAAK,KAAK,YAAY;AAAA,EACrC;AAAA,EAEA,MAAc,wBAAuC;AAKnD,SAAK,YAAY,KAAK;AAAA,MACpB,MAAM,KAAK,UAAU,oBAAoB;AAAA,MACzC,IAAI,KAAK;AAAA,MACT,IAAI,KAAK;AAAA,MACT,OAAO,KAAK,KAAK,SAAS;AAAA,MAC1B,UAAU,KAAK,KAAK,YAAY;AAAA,IAClC,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAO,OAAoC;AAC/C,QAAI,KAAK,OAAQ;AACjB,UAAM,KAAK,WAAW;AAItB,UAAM,WAAW,KAAK,WAAW,KAAK;AAKtC,SAAK,kBAAkB,QAAQ;AAC/B,SAAK,YAAY,KAAK,QAAQ;AAE9B,QAAI,KAAK,YAAY,UAAU,mBAAkB,YAAY;AAG3D,UAAI,KAAK,YAAY;AACnB,qBAAa,KAAK,UAAU;AAC5B,aAAK,aAAa;AAAA,MACpB;AACA,YAAM,KAAK,YAAY,EAAE,MAAM,MAAM;AAAA,MAIrC,CAAC;AAAA,IACH,OAAO;AACL,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,QAAuC;AACvD,QAAI,KAAK,UAAU,OAAO,WAAW,EAAG;AACxC,UAAM,KAAK,WAAW;AACtB,eAAW,SAAS,QAAQ;AAC1B,YAAM,WAAW,KAAK,WAAW,KAAK;AACtC,WAAK,kBAAkB,QAAQ;AAC/B,WAAK,YAAY,KAAK,QAAQ;AAAA,IAChC;AACA,QAAI,KAAK,YAAY,UAAU,mBAAkB,YAAY;AAC3D,UAAI,KAAK,YAAY;AACnB,qBAAa,KAAK,UAAU;AAC5B,aAAK,aAAa;AAAA,MACpB;AACA,YAAM,KAAK,YAAY,EAAE,MAAM,MAAM;AAAA,MAErC,CAAC;AAAA,IACH,OAAO;AACL,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,QAAuB;AAC3B,QAAI,KAAK,OAAQ;AACjB,QAAI,KAAK,YAAY;AACnB,mBAAa,KAAK,UAAU;AAC5B,WAAK,aAAa;AAAA,IACpB;AACA,UAAM,KAAK,YAAY;AACvB,UAAM,KAAK;AACX,UAAM,KAAK,OAAO,SAAS;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,YAAkB;AAChB,QAAI,KAAK,YAAY,WAAW,KAAK,CAAC,KAAK,SAAU;AACrD,QAAI,KAAK,YAAY;AACnB,mBAAa,KAAK,UAAU;AAC5B,WAAK,aAAa;AAAA,IACpB;AACA,UAAM,QAAQ,KAAK,YAAY,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI;AAC1E,SAAK,cAAc,CAAC;AACpB,QAAI;AACJ,QAAI;AACF,WAAK,SAAS,KAAK,UAAU,GAAG;AAChC,gBAAU,IAAI,OAAO,MAAM,MAAM;AACjC,gBAAU,EAAE;AAAA,IACd,QAAQ;AAAA,IAER,UAAE;AACA,UAAI,OAAO,QAAW;AACpB,YAAI;AACF,oBAAU,EAAE;AAAA,QACd,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGQ,gBAAsB;AAC5B,QAAI,KAAK,WAAY;AACrB,SAAK,aAAa,WAAW,MAAM;AACjC,WAAK,aAAa;AAElB,WAAK,YAAY,EAAE,MAAM,MAAM;AAAA,MAG/B,CAAC;AAAA,IAEH,GAAG,mBAAkB,iBAAiB;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,cAA6B;AACnC,UAAM,QAAQ,KAAK,WAAW,KAAK,MAAM,KAAK,gBAAgB,CAAC;AAC/D,SAAK,aAAa,MAAM;AAAA,MACtB,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,kBAAiC;AAC7C,QAAI,KAAK,YAAY,WAAW,EAAG;AACnC,UAAM,SAAS,KAAK;AACpB,UAAM,aAAa,OAAO;AAC1B,UAAM,QAAQ,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI;AAChE,SAAK,cAAc,CAAC;AACpB,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI,UAAiC;AACrC,QAAI;AACJ,QAAI;AACF,YAAM,KAAK,aAAa,KAAK;AAAA,IAC/B,SAAS,KAAK;AACZ,gBAAU;AACV,iBAAW,eAAe,GAAG;AAG7B,WAAK,cAAc,CAAC,GAAG,QAAQ,GAAG,KAAK,WAAW;AAClD,WAAK,mBAAmB;AACxB,YAAM,MAAM,KAAK,IAAI;AACrB,UAAI,MAAM,KAAK,mBAAmB,KAAM;AACtC,cAAM,aAAa,KAAK,kBAAkB;AAC1C,cAAM,OAAO,aAAa,IAAI,MAAM,UAAU,iBAAiB;AAC/D,gBAAQ;AAAA,UACN;AAAA,UACA,eAAe,GAAG;AAAA,UAClB;AAAA,QACF;AACA,aAAK,mBAAmB;AACxB,aAAK,kBAAkB;AAAA,MACzB;AACA,UAAI,CAAC,KAAK,OAAQ,MAAK,cAAc;AACrC,YAAM;AAAA,IACR,UAAE;AACA,WAAK,QAAQ,KAAK,iBAAiB;AAAA,QACjC,WAAW,KAAK;AAAA,QAChB,OAAO;AAAA,QACP,UAAU,KAAK;AAAA,QACf,WAAW;AAAA,QACX;AAAA,QACA,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,GAAI,aAAa,SAAY,EAAE,OAAO,SAAS,IAAI,CAAC;AAAA,QACpD,GAAI,eAAe,SAAY,EAAE,WAAW,IAAI,CAAC;AAAA,QACjD,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,kBAAkB,OAA2B;AAKnD,QAAI,MAAM,SAAS,gBAAgB;AACjC,iBAAW,SAAS,MAAM,SAAS;AACjC,YAAI,MAAM,SAAS,WAAY,MAAK,aAAa,IAAI,MAAM,EAAE;AAAA,MAC/D;AAAA,IACF;AACA,QAAI,MAAM,SAAS,YAAY;AAC7B,WAAK,aAAa,IAAI,MAAM,EAAE;AAAA,IAChC,WAAW,MAAM,SAAS,mBAAmB;AAC3C,WAAK;AACL,WAAK,cAAc,MAAM,IAAI,KAAK,KAAK,cAAc,MAAM,IAAI,KAAK,KAAK;AAAA,IAC3E,WAAW,MAAM,SAAS,eAAe;AACvC,WAAK,aAAa,OAAO,MAAM,EAAE;AACjC,UAAI,MAAM,SAAS;AACjB,aAAK;AACL,aAAK,UAAU;AAAA,MACjB;AAAA,IACF,WAAW,MAAM,SAAS,iBAAiB;AACzC,WAAK,mBAAmB,MAAM,MAAM;AAAA,IACtC,WAAW,MAAM,SAAS,cAAc;AACtC,WAAK;AAAA,IACP;AAEA,QAAI,MAAM,SAAS,WAAW,MAAM,SAAS,kBAAkB;AAC7D,WAAK,UAAU;AAAA,IACjB;AACA,QAAI,MAAM,SAAS,gBAAgB,KAAK,QAAQ,UAAU,mBAAmB;AAC3E,WAAK,UAAU,EAAE,GAAG,KAAK,SAAS,OAAO,eAAe,MAAM,OAAO,EAAE;AAAA,IACzE,WAAW,MAAM,SAAS,gBAAgB;AACxC,WAAK,WAAW,MAAM,MAAM;AAC5B,WAAK,YAAY,MAAM,MAAM;AAC7B,WAAK,UAAU,EAAE,GAAG,KAAK,SAAS,YAAY,KAAK,UAAU,KAAK,SAAS;AAAA,IAC7E,WAAW,MAAM,SAAS,eAAe;AACvC,YAAM,QAAQ,MAAM,MAAM,QAAQ,MAAM,MAAM;AAC9C,UAAI,QAAQ,EAAG,MAAK,UAAU,EAAE,GAAG,KAAK,SAAS,YAAY,MAAM;AAAA,IACrE,WAAW,MAAM,SAAS,mBAAmB;AAC3C,WAAK;AAAA,IACP;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAI3B,QAAI,KAAK,aAAc,QAAO,KAAK;AACnC,SAAK,eAAe,KAAK,QAAQ,EAAE,MAAM,CAAC,QAAQ;AAGhD,WAAK,SAAS;AACd,WAAK,eAAe;AACpB,UAAI,KAAK,YAAY,SAAS,EAAG,MAAK,cAAc;AACpD,YAAM;AAAA,IACR,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,UAAyB;AACrC,QAAI,KAAK,qBAAqB,SAAS,GAAG;AACxC,YAAM,KAAK;AAAA,QACT,KAAK,qBAAqB;AAAA,QAC1B,CAAC,GAAG,KAAK,oBAAoB;AAAA,MAC/B;AACA,WAAK,uBAAuB,CAAC;AAAA,IAC/B;AACA,SAAK,SAAS;AAKd,QAAI,KAAK,YAAY;AACnB,mBAAa,KAAK,UAAU;AAC5B,WAAK,aAAa;AAAA,IACpB;AACA,UAAM,KAAK,YAAY;AAGvB,UAAM,KAAK;AACX,UAAM,KAAK,OAAO,SAAS;AAE3B,SAAK,UAAU;AAAA,MACb,GAAG,KAAK;AAAA,MACR,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,MAChC,gBAAgB,KAAK;AAAA,MACrB,eAAe,KAAK;AAAA,MACpB,gBAAgB,KAAK;AAAA,MACrB,iBAAiB,KAAK;AAAA,MACtB,iBAAiB,KAAK,kBAAkB,IAAI,KAAK,kBAAkB;AAAA,MACnE,eACE,EAAE,GAAG,KAAK,cAAc;AAAA,MAC1B,SAAS,KAAK,WAAW;AAAA,IAC3B;AAEA,QAAI,KAAK,cAAc;AACrB,YAAM,KAAK,KAAK,IAAI;AACpB,UAAI,UAAiC;AACrC,UAAI;AACJ,UAAI;AACF,cAAM,YAAY,KAAK,cAAc,KAAK,UAAU,KAAK,OAAO,GAAG,EAAE,MAAM,IAAM,CAAC;AAAA,MACpF,SAAS,KAAK;AACZ,kBAAU;AACV,mBAAW,eAAe,GAAG;AAAA,MAE/B,UAAE;AACA,aAAK,QAAQ,KAAK,iBAAiB;AAAA,UACjC,WAAW,KAAK;AAAA,UAChB,OAAO;AAAA,UACP,UAAU,KAAK;AAAA,UACf,WAAW;AAAA,UACX;AAAA,UACA,YAAY,KAAK,IAAI,IAAI;AAAA,UACzB,GAAI,aAAa,SAAY,EAAE,OAAO,SAAS,IAAI,CAAC;AAAA,UACpD,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,QAChE,CAAC;AAAA,MACH;AAAA,IACF;AAOA,UAAM,QAAQ,KAAK,IAAI;AACvB,QAAI,aAAoC;AACxC,QAAI;AACJ,QAAI;AACF,YAAM,KAAK,YAAY,KAAK,OAAO;AAAA,IAErC,SAAS,KAAK;AACZ,mBAAa;AACb,iBAAW,eAAe,GAAG;AAAA,IAE/B,UAAE;AAEA,WAAK,QAAQ,KAAK,iBAAiB;AAAA,QACjC,WAAW,KAAK,QAAQ;AAAA,QACxB,OAAO;AAAA,QACP,UAAU,KAAK;AAAA,QACf,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,GAAI,aAAa,SAAY,EAAE,OAAO,SAAS,IAAI,CAAC;AAAA,QACpD,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AAAA,IACH;AACA,QAAI;AACF,YAAM,KAAK,OAAO,MAAM;AAAA,IAC1B,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAM,gBAAgB,aAAqB,eAAsC;AAC/E,UAAM,YAAY,KAAK,qBAAqB;AAC5C,QAAI,YAAY,GAAG;AACjB,YAAM,KAAK,kBAAkB,aAAa,CAAC,GAAG,KAAK,oBAAoB,CAAC;AACxE,WAAK,uBAAuB,CAAC;AAAA,IAC/B;AACA,QAAI;AACJ,QAAI;AACF,4BAAsB,MAAM,KAAK,eAAe,QAAQ,KAAK,IAAI,WAAW;AAAA,IAC9E,SAAS,KAAK;AAIZ,cAAQ;AAAA,QACN,KAAK,UAAU;AAAA,UACb,OAAO;AAAA,UACP,OAAO;AAAA,UACP,WAAW,KAAK;AAAA,UAChB;AAAA,UACA,SAAS,eAAe,GAAG;AAAA,UAC3B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM,KAAK,OAAO;AAAA,MAChB,MAAM;AAAA,MACN,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,GAAI,sBAAsB,EAAE,oBAAoB,IAAI,CAAC;AAAA,IACvD,CAAC;AACD,SAAK,oBAAoB;AACzB,SAAK,QAAQ,KAAK,sBAAsB;AAAA,MACtC,WAAW,KAAK;AAAA,MAChB;AAAA,MACA;AAAA,MACA,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC3B;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,kBACJ,aACA,OACe;AACf,UAAM,KAAK,OAAO;AAAA,MAChB,MAAM;AAAA,MACN,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC3B;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,qBAAqB,mBAA4C;AAErE,QAAI,CAAC,KAAK,SAAU,QAAO;AAK3B,QAAI,KAAK,YAAY;AACnB,mBAAa,KAAK,UAAU;AAC5B,WAAK,aAAa;AAAA,IACpB;AACA,UAAM,KAAK,YAAY;AAEvB,UAAM,KAAK;AAIX,UAAM,aAAa;AACnB,QAAI;AACJ,QAAI,aAAa;AACjB,QAAI,kBAAkB;AACtB,QAAI,uBAAuB;AAC3B,QAAI,eAAe;AACnB,QAAI,uBAAuB;AAE3B,QAAI;AACF,WAAK,MAAU,SAAK,KAAK,UAAU,KAAK,GAAK;AAE7C,aAAO,MAAM;AACX,cAAM,MAAM,OAAO,MAAM,UAAU;AACnC,cAAM,EAAE,UAAU,IAAI,MAAM,GAAG,KAAK,KAAK,GAAG,YAAY,UAAU;AAClE,YAAI,cAAc,EAAG;AAErB,YAAI,WAAW;AACf,eAAO,WAAW,WAAW;AAC3B,gBAAM,MAAM,IAAI,QAAQ,MAAM,QAAQ;AACtC,cAAI,QAAQ,IAAI;AAEd,8BAAkB,aAAa;AAC/B;AAAA,UACF;AAEA,cAAI,yBAAyB,IAAI;AAE/B;AAAA,UACF,OAAO;AAEL,kBAAM,YAAY,IAAI,SAAS,UAAU,GAAG;AAE5C,kBAAM,OAAO,IAAI,YAAY,SAAS,EAAE,OAAO,MAAM,CAAC,EAAE,OAAO,SAAS;AACxE,gBAAI,KAAK,KAAK,GAAG;AACf,kBAAI;AACF,sBAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,oBAAI,MAAM,SAAS,cAAc;AAC/B,sBAAI,MAAM,gBAAgB,mBAAmB;AAE3C,2CAAuB;AACvB,2CAAuB;AAAA,kBACzB,WAAW,MAAM,gBAAgB,UAAa,MAAM,cAAc,mBAAmB;AAInF,2CAAuB;AAAA,kBACzB;AAAA,gBACF,WAAW,wBAAwB,MAAM,gBAAgB,UAAa,MAAM,cAAc,mBAAmB;AAE3G;AAAA,gBACF,WAAW,wBAAwB,MAAM,gBAAgB,QAAW;AAIlE;AAAA,gBACF,WAAW,CAAC,wBAAwB,MAAM,gBAAgB,QAAW;AAKnE;AAAA,gBACF,WAAW,CAAC,wBAAwB,MAAM,gBAAgB,UAAa,MAAM,cAAc,mBAAmB;AAK5G;AAAA,gBACF;AAAA,cAGF,QAAQ;AAAA,cAER;AAAA,YACF;AAAA,UACF;AAGA,qBAAW,MAAM;AACjB,4BAAkB,aAAa;AAAA,QACjC;AAEA,sBAAc;AACd,YAAI,YAAY,WAAW;AAEzB,4BAAkB;AAAA,QACpB;AAAA,MACF;AAAA,IACF,UAAE;AACA,YAAM,IAAI,MAAM;AAAA,IAClB;AAEA,QAAI,yBAAyB,GAAI,QAAO;AAKxC,UAAM,KAAK;AACX,UAAM,KAAK,OAAO,MAAM;AACxB,UAAM,UAAU,GAAG,KAAK,QAAQ;AAChC,UAAM,MAAM,MAAU,SAAK,KAAK,UAAU,KAAK,GAAK;AACpD,QAAI;AACF,YAAM,aAAa,MAAM,IAAI,KAAK;AAClC,YAAM,YAAY,WAAW;AAM7B,YAAM,cAAc;AACpB,UAAI,yBAAyB;AAE7B,UAAI,cAAc,WAAW;AAC3B,cAAM,WAAW,OAAO,MAAM,KAAK,IAAI,YAAY,YAAY,WAAW,CAAC;AAC3E,cAAM,EAAE,WAAW,UAAU,IAAI,MAAM,IAAI,KAAK,UAAU,GAAG,SAAS,QAAQ,WAAW;AACzF,YAAI,YAAY,GAAG;AACjB,gBAAM,KAAK,SAAS,QAAQ,IAAI;AAChC,mCAAyB,OAAO,KAAK,cAAc,KAAK,IAAI;AAAA,QAC9D;AAAA,MACF,OAAO;AACL,iCAAyB;AAAA,MAC3B;AAEA,YAAM,UAAU,MAAU,SAAK,SAAS,KAAK,GAAK;AAClD,UAAI;AACF,YAAI,aAAa;AACjB,eAAO,aAAa,wBAAwB;AAC1C,gBAAM,SAAS,KAAK,IAAI,YAAY,yBAAyB,UAAU;AACvE,gBAAM,UAAU,OAAO,MAAM,MAAM;AACnC,gBAAM,EAAE,WAAW,EAAE,IAAI,MAAM,IAAI,KAAK,SAAS,GAAG,QAAQ,UAAU;AACtE,cAAI,MAAM,EAAG;AACb,gBAAM,QAAQ,MAAM,SAAS,GAAG,CAAC;AACjC,wBAAc;AAAA,QAChB;AAQA,YAAI,aAAa;AACjB,YAAI,WAAW;AACf,eAAO,aAAa,WAAW;AAC7B,gBAAM,SAAS,KAAK,IAAI,YAAY,YAAY,UAAU;AAC1D,gBAAM,UAAU,OAAO,MAAM,MAAM;AACnC,gBAAM,EAAE,WAAW,GAAG,IAAI,MAAM,IAAI,KAAK,SAAS,GAAG,QAAQ,UAAU;AACvE,cAAI,OAAO,EAAG;AACd,gBAAM,QAAQ,WAAW,QAAQ,SAAS,GAAG,EAAE,EAAE,SAAS,MAAM;AAChE,gBAAM,SAAS,MAAM,YAAY,IAAI;AACrC,cAAI,WAAW,IAAI;AAEjB,uBAAW;AAAA,UACb,OAAO;AACL,uBAAW,QAAQ,MAAM,MAAM,GAAG,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG;AACzD,kBAAI,CAAC,KAAK,KAAK,EAAG;AAClB,kBAAI;AACF,qBAAK,MAAM,IAAI;AAAA,cACjB,QAAQ;AACN,sBAAM,QAAQ,MAAM,GAAG,IAAI;AAAA,GAAM,QAAW,MAAM;AAAA,cACpD;AAAA,YACF;AACA,uBAAW,MAAM,MAAM,SAAS,CAAC;AAAA,UACnC;AACA,wBAAc;AAAA,QAChB;AAEA,YAAI,SAAS,KAAK,GAAG;AACnB,cAAI;AACF,iBAAK,MAAM,QAAQ;AAAA,UACrB,QAAQ;AACN,kBAAM,QAAQ,MAAM,GAAG,QAAQ;AAAA,GAAM,QAAW,MAAM;AAAA,UACxD;AAAA,QACF;AAAA,MACF,UAAE;AACA,cAAM,QAAQ,MAAM;AAAA,MACtB;AAEA,YAAM,IAAI,MAAM;AAChB,YAAU,WAAO,SAAS,KAAK,QAAQ;AAEvC,WAAK,SAAS,MAAU,SAAK,KAAK,UAAU,KAAK,GAAK;AAAA,IAExD,SAAS,KAAK;AACZ,YAAU,WAAO,OAAO,EAAE,MAAM,MAAM,MAAS;AAC/C,WAAK,SAAS,MAAU,SAAK,KAAK,UAAU,KAAK,GAAK,EAAE,MAAM,MAAM,KAAK,MAAM;AAC/E,YAAM;AAAA,IACR;AAGA,UAAM,KAAK,OAAO;AAAA,MAChB,MAAM;AAAA,MACN,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC3B,eAAe;AAAA,MACf,eAAe,CAAC;AAAA,IAClB,CAAC;AACD,SAAK,oBAAoB;AAEzB,SAAK,QAAQ,KAAK,mBAAmB;AAAA,MACnC,WAAW,KAAK;AAAA,MAChB,eAAe;AAAA,MACf,eAAe,CAAC;AAAA,MAChB,eAAe;AAAA,IACjB,CAAC;AAED,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,eAA8B;AAElC,QAAI,CAAC,KAAK,SAAU;AAIpB,QAAI,KAAK,YAAY;AACnB,mBAAa,KAAK,UAAU;AAC5B,WAAK,aAAa;AAAA,IACpB;AAIA,UAAM,KAAK;AACX,SAAK,cAAc,CAAC;AAGpB,UAAM,KAAK;AACX,UAAM,SAAS,GAAG,KAAK,UAAU;AAAA,MAC/B,MAAM;AAAA,MACN,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC3B,IAAI,KAAK;AAAA,MACT,OAAO,KAAK,KAAK,SAAS;AAAA,MAC1B,UAAU,KAAK,KAAK,YAAY;AAAA,IAClC,CAAC,CAAC;AAAA;AACF,UAAU,cAAU,KAAK,UAAU,QAAQ,MAAM;AACjD,SAAK,oBAAoB;AACzB,SAAK,uBAAuB,CAAC;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,oBAAoB,SAAgC;AACxD,QAAI,CAAC,WAAW,QAAQ,SAAS,KAAK;AACpC,YAAM,IAAI,MAAM,wCAAwC;AAAA,IAC1D;AACA,UAAM,KAAK,OAAO;AAAA,MAChB,MAAM;AAAA,MACN,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC3B;AAAA,IACF,CAAC;AACD,SAAK,QAAQ,KAAK,qBAAqB,EAAE,WAAW,KAAK,IAAI,SAAS,KAAI,oBAAI,KAAK,GAAE,YAAY,EAAE,CAAC;AAAA,EACtG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,oBAAoB,QAA0D;AAClF,QAAI,KAAK,qBAAqB,SAAS,GAAG;AACxC,YAAM,KAAK;AAAA,QACT,KAAK,qBAAqB;AAAA,QAC1B,CAAC,GAAG,KAAK,oBAAoB;AAAA,MAC/B;AACA,WAAK,uBAAuB,CAAC;AAAA,IAC/B;AACA,UAAM,KAAK,OAAO;AAAA,MAChB,MAAM;AAAA,MACN,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC3B;AAAA,IACF,CAAC;AACD,SAAK,QAAQ,KAAK,mBAAmB,EAAE,WAAW,KAAK,IAAI,QAAQ,KAAI,oBAAI,KAAK,GAAE,YAAY,EAAE,CAAC;AAAA,EACnG;AACF;;;AE19BA,SAAS,aAAa;AACtB,SAAS,cAAAC,aAAY,kBAAkB;AACvC,YAAYC,UAAS;AACrB,YAAYC,WAAU;;;ACHtB,eAAsB,mBACpB,OACA,aACA,QACc;AACd,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,WAAW,CAAC;AACjD,QAAM,UAAU,IAAI,MAAS,MAAM,MAAM;AACzC,MAAI,OAAO;AAEX,iBAAe,SAAwB;AACrC,WAAO,MAAM;AACX,YAAM,QAAQ;AACd;AACA,UAAI,SAAS,MAAM,OAAQ;AAC3B,cAAQ,KAAK,IAAI,MAAM,OAAO,MAAM,KAAK,GAAQ,KAAK;AAAA,IACxD;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,OAAO,MAAM,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC;AACpF,QAAM,QAAQ,IAAI,OAAO;AACzB,SAAO;AACT;;;ADmBA,IAAM,UAAU;AAChB,IAAM,sBAAsB;AAC5B,IAAM,iBAAiB,KAAK,OAAO;AACnC,IAAM,iBAAiB,KAAK,OAAO;AACnC,IAAM,uBAAuB,MAAM,OAAO;AAE1C,SAAS,OAAO,SAAsC;AACpD,SAAOC,YAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAC1D;AAEA,SAAS,SAAS,MAAc,QAAyB;AACvD,QAAMC,YAAgB,eAAS,MAAM,MAAM;AAC3C,SAAOA,cAAa,MAAO,CAACA,UAAS,WAAW,IAAI,KAAK,CAAM,iBAAWA,SAAQ;AACpF;AAEA,SAAS,kBAAkB,OAA8B;AACvD,MAAI,CAAC,SAAc,iBAAW,KAAK,EAAG,QAAO;AAC7C,QAAM,aAAa,MAAM,QAAQ,OAAO,GAAG,EAAE,QAAQ,SAAS,EAAE;AAChE,QAAM,WAAgB,YAAM,UAAU,UAAU;AAChD,MAAI,CAAC,YAAY,aAAa,OAAO,aAAa,QAAQ,SAAS,WAAW,KAAK,EAAG,QAAO;AAC7F,SAAO;AACT;AAEA,SAAS,cAAc,QAA0B;AAC/C,SAAO,OACJ,MAAM,IAAI,EACV,IAAI,iBAAiB,EACrB,OAAO,CAAC,UAA2B,UAAU,IAAI;AACtD;AAEA,SAAS,yBAAyBA,WAA2B;AAC3D,SAAOA,cAAa,2BAA2BA,UAAS,WAAW,wBAAwB;AAC7F;AASO,IAAM,uBAAN,MAA2B;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,MAAmC;AAC7C,SAAK,UAAe,cAAQ,KAAK,OAAO;AACxC,SAAK,cAAmB,cAAQ,KAAK,WAAW;AAChD,SAAK,SAAS,KAAK,UAAU;AAAA,EAC/B;AAAA,EAEA,MAAM,QAAQ,YAAoB,cAAmE;AACnG,UAAM,OAAO,MAAM,KAAK,OAAO,CAAC,aAAa,YAAY,MAAM,GAAG,KAAK,WAAW;AAClF,UAAM,WAAW,KAAK,OAAO,KAAK;AAClC,QACE,KAAK,SAAS,KACd,KAAK,mBACL,CAAC,oBAAoB,KAAK,QAAQ,GAClC;AACA,aAAO;AAAA,IACT;AAEA,UAAM,CAAC,SAAS,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC7C,KAAK,OAAO,CAAC,QAAQ,QAAQ,eAAe,MAAM,IAAI,GAAG,KAAK,WAAW;AAAA,MACzE,KAAK,OAAO,CAAC,YAAY,YAAY,sBAAsB,IAAI,GAAG,KAAK,WAAW;AAAA,IACpF,CAAC;AACD,QACE,QAAQ,SAAS,KACjB,UAAU,SAAS,KACnB,QAAQ,mBACR,UAAU,iBACV;AACA,aAAO;AAAA,IACT;AAEA,UAAM,gBAAgB;AAAA,MACpB,GAAG,oBAAI,IAAI,CAAC,GAAG,cAAc,QAAQ,MAAM,GAAG,GAAG,cAAc,UAAU,MAAM,CAAC,CAAC;AAAA,IACnF,EAGG,OAAO,CAACA,cAAa,CAAC,yBAAyBA,SAAQ,CAAC,EACxD,KAAK;AACR,UAAM,aAAwD,CAAC;AAC/D,QAAI,iBAAiB;AACrB,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA;AAAA,MACA,OAAOA,cAAuD;AAC5D,cAAM,WAAgB,cAAQ,KAAK,aAAa,GAAGA,UAAS,MAAM,GAAG,CAAC;AACtE,YAAI,CAAC,SAAS,KAAK,aAAa,QAAQ,GAAG;AACzC,qBAAW,KAAK,EAAE,MAAMA,WAAU,QAAQ,4BAA4B,CAAC;AACvE,iBAAO;AAAA,QACT;AACA,YAAI;AACF,gBAAMC,SAAO,MAAU,WAAM,QAAQ;AACrC,cAAIA,OAAK,eAAe,GAAG;AACzB,kBAAM,aAAa,MAAU,cAAS,QAAQ;AAC9C,kBAAM,eAAoB,cAAa,cAAQ,QAAQ,GAAG,UAAU;AACpE,gBAAS,iBAAW,UAAU,KAAK,CAAC,SAAS,KAAK,aAAa,YAAY,GAAG;AAC5E,yBAAW,KAAK;AAAA,gBACd,MAAMD;AAAA,gBACN,QAAQ;AAAA,cACV,CAAC;AACD,qBAAO;AAAA,YACT;AACA,mBAAO,EAAE,MAAMA,WAAU,OAAO,WAAW,WAAW;AAAA,UACxD;AACA,cAAI,CAACC,OAAK,OAAO,GAAG;AAClB,uBAAW,KAAK,EAAE,MAAMD,WAAU,QAAQ,qCAAqC,CAAC;AAChF,mBAAO;AAAA,UACT;AACA,cAAIC,OAAK,OAAO,gBAAgB;AAC9B,uBAAW,KAAK;AAAA,cACd,MAAMD;AAAA,cACN,QAAQ,gBAAgB,cAAc;AAAA,YACxC,CAAC;AACD,mBAAO;AAAA,UACT;AACA,cAAI,iBAAiBC,OAAK,OAAO,sBAAsB;AACrD,uBAAW,KAAK;AAAA,cACd,MAAMD;AAAA,cACN,QAAQ,sBAAsB,oBAAoB;AAAA,YACpD,CAAC;AACD,mBAAO;AAAA,UACT;AACA,4BAAkBC,OAAK;AACvB,gBAAM,UAAU,MAAU,cAAS,QAAQ;AAC3C,gBAAM,WAAW,OAAO,OAAO;AAC/B,gBAAM,KAAK,QAAQ,UAAU,OAAO;AACpC,iBAAO,EAAE,MAAMD,WAAU,OAAO,QAAQ,UAAU,MAAMC,OAAK,OAAO,IAAM;AAAA,QAC5E,SAAS,KAAK;AACZ,cAAK,IAA8B,SAAS,UAAU;AACpD,mBAAO,EAAE,MAAMD,WAAU,OAAO,SAAS;AAAA,UAC3C;AACA,qBAAW,KAAK,EAAE,MAAMA,WAAU,QAAQ,eAAe,GAAG,EAAE,CAAC;AAC/D,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAEA,UAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAC1C,UAAM,WAAwC;AAAA,MAC5C,SAAS;AAAA,MACT,UAAU,SAAS,YAAY;AAAA,MAC/B,UAAU;AAAA,MACV,SAAS,SAAS,OAAO,CAAC,UAA6C,UAAU,IAAI;AAAA,MACrF,YAAY,CAAC,GAAG,UAAU,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAAA,IACzE;AACA,UAAM,UAAU,KAAK,UAAU,QAAQ;AACvC,UAAM,eAAe,OAAO,OAAO;AACnC,UAAM,KAAK,YAAY,cAAc,OAAO;AAC5C,WAAO;AAAA,MACL;AAAA,MACA,UAAU,SAAS;AAAA,MACnB,YAAY,SAAS,QAAQ;AAAA,MAC7B,iBAAiB,SAAS,WAAW;AAAA,MACrC;AAAA,MACA,UAAU,SAAS;AAAA,IACrB;AAAA,EACF;AAAA,EAEA,MAAM,YACJ,YACA,YACyC;AACzC,UAAM,SAAc,cAAQ,UAAU;AACtC,QAAI,WAAW,KAAK,aAAa;AAC/B,YAAM,IAAI,MAAM,6EAA6E;AAAA,IAC/F;AACA,UAAM,aAAa,MAAU,UAAK,MAAM;AACxC,QAAI,CAAC,WAAW,YAAY,EAAG,OAAM,IAAI,MAAM,yCAAyC,MAAM,EAAE;AAEhG,UAAM,WAAW,MAAM,KAAK,aAAa,WAAW,YAAY;AAChE,QACE,SAAS,aAAa,WAAW,SAAS,YAAY,KACtD,SAAS,aAAa,WAAW,YACjC,SAAS,QAAQ,WAAW,WAAW,cACvC,SAAS,WAAW,WAAW,WAAW,iBAC1C;AACA,YAAM,IAAI,MAAM,gEAAgE;AAAA,IAClF;AACA,QAAI,SAAS,WAAW,SAAS,GAAG;AAClC,YAAM,IAAI;AAAA,QACR,4BAA4B,SAAS,WAAW,MAAM;AAAA,MACxD;AAAA,IACF;AACA,UAAM,aAAa,MAAM,KAAK,OAAO,CAAC,aAAa,YAAY,MAAM,GAAG,MAAM;AAC9E,QACE,WAAW,SAAS,KACpB,WAAW,mBACX,WAAW,OAAO,KAAK,EAAE,YAAY,MAAM,SAAS,UACpD;AACA,YAAM,IAAI;AAAA,QACR,qCAAqC,SAAS,QAAQ,SAAS,WAAW,OAAO,KAAK,KAAK,SAAS;AAAA,MACtG;AAAA,IACF;AACA,UAAM,eAAe,MAAM,KAAK;AAAA,MAC9B,CAAC,UAAU,kBAAkB,MAAM,yBAAyB,oBAAoB;AAAA,MAChF;AAAA,IACF;AACA,QAAI,aAAa,SAAS,KAAK,aAAa,iBAAiB;AAC3D,YAAM,IAAI,MAAM,sDAAsD;AAAA,IACxE;AACA,QAAI,aAAa,OAAO,SAAS,GAAG;AAClC,YAAM,IAAI,MAAM,mEAAmE;AAAA,IACrF;AAEA,UAAM,aAAa,MAAU,cAAS,MAAM;AAC5C,UAAM,eAAyB,CAAC;AAChC,UAAM,eAAyB,CAAC;AAChC,UAAM,SAAmB,CAAC;AAC1B,UAAM,WAID,CAAC;AACN,eAAW,SAAS,SAAS,SAAS;AACpC,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,eAAe,QAAQ,YAAY,MAAM,IAAI;AACvE,YAAI,MAAM,UAAU,WAAW;AAC7B,cAAS,iBAAW,MAAM,UAAU,GAAG;AACrC,kBAAM,IAAI,MAAM,iCAAiC;AAAA,UACnD;AACA,gBAAM,eAAoB,cAAa,cAAQ,MAAM,GAAG,MAAM,UAAU;AACxE,cAAI,CAAC,SAAS,QAAQ,YAAY,EAAG,OAAM,IAAI,MAAM,wCAAwC;AAAA,QAC/F;AACA,iBAAS,KAAK;AAAA,UACZ;AAAA,UACA;AAAA,UACA,GAAI,MAAM,UAAU,SAAS,EAAE,SAAS,MAAM,KAAK,SAAS,MAAM,QAAQ,EAAE,IAAI,CAAC;AAAA,QACnF,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,eAAO,KAAK,GAAG,MAAM,IAAI,KAAK,eAAe,GAAG,CAAC,EAAE;AAAA,MACrD;AAAA,IACF;AAIA,QAAI,OAAO,SAAS,GAAG;AACrB,aAAO,EAAE,YAAY,QAAQ,cAAc,cAAc,OAAO;AAAA,IAClE;AAEA,eAAW,EAAE,OAAO,QAAQ,QAAQ,KAAK,UAAU;AACjD,UAAI;AACF,YAAI,MAAM,UAAU,UAAU;AAC5B,gBAAU,YAAO,MAAM,EAAE,MAAM,CAAC,QAAQ;AACtC,gBAAK,IAA8B,SAAS,SAAU,OAAM;AAAA,UAC9D,CAAC;AACD,uBAAa,KAAK,MAAM,IAAI;AAC5B;AAAA,QACF;AACA,YAAI,MAAM,UAAU,WAAW;AAC7B,gBAAU,YAAO,MAAM,EAAE,MAAM,CAAC,QAAQ;AACtC,gBAAK,IAA8B,SAAS,SAAU,OAAM;AAAA,UAC9D,CAAC;AACD,gBAAU,WAAW,cAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACzD,gBAAU,aAAQ,MAAM,YAAY,MAAM;AAC1C,uBAAa,KAAK,MAAM,IAAI;AAC5B;AAAA,QACF;AACA,YAAI,CAAC,QAAS,OAAM,IAAI,MAAM,2BAA2B;AACzD,cAAM,YAAY,QAAQ,SAAS,EAAE,MAAM,MAAM,KAAK,CAAC;AACvD,cAAU,WAAM,QAAQ,MAAM,IAAI,EAAE,MAAM,MAAM,MAAS;AACzD,qBAAa,KAAK,MAAM,IAAI;AAAA,MAC9B,SAAS,KAAK;AACZ,eAAO,KAAK,GAAG,MAAM,IAAI,KAAK,eAAe,GAAG,CAAC,EAAE;AAAA,MACrD;AAAA,IACF;AACA,WAAO,EAAE,YAAY,QAAQ,cAAc,cAAc,OAAO;AAAA,EAClE;AAAA,EAEQ,WAAW,MAAsB;AACvC,QAAI,CAAC,QAAQ,KAAK,IAAI,EAAG,OAAM,IAAI,MAAM,4BAA4B,IAAI,EAAE;AAC3E,WAAY,WAAK,KAAK,SAAS,WAAW,KAAK,MAAM,GAAG,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;AAAA,EAC3E;AAAA,EAEQ,aAAa,MAAsB;AACzC,QAAI,CAAC,QAAQ,KAAK,IAAI,EAAG,OAAM,IAAI,MAAM,qCAAqC,IAAI,EAAE;AACpF,WAAY,WAAK,KAAK,SAAS,aAAa,GAAG,IAAI,OAAO;AAAA,EAC5D;AAAA,EAEA,MAAc,QAAQ,MAAc,SAAgC;AAClE,UAAM,SAAS,KAAK,WAAW,IAAI;AACnC,UAAU,WAAW,cAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACzD,QAAI;AACF,YAAM,WAAW,MAAU,cAAS,MAAM;AAC1C,UAAI,OAAO,QAAQ,MAAM,KAAM,OAAM,IAAI,MAAM,iCAAiC,IAAI,EAAE;AACtF;AAAA,IACF,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,SAAU,OAAM;AAAA,IAC9D;AAKA,UAAM,OAAY;AAAA,MACX,cAAQ,MAAM;AAAA,MACnB,IAAS,eAAS,MAAM,CAAC,IAAI,QAAQ,GAAG,IAAI,WAAW,CAAC;AAAA,IAC1D;AACA,QAAI;AACJ,QAAI;AACF,eAAS,MAAU,UAAK,MAAM,MAAM,GAAK;AACzC,YAAM,OAAO,UAAU,OAAO;AAC9B,YAAM,OAAO,KAAK;AAClB,YAAM,OAAO,MAAM;AACnB,eAAS;AACT,UAAI;AACF,cAAU,UAAK,MAAM,MAAM;AAAA,MAC7B,SAAS,KAAK;AACZ,YAAK,IAA8B,SAAS,SAAU,OAAM;AAC5D,cAAM,WAAW,MAAU,cAAS,MAAM;AAC1C,YAAI,OAAO,QAAQ,MAAM,KAAM,OAAM,IAAI,MAAM,iCAAiC,IAAI,EAAE;AAAA,MACxF;AAAA,IACF,UAAE;AACA,YAAM,QAAQ,MAAM,EAAE,MAAM,MAAM,MAAS;AAC3C,YAAU,YAAO,IAAI,EAAE,MAAM,MAAM,MAAS;AAAA,IAC9C;AAAA,EACF;AAAA,EAEA,MAAc,YAAY,MAAc,SAAgC;AACtE,UAAM,SAAS,KAAK,aAAa,IAAI;AACrC,UAAM,WAAW,MAAU,cAAS,MAAM,EAAE,MAAM,MAAM,IAAI;AAC5D,QAAI,UAAU;AACZ,UAAI,OAAO,QAAQ,MAAM,KAAM,OAAM,IAAI,MAAM,0CAA0C,IAAI,EAAE;AAC/F;AAAA,IACF;AACA,UAAM,YAAY,QAAQ,SAAS,EAAE,MAAM,IAAM,CAAC;AAAA,EACpD;AAAA,EAEA,MAAc,SAAS,MAA+B;AACpD,UAAM,UAAU,MAAU,cAAS,KAAK,WAAW,IAAI,CAAC;AACxD,QAAI,OAAO,OAAO,MAAM,KAAM,OAAM,IAAI,MAAM,oCAAoC,IAAI,EAAE;AACxF,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,aAAa,MAAoD;AAC7E,UAAM,MAAM,MAAU,cAAS,KAAK,aAAa,IAAI,CAAC;AACtD,QAAI,OAAO,GAAG,MAAM,KAAM,OAAM,IAAI,MAAM,+CAA+C,IAAI,EAAE;AAC/F,UAAM,SAAS,KAAK,MAAM,IAAI,SAAS,MAAM,CAAC;AAC9C,QACE,OAAO,YAAY,KACnB,OAAO,OAAO,aAAa,YAC3B,OAAO,aAAa,yBACpB,CAAC,MAAM,QAAQ,OAAO,OAAO,KAC7B,CAAC,MAAM,QAAQ,OAAO,UAAU,GAChC;AACA,YAAM,IAAI,MAAM,0CAA0C,IAAI,EAAE;AAAA,IAClE;AACA,UAAM,eAAe,OAAO,QAAQ,MAAM,CAAC,UAAU;AACnD,UAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,YAAM,YAAY;AAClB,UAAI,OAAO,UAAU,SAAS,YAAY,CAAC,kBAAkB,UAAU,IAAI,EAAG,QAAO;AACrF,UAAI,UAAU,UAAU,SAAU,QAAO;AACzC,UAAI,UAAU,UAAU,UAAW,QAAO,OAAO,UAAU,eAAe;AAC1E,aACE,UAAU,UAAU,UACpB,OAAO,UAAU,aAAa,YAC9B,QAAQ,KAAK,UAAU,QAAQ,KAC/B,OAAO,UAAU,SAAS,YAC1B,OAAO,UAAU,UAAU,IAAI,KAC/B,UAAU,QAAQ,KAClB,UAAU,QAAQ;AAAA,IAEtB,CAAC;AACD,UAAM,kBAAkB,OAAO,WAAW;AAAA,MACxC,CAAC,UACC,UAAU,QACV,OAAO,UAAU,YACjB,OAAO,MAAM,SAAS,YACtB,OAAO,MAAM,WAAW;AAAA,IAC5B;AACA,QAAI,CAAC,gBAAgB,CAAC,iBAAiB;AACrC,YAAM,IAAI,MAAM,kDAAkD,IAAI,EAAE;AAAA,IAC1E;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,eAAe,QAAgB,YAAoBA,WAAmC;AAClG,UAAM,aAAa,kBAAkBA,SAAQ;AAC7C,QAAI,CAAC,WAAY,OAAM,IAAI,MAAM,uBAAuB;AACxD,UAAM,SAAc,cAAQ,QAAQ,GAAG,WAAW,MAAM,GAAG,CAAC;AAC5D,QAAI,CAAC,SAAS,QAAQ,MAAM,EAAG,OAAM,IAAI,MAAM,gCAAgC;AAE/E,QAAI,QAAQ;AACZ,eAAS;AACP,UAAI;AACF,cAAM,OAAO,MAAU,cAAS,KAAK;AACrC,YAAI,CAAC,SAAS,YAAY,IAAI,EAAG,OAAM,IAAI,MAAM,2DAA2D;AAC5G,eAAO;AAAA,MACT,SAAS,KAAK;AACZ,YAAK,IAA8B,SAAS,SAAU,OAAM;AAC5D,cAAM,SAAc,cAAQ,KAAK;AACjC,YAAI,WAAW,MAAO,OAAM;AAC5B,gBAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,cAAc,MAAgB,KAA2C;AAChF,SAAO,IAAI,QAAQ,CAACE,cAAY;AAC9B,UAAM,eAAyB,CAAC;AAChC,UAAM,eAAyB,CAAC;AAChC,QAAI,cAAc;AAClB,QAAI,cAAc;AAClB,QAAI,kBAAkB;AACtB,QAAI,kBAAkB;AACtB,UAAM,QAAQ,MAAM,OAAO,MAAM;AAAA,MAC/B;AAAA,MACA,KAAK,cAAc;AAAA,MACnB,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,MAChC,QAAQ,YAAY,QAAQ,GAAM;AAAA,MAClC,aAAa;AAAA,IACf,CAAC;AACD,UAAM,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AAC1C,YAAM,YAAY,iBAAiB;AACnC,UAAI,aAAa,GAAG;AAClB,0BAAkB;AAClB;AAAA,MACF;AACA,YAAM,OAAO,MAAM,SAAS,GAAG,SAAS;AACxC,mBAAa,KAAK,IAAI;AACtB,qBAAe,KAAK;AACpB,UAAI,KAAK,SAAS,MAAM,OAAQ,mBAAkB;AAAA,IACpD,CAAC;AACD,UAAM,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AAC1C,YAAM,YAAY,iBAAiB;AACnC,UAAI,aAAa,GAAG;AAClB,0BAAkB;AAClB;AAAA,MACF;AACA,YAAM,OAAO,MAAM,SAAS,GAAG,SAAS;AACxC,mBAAa,KAAK,IAAI;AACtB,qBAAe,KAAK;AACpB,UAAI,KAAK,SAAS,MAAM,OAAQ,mBAAkB;AAAA,IACpD,CAAC;AACD,UAAM,SAAS,CAAC,MAAc,iBAA+C;AAAA,MAC3E;AAAA,MACA,QAAQ,OAAO,OAAO,YAAY,EAAE,SAAS,MAAM;AAAA,MACnD,QAAQ,GAAG,OAAO,OAAO,YAAY,EAAE,SAAS,MAAM,CAAC,GAAG,eAAe,EAAE;AAAA,MAC3E;AAAA,MACA;AAAA,IACF;AACA,UAAM,GAAG,SAAS,CAAC,QAAQA,UAAQ,OAAO,GAAG,IAAI,OAAO,CAAC,CAAC;AAC1D,UAAM,GAAG,SAAS,CAAC,SAASA,UAAQ,OAAO,QAAQ,CAAC,CAAC,CAAC;AAAA,EACxD,CAAC;AACH;;;AExeA,SAAS,cAAAC,mBAAkB;AAC3B,YAAYC,UAAS;AACrB,YAAYC,WAAU;AAStB,IAAM,uBAAuB,IAAI,OAAO;AACxC,IAAM,yBAAyB;AAC/B,IAAM,oBAAoB;AAQ1B,SAASC,UAAS,MAAc,QAAyB;AACvD,QAAMC,YAAgB,eAAS,MAAM,MAAM;AAC3C,SAAOA,cAAa,MAAO,CAACA,UAAS,WAAW,IAAI,KAAK,CAAM,iBAAWA,SAAQ;AACpF;AAEA,SAAS,MAAM,KAAkC;AAC/C,SAAO,OAAO,OAAO,QAAQ,YAAY,UAAU,MAC/C,OAAQ,IAA8B,IAAI,IAC1C;AACN;AAEA,SAAS,mBAAmB,QAAiC,aAAwC;AACnG,QAAM,SAAS,oBAAI,IAA6B;AAChD,aAAW,SAAS,QAAQ;AAC1B,QACE,MAAM,SAAS,sBACf,OAAO,MAAM,SAAS,YACtB,MAAM,KAAK,WAAW,KACtB,OAAO,MAAM,SAAS,YACtB,CAAC,iBAAiB,KAAK,MAAM,IAAI,GACjC;AACA;AAAA,IACF;AACA,UAAM,aAAkB,cAAQ,aAAa,MAAM,IAAI;AACvD,WAAO,IAAI,YAAY;AAAA,MACrB,MAAM;AAAA,MACN,MAAM,MAAM,KAAK,YAAY;AAAA,MAC7B,IAAI,MAAM;AAAA,IACZ,CAAC;AAAA,EACH;AACA,SAAO,CAAC,GAAG,OAAO,OAAO,CAAC;AAC5B;AAEA,eAAe,YACb,aACA,aACA,UAC2C;AAC3C,QAAM,OAAO;AAAA,IACX,MAAM,YAAY;AAAA,IAClB,YAAY,YAAY;AAAA,IACxB,cAAc,YAAY;AAAA,EAC5B;AAEA,MAAI,CAACD,UAAS,aAAa,YAAY,IAAI,GAAG;AAC5C,WAAO;AAAA,MACL,GAAG;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,eAAW,MAAU,cAAS,YAAY,IAAI;AAAA,EAChD,SAAS,KAAK;AACZ,QAAI,MAAM,GAAG,MAAM,SAAU,QAAO,EAAE,GAAG,MAAM,QAAQ,UAAU;AACjE,WAAO,EAAE,GAAG,MAAM,QAAQ,cAAc,QAAQ,eAAe,GAAG,EAAE;AAAA,EACtE;AAEA,MAAI,CAACA,UAAS,UAAU,QAAQ,GAAG;AACjC,WAAO;AAAA,MACL,GAAG;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI;AACF,UAAME,SAAO,MAAU,UAAK,QAAQ;AACpC,QAAI,CAACA,OAAK,OAAO,GAAG;AAClB,aAAO,EAAE,GAAG,MAAM,QAAQ,cAAc,QAAQ,oCAAoC;AAAA,IACtF;AACA,QAAIA,OAAK,OAAO,sBAAsB;AACpC,aAAO;AAAA,QACL,GAAG;AAAA,QACH,QAAQ;AAAA,QACR,QAAQ,wBAAwB,oBAAoB;AAAA,MACtD;AAAA,IACF;AAGA,UAAM,UAAU,MAAU,cAAS,UAAU,MAAM;AACnD,UAAM,aAAaC,YAAW,QAAQ,EAAE,OAAO,SAAS,MAAM,EAAE,OAAO,KAAK;AAC5E,QAAI,eAAe,YAAY,KAAM,QAAO;AAC5C,WAAO,EAAE,GAAG,MAAM,QAAQ,YAAY,WAAW;AAAA,EACnD,SAAS,KAAK;AACZ,QAAI,MAAM,GAAG,MAAM,SAAU,QAAO,EAAE,GAAG,MAAM,QAAQ,UAAU;AACjE,WAAO,EAAE,GAAG,MAAM,QAAQ,cAAc,QAAQ,eAAe,GAAG,EAAE;AAAA,EACtE;AACF;AAGA,eAAsB,+BACpB,QACA,aAC2B;AAC3B,QAAM,cAAmB,cAAQ,WAAW;AAC5C,QAAM,WAAW,MAAU,cAAS,WAAW,EAAE,MAAM,MAAM,WAAW;AACxE,QAAM,eAAe,mBAAmB,QAAQ,WAAW;AAC3D,QAAM,UAAU,MAAM;AAAA,IACpB;AAAA,IACA;AAAA,IACA,CAAC,gBAAgB,YAAY,aAAa,aAAa,QAAQ;AAAA,EACjE;AACA,SAAO;AAAA,IACL,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,kBAAkB,aAAa;AAAA,IAC/B,YAAY,QAAQ,OAAO,CAAC,UAA8C,UAAU,IAAI;AAAA,EAC1F;AACF;AAGO,SAAS,6BACd,YACA,aACe;AACf,MAAI,WAAW,WAAW,WAAW,EAAG,QAAO;AAC/C,QAAM,OAAY,cAAQ,WAAW;AACrC,QAAM,QAAQ,WAAW,WAAW,MAAM,GAAG,iBAAiB,EAAE,IAAI,CAAC,UAAU;AAC7E,UAAMF,YAAgB,eAAS,MAAM,MAAM,IAAI;AAC/C,UAAM,UAAUD,UAAS,MAAM,MAAM,IAAI,IAAIC,aAAY,MAAM,MAAM;AACrE,WAAO,KAAK,KAAK,UAAU,OAAO,CAAC,KAAK,MAAM,MAAM;AAAA,EACtD,CAAC;AACD,QAAM,UAAU,WAAW,WAAW,SAAS,MAAM;AACrD,MAAI,UAAU,EAAG,OAAM,KAAK,aAAa,OAAO,qBAAqB;AAErE,SAAO;AAAA,IACL;AAAA,IACA,iCAAiC,WAAW,WAAW,MAAM;AAAA,IAC7D;AAAA,IACA,GAAG;AAAA,EACL,EAAE,KAAK,IAAI;AACb;;;AC9IO,SAAS,cAAc,OAAuB;AACnD,SAAO,MACJ,QAAQ,mBAAmB,GAAG,EAC9B,QAAQ,OAAO,GAAG,EAClB,QAAQ,UAAU,EAAE,EACpB,MAAM,GAAG,EAAE;AAChB;AAaO,SAAS,kBAAkB,WAAmB,QAAyB;AAC5E,QAAM,OAAO,UAAU,MAAM,GAAG,EAAE;AAClC,QAAM,WAAW,OAAO,MAAM,KAAK,MAAM,SAAS,CAAC,IAAI,KAAK,IAAI,IAAI,KAAK,MAAM,SAAS;AACxF,SAAO,GAAG,IAAI,SAAS,KAAK,QAAQ,CAAC;AACvC;;;ACxBO,SAAS,wBAAwB,GAAmB,GAA2B;AACpF,MAAI,EAAE,YAAY,EAAE,UAAW,QAAO;AACtC,MAAI,EAAE,YAAY,EAAE,UAAW,QAAO;AACtC,SAAO,EAAE,GAAG,cAAc,EAAE,EAAE;AAChC;AAEO,SAAS,qBAAqB,SAAyB,UAA0C;AACtG,MAAI,SAAS,SAAS,QAAQ,YAAY,SAAS,MAAO,QAAO;AACjE,MAAI,SAAS,SAAS,QAAQ,YAAY,SAAS,MAAO,QAAO;AACjE,MAAI,SAAS,YAAY,QAAQ,aAAa,SAAS,SAAU,QAAO;AACxE,MAAI,SAAS,SAAS,QAAQ,UAAU,SAAS,MAAO,QAAO;AAC/D,MAAI,SAAS,cAAc,UAAa,QAAQ,aAAa,SAAS,UAAW,QAAO;AACxF,MAAI,SAAS,eAAe;AAC1B,UAAM,SAAS,SAAS,cAAc,kBAAkB;AACxD,QAAI,CAAC,QAAQ,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAAG,QAAO;AAAA,EAClE;AACA,SAAO;AACT;;;ACxBO,SAAS,oBAAoB,QAAgD;AAClF,QAAM,MAAqB,CAAC;AAC5B,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,gBAAiB;AACpC,QAAI,KAAK;AAAA,MACP,MAAM,MAAM;AAAA,MACZ,IAAI,MAAM;AAAA,MACV,YAAY,MAAM;AAAA,MAClB,IAAI,MAAM,MAAM;AAAA,MAChB,aAAa,MAAM,eAAe,MAAM;AAAA,MACxC,cAAc,MAAM;AAAA,MACpB,aAAa,MAAM;AAAA,IACrB,CAAC;AAAA,EACH;AACA,SAAO;AACT;;;AvBkFA,SAAS,oBAAoB,OAAkC;AAC7D,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,YAAY;AAClB,UACG,UAAU,SAAS,UAClB,UAAU,SAAS,eACnB,UAAU,SAAS,cACpB,OAAO,UAAU,YAAY,YAAY,MAAM,QAAQ,UAAU,OAAO;AAE7E;AAEA,SAAS,qBACP,QACA,cACA,UACS;AACT,MAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,CAAC,SAAS,MAAM,mBAAmB,EAAG,QAAO;AAC7E,SAAO,SAAS;AAChB,eAAa,MAAM;AACnB,aAAW,OAAO,UAAU;AAC1B,UAAM,EAAE,YAAY,UAAU,GAAG,QAAQ,IAAI;AAC7C,WAAO,KAAK,OAAO;AACnB,QAAI,CAAC,MAAM,QAAQ,QAAQ,OAAO,EAAG;AACrC,eAAW,SAAS,QAAQ,SAAS;AACnC,UAAI,MAAM,SAAS,WAAY,cAAa,IAAI,MAAM,EAAE;AAAA,eAC/C,MAAM,SAAS,cAAe,cAAa,OAAO,MAAM,WAAW;AAAA,IAC9E;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,OAA8B;AACtD,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA;AAAA;AAAA;AAAA,IAIT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEO,IAAM,sBAAN,MAAM,qBAA4C;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,aAAa,oBAAI,IAA4B;AAAA,EACtD,cAAsC;AAAA,EAC7B,qBAAqB,oBAAI,IAAgC;AAAA,EAC1E,OAAwB,yBAAyB;AAAA,EACjD,OAAwB,wBAAwB;AAAA,EAEhD,YAAY,MAA2B;AACrC,SAAK,MAAM,KAAK;AAChB,SAAK,cAAc,KAAK,cAAmB,cAAQ,KAAK,WAAW,IAAI;AACvE,SAAK,gBAAgB,KAAK,cACtB,IAAI,qBAAqB;AAAA,MACvB,SAAc,WAAK,KAAK,KAAK,MAAM;AAAA,MACnC,aAAa,KAAK;AAAA,IACpB,CAAC,IACD;AACJ,SAAK,SAAS,KAAK;AACnB,SAAK,iBAAiB,KAAK;AAC3B,SAAK,iBAAiB,KAAK;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAe,WAA0B;AACvC,QAAI,cAAc,QAAW;AAC3B,WAAK,WAAW,OAAO,SAAS;AAAA,IAClC,OAAO;AACL,WAAK,WAAW,MAAM;AAAA,IACxB;AAAA,EACF;AAAA;AAAA,EAIQ,SACN,WACA,UACA,WACA,SACA,YACA,OACM;AACN,SAAK,QAAQ,KAAK,gBAAgB;AAAA,MAChC;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,IACzC,CAAC;AAAA,EACH;AAAA,EAEQ,UACN,WACA,UACA,WACA,SACA,YACA,YACA,OACM;AACN,SAAK,QAAQ,KAAK,iBAAiB;AAAA,MACjC;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAI,eAAe,SAAY,EAAE,WAAW,IAAI,CAAC;AAAA,MACjD,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,IACzC,CAAC;AAAA,EACH;AAAA,EAEQ,UACN,WACA,UACA,WACA,OACA,aACM;AACN,SAAK,QAAQ,KAAK,iBAAiB;AAAA,MACjC;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,IAAY,YAAoB;AAC9B,WAAY,WAAK,KAAK,KAAK,cAAc;AAAA,EAC3C;AAAA;AAAA,EAGQ,YAAY,IAAY,KAAyC;AACvE,WAAO,kBAAkB,KAAK,KAAK,IAAI,GAAG;AAAA,EAC5C;AAAA,EAEQ,kBAAkB,UAA0B;AAClD,WAAO,WAAgB,WAAK,KAAK,KAAK,UAAU,gBAAgB,IAAS,WAAK,KAAK,KAAK,gBAAgB;AAAA,EAC1G;AAAA,EAEQ,qBAAqB,IAAoB;AAC/C,UAAM,UAAe,cAAQ,EAAE;AAC/B,WAAO,YAAY,MAAM,KAAK;AAAA,EAChC;AAAA,EAEQ,mCAAmC,IAAkB;AAC3D,SAAK,mBAAmB,OAAO,KAAK,qBAAqB,EAAE,CAAC;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,eAAe,IAA6B;AACxD,UAAM,UAAe,cAAQ,kBAAkB,KAAK,KAAK,IAAI,EAAE,CAAC;AAChE,UAAM,UAAU,OAAO;AACvB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,OAAO,MAAkE;AAC7E,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,UAAM,KACJ,KAAK,MAAM,KAAK,GAAG,SAAS,IACxB,KAAK,KACL,kBAAkB,SAAS;AACjC,UAAM,WAAW,MAAM,KAAK,eAAe,EAAE;AAC7C,UAAM,OAAO,KAAK,YAAY,IAAI,QAAQ;AAC1C,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI;AACJ,QAAI;AACF,eAAS,MAAU,UAAK,MAAM,KAAK,GAAK;AAAA,IAC1C,SAAS,KAAK;AACZ,WAAK,UAAU,IAAI,MAAM,UAAU,eAAe,GAAG,GAAG,KAAK;AAC7D,YAAM,IAAI;AAAA,QACR,gCAAgC,eAAe,GAAG,CAAC;AAAA,QACnD,EAAE,OAAO,IAAI;AAAA,MACf;AAAA,IACF;AACA,QAAI;AACF,YAAM,SAAS,IAAI,kBAAkB,IAAI,QAAQ,WAAW,MAAM,KAAK,QAAQ;AAAA,QAC7E,KAAK;AAAA,QACL,UAAU;AAAA,QACV,gBAAgB,KAAK;AAAA,QACrB,eAAe,KAAK;AAAA,QACpB,SAAS,CAAC,MAAM,KAAK,cAAc,CAAC;AAAA,MACtC,CAAC;AACD,WAAK,UAAU,IAAI,MAAM,UAAU,WAAW,KAAK,IAAI,IAAI,EAAE;AAC7D,aAAO;AAAA,IAET,SAAS,KAAK;AACZ,YAAM,OAAO,MAAM,EAAE,MAAM,CAAC,MAAM,QAAQ,KAAK,KAAK,UAAU;AAAA,QAC5D,OAAO;AAAA,QACP,OAAO;AAAA,QACP,SAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAAA,QAClD,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC,CAAC,CAAC;AACH,WAAK,UAAU,IAAI,MAAM,UAAU,eAAe,GAAG,GAAG,IAAI;AAC5D,YAAM;AAAA,IACR;AAAA,EAEF;AAAA,EAEA,MAAM,KAAK,IAAY,OAA2B,CAAC,GAA2B;AAC5E,UAAM,SAAS,MAAM,KAAK,KAAK,EAAE;AACjC,QAAI,WAAW,OAAO,OAAO,SAAS;AACtC,QAAI;AACJ,QAAI,KAAK,0BAA0B,QAAW;AAC5C,iBAAW;AACX,eAAS,IAAI,GAAG,IAAI,OAAO,OAAO,QAAQ,KAAK;AAC7C,cAAM,QAAQ,OAAO,OAAO,CAAC;AAC7B,YACE,OAAO,SAAS,gBAChB,MAAM,gBAAgB,KAAK,uBAC3B;AAGA,qBAAW;AACX,6BAAmB;AAAA,QACrB;AAAA,MACF;AACA,UAAI,aAAa,IAAI;AACnB,cAAM,IAAI;AAAA,UACR,cAAc,KAAK,qBAAqB,0BAA0B,EAAE;AAAA,QACtE;AAAA,MACF;AAAA,IACF;AAEA,UAAM,eAAe,OAAO,OAAO,MAAM,GAAG,WAAW,CAAC;AACxD,UAAM,sBAAsB,kBAAkB;AAC9C,UAAM,iBAAiBG,YAAW,QAAQ,EACvC,OAAO,aAAa,IAAI,CAAC,UAAU,KAAK,UAAU,KAAK,CAAC,EAAE,KAAK,IAAI,IAAI,MAAM,MAAM,EACnF,OAAO,KAAK;AACf,UAAM,YAAY,aAAa,OAAO,gBAAgB;AACtD,UAAM,SAAS,MAAM,KAAK,OAAO;AAAA,MAC/B,IAAI;AAAA,MACJ,OAAO,OAAO,SAAS;AAAA,MACvB,OAAO,OAAO,SAAS;AAAA,MACvB,UAAU,OAAO,SAAS;AAAA,IAC5B,CAAC;AAED,QAAI;AACF,YAAM,OAAO,OAAO;AAAA,QAClB,MAAM;AAAA,QACN,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,QAC3B,iBAAiB;AAAA,QACjB,6BAA6B,KAAK;AAAA,QAClC,sBAAsB;AAAA,QACtB,WAAW;AAAA,QACX,yBAAyB,qBAAqB;AAAA,MAChD,CAAC;AACD,YAAM,YAAY;AAClB,eAAS,SAAS,GAAG,SAAS,UAAU,QAAQ,UAAU,WAAW;AACnE,cAAM,OAAO,YAAY,UAAU,MAAM,QAAQ,SAAS,SAAS,CAAC;AAAA,MACtE;AACA,YAAM,OAAO,MAAM;AACnB,YAAM,OAAO,MAAM;AACnB,YAAM,OAAO,MAAM,KAAK,KAAK,OAAO,EAAE;AACtC,aAAO;AAAA,QACL,IAAI,OAAO;AAAA,QACX;AAAA,QACA,iBAAiB;AAAA,QACjB,uBAAuB,KAAK;AAAA,QAC5B;AAAA,QACA,WAAW;AAAA,QACX;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,OAAO,MAAM,EAAE,MAAM,MAAM,MAAS;AAC1C,YAAM,KAAK,OAAO,OAAO,EAAE,EAAE,MAAM,MAAM,MAAS;AAClD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,+BACJ,YACA,YACyC;AACzC,QAAI,CAAC,KAAK,eAAe;AACvB,YAAM,IAAI,MAAM,iFAAiF;AAAA,IACnG;AACA,WAAO,KAAK,cAAc,YAAY,YAAY,UAAU;AAAA,EAC9D;AAAA,EAEA,MAAM,OAAO,IAAqC;AAChD,UAAM,OAAO,KAAK,YAAY,IAAI,QAAQ;AAC1C,UAAM,KAAK,KAAK,IAAI;AACpB,UAAM,OAAO,MAAM,KAAK,KAAK,EAAE;AAC/B,QAAI,cAAc;AAClB,QAAI,KAAK,aAAa;AACpB,UAAI;AACF,cAAM,mBAAmB,MAAM,+BAA+B,KAAK,QAAQ,KAAK,WAAW;AAC3F,cAAM,SAAS,6BAA6B,kBAAkB,KAAK,WAAW;AAC9E,sBAAc;AAAA,UACZ,GAAG;AAAA,UACH;AAAA,UACA,GAAI,SACA;AAAA,YACE,UAAU;AAAA,cACR,GAAG,KAAK;AAAA,cACR,EAAE,MAAM,UAAmB,SAAS,QAAQ,IAAI,iBAAiB,UAAU;AAAA,YAC7E;AAAA,UACF,IACA,CAAC;AAAA,QACP;AAAA,MACF,SAAS,KAAK;AAIZ,aAAK,UAAU,IAAI,MAAM,qBAAqB,eAAe,GAAG,GAAG,IAAI;AAAA,MACzE;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACF,eAAS,MAAU,UAAK,MAAM,KAAK,GAAK;AAAA,IAE1C,SAAS,KAAK;AACZ,WAAK,UAAU,IAAI,MAAM,UAAU,eAAe,GAAG,GAAG,KAAK;AAC7D,YAAM,IAAI;AAAA,QACR,2BAA2B,EAAE,iBAAiB,eAAe,GAAG,CAAC;AAAA,QACjE,EAAE,OAAO,IAAI;AAAA,MACf;AAAA,IACF;AAEA,QAAI;AACF,YAAM,SAAS,IAAI;AAAA,QACjB;AAAA,QACA;AAAA,SACA,oBAAI,KAAK,GAAE,YAAY;AAAA,QACvB;AAAA,UACE;AAAA,UACA,OAAO,KAAK,SAAS;AAAA,UACrB,UAAU,KAAK,SAAS;AAAA,QAC1B;AAAA,QACA,KAAK;AAAA,QACL;AAAA,UACE,SAAS;AAAA;AAAA;AAAA;AAAA,UAIT,KAAU,cAAQ,IAAI;AAAA,UACtB,UAAU;AAAA,UACV,gBAAgB,KAAK;AAAA,UACrB,eAAe,KAAK;AAAA,UACpB,SAAS,CAAC,MAAM,KAAK,cAAc,CAAC;AAAA,QACtC;AAAA,MACF;AACA,WAAK,UAAU,IAAI,MAAM,UAAU,WAAW,KAAK,IAAI,IAAI,EAAE;AAC7D,aAAO,EAAE,QAAQ,MAAM,YAAY;AAAA,IAErC,SAAS,KAAK;AACZ,YAAM,OAAO,MAAM,EAAE,MAAM,CAAC,MAAM,QAAQ,KAAK,KAAK,UAAU;AAAA,QAC5D,OAAO;AAAA,QACP,OAAO;AAAA,QACP,SAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAAA,QAClD,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC,CAAC,CAAC;AACH,WAAK,UAAU,IAAI,MAAM,UAAU,eAAe,GAAG,GAAG,IAAI;AAC5D,YAAM;AAAA,IACR;AAAA,EAEF;AAAA,EAEA,MAAM,KAAK,IAAkC;AAC3C,WAAO,KAAK,aAAa,IAAI,EAAE,MAAM,KAAK,CAAC;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,eAAe,IAAkC;AACrD,WAAO,KAAK,aAAa,IAAI,EAAE,MAAM,MAAM,CAAC;AAAA,EAC9C;AAAA,EAEA,MAAc,aACZ,IACA,MACsB;AACtB,UAAM,OAAO,KAAK,YAAY,IAAI,QAAQ;AAC1C,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI,UAAiC;AACrC,QAAI;AACJ,QAAI,WAAW;AACf,QAAI;AAIF,YAAM,IAAI,MAAU,UAAK,IAAI;AAC7B,YAAMC,SAA0C,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,KAAK;AAGnF,YAAM,SAAS,KAAK,WAAW,IAAI,EAAE;AACrC,UAAI,UAAU,OAAO,YAAYA,OAAK,WAAW,OAAO,SAASA,OAAK,MAAM;AAC1E,mBAAW;AAGX,aAAK,WAAW,OAAO,EAAE;AACzB,aAAK,WAAW,IAAI,IAAI,MAAM;AAC9B,YAAI,KAAK,KAAM,QAAO,OAAO;AAC7B,eAAO,EAAE,GAAG,OAAO,MAAM,UAAU,CAAC,EAAE;AAAA,MACxC;AAOA,YAAM,SAAyB,CAAC;AAGhC,UAAI;AACJ,UAAI;AACJ,UAAI;AACJ,UAAI;AACJ,UAAI;AACJ,UAAI;AAGJ,YAAM,WAAkC,KAAK,OAAO,CAAC,IAAI;AACzD,YAAM,eAAwC,KAAK,OAAO,oBAAI,IAAY,IAAI;AAC9E,UAAI,QAAQ,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,YAAY,EAAE;AAE/D,YAAM,SAAS,iBAAiB,MAAM,EAAE,UAAU,OAAO,CAAC;AAC1D,YAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,WAAW,SAAS,CAAC;AAEjE,UAAI;AACF,yBAAiB,QAAQ,IAAI;AAC3B,cAAI,CAAC,KAAK,KAAK,EAAG;AAClB,cAAI;AACF,kBAAM,SAAkB,KAAK,MAAM,IAAI;AACvC,gBACE,WAAW,QACX,OAAO,WAAW,YAClB,OAAQ,OAA0C,SAAS,YAC3D,OAAQ,OAAwC,OAAO,UACvD;AACA,oBAAM,KAAK;AACX,qBAAO,KAAK,EAAE;AAGd,kBAAI,GAAG,SAAS,mBAAmB,CAAC,mBAAmB;AACrD,oCAAoB;AACpB,+BAAe,GAAG;AAClB,kCAAkB,GAAG;AAAA,cACvB;AACA,kBAAI,GAAG,SAAS,eAAe;AAC7B,kCAAkB;AAClB,yCAAyB,GAAG;AAAA,cAC9B;AACA,kBAAI,GAAG,SAAS,oBAAoB,CAAC,oBAAoB;AACvD,qCAAqB;AAAA,cACvB;AAIA,kBAAI,KAAK,QAAQ,aAAa,UAAa,iBAAiB,QAAW;AACrE,oBAAI,GAAG,SAAS,oBAAoB;AAClC,sBAAI,CAAC,qBAAqB,UAAU,cAAc,GAAG,QAAQ,GAAG;AAC9D,yBAAK,QAAQ,KAAK,mBAAmB;AAAA,sBACnC,WAAW;AAAA,sBACX,QAAQ;AAAA,oBACV,CAAC;AAAA,kBACH;AAAA,gBACF,WAAW,GAAG,SAAS,cAAc;AACnC,+BAAa,MAAM;AACnB,2BAAS,KAAK,EAAE,MAAM,QAAQ,SAAS,GAAG,SAAS,IAAI,GAAG,GAAG,CAAC;AAAA,gBAChE,WAAW,GAAG,SAAS,gBAAgB;AACrC,2BAAS,KAAK,EAAE,MAAM,aAAa,SAAS,GAAG,SAAS,IAAI,GAAG,GAAG,CAAC;AACnE,6BAAW,KAAK,GAAG,SAAS;AAC1B,wBAAI,EAAE,SAAS,WAAY,cAAa,IAAI,EAAE,EAAE;AAAA,kBAClD;AACA,0BAAQ;AAAA,oBACN,OAAO,MAAM,SAAS,GAAG,MAAM,SAAS;AAAA,oBACxC,QAAQ,MAAM,UAAU,GAAG,MAAM,UAAU;AAAA,oBAC3C,YAAY,MAAM,aAAa,MAAM,GAAG,MAAM,aAAa;AAAA,oBAC3D,aAAa,MAAM,cAAc,MAAM,GAAG,MAAM,cAAc;AAAA,kBAChE;AAAA,gBACF,WAAW,GAAG,SAAS,eAAe;AACpC,sBAAI,CAAC,aAAa,IAAI,GAAG,EAAE,GAAG;AAC5B,yBAAK,QAAQ,KAAK,mBAAmB;AAAA,sBACnC,WAAW;AAAA,sBACX,QAAQ,uBAAuB,GAAG,EAAE;AAAA,oBACtC,CAAC;AACD;AAAA,kBACF;AACA,+BAAa,OAAO,GAAG,EAAE;AACzB,wBAAM,cAA4B;AAAA,oBAChC,MAAM;AAAA,oBACN,aAAa,GAAG;AAAA,oBAChB,SAAS,OAAO,GAAG,YAAY,WAAW,GAAG,UAAU,KAAK,UAAU,GAAG,OAAO;AAAA,oBAChF,UAAU,GAAG;AAAA,kBACf;AACA,wBAAM,OAAO,SAAS,SAAS,SAAS,CAAC;AACzC,wBAAM,uBACJ,MAAM,SAAS,UACf,MAAM,QAAQ,KAAK,OAAO,KAC1B,KAAK,QAAQ,MAAM,CAAC,MAAO,EAAmB,SAAS,aAAa;AACtE,sBAAI,wBAAwB,MAAM,QAAQ,KAAK,OAAO,GAAG;AACvD,yBAAK,QAAQ,KAAK,WAAW;AAAA,kBAC/B,OAAO;AACL,6BAAS,KAAK,EAAE,MAAM,QAAQ,SAAS,CAAC,WAAW,GAAG,IAAI,GAAG,GAAG,CAAC;AAAA,kBACnE;AAAA,gBACF;AAAA,cACF,WAAW,GAAG,SAAS,gBAAgB;AAErC,wBAAQ;AAAA,kBACN,OAAO,MAAM,SAAS,GAAG,MAAM,SAAS;AAAA,kBACxC,QAAQ,MAAM,UAAU,GAAG,MAAM,UAAU;AAAA,kBAC3C,YAAY,MAAM,aAAa,MAAM,GAAG,MAAM,aAAa;AAAA,kBAC3D,aAAa,MAAM,cAAc,MAAM,GAAG,MAAM,cAAc;AAAA,gBAChE;AAAA,cACF;AAAA,YACF;AAAA,UACF,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF,UAAE;AACA,WAAG,MAAM;AACT,eAAO,MAAM;AAAA,MACf;AAEA,UAAI,gBAA2B,CAAC;AAChC,UAAI,KAAK,QAAQ,aAAa,UAAa,iBAAiB,QAAW;AAErE,YAAI,aAAa,OAAO,GAAG;AACzB,eAAK,QAAQ,KAAK,mBAAmB;AAAA,YACnC,WAAW;AAAA,YACX,QAAQ,GAAG,aAAa,IAAI;AAAA,UAC9B,CAAC;AAAA,QACH;AACA,cAAM,WAAW,uBAAuB,QAAQ;AAChD,YAAI,SAAS,OAAO,SAAS;AAC3B,eAAK,QAAQ,KAAK,mBAAmB;AAAA,YACnC,WAAW;AAAA,YACX,QACE,sCAAsC,SAAS,OAAO,gBAAgB,MAAM,cACzE,SAAS,OAAO,mBAAmB,MAAM,iBACzC,SAAS,OAAO,eAAe;AAAA,UACtC,CAAC;AAAA,QACH;AACA,wBAAgB,SAAS;AAAA,MAC3B;AAGA,YAAM,OAAwB;AAAA,QAC5B;AAAA,QACA,WAAW,mBAAmB,OAAM,oBAAI,KAAK,CAAC,GAAE,YAAY;AAAA,QAC5D,SAAS,iBAAiB;AAAA,QAC1B,OAAO;AAAA,QACP,UAAU;AAAA,QACV,iBAAiB;AAAA,QACjB,YAAY,qBACR;AAAA,UACE,WAAW,mBAAmB;AAAA,UAC9B,uBAAuB,mBAAmB;AAAA,UAC1C,gBAAgB,mBAAmB;AAAA,UACnC,WAAW,mBAAmB;AAAA,UAC9B,yBAAyB,mBAAmB;AAAA,QAC9C,IACA;AAAA,MACN;AAGA,YAAM,eAAe,oBAAoB,MAAM;AAC/C,YAAM,OAAoB,EAAE,UAAU,MAAM,QAAQ,UAAU,eAAe,OAAO,aAAa;AAMjG,UAAI,KAAK,MAAM;AACb,YAAI,KAAK,WAAW,QAAQ,qBAAoB,wBAAwB;AAExE,gBAAM,SAAS,KAAK,WAAW,KAAK,EAAE,KAAK,EAAE;AAC7C,cAAI,WAAW,QAAW;AACxB,iBAAK,WAAW,OAAO,MAAM;AAAA,UAC/B;AAAA,QACF;AACA,aAAK,WAAW,IAAI,IAAI,EAAE,SAASA,OAAK,SAAS,MAAMA,OAAK,MAAM,KAAK,CAAC;AAAA,MACxE;AAEA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,gBAAU;AACV,iBAAW,eAAe,GAAG;AAC7B,YAAM;AAAA,IACR,UAAE;AACA,WAAK,SAAS,IAAI,MAAM,KAAK,OAAO,SAAS,oBAAoB,SAAS,KAAK,IAAI,IAAI,IAAI,QAAQ;AACnG,UAAI,UAAU;AACZ,aAAK,QAAQ,KAAK,qBAAqB;AAAA,UACrC,WAAW;AAAA,UACX,OAAO;AAAA,UACP,UAAU;AAAA,UACV,WAAW,KAAK,OAAO,SAAS;AAAA,UAChC,YAAY,KAAK,IAAI,IAAI;AAAA,QAC3B,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,MAAM,aACJ,IACA,WACA,MACyE;AACzE,UAAM,OAAO,KAAK,YAAY,IAAI,QAAQ;AAC1C,UAAM,QAAQ,MAAM;AACpB,UAAM,SAAS,MAAM;AACrB,UAAM,MAAsE,CAAC;AAI7E,QAAIA;AACJ,QAAI;AACF,MAAAA,SAAO,MAAU,UAAK,IAAI;AAAA,IAC5B,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,YAAM;AAAA,IACR;AACA,QAAIA,OAAK,SAAS,EAAG,QAAO,CAAC;AAE7B,QAAI;AACJ,QAAI;AACF,WAAK,MAAU,UAAK,MAAM,GAAG;AAG7B,YAAM,QAAQ,KAAK;AACnB,YAAM,MAAM,OAAO,MAAM,KAAK;AAC9B,UAAI,WAAW;AACf,UAAI,aAAa;AACjB,eAAS,WAAW,KAAK,YAAY,IAAI,YAAY;AACnD,YAAI,QAAQ,SAAS;AACnB,gBAAM,SAAS,OAAO,UAAU,IAAI,aAAa,WAAW,YAAY;AACxE,gBAAM;AAAA,QACR;AACA,cAAM,EAAE,UAAU,IAAI,MAAM,GAAG,KAAK,KAAK,GAAG,OAAO,QAAQ;AAC3D,YAAI,cAAc,EAAG;AACrB,cAAM,OAAO,WAAW,IAAI,SAAS,GAAG,SAAS,EAAE,SAAS,MAAM;AAGlE,cAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,mBAAW,MAAM,IAAI,KAAK;AAC1B,mBAAW,QAAQ,OAAO;AACxB,cAAI,CAAC,KAAM;AACX,cAAI;AACJ,cAAI;AACF,kBAAM,SAAkB,KAAK,MAAM,IAAI;AACvC,gBACE,WAAW,QACX,OAAO,WAAW,YAClB,OAAQ,OAA8B,SAAS,YAC/C,OAAQ,OAA4B,OAAO,UAC3C;AAGA;AAAA,YACF;AACA,iBAAK;AAAA,UACP,QAAQ;AAEN;AAAA,UACF;AACA,cAAI,UAAU,IAAI,YAAY,GAAG,EAAE,GAAG;AACpC,gBAAI,KAAK,EAAE,OAAO,IAAI,YAAY,IAAI,GAAG,GAAG,CAAC;AAC7C,gBAAI,UAAU,UAAa,IAAI,UAAU,OAAO;AAC9C,qBAAO;AAAA,YACT;AAAA,UACF;AACA;AAAA,QACF;AAAA,MACF;AAEA,UAAI,SAAS,KAAK,GAAG;AACnB,YAAI;AACF,gBAAM,SAAkB,KAAK,MAAM,QAAQ;AAC3C,cACE,WAAW,QACX,OAAO,WAAW,YAClB,OAAQ,OAA8B,SAAS,YAC/C,OAAQ,OAA4B,OAAO,UAC3C;AACA,kBAAM,KAAK;AACX,gBAAI,UAAU,IAAI,YAAY,GAAG,EAAE,GAAG;AACpC,kBAAI,KAAK,EAAE,OAAO,IAAI,YAAY,IAAI,GAAG,GAAG,CAAC;AAAA,YAC/C;AAAA,UACF;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AACA,aAAO;AAAA,IACT,UAAE;AACA,UAAI,GAAI,OAAM,GAAG,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,QAAQ,IAA+B;AAChD,QAAI;AACF,YAAM,UAAU,KAAK,GAAG;AAGxB,YAAM,UAAU,MAAM,KAAK,UAAU;AACrC,UAAI,QAAQ,SAAS,GAAG;AAGtB,eAAO,QAAQ,MAAM,GAAG,KAAK;AAAA,MAC/B;AAIA,aAAO,MAAM,KAAK,sBAAsB,KAAK;AAAA,IAC/C,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,aAAa,UAQW;AAC5B,UAAM,QAAQ,SAAS,SAAS;AAChC,QAAI;AACF,YAAM,UAAU,KAAK,GAAG;AACxB,YAAM,UAAU,MAAM,KAAK,UAAU;AACrC,UAAI,QAAQ,WAAW,GAAG;AAExB,cAAM,MAAM,MAAM,KAAK,KAAK,KAAK,IAAI,OAAO,GAAG,CAAC;AAChD,eAAO,IAAI,OAAO,CAAC,MAAM,qBAAqB,GAAG,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK;AAAA,MAC5E;AACA,YAAM,WAAW,QAAQ,OAAO,CAAC,MAAM,qBAAqB,GAAG,QAAQ,CAAC;AACxE,eAAS,KAAK,CAAC,GAAG,MAAM;AACtB,YAAI,EAAE,YAAY,EAAE,UAAW,QAAO;AACtC,YAAI,EAAE,YAAY,EAAE,UAAW,QAAO;AACtC,eAAO,EAAE,GAAG,cAAc,EAAE,EAAE;AAAA,MAChC,CAAC;AACD,aAAO,SAAS,MAAM,GAAG,KAAK;AAAA,IAChC,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYQ,mBAAmB;AAAA,EAC3B,OAAwB,gBAAgB;AAAA;AAAA,EAGxC,MAAc,cAAc,SAAwC;AAGlE,QAAI;AACF,YAAM,UAAU,KAAK,GAAG;AAOxB,UAAI,gBAAgB;AACpB,YAAM,aAAa,KAAK,WAAW,YAAY;AAC7C,cAAM,OAAO,KAAK,UAAU,OAAO,IAAI;AACvC,cAAU,gBAAW,KAAK,WAAW,MAAM,MAAM;AACjD,aAAK,cAAc;AACnB,aAAK,mCAAmC,QAAQ,EAAE;AAClD,aAAK;AAIL,YAAI,KAAK,oBAAoB,qBAAoB,eAAe;AAC9D,0BAAgB;AAChB,eAAK,mBAAmB;AAAA,QAC1B;AAAA,MACF,CAAC;AACD,UAAI,eAAe;AACjB,cAAM,aAAa,KAAK,WAAW,MAAM,KAAK,kBAAkB,CAAC;AAAA,MACnE;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA,EAGA,MAAc,eAAe,IAA2B;AACtD,QAAI;AACF,YAAM,UAAU,KAAK,GAAG;AACxB,YAAM,aAAa,KAAK,WAAW,YAAY;AAC7C,cAAM,OAAO,KAAK,UAAU,EAAE,QAAQ,UAAU,GAAG,CAAC,IAAI;AACxD,cAAU,gBAAW,KAAK,WAAW,MAAM,MAAM;AACjD,aAAK,cAAc;AACnB,aAAK,mCAAmC,EAAE;AAC1C,aAAK;AAAA,MACP,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,eAA8B;AAC1C,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI,UAAiC;AACrC,QAAI;AACJ,QAAI;AACF,YAAM,aAAa,KAAK,WAAW,MAAM,KAAK,kBAAkB,CAAC;AAAA,IACnE,SAAS,KAAK;AACZ,gBAAU;AACV,iBAAW,eAAe,GAAG;AAAA,IAC/B,UAAE;AAEA,WAAK,UAAU,aAAa,KAAK,WAAW,WAAW,SAAS,KAAK,IAAI,IAAI,IAAI,QAAW,QAAQ;AAAA,IACtG;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,oBAAmC;AAC/C,UAAM,UAAU,MAAM,KAAK,UAAU;AACrC,QAAI,QAAQ,WAAW,EAAG;AAC1B,UAAM,QAAQ,QAAQ,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI;AACjE,UAAM,YAAY,KAAK,WAAW,OAAO,EAAE,MAAM,IAAM,CAAC;AACxD,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,YAAuC;AACnD,QAAIA;AACJ,QAAI;AACF,YAAM,IAAI,MAAU,UAAK,KAAK,SAAS;AACvC,MAAAA,SAAO,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,KAAK;AAAA,IAC5C,QAAQ;AACN,WAAK,cAAc;AACnB,aAAO,CAAC;AAAA,IACV;AAEA,QACE,KAAK,gBAAgB,QACrB,KAAK,YAAY,YAAYA,OAAK,WAClC,KAAK,YAAY,SAASA,OAAK,MAC/B;AACA,aAAO,CAAC,GAAG,KAAK,YAAY,SAAS;AAAA,IACvC;AAEA,QAAI;AACJ,QAAI;AACF,YAAM,MAAU,cAAS,KAAK,WAAW,MAAM;AAAA,IACjD,QAAQ;AACN,WAAK,cAAc;AACnB,aAAO,CAAC;AAAA,IACV;AACA,UAAM,UAAU,oBAAI,IAAY;AAChC,UAAM,OAAO,oBAAI,IAA4B;AAC7C,eAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,UAAI,CAAC,KAAK,KAAK,EAAG;AAClB,UAAI;AACF,cAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,YAAI,MAAM,WAAW,YAAY,MAAM,IAAI;AACzC,kBAAQ,IAAI,MAAM,EAAE;AACpB,eAAK,OAAO,MAAM,EAAE;AACpB;AAAA,QACF;AACA,YAAI,MAAM,MAAM,CAAC,QAAQ,IAAI,MAAM,EAAE,GAAG;AAEtC,eAAK,IAAI,MAAM,IAAI,KAAuB;AAAA,QAC5C;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AACA,UAAM,YAAY,MAAM,KAAK,KAAK,OAAO,CAAC;AAK1C,cAAU,KAAK,CAAC,GAAG,MAAM;AACvB,UAAI,EAAE,YAAY,EAAE,UAAW,QAAO;AACtC,UAAI,EAAE,YAAY,EAAE,UAAW,QAAO;AACtC,aAAO,EAAE,GAAG,cAAc,EAAE,EAAE;AAAA,IAChC,CAAC;AACD,SAAK,cAAc,EAAE,GAAGA,QAAM,UAAU;AACxC,WAAO,CAAC,GAAG,SAAS;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,eAAgC;AACpC,UAAM,MAAM,MAAM,KAAK,kBAAkB,KAAK,GAAG;AAEjD,UAAM,YAAY,MAAM,QAAQ,IAAI,IAAI,IAAI,CAAC,OAAO,KAAK,WAAW,EAAE,EAAE,MAAM,MAAM,IAAI,CAAC,CAAC;AAC1F,UAAM,QAAQ,UAAU,OAAO,CAAC,MAA2B,MAAM,IAAI;AAMrE,UAAM,QAAQ,MAAM,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI;AAC/D,UAAM,aAAa,KAAK,WAAW,YAAY;AAC7C,YAAM,YAAY,KAAK,WAAW,OAAO,EAAE,MAAM,IAAM,CAAC;AACxD,WAAK,cAAc;AAAA,IACrB,CAAC;AACD,WAAO,MAAM;AAAA,EACf;AAAA,EAEA,MAAc,sBAAsB,OAA0C;AAC5E,UAAM,YAAY,MAAM,KAAK,iBAAiB;AAC9C,UAAM,eAAe,MAAM;AAAA,MACzB;AAAA,MACA,qBAAoB;AAAA,MACpB,OAAO,aAAa,MAAM,KAAK,yBAAyB,QAAQ;AAAA,IAClE;AAEA,UAAM,MAAmC,CAAC;AAC1C,eAAW,SAAS,cAAc;AAChC,iBAAW,WAAW,MAAM,WAAW;AACrC,YAAI,KAAK,EAAE,SAAS,eAAe,MAAM,CAAC;AAAA,MAC5C;AAAA,IACF;AACA,QAAI,KAAK,CAAC,GAAG,MAAM,wBAAwB,EAAE,SAAS,EAAE,OAAO,CAAC;AAEhE,UAAM,WAAW,IAAI,MAAM,GAAG,KAAK;AACnC,UAAM,YAAY,MAAM;AAAA,MACtB;AAAA,MACA,KAAK,IAAI,qBAAoB,uBAAuB,KAAK,IAAI,GAAG,KAAK,CAAC;AAAA,MACtE,OAAO,cAA8C,UAAU;AAAA,IACjE;AACA,WAAO,UAAU,OAAO,CAAC,MAA2B,MAAM,IAAI;AAAA,EAChE;AAAA,EAEA,MAAc,mBAAsC;AAClD,QAAI;AACJ,QAAI;AACF,gBAAU,MAAU,aAAQ,KAAK,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,IAC/D,QAAQ;AACN,aAAO,CAAC,EAAE;AAAA,IACZ;AAEA,UAAM,YAAY,CAAC,EAAE;AACrB,eAAW,SAAS,SAAS;AAC3B,UAAI,MAAM,KAAK,WAAW,GAAG,KAAK,MAAM,SAAS,cAAe;AAChE,UAAI,MAAM,SAAS,YAAY,MAAM,SAAS,eAAe,MAAM,SAAS,cAAe;AAC3F,UAAI,MAAM,YAAY,EAAG,WAAU,KAAK,MAAM,IAAI;AAAA,IACpD;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,yBAAyB,UAA+C;AACpF,UAAM,SAAS,KAAK,mBAAmB,IAAI,QAAQ;AACnD,QAAI,OAAQ,QAAO;AAEnB,UAAM,eAAe,KAAK,kBAAkB,QAAQ;AACpD,QAAI;AACF,YAAM,MAAM,MAAU,cAAS,cAAc,MAAM;AACnD,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,YAAMC,SAA4B;AAAA,QAChC,WAAW,MAAM,QAAQ,OAAO,SAAS,IAAI,OAAO,YAAY,CAAC;AAAA,QACjE,KAAK,MAAM,QAAQ,OAAO,GAAG,IAAI,OAAO,MAAM,CAAC;AAAA,MACjD;AACA,WAAK,mBAAmB,IAAI,UAAUA,MAAK;AAC3C,aAAOA;AAAA,IACT,QAAQ;AAAA,IAER;AAEA,UAAM,OAAO,MAAM,KAAK,2BAA2B,QAAQ;AAC3D,UAAM,aAAa,MAAM;AAAA,MACvB;AAAA,MACA,qBAAoB;AAAA,MACpB,OAAO,QAAmD;AACxD,cAAM,WAAW,MAAM,KAAK,oBAAoB,IAAI,EAAE;AACtD,YAAI,SAAU,QAAO,EAAE,SAAS,UAAU,eAAe,MAAM;AAC/D,cAAM,UAAU,MAAM,KAAK,iBAAiB,GAAG;AAC/C,YAAI,CAAC,QAAS,QAAO;AACrB,cAAM,WAAW,MAAM,KAAK,WAAW,QAAQ,EAAE,EAAE,MAAM,MAAM,OAAO;AACtE,eAAO,EAAE,SAAS,UAAU,eAAe,MAAM;AAAA,MACnD;AAAA,IACF;AACA,UAAM,YAAY,WACf,OAAO,CAAC,cAAsD,cAAc,IAAI,EAChF,IAAI,CAAC,cAAc,UAAU,OAAO;AACvC,cAAU,KAAK,uBAAuB;AACtC,UAAM,QAA4B,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC,YAAY,QAAQ,EAAE,EAAE;AAC3F,SAAK,mBAAmB,IAAI,UAAU,KAAK;AAC3C,UAAM,YAAY,cAAc,KAAK,UAAU,KAAK,GAAG,EAAE,MAAM,IAAM,CAAC,EAAE,MAAM,MAAM,MAAS;AAC7F,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,2BAA2B,UAA6C;AACpF,UAAM,MAAM,WAAgB,WAAK,KAAK,KAAK,QAAQ,IAAI,KAAK;AAC5D,UAAM,UAAU,MAAM,KAAK,oBAAoB,KAAK,QAAQ;AAC5D,WAAO,WACH,QAAQ,OAAO,CAAC,UAAU,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,CAAC,IAC7D,QAAQ,OAAO,CAAC,UAAU,CAAC,MAAM,GAAG,SAAS,GAAG,CAAC;AAAA,EACvD;AAAA,EAEA,MAAc,oBACZ,KACA,SAAS,IACT,QAAQ,GACmB;AAC3B,QAAI;AACJ,QAAI;AACF,gBAAU,MAAU,aAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,IAC1D,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,aAAuB,CAAC;AAC9B,UAAM,QAA0B,CAAC;AACjC,eAAW,SAAS,SAAS;AAC3B,UAAI,MAAM,KAAK,WAAW,GAAG,KAAK,MAAM,SAAS,cAAe;AAChE,UAAI,MAAM,SAAS,YAAY,MAAM,SAAS,eAAe,MAAM,SAAS;AAC1E;AACF,UAAI,MAAM,YAAY,GAAG;AACvB,mBAAW,KAAK,KAAK;AAAA,MACvB,WAAW,MAAM,OAAO,KAAK,MAAM,KAAK,SAAS,QAAQ,GAAG;AAC1D,YAAI,MAAM,SAAS,eAAgB;AACnC,cAAM,OAAO,MAAM,KAAK,QAAQ,YAAY,EAAE;AAC9C,cAAM,KAAK,SAAS,GAAG,MAAM,IAAI,IAAI,KAAK;AAC1C,cAAM,KAAK,EAAE,IAAI,UAAe,WAAK,KAAK,MAAM,IAAI,EAAE,CAAC;AAAA,MACzD;AAAA,IACF;AAEA,UAAM,kBAAkB,MAAM,QAAQ;AAAA,MACpC,WAAW,IAAI,CAAC,UAAU;AACxB,cAAM,cAAc,UAAU,IAAI,MAAM,OAAO,GAAG,MAAM,IAAI,MAAM,IAAI;AACtE,eAAO,KAAK,oBAAyB,WAAK,KAAK,MAAM,IAAI,GAAG,aAAa,QAAQ,CAAC;AAAA,MACpF,CAAC;AAAA,IACH;AAEA,WAAO,CAAC,GAAG,gBAAgB,KAAK,GAAG,GAAG,KAAK;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,kBACZ,KACA,SAAS,IACT,QAAQ,GACW;AACnB,QAAI;AACJ,QAAI;AACF,gBAAU,MAAU,aAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,IAC1D,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAGA,UAAM,aAAuB,CAAC;AAC9B,UAAM,UAAoB,CAAC;AAC3B,eAAW,SAAS,SAAS;AAC3B,UAAI,MAAM,KAAK,WAAW,GAAG,KAAK,MAAM,SAAS,cAAe;AAChE,UAAI,MAAM,SAAS,YAAY,MAAM,SAAS,eAAe,MAAM,SAAS;AAC1E;AACF,UAAI,MAAM,YAAY,GAAG;AACvB,mBAAW,KAAK,KAAK;AAAA,MACvB,WAAW,MAAM,OAAO,KAAK,MAAM,KAAK,SAAS,QAAQ,GAAG;AAC1D,YAAI,MAAM,SAAS,eAAgB;AACnC,cAAM,OAAO,MAAM,KAAK,QAAQ,YAAY,EAAE;AAC9C,gBAAQ,KAAK,SAAS,GAAG,MAAM,IAAI,IAAI,KAAK,IAAI;AAAA,MAClD;AAAA,IACF;AAIA,UAAM,gBAAgB,MAAM,QAAQ;AAAA,MAClC,WAAW,IAAI,CAAC,UAAU;AACxB,cAAM,cAAc,UAAU,IAAI,MAAM,OAAO,GAAG,MAAM,IAAI,MAAM,IAAI;AACtE,eAAO,KAAK,kBAAuB,WAAK,KAAK,MAAM,IAAI,GAAG,aAAa,QAAQ,CAAC;AAAA,MAClF,CAAC;AAAA,IACH;AAEA,WAAO,CAAC,GAAG,cAAc,KAAK,GAAG,GAAG,OAAO;AAAA,EAC7C;AAAA,EAEA,MAAc,WAAW,IAAqC;AAC5D,UAAM,WAAW,KAAK,YAAY,IAAI,eAAe;AACrD,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI,UAAiC;AACrC,QAAI;AACJ,UAAM,eAAe,MAAM,KAAK,oBAAoB,IAAI,EAAE;AAC1D,QAAI,aAAc,QAAO;AAEzB,QAAI;AACF,YAAM,OAAO,KAAK,YAAY,IAAI,QAAQ;AAC1C,YAAMD,SAAO,MAAU,UAAK,IAAI;AAChC,YAAM,UAAU,MAAM,KAAK,UAAU,IAAIA,OAAK,MAAM,YAAY,CAAC;AACjE,YAAM,YAAY,UAAU,KAAK,UAAU,OAAO,GAAG,EAAE,MAAM,IAAM,CAAC,EAAE,MAAM,CAAC,QAAQ;AACnF,cAAM,MAAM,eAAe,GAAG;AAC9B,aAAK,UAAU,IAAI,UAAU,oBAAoB,KAAK,IAAI;AAC1D,gBAAQ,KAAK,KAAK,UAAU;AAAA,UAC1B,OAAO;AAAA,UACP,OAAO;AAAA,UACP,WAAW;AAAA,UACX,SAAS;AAAA,UACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC,CAAC,CAAC;AAAA,MACJ,CAAC;AACD,gBAAU;AACV,iBAAW;AACX,WAAK,SAAS,IAAI,UAAU,WAAW,SAAS,KAAK,IAAI,IAAI,IAAI,QAAQ;AACzE,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,gBAAU;AACV,iBAAW,eAAe,GAAG;AAC7B,WAAK,SAAS,IAAI,UAAU,WAAW,SAAS,KAAK,IAAI,IAAI,IAAI,QAAQ;AACzE,aAAO;AAAA,QACL;AAAA,QACA,OAAO;AAAA,QACP,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,OAAO;AAAA,QACP,UAAU;AAAA,QACV,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,oBACZ,IACA,YAAY,KAAK,IAAI,GACW;AAChC,UAAM,WAAW,KAAK,YAAY,IAAI,eAAe;AACrD,QAAI;AACF,YAAM,MAAM,MAAU,cAAS,UAAU,MAAM;AAC/C,WAAK,SAAS,IAAI,UAAU,WAAW,WAAW,KAAK,IAAI,IAAI,SAAS;AACxE,aAAO,KAAK,MAAM,GAAG;AAAA,IACvB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAc,iBAAiB,KAAqD;AAClF,QAAI,SAAQ,oBAAI,KAAK,CAAC,GAAE,YAAY;AACpC,QAAI;AACF,YAAMA,SAAO,MAAU,UAAK,IAAI,QAAQ;AACxC,UAAI,CAACA,OAAK,OAAO,GAAG;AAClB,eAAO;AAAA,UACL,IAAI,IAAI;AAAA,UACR,OAAO;AAAA,UACP,WAAWA,OAAK,MAAM,YAAY;AAAA,UAClC,OAAO;AAAA,UACP,UAAU;AAAA,UACV,YAAY;AAAA,QACd;AAAA,MACF;AACA,cAAQA,OAAK,MAAM,YAAY;AAAA,IACjC,QAAQ;AACN,aAAO;AAAA,IACT;AAEA,QAAI;AACF,uBAAiB,SAAS,KAAK,kBAAkB,IAAI,QAAQ,GAAG;AAC9D,YAAI,MAAM,SAAS,iBAAiB;AAClC,iBAAO;AAAA,YACL,IAAI,IAAI;AAAA,YACR,OAAO;AAAA,YACP,WAAW,MAAM;AAAA,YACjB,OAAO,MAAM,SAAS;AAAA,YACtB,UAAU,MAAM,YAAY;AAAA,YAC5B,YAAY;AAAA,UACd;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,QACL,IAAI,IAAI;AAAA,QACR,OAAO;AAAA,QACP,YAAW,oBAAI,KAAK,CAAC,GAAE,YAAY;AAAA,QACnC,OAAO;AAAA,QACP,UAAU;AAAA,QACV,YAAY;AAAA,MACd;AAAA,IACF,QAAQ;AACN,aAAO;AAAA,QACL,IAAI,IAAI;AAAA,QACR,OAAO;AAAA,QACP,WAAW;AAAA,QACX,OAAO;AAAA,QACP,UAAU;AAAA,QACV,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,cAAc,IAA2B;AACrD,UAAM,YAAY,KAAK,YAAY,IAAI,QAAQ;AAC/C,UAAM,cAAc,KAAK,YAAY,IAAI,eAAe;AACxD,UAAM,WAAgB,cAAQ,SAAS;AACvC,UAAM,OAAY,eAAS,EAAE;AAC7B,UAAM,UAAe,WAAK,UAAU,IAAI;AAExC,UAAM,YAAkC;AAAA,MAClC,YAAO,SAAS;AAAA,MAChB,YAAO,WAAW;AAAA,MAClB,YAAO,kBAAkB,KAAK,KAAK,IAAI,YAAY,CAAC;AAAA,MACpD,YAAO,kBAAkB,KAAK,KAAK,IAAI,aAAa,CAAC;AAAA,MACrD,YAAO,kBAAkB,KAAK,KAAK,IAAI,aAAa,CAAC;AAAA,IAC3D;AAEA,UAAM,UAAU,MAAM,QAAQ,WAAW,SAAS;AAClD,eAAW,KAAK,SAAS;AACvB,UAAI,EAAE,WAAW,YAAY;AAC3B,cAAM,MAAM,EAAE,kBAAkB,QAAQ,EAAE,OAAO,UAAU,OAAO,EAAE,MAAM;AAE1E,YAAK,EAAE,QAAkC,SAAS,UAAU;AAC1D,kBAAQ,KAAK,KAAK,UAAU;AAAA,YAC1B,OAAO;AAAA,YACP,OAAO;AAAA,YACP,WAAW;AAAA,YACX,SAAS;AAAA,YACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC,CAAC,CAAC;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAIA,UAAU,QAAG,SAAS,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC,EAAE,MAAM,CAAC,QAAQ;AACrE,cAAQ,KAAK,KAAK,UAAU;AAAA,QAC1B,OAAO;AAAA,QACP,OAAO;AAAA,QACP,WAAW;AAAA,QACX,SAAS,eAAe,GAAG;AAAA,QAC3B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC,CAAC;AAAA,IACJ,CAAC;AAID,UAAM,KAAK,eAAe,EAAE;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,sBAA8C;AAC1D,QAAI;AACF,YAAM,MAAM,MAAU,cAAc,WAAK,KAAK,KAAK,aAAa,GAAG,MAAM;AACzE,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,aAAO,OAAO,aAAa;AAAA,IAC7B,QAAQ;AAEN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,IAA2B;AAOtC,UAAM,WAAW,MAAM,KAAK,oBAAoB;AAChD,QAAI,YAAY,OAAO,UAAU;AAC/B,YAAM,IAAI;AAAA,QACR,WAAW,EAAE;AAAA,MACf;AAAA,IACF;AAMA,QAAI,KAAK,gBAAgB;AACvB,YAAM,SAAS,MAAM,KAAK,eAAe,EAAE;AAC3C,UAAI,QAAQ;AACV,cAAM,IAAI,MAAM,WAAW,EAAE,eAAe,MAAM,0BAA0B;AAAA,MAC9E;AAAA,IACF;AACA,UAAM,KAAK,cAAc,EAAE;AAAA,EAC7B;AAAA,EAEA,MAAM,OAAO,IAAY,MAAuC;AAC9D,UAAM,UAAU,KAAK,KAAK;AAC1B,UAAM,WAAW,KAAK,YAAY,IAAI,eAAe;AACrD,UAAM,YAAY,KAAK,YAAY,IAAI,QAAQ;AAK/C,QAAI;AACF,YAAU,UAAK,SAAS;AAAA,IAC1B,SAAS,KAAK;AACZ,YAAM,OAAQ,KAA+B;AAC7C,UAAI,SAAS,UAAU;AACrB,cAAM,IAAI,MAAM,sBAAsB,EAAE,EAAE;AAAA,MAC5C;AACA,YAAM;AAAA,IACR;AAOA,UAAM,UAAU,MAAM,KAAK,WAAW,EAAE;AACxC,UAAM,EAAE,MAAM,OAAO,GAAG,KAAK,IAAI;AACjC,UAAM,UAA0B,UAAU,EAAE,GAAG,MAAM,MAAM,QAAQ,IAAI;AAEvE,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI,UAAiC;AACrC,QAAI;AACJ,QAAI;AACF,YAAM,YAAY,UAAU,KAAK,UAAU,OAAO,GAAG,EAAE,MAAM,IAAM,CAAC;AAAA,IACtE,SAAS,KAAK;AACZ,gBAAU;AACV,iBAAW,eAAe,GAAG;AAC7B,WAAK,UAAU,IAAI,UAAU,UAAU,UAAU,KAAK;AACtD,YAAM;AAAA,IACR,UAAE;AACA,WAAK,UAAU,IAAI,UAAU,SAAS,SAAS,KAAK,IAAI,IAAI,IAAI,QAAW,QAAQ;AAAA,IACrF;AAKA,UAAM,KAAK,cAAc,OAAO;AAChC,SAAK,mCAAmC,EAAE;AAC1C,SAAK,cAAc;AACnB,SAAK,eAAe,EAAE;AACtB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,MAAM,aAAa,IAAqB;AAC5C,UAAM,SAAS,KAAK,IAAI,IAAI,aAAa;AACzC,QAAI,UAAU;AAGd,UAAM,kBAAkB,MAAM,KAAK,oBAAoB;AAEvD,UAAM,kBAAkB,CAAC,SACvB,KAAK,SAAS,QAAQ,KACtB,SAAS,kBACT,SAAS,oBACT,CAAC,KAAK,SAAS,eAAe,KAC9B,CAAC,KAAK,SAAS,cAAc;AAE/B,UAAM,YAAY,OAAO,KAAa,MAAc,WAAkC;AACpF,YAAM,YAAiB,WAAK,KAAK,IAAI;AACrC,UAAI;AACF,cAAMA,SAAO,MAAU,UAAK,SAAS;AACrC,YAAIA,OAAK,WAAW,OAAQ;AAAA,MAE9B,QAAQ;AACN;AAAA,MACF;AAEA,YAAM,OAAO,KAAK,QAAQ,YAAY,EAAE;AACxC,YAAM,KAAK,SAAS,GAAG,MAAM,IAAI,IAAI,KAAK;AAE1C,UAAI,mBAAmB,OAAO,gBAAiB;AAC/C,YAAM,KAAK,cAAc,EAAE;AAC3B;AAAA,IACF;AAGA,UAAM,UAAU,MAAU,aAAQ,KAAK,KAAK,EAAE,eAAe,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC,CAAC;AACnF,eAAW,SAAS,SAAS;AAC3B,UAAI,MAAM,OAAO,GAAG;AAGlB,YAAI,gBAAgB,MAAM,IAAI,EAAG,OAAM,UAAU,KAAK,KAAK,MAAM,MAAM,EAAE;AACzE;AAAA,MACF;AAEA,UAAI,CAAC,MAAM,YAAY,EAAG;AAE1B,YAAM,UAAe,WAAK,KAAK,KAAK,MAAM,IAAI;AAE9C,YAAM,QAAQ,MAAU,aAAQ,SAAS,EAAE,eAAe,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC,CAAC;AAChF,iBAAW,QAAQ,OAAO;AACxB,YAAI,CAAC,KAAK,OAAO,KAAK,CAAC,gBAAgB,KAAK,IAAI,EAAG;AACnD,cAAM,UAAU,SAAS,KAAK,MAAM,MAAM,IAAI;AAAA,MAChD;AAAA,IACF;AACA,QAAI,UAAU,GAAG;AAGf,YAAM,KAAK,aAAa,EAAE,MAAM,MAAM,MAAS;AAAA,IACjD;AAEA,eAAW,SAAS,SAAS;AAC3B,UAAI,CAAC,MAAM,YAAY,EAAG;AAC1B,YAAM,UAAe,WAAK,KAAK,KAAK,MAAM,IAAI;AAC9C,UAAI;AACF,cAAM,YAAY,MAAU,aAAQ,OAAO;AAC3C,YAAI,UAAU,WAAW,GAAG;AAE1B,gBAAU,WAAM,OAAO,EAAE,MAAM,MAAM,MAAS;AAAA,QAChD;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,aAAa,IAA2B;AAC5C,UAAM,KAAK,eAAe,EAAE;AAC5B,UAAM,OAAO,KAAK,YAAY,IAAI,QAAQ;AAC1C,UAAM,OAAO,KAAK,YAAY,IAAI,eAAe;AACjD,UAAM,SAAS,GAAG,KAAK,UAAU;AAAA,MAC/B,MAAM;AAAA,MACN,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC3B;AAAA,MACA,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC,CAAC;AAAA;AACF,UAAU,eAAU,MAAM,QAAQ,MAAM;AACxC,UAAU,YAAO,IAAI,EAAE,MAAM,MAAM,MAAS;AAAA,EAC9C;AAAA,EAEA,MAAc,UAAU,IAAY,OAAwC;AAC1E,QAAI;AACF,YAAM,OAAO,KAAK,YAAY,IAAI,QAAQ;AAC1C,UAAI,QAAQ;AACZ,UAAI,aAAY,oBAAI,KAAK,CAAC,GAAE,YAAY;AACxC,UAAI;AACJ,UAAI,QAAQ;AACZ,UAAI,WAAW;AACf,UAAI,UAAU;AACd,UAAI,WAAW;AACf,UAAI,iBAAiB;AACrB,UAAI,gBAAgB;AACpB,UAAI,iBAAiB;AACrB,UAAI,kBAAkB;AACtB,YAAM,gBAAwC,CAAC;AAC/C,UAAI;AACJ,UAAI;AACJ,UAAI,WAAW;AACf,UAAI,WAAW;AAEf,uBAAiB,KAAK,KAAK,kBAAkB,IAAI,GAAG;AAClD,wBAAgB,EAAE;AAClB,YAAI,EAAE,SAAS,iBAAiB;AAC9B,cAAI,CAAC,UAAU;AACb,uBAAW;AACX,wBAAY,EAAE;AACd,oBAAQ,EAAE,SAAS;AACnB,uBAAW,EAAE,YAAY;AAAA,UAC3B;AAAA,QACF,WAAW,EAAE,SAAS,eAAe;AACnC,oBAAU,EAAE;AAAA,QACd,WAAW,EAAE,SAAS,cAAc;AAClC,cAAI,UAAU,kBAAmB,SAAQ,eAAe,EAAE,OAAO;AAAA,QACnE,WAAW,EAAE,SAAS,gBAAgB;AACpC,qBAAW,EAAE,MAAM,SAAS;AAC5B,sBAAY,EAAE,MAAM,UAAU;AAAA,QAChC,WAAW,EAAE,SAAS,kBAAmB;AAAA,iBAChC,EAAE,SAAS,mBAAmB;AACrC;AACA,wBAAc,EAAE,IAAI,KAAK,cAAc,EAAE,IAAI,KAAK,KAAK;AAAA,QACzD,WAAW,EAAE,SAAS,iBAAiB,EAAE,QAAS;AAAA,iBACzC,EAAE,SAAS,gBAAiB,oBAAmB,EAAE,MAAM;AAAA,iBACvD,EAAE,SAAS,WAAW,EAAE,SAAS,iBAAkB,YAAW;AAAA,MACzE;AAGA,UAAI,kBAAkB,eAAe;AACnC,kBAAU;AAAA,MACZ,WAAW,kBAAkB,mBAAmB;AAC9C,kBAAU;AAAA,MACZ,WAAW,UAAU;AACnB,kBAAU;AAAA,MACZ;AAEA,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,YAAY,UAAU;AAAA,QACtB,gBAAgB,iBAAiB,IAAI,iBAAiB;AAAA,QACtD,eAAe,gBAAgB,IAAI,gBAAgB;AAAA,QACnD,gBAAgB,iBAAiB,IAAI,iBAAiB;AAAA,QACtD,iBAAiB,kBAAkB,IAAI,kBAAkB;AAAA,QACzD,eAAe,OAAO,KAAK,aAAa,EAAE,SAAS,IAAI,gBAAgB,CAAC;AAAA,QACxE;AAAA,MACF;AAAA,IACF,QAAQ;AACN,aAAO;AAAA,QACL;AAAA,QACA,OAAO;AAAA,QACP,WAAW;AAAA,QACX,OAAO;AAAA,QACP,UAAU;AAAA,QACV,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAe,kBAAkB,MAA4C;AAC3E,UAAM,SAAS,iBAAiB,MAAM,EAAE,UAAU,OAAO,CAAC;AAC1D,UAAM,QAAQ,gBAAgB,EAAE,OAAO,QAAQ,WAAW,SAAS,CAAC;AACpE,QAAI;AACF,uBAAiB,QAAQ,OAAO;AAC9B,YAAI,CAAC,KAAK,KAAK,EAAG;AAClB,YAAI;AACF,gBAAM,SAAkB,KAAK,MAAM,IAAI;AACvC,cACE,WAAW,QACX,OAAO,WAAW,YAClB,OAAQ,OAA0C,SAAS,YAC3D,OAAQ,OAAwC,OAAO,UACvD;AACA,kBAAM;AAAA,UACR;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF,UAAE;AACA,YAAM,MAAM;AACZ,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AAGF;;;AwBtsDA,YAAYE,UAAS;AACrB,YAAYC,WAAU;AA4Bf,IAAM,aAAN,MAAiB;AAAA,EACL;AAAA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EAEjB,YAAY,MAA4D;AACtE,SAAK,OAAY,WAAK,KAAK,KAAK,YAAY;AAC5C,SAAK,SAAS,KAAK;AACnB,SAAK,UAAU,KAAK;AAAA,EACtB;AAAA,EAEA,MAAM,MAAM,OAA4C;AACtD,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI,MAAM,WAAW,GAAG;AAGtB,YAAM,KAAK,MAAM;AACjB;AAAA,IACF;AACA,QAAI;AACF,YAAM,YAAY,KAAK,MAAM,KAAK,UAAU,KAAK,GAAG,EAAE,MAAM,IAAM,CAAC;AACnE,WAAK,QAAQ,KAAK,iBAAiB;AAAA,QACjC,WAAW,KAAK,WAAW;AAAA,QAC3B,OAAO;AAAA,QACP,UAAU,KAAK;AAAA,QACf,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,MAC5D,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,WAAK,QAAQ,KAAK,iBAAiB;AAAA,QACjC,WAAW,KAAK,WAAW;AAAA,QAC3B,OAAO;AAAA,QACP,UAAU,KAAK;AAAA,QACf,WAAW;AAAA,QACX,SAAS;AAAA,QACT,OAAO,eAAe,GAAG;AAAA,QACzB,aAAa;AAAA,QACb,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,MAC5D,CAAC;AACD,cAAQ,KAAK,KAAK,UAAU;AAAA,QAC1B,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM,KAAK;AAAA,QACX,SAAS,eAAe,GAAG;AAAA,QAC3B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC,CAAC;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,MAAM,OAAsC;AAC1C,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI;AACJ,QAAI;AACF,YAAM,MAAU,cAAS,KAAK,MAAM,MAAM;AAAA,IAC5C,SAAS,KAAK;AACZ,YAAM,OAAQ,IAA8B;AAC5C,UAAI,SAAS,UAAU;AACrB,aAAK,QAAQ,KAAK,gBAAgB;AAAA,UAChC,WAAW,KAAK,WAAW;AAAA,UAC3B,OAAO;AAAA,UACP,UAAU,KAAK;AAAA,UACf,WAAW;AAAA,UACX,SAAS;AAAA,UACT,YAAY,KAAK,IAAI,IAAI;AAAA,UACzB,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,QAC5D,CAAC;AACD,eAAO,CAAC;AAAA,MACV;AACA,WAAK,QAAQ,KAAK,iBAAiB;AAAA,QACjC,WAAW,KAAK,WAAW;AAAA,QAC3B,OAAO;AAAA,QACP,UAAU,KAAK;AAAA,QACf,WAAW;AAAA,QACX,SAAS;AAAA,QACT,OAAO,eAAe,GAAG;AAAA,QACzB,aAAa;AAAA,QACb,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,MAC5D,CAAC;AACD,cAAQ,KAAK,KAAK,UAAU;AAAA,QAC1B,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM,KAAK;AAAA,QACX,SAAS,eAAe,GAAG;AAAA,QAC3B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC,CAAC;AACF,aAAO,CAAC;AAAA,IACV;AACA,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,MAAM,GAAG;AAAA,IACzB,QAAQ;AACN,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC,WAAW,KAAK,WAAW;AAAA,QAC3B,OAAO;AAAA,QACP,UAAU,KAAK;AAAA,QACf,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,OAAO;AAAA,QACP,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,MAC5D,CAAC;AACD,aAAO,CAAC;AAAA,IACV;AACA,QAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC,WAAW,KAAK,WAAW;AAAA,QAC3B,OAAO;AAAA,QACP,UAAU,KAAK;AAAA,QACf,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,OAAO;AAAA,QACP,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,MAC5D,CAAC;AACD,aAAO,CAAC;AAAA,IACV;AACA,SAAK,QAAQ,KAAK,gBAAgB;AAAA,MAChC,WAAW,KAAK,WAAW;AAAA,MAC3B,OAAO;AAAA,MACP,UAAU,KAAK;AAAA,MACf,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,MACzB,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,IAC5D,CAAC;AACD,UAAM,MAA4B,CAAC;AACnC,eAAW,KAAK,QAAQ;AACtB,UAAI,qBAAqB,CAAC,EAAG,KAAI,KAAK,CAAC;AAAA,IACzC;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI;AACF,YAAU,YAAO,KAAK,IAAI;AAC1B,WAAK,QAAQ,KAAK,iBAAiB;AAAA,QACjC,WAAW,KAAK,WAAW;AAAA,QAC3B,OAAO;AAAA,QACP,UAAU,KAAK;AAAA,QACf,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,MAC5D,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,OAAQ,IAA8B;AAC5C,UAAI,SAAS,SAAU;AACvB,WAAK,QAAQ,KAAK,iBAAiB;AAAA,QACjC,WAAW,KAAK,WAAW;AAAA,QAC3B,OAAO;AAAA,QACP,UAAU,KAAK;AAAA,QACf,WAAW;AAAA,QACX,SAAS;AAAA,QACT,OAAO,eAAe,GAAG;AAAA,QACzB,aAAa;AAAA,QACb,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,MAC5D,CAAC;AAID,cAAQ,KAAK,KAAK,UAAU;AAAA,QAC1B,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM,KAAK;AAAA,QACX,SAAU,IAAc;AAAA,QACxB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC,CAAC;AAAA,IACJ;AAAA,EACF;AACF;AAEA,SAAS,qBAAqB,GAAqC;AACjE,MAAI,OAAO,MAAM,YAAY,MAAM,KAAM,QAAO;AAChD,QAAM,IAAI;AACV,SAAO,OAAO,EAAE,aAAa,MAAM,YAAY,MAAM,QAAQ,EAAE,QAAQ,CAAC;AAC1E;;;ACjNA,SAAS,eAAAC,oBAAmB;AAC5B,YAAYC,UAAS;AACrB,YAAYC,WAAU;AAoBtB,IAAM,0BAA0B,MAAM;AAOtC,IAAM,iBAAiB;AAQhB,IAAM,yBAAN,MAAwD;AAAA,EAC5C,QAAQ,oBAAI,IAAwB;AAAA,EACpC,OAAwB,CAAC;AAAA,EAClC,UAA0C,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE;AAAA,EAC9D;AAAA,EACA;AAAA,EAEjB,YAAY,OAA+B,CAAC,GAAG;AAC7C,SAAK,WAAW,KAAK;AACrB,SAAK,iBAAiB,KAAK,uBAAuB;AAAA,EACpD;AAAA,EAEA,MAAM,IAAI,OAAmD;AAC3D,UAAM,MAAM,EAAE,KAAK,QAAQ,MAAM,IAAI;AACrC,UAAM,KAAK,GAAG,WAAW,MAAM,IAAI,CAAC,IAAI,GAAG,IAAIC,aAAY,CAAC,EAAE,SAAS,KAAK,CAAC;AAC7E,UAAM,QAAQ,OAAO,WAAW,MAAM,MAAM,MAAM,SAAS,UAAU,WAAW,MAAM;AACtF,QAAI;AACJ,QAAI,OAA2B,MAAM;AACrC,QAAI,KAAK,YAAY,SAAS,KAAK,gBAAgB;AACjD,YAAU,WAAM,KAAK,UAAU,EAAE,WAAW,KAAK,CAAC;AAClD,oBAAmB,WAAK,KAAK,UAAU,GAAG,EAAE,MAAM;AAGlD,YAAM,YAAY,aAAa,MAAM,MAAM;AAAA,QACzC,UAAU,MAAM,SAAS,UAAU,WAAW;AAAA,MAChD,CAAC;AACD,aAAO;AAAA,IACT;AACA,UAAM,MAAkB;AAAA,MACtB;AAAA,MACA,MAAM,MAAM;AAAA,MACZ,MAAM,MAAM,QAAQ,CAAC;AAAA,MACrB;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AACA,SAAK,MAAM,IAAI,IAAI,GAAG;AACtB,UAAM,MAAqB,EAAE,IAAI,MAAM,MAAM,MAAM,KAAK,MAAM,IAAI,KAAK;AACvE,SAAK,KAAK,KAAK,GAAG;AAClB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,IAAI,IAA6C;AACrD,WAAO,KAAK,MAAM,IAAI,EAAE;AAAA,EAC1B;AAAA,EAEA,OAAwB;AACtB,WAAO,CAAC,GAAG,KAAK,IAAI;AAAA,EACtB;AAAA,EAEA,MAAM,OAAO,MAAuC;AAClD,UAAM,UAAU,CAAC,GAAG,KAAK,SAAS,cAAc,CAAC;AACjD,QAAI,QAAQ,WAAW,EAAG,QAAO,OAAO,CAAC,EAAE,MAAM,QAAQ,KAAK,CAAC,IAAI,CAAC;AACpE,UAAM,SAAyB,CAAC;AAChC,QAAI,YAAY;AAChB,eAAW,KAAK,SAAS;AACvB,YAAM,MAAM,EAAE,SAAS;AACvB,YAAM,SAAS,KAAK,MAAM,WAAW,GAAG;AACxC,UAAI,OAAQ,QAAO,KAAK,EAAE,MAAM,QAAQ,MAAM,OAAO,CAAC;AACtD,UAAI;AACJ,UAAI,EAAE,CAAC,MAAM,QAAW;AAEtB,cAAM,WAAW,EAAE,CAAC;AACpB,cAAM,SAAS,KAAK,MAAM,CAAC,MAAM,EAAE,SAAS,UAAU,QAAQ,CAAC,MAAM,QAAQ;AAAA,MAC/E,OAAO;AACL,cAAM,OAAO,aAAa,EAAE,CAAC,CAAW;AACxC,cAAM,MAAM,OAAO,EAAE,CAAC,CAAC;AACvB,cAAM,KAAK,KAAK,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,QAAQ,GAAG;AAAA,MAC9D;AACA,YAAM,MAAM,MAAM,KAAK,MAAM,IAAI,IAAI,EAAE,IAAI;AAC3C,UAAI,CAAC,KAAK;AACR,eAAO,KAAK,EAAE,MAAM,QAAQ,MAAM,EAAE,CAAC,EAAE,CAAC;AAAA,MAC1C,OAAO;AACL,eAAO,KAAK,MAAM,KAAK,QAAQ,GAAG,CAAC;AAAA,MACrC;AACA,kBAAY,MAAM,EAAE,CAAC,EAAE;AAAA,IACzB;AACA,UAAM,OAAO,KAAK,MAAM,SAAS;AACjC,QAAI,KAAM,QAAO,KAAK,EAAE,MAAM,QAAQ,MAAM,KAAK,CAAC;AAClD,WAAO,kBAAkB,MAAM;AAAA,EACjC;AAAA,EAEA,MAAM,QAAuB;AAE3B,QAAI,KAAK,UAAU;AACjB,YAAM,WAAqB,CAAC;AAC5B,iBAAW,OAAO,KAAK,MAAM,OAAO,GAAG;AACrC,YAAI,IAAI,KAAM,UAAS,KAAK,IAAI,IAAI;AAAA,MACtC;AAEA,YAAM,QAAQ,IAAI,SAAS,IAAI,CAAC,MAAU,YAAO,CAAC,EAAE,MAAM,MAAM,MAAS,CAAC,CAAC;AAAA,IAC7E;AACA,SAAK,MAAM,MAAM;AACjB,SAAK,KAAK,SAAS;AACnB,SAAK,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE;AAAA,EAC9C;AAAA,EAEA,MAAc,QAAQ,KAAwC;AAC5D,QAAI,IAAI,SAAS,SAAS;AACxB,YAAM,OACJ,IAAI,SAAS,IAAI,OAAO,MAAU,cAAS,IAAI,MAAM,EAAE,UAAU,SAAS,CAAC,IAAI;AACjF,aAAO;AAAA,QACL,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,YAAY,IAAI,KAAK,aAAa;AAAA,UAClC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,UAAM,MAAM,IAAI,SAAS,IAAI,OAAO,MAAU,cAAS,IAAI,MAAM,MAAM,IAAI;AAC3E,UAAM,QAAQ,IAAI,KAAK,WAAW,eAAe,IAAI,KAAK,QAAQ,OAAO;AACzE,UAAM,QAAQ,IAAI,KAAK,WAAW,YAAY;AAC9C,WAAO,EAAE,MAAM,QAAQ,MAAM,GAAG,KAAK;AAAA,EAAK,GAAG;AAAA,EAAK,KAAK,GAAG;AAAA,EAC5D;AACF;AAEA,SAAS,WAAW,MAA8B;AAChD,SAAO,SAAS,SAAS,WAAW;AACtC;AAEA,SAAS,aAAa,QAAgC;AACpD,MAAI,WAAW,SAAU,QAAO;AAChC,MAAI,WAAW,QAAS,QAAO;AAC/B,SAAO;AACT;AAGA,SAAS,QAAQ,KAAwC;AACvD,SAAO,IAAI,KAAK,YAAY,IAAI,KAAK;AACvC;AAGA,SAAS,SAAY,KAAmB,MAAwC;AAC9E,WAAS,IAAI,IAAI,SAAS,GAAG,KAAK,GAAG,KAAK;AACxC,QAAI,KAAK,IAAI,CAAC,CAAM,EAAG,QAAO,IAAI,CAAC;AAAA,EACrC;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,QAAwC;AACjE,QAAM,MAAsB,CAAC;AAC7B,aAAW,KAAK,QAAQ;AACtB,UAAM,OAAO,IAAI,IAAI,SAAS,CAAC;AAC/B,QAAI,EAAE,SAAS,UAAU,QAAQ,KAAK,SAAS,QAAQ;AACrD,WAAK,QAAQ,EAAE;AAAA,IACjB,OAAO;AACL,UAAI,KAAK,CAAC;AAAA,IACZ;AAAA,EACF;AACA,SAAO;AACT;;;AC7LA,SAAS,cAAAC,mBAAkB;AAC3B,YAAYC,SAAQ;AACpB,YAAYC,YAAU;;;ACIf,IAAM,qBAAiD;AAAA,EAC5D,MAAM;AAAA,EACN,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,cAAc;AAChB;;;ADsBA,IAAM,mBAAmB;AACzB,IAAM,SAAS;AACf,IAAM,iCAAiC;AAEvC,SAAS,eAAe,OAA4B;AAClD,QAAM,QAAkB,CAAC;AACzB,MAAI,MAAM,QAAQ,MAAM,UAAU;AAChC,UAAM,KAAK,IAAI,MAAM,IAAI,IAAI,MAAM,QAAQ,GAAG;AAAA,EAChD,WAAW,MAAM,MAAM;AACrB,UAAM,KAAK,IAAI,MAAM,IAAI,GAAG;AAAA,EAC9B,WAAW,MAAM,UAAU;AACzB,UAAM,KAAK,IAAI,MAAM,QAAQ,GAAG;AAAA,EAClC;AACA,MAAI,MAAM,QAAQ,MAAM,KAAK,SAAS,GAAG;AACvC,UAAM,KAAK,MAAM,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,EAAE,KAAK,GAAG,CAAC;AAAA,EACrD;AACA,SAAO,MAAM,SAAS,IAAI,IAAI,MAAM,KAAK,GAAG,CAAC,KAAK;AACpD;AAEA,SAAS,YAAY,MAAc,OAAwC;AACzE,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,CAAC,QAAQ,WAAW,KAAK,EAAG,QAAO;AAGvC,QAAM,UAAU,QAAQ,MAAM,mBAAmB;AACjD,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,KAAK,QAAQ,CAAC,KAAK;AAEzB,MAAI,OAAO,QAAQ,MAAM,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK;AAGjD,MAAI;AACJ,MAAI;AACJ,QAAM,UAAU,KAAK,MAAM,gBAAgB;AAC3C,MAAI,SAAS;AACX,UAAM,IAAI,QAAQ,CAAC,KAAK;AACxB,UAAM,IAAI,QAAQ,CAAC,KAAK;AACxB,QAAI,aAAa,CAAC,GAAG;AACnB,aAAO;AACP,iBAAW,WAAW,CAAC,IAAI,IAAI;AAAA,IACjC,WAAW,WAAW,CAAC,GAAG;AACxB,iBAAW;AAAA,IACb;AACA,WAAO,KAAK,MAAM,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK;AAAA,EAC5C;AAGA,QAAM,UAAU,KAAK,MAAM,iBAAiB;AAC5C,MAAI;AACJ,MAAI,SAAS;AACX,WAAO,KAAK,MAAM,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK;AAAA,EAC5C,OAAO;AACL,WAAO,KAAK,KAAK;AAAA,EACnB;AAGA,QAAM,OAAiB,CAAC;AACxB,MAAI;AACJ,SAAO,YAAY;AAEnB,UAAQ,WAAW,OAAO,KAAK,IAAI,OAAO,MAAM;AAC9C,SAAK,KAAK,SAAS,CAAC,KAAK,EAAE;AAAA,EAC7B;AAEA,QAAM,YAAY,KAAK,QAAQ,QAAQ,EAAE,EAAE,QAAQ,WAAW,GAAG,EAAE,KAAK;AAExE,MAAI,CAAC,UAAW,QAAO;AAEvB,SAAO;AAAA,IACL;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,KAAK,SAAS,IAAI,OAAO;AAAA,EACjC;AACF;AAEA,SAAS,aAAa,GAA4B;AAChD,SAAO,KAAK;AACd;AAEA,SAAS,WAAW,GAAgC;AAClD,SAAO,MAAM,cAAc,MAAM,UAAU,MAAM,YAAY,MAAM;AACrE;AAyBO,SAAS,mBAAmB,SAAuC;AACxE,QAAM,UAAU,oBAAI,IAAsB;AAC1C,QAAM,SAAS,oBAAI,IAAsB;AACzC,QAAM,UAA0B,IAAI,MAAM,QAAQ,MAAM;AAExD,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,IAAI,QAAQ,CAAC;AACnB,UAAM,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC1E,UAAM,QAAQ,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;AACtD,YAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,KAAK;AAErC,eAAW,KAAK,OAAO;AACrB,YAAM,MAAM,QAAQ,IAAI,CAAC;AACzB,UAAI,IAAK,KAAI,KAAK,CAAC;AAAA,UACd,SAAQ,IAAI,GAAG,CAAC,CAAC,CAAC;AAAA,IACzB;AACA,eAAW,KAAK,MAAM;AACpB,YAAM,MAAM,OAAO,IAAI,CAAC;AACxB,UAAI,IAAK,KAAI,KAAK,CAAC;AAAA,UACd,QAAO,IAAI,GAAG,CAAC,CAAC,CAAC;AAAA,IACxB;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,QAAQ,SAAS,SAAS,SAAS,EAAE;AACzD;AASA,IAAM,2BAA2B;AAG1B,SAAS,YACd,OACA,OACA,OACe;AACf,QAAM,UAAU,MAAM,YAAY,EAAE,MAAM,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC3E,MAAI,QAAQ,WAAW,EAAG,QAAO,CAAC;AAElC,QAAM,SAAS,oBAAI,IAAoB;AAEvC,aAAW,KAAK,SAAS;AAGvB,QAAI,UAAU;AAEd,UAAM,YAAY,MAAM,QAAQ,IAAI,CAAC;AACrC,QAAI,WAAW;AACb,iBAAW,OAAO,UAAW,QAAO,IAAI,MAAM,OAAO,IAAI,GAAG,KAAK,KAAK,CAAC;AACvE,gBAAU;AAAA,IACZ;AACA,UAAM,WAAW,MAAM,OAAO,IAAI,CAAC;AACnC,QAAI,UAAU;AACZ,iBAAW,OAAO,SAAU,QAAO,IAAI,MAAM,OAAO,IAAI,GAAG,KAAK,KAAK,CAAC;AACtE,gBAAU;AAAA,IACZ;AAMA,QAAI,WAAW,EAAE,SAAS,yBAA0B;AAEpD,eAAW,CAAC,MAAM,OAAO,KAAK,MAAM,SAAS;AAC3C,UAAI,KAAK,SAAS,CAAC,KAAK,EAAE,SAAS,IAAI,GAAG;AACxC,mBAAW,OAAO,SAAS;AACzB,iBAAO,IAAI,MAAM,OAAO,IAAI,GAAG,KAAK,KAAK,CAAC;AAAA,QAC5C;AAAA,MACF;AAAA,IACF;AACA,eAAW,CAAC,KAAK,OAAO,KAAK,MAAM,QAAQ;AACzC,UAAI,IAAI,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,GAAG;AACtC,mBAAW,OAAO,SAAS;AACzB,iBAAO,IAAI,MAAM,OAAO,IAAI,GAAG,KAAK,KAAK,CAAC;AAAA,QAC5C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,EAAG,QAAO,CAAC;AAE/B,QAAM,SAAS,MAAM,KAAK,OAAO,QAAQ,CAAC;AAC1C,SAAO,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AAEjC,QAAM,SAAwB,CAAC;AAC/B,QAAM,MAAM,QAAQ,KAAK,IAAI,OAAO,OAAO,MAAM,IAAI,OAAO;AAC5D,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,WAAO,KAAK,MAAM,QAAQ,OAAO,CAAC,EAAG,CAAC,CAAC,EAAG,KAAK;AAAA,EACjD;AACA,SAAO;AACT;AAQO,IAAM,oBAAN,MAAiD;AAAA,EAC7C,OAAO;AAAA,EACC;AAAA;AAAA,EAEA,aAAa,oBAAI,IAA2B;AAAA;AAAA,EAE5C,aAAa,oBAAI,IAA2B;AAAA;AAAA,EAE5C,aAAa,oBAAI,IAAoB;AAAA,EAEtD,YAAY,MAAgC;AAC1C,SAAK,QAAQ;AAAA,MACX,kBAAkB,KAAK,MAAM;AAAA,MAC7B,kBAAkB,KAAK,MAAM;AAAA,MAC7B,eAAe,KAAK,MAAM;AAAA,IAC5B;AAAA,EACF;AAAA,EAEQ,YAAY,UAAkB,OAA4B;AAChE,WAAO,YAAY,KAAK,MAAM,KAAK;AAAA,EACrC;AAAA,EAEA,MAAc,SAAS,MAA+B;AACpD,QAAI;AACF,YAAMC,SAAO,MAAS,SAAK,IAAI;AAC/B,aAAOA,OAAK;AAAA,IACd,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,gBAAgB,MAAoB;AAC1C,SAAK,WAAW,OAAO,IAAI;AAC3B,SAAK,WAAW,OAAO,IAAI;AAC3B,SAAK,WAAW,OAAO,IAAI;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,YACZ,MACA,OACA,OACwB;AACxB,UAAM,gBAAgB,SAAU,MAAM,KAAK,SAAS,IAAI;AACxD,UAAM,cAAc,KAAK,WAAW,IAAI,IAAI;AAC5C,QAAI,gBAAgB,iBAAiB,KAAK,WAAW,IAAI,IAAI,GAAG;AAC9D,aAAO,KAAK,WAAW,IAAI,IAAI;AAAA,IACjC;AAEA,UAAM,MAAM,MAAM,KAAK,QAAQ,OAAO,IAAI;AAC1C,QAAI,CAAC,IAAI,KAAK,GAAG;AACf,WAAK,WAAW,IAAI,MAAM,CAAC,CAAC;AAC5B,WAAK,WAAW,IAAI,MAAM,aAAa;AACvC,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,UAAU,aAAa,KAAK,KAAK;AACvC,SAAK,WAAW,IAAI,MAAM,OAAO;AACjC,SAAK,WAAW,IAAI,MAAM,aAAa;AACvC,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,SAAS,MAAc,OAA4C;AAC/E,UAAM,QAAQ,MAAM,KAAK,SAAS,IAAI;AACtC,UAAM,SAAS,KAAK,WAAW,IAAI,IAAI;AACvC,QAAI,UAAU,OAAO,YAAY,OAAO;AACtC,aAAO;AAAA,IACT;AAGA,UAAM,UAAU,MAAM,KAAK,YAAY,MAAM,OAAO,KAAK;AACzD,UAAM,QAAQ,mBAAmB,OAAO;AACxC,UAAM,UAAU;AAChB,SAAK,WAAW,IAAI,MAAM,KAAK;AAC/B,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,SAAS,OAAoB,OAAoB,UAAiC;AACtF,UAAM,OAAO,KAAK,YAAY,UAAU,KAAK;AAC7C,UAAM,UAAe,eAAQ,IAAI,CAAC;AAClC,QAAI,WAAW;AACf,QAAI;AAAE,iBAAW,MAAS,aAAS,MAAM,MAAM;AAAA,IAAG,QAAQ;AAAA,IAAiB;AAE3E,UAAM,KAAK,OAAO,KAAK,IAAI,CAAC,IAAIC,YAAW,EAAE,MAAM,GAAG,CAAC,CAAC;AACxD,UAAM,OAAO,eAAe,KAAK;AACjC,UAAM,OAAO;AAAA,KAAQ,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,IAAI,MAAM,KAAK,QAAQ,OAAO,GAAG,CAAC;AAAA;AAC7E,UAAM,OAAO,SAAS,KAAK,IACvB,SAAS,QAAQ,QAAQ,EAAE,IAAI,OAC/B;AAAA,EAAmB,IAAI;AAC3B,UAAM,YAAY,MAAM,IAAI;AAC5B,SAAK,gBAAgB,IAAI;AAAA,EAC3B;AAAA,EAEA,MAAM,OAAO,OAAoB,OAAe,UAAmC;AACjF,UAAM,OAAO,KAAK,YAAY,UAAU,KAAK;AAC7C,WAAO,aAAa,MAAM,YAAY;AACpC,UAAI;AACJ,UAAI;AAAE,mBAAW,MAAS,aAAS,MAAM,MAAM;AAAA,MAAG,QAAQ;AAAE,eAAO;AAAA,MAAqB;AAExF,YAAM,SAAS,MAAM,YAAY;AACjC,YAAM,YAAY;AAClB,UAAI,UAAU;AACd,YAAM,QAAQ,SAAS,MAAM,IAAI,EAAE,OAAO,CAAC,SAAS;AAClD,cAAM,UAAU,KAAK,KAAK;AAC1B,YAAI,CAAC,QAAQ,WAAW,IAAI,EAAG,QAAO;AACtC,YAAI,UAAU,KAAK,KAAK,GAAG;AACzB,gBAAM,eAAe,cAAc,KAAK,OAAO;AAC/C,cAAI,gBAAgB,aAAa,CAAC,MAAM,OAAO;AAAE;AAAW,mBAAO;AAAA,UAAO;AAAA,QAC5E;AACA,YAAI,QAAQ,YAAY,EAAE,SAAS,MAAM,GAAG;AAAE;AAAW,iBAAO;AAAA,QAAO;AACvE,eAAO;AAAA,MACT,CAAC;AACD,UAAI,UAAU,GAAG;AACf,YAAI,MAAM,WAAW,KAAM,MAAM,WAAW,KAAK,CAAC,MAAM,CAAC,GAAG,KAAK,GAAI;AACnE,gBAAM,YAAY,MAAM,EAAE;AAAA,QAC5B,OAAO;AACL,gBAAM,YAAY,MAAM,MAAM,KAAK,IAAI,CAAC;AAAA,QAC1C;AAAA,MACF;AACA,WAAK,gBAAgB,IAAI;AACzB,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAQ,OAAoB,UAAmC;AACnE,UAAM,OAAO,KAAK,YAAY,UAAU,KAAK;AAC7C,QAAI;AAAE,aAAO,MAAS,aAAS,MAAM,MAAM;AAAA,IAAG,QAAQ;AAAE,aAAO;AAAA,IAAsB;AAAA,EACvF;AAAA,EAEA,MAAM,KAAK,OAAoB,UAAkB,OAAwC;AACvF,UAAM,OAAO,KAAK,YAAY,UAAU,KAAK;AAC7C,UAAM,UAAU,MAAM,KAAK,YAAY,MAAM,KAAK;AAClD,WAAO,QAAQ,QAAQ,MAAM,GAAG,KAAK,IAAI;AAAA,EAC3C;AAAA,EAEA,MAAM,OAAO,OAAoB,OAAe,UAAkB,OAAwC;AACxG,UAAM,OAAO,KAAK,YAAY,UAAU,KAAK;AAC7C,UAAM,QAAQ,MAAM,KAAK,SAAS,MAAM,KAAK;AAC7C,WAAO,YAAY,OAAO,OAAO,KAAK;AAAA,EACxC;AAAA,EAEA,MAAM,MAAM,OAAoB,UAAiC;AAC/D,UAAM,OAAO,KAAK,YAAY,UAAU,KAAK;AAC7C,UAAM,YAAY,MAAM,EAAE;AAC1B,SAAK,gBAAgB,IAAI;AAAA,EAC3B;AAAA,EAEA,MAAM,YAAY,OAAoB,UAAmC;AACvE,UAAM,OAAO,KAAK,YAAY,UAAU,KAAK;AAC7C,QAAI;AACJ,QAAI;AAAE,iBAAW,MAAS,aAAS,MAAM,MAAM;AAAA,IAAG,QAAQ;AAAE,aAAO;AAAA,IAAqB;AAExF,UAAM,OAAO,oBAAI,IAAY;AAC7B,QAAI,UAAU;AACd,UAAM,QAAQ,SAAS,MAAM,IAAI,EAAE,OAAO,CAAC,SAAS;AAClD,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,CAAC,QAAQ,WAAW,IAAI,EAAG,QAAO;AAEtC,YAAM,OAAO,QACV,QAAQ,eAAe,EAAE,EACzB,QAAQ,oBAAoB,EAAE,EAC9B,QAAQ,YAAY,EAAE,EACtB,QAAQ,QAAQ,GAAG,EACnB,KAAK,EACL,YAAY;AACf,UAAI,KAAK,IAAI,IAAI,GAAG;AAAE;AAAW,eAAO;AAAA,MAAO;AAC/C,WAAK,IAAI,IAAI;AACb,aAAO;AAAA,IACT,CAAC;AAED,UAAM,OAAO,MAAM,KAAK,IAAI;AAC5B,UAAM,SAAS,GAAG,IAAI,QAAQ,KAAK,IAAI,CAAC;AACxC,QAAI;AACF,YAAS,aAAS,MAAM,MAAM;AAC9B,YAAM,wBAAwB,IAAI;AAAA,IACpC,QAAQ;AAAA,IAAoB;AAE5B,QAAI;AAAE,YAAM,YAAY,MAAM,IAAI;AAAA,IAAG,QAAQ;AAAE,aAAO;AAAA,IAAqB;AAC3E,SAAK,gBAAgB,IAAI;AACzB,WAAO;AAAA,EACT;AACF;AAEA,eAAe,wBAAwB,MAA6B;AAClE,QAAM,MAAW,eAAQ,IAAI;AAC7B,QAAM,OAAY,gBAAS,IAAI;AAC/B,QAAM,SAAS,GAAG,IAAI;AACtB,QAAM,WAAW,MAAS,YAAQ,GAAG,GAClC,OAAO,CAAC,SAAS,KAAK,WAAW,MAAM,CAAC,EACxC,KAAK,EACL,QAAQ;AAEX,QAAM,QAAQ;AAAA,IACZ,QAAQ,MAAM,8BAA8B,EAAE,IAAI,OAAO,SAAS;AAChE,UAAI;AACF,cAAS,WAAY,YAAK,KAAK,IAAI,CAAC;AAAA,MACtC,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAIO,SAAS,aAAa,KAAa,QAAqB,kBAAiC;AAC9F,QAAM,UAAyB,CAAC;AAChC,aAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,UAAM,QAAQ,YAAY,MAAM,KAAK;AACrC,QAAI,MAAO,SAAQ,KAAK,KAAK;AAAA,EAC/B;AACA,SAAO,QAAQ,QAAQ;AACzB;;;AE/bA,IAAM,kBAAkB;AAuBjB,IAAM,qBAAN,MAAgD;AAAA,EACpC;AAAA,EACA;AAAA,EACT;AAAA,EACS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,aAAa,oBAAI,IAAmC;AAAA;AAAA,EAEpD,cAAc,oBAAI,IAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ1C;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,oBAA0D,CAAC;AAAA;AAAA,EAG3D,cAAc,oBAAI,IAAkF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ7G,eAAwF;AAAA,EAEhG,YAAY,MAA0B;AACpC,SAAK,QAAQ;AAAA,MACX,kBAAkB,KAAK,MAAM;AAAA,MAC7B,kBAAkB,KAAK,MAAM;AAAA,MAC7B,eAAe,KAAK,MAAM;AAAA,IAC5B;AACA,SAAK,SAAS,KAAK;AACnB,SAAK,UAAU,KAAK,WAAW,IAAI,kBAAkB,EAAE,OAAO,KAAK,MAAM,CAAC;AAI1E,UAAM,OAAO,KAAK,MAAM,WAAW,YAAY;AAC/C,SAAK,gBAAgB,6BAA6B,KAAK,IAAI;AAC3D,SAAK,YAAY,KAAK,gBAClB,KAAK,MAAM,oBAAoB,QAAQ,eAAe,gBAAgB,IACtE;AAAA,EACN;AAAA;AAAA,EAGA,aAA4B;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,cAAiB,OAAoB,MAAoC;AACrF,UAAM,QAAQ,KAAK,WAAW,IAAI,KAAK,KAAK,QAAQ,QAAQ;AAE5D,UAAM,OAAO,MACV,MAAM,CAAC,QAAQ;AACd,WAAK,YAAY,IAAI,OAAO,GAAY;AAAA,IAC1C,CAAC,EACA,KAAK,MAAM,KAAK,CAAC;AACpB,SAAK,WAAW,IAAI,OAAO,IAAwB;AACnD,QAAI;AACF,aAAO,MAAM;AAAA,IACf,SAAS,KAAK;AACZ,WAAK,YAAY,IAAI,OAAO,GAAY;AACxC,YAAM;AAAA,IACR,UAAE;AACA,UAAI,KAAK,WAAW,IAAI,KAAK,MAAM,MAAM;AACvC,aAAK,WAAW,OAAO,KAAK;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,uBAAuB,OAAqC;AACxE,UAAM,MAAM,MAAM,KAAK,QAAQ,QAAQ,OAAO,KAAK,MAAM,KAAK,CAAC;AAC/D,QAAI,QAAQ;AACZ,eAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,UAAI,KAAK,KAAK,EAAG,UAAS,OAAO,WAAW,MAAM,MAAM;AAAA,IAC1D;AACA,SAAK,kBAAkB,KAAK,IAAI;AAChC,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,UAA2B;AAC/B,UAAM,QAAkB,CAAC;AACzB,eAAW,SAAS,CAAC,kBAAkB,kBAAkB,aAAa,GAAoB;AACxF,YAAM,WAAW,KAAK,YAAY,IAAI,KAAK;AAC3C,UAAI,UAAU;AACZ,cAAM,KAAK,sCAA4B,QAAQ,KAAK,CAAC,MAAM,SAAS,OAAO,EAAE;AAAA,MAC/E;AACA,YAAM,KAAK,KAAK,IAAI;AACpB,YAAM,WAAW,KAAK,MAAM,KAAK;AACjC,UAAI;AACF,cAAM,OAAO,MAAM,KAAK,QAAQ,QAAQ,OAAO,QAAQ;AACvD,cAAM,MAAM,KAAK,IAAI,IAAI;AACzB,aAAK,QAAQ,KAAK,gBAAgB;AAAA,UAChC,WAAW;AAAA,UACX,OAAO;AAAA,UACP;AAAA,UACA,WAAW;AAAA,UACX,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,QAC5D,CAAC;AACD,YAAI,KAAK,KAAK,EAAG,OAAM,KAAK,MAAM,QAAQ,KAAK,CAAC;AAAA;AAAA,EAAO,KAAK,KAAK,CAAC,EAAE;AAAA,MACtE,SAAS,KAAK;AACZ,cAAM,MAAM,KAAK,IAAI,IAAI;AACzB,aAAK,QAAQ,KAAK,gBAAgB;AAAA,UAChC,WAAW;AAAA,UACX,OAAO;AAAA,UACP;AAAA,UACA,WAAW;AAAA,UACX,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,OAAO,eAAe,GAAG;AAAA,UACzB,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,QAC5D,CAAC;AACD,cAAM;AAAA,MACR;AAAA,IACF;AACA,WAAO,MAAM,KAAK,MAAM;AAAA,EAC1B;AAAA,EAEA,MAAM,KAAK,OAAqC;AAC9C,UAAM,KAAK,KAAK,IAAI;AACpB,UAAM,WAAW,KAAK,MAAM,KAAK;AACjC,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,QAAQ,QAAQ,OAAO,QAAQ;AACvD,YAAM,MAAM,KAAK,IAAI,IAAI;AACzB,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC,WAAW;AAAA,QACX,OAAO;AAAA,QACP;AAAA,QACA,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY;AAAA,QACZ,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,MAC5D,CAAC;AACD,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,MAAM,KAAK,IAAI,IAAI;AACzB,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC,WAAW;AAAA,QACX,OAAO;AAAA,QACP;AAAA,QACA,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY;AAAA,QACZ,OAAO,eAAe,GAAG;AAAA,QACzB,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,MAC5D,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,KAAK,QAAqB,kBAAkB,OAAwC;AACxF,WAAO,KAAK,QAAQ,KAAK,OAAO,KAAK,MAAM,KAAK,GAAG,KAAK;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YAAY,MAAc,QAAqB,kBAAkB,QAAQ,GAA2B;AACxG,QAAI,KAAK,QAAQ,aAAa;AAC5B,aAAO,KAAK,QAAQ,YAAY,OAAO,KAAK,MAAM,KAAK,GAAG,MAAM,KAAK;AAAA,IACvE;AACA,WAAO,KAAK,OAAO,MAAM,OAAO,KAAK;AAAA,EACvC;AAAA,EAEA,MAAM,OAAO,OAAe,QAAqB,kBAAkB,OAAwC;AACzG,WAAO,KAAK,QAAQ,OAAO,OAAO,OAAO,KAAK,MAAM,KAAK,GAAG,KAAK;AAAA,EACnE;AAAA,EAEA,MAAM,SACJ,MACA,QAAqB,kBACrB,UACe;AACf,UAAM,MAAK,oBAAI,KAAK,GAAE,YAAY;AAClC,WAAO,KAAK,cAAc,OAAO,YAAY;AAC3C,YAAM,QAAqB,EAAE,OAAO,MAAM,IAAI,GAAG,SAAS;AAC1D,YAAM,WAAW,KAAK,MAAM,KAAK;AACjC,YAAM,KAAK,KAAK,IAAI;AACpB,UAAI;AACF,cAAM,KAAK,QAAQ,SAAS,OAAO,OAAO,QAAQ;AAElD,aAAK,sBAAsB;AAC3B,cAAM,MAAM,KAAK,IAAI,IAAI;AACzB,aAAK,QAAQ,KAAK,iBAAiB;AAAA,UACjC,WAAW;AAAA,UACX,OAAO;AAAA,UACP;AAAA,UACA,WAAW;AAAA,UACX,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,QAC5D,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,cAAM,MAAM,KAAK,IAAI,IAAI;AACzB,aAAK,QAAQ,KAAK,iBAAiB;AAAA,UACjC,WAAW;AAAA,UACX,OAAO;AAAA,UACP;AAAA,UACA,WAAW;AAAA,UACX,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,OAAO,eAAe,GAAG;AAAA,UACzB,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,QAC5D,CAAC;AACD,cAAM;AAAA,MACR;AAMA,UAAI,cAAc,KAAK,kBAAkB,KAAK;AAC9C,UAAI,gBAAgB,QAAW;AAC7B,sBAAc,MAAM,KAAK,uBAAuB,KAAK;AAAA,MACvD;AACA,UAAI,cAAc,iBAAiB;AACjC,cAAM,UAAU,MAAM,KAAK,QAAQ,YAAY,OAAO,KAAK,MAAM,KAAK,CAAC;AACvE,YAAI,UAAU,GAAG;AACf,eAAK,QAAQ,KAAK,uBAAuB;AAAA,YACvC;AAAA,YACA;AAAA,UACF,CAAqC;AAErC,gBAAM,KAAK,uBAAuB,KAAK;AAAA,QACzC;AAAA,MACF;AAGA,WAAK,kBAAkB,KAAK,KAAK,KAAK,kBAAkB,KAAK,KAAK,KAAK,OAAO,WAAW,KAAK,UAAU,KAAK,GAAG,MAAM;AAGtH,YAAM,KAAK,aAAa,KAAK;AAE7B,WAAK,QAAQ,KAAK,qBAAqB;AAAA,QACrC;AAAA,QACA;AAAA,QACA;AAAA,QACA,MAAM,MAAM;AAAA,QACZ,MAAM,MAAM;AAAA,QACZ,UAAU,MAAM;AAAA,MAClB,CAAmC;AAAA,IACrC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,wBAA8B;AACpC,SAAK,YAAY,MAAM;AACvB,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cACJ,KACA,QAAqB,kBACrB,QAAQ,GACgB;AACxB,UAAM,MAAM,MAAM,KAAK,KAAK,KAAK;AACjC,QAAI,IAAI,WAAW,EAAG,QAAO,CAAC;AAG9B,UAAM,UACJ,GAAG,KAAK,IAAI,IAAI,WAAW,KAAK,IAAI,gBAAgB,CAAC,GAAG,KAAK,GAAG,CAAC,KAAK,IAAI,aAAa,CAAC,GAAG,KAAK,GAAG,CAAC;AACtG,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,SAAS;AAIf,UAAM,SAAS,KAAK,YAAY,IAAI,OAAO;AAC3C,QAAI,UAAU,OAAO,YAAY,OAAO,OAAO,YAAY,KAAK;AAC9D,aAAO,OAAO,OAAO,MAAM,GAAG,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,IACnD;AAEA,UAAM,YAAY,IAAI,YAAY,YAAY,EAAE,MAAM,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AACvF,UAAM,cAAc,IAAI,gBAAgB,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC;AACvE,UAAM,aAAa,IAAI,aAAa,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,CAAC;AAQjF,SAAK,iBAAiB,oBAAI,QAAiE;AAC3F,UAAM,aAAa,KAAK;AAExB,UAAM,SAAwB,CAAC;AAE/B,eAAW,SAAS,KAAK;AACvB,UAAI,QAAQ;AACZ,YAAM,UAAoB,CAAC;AAG3B,UAAI,cAAc,WAAW,IAAI,KAAK;AACtC,UAAI,CAAC,aAAa;AAChB,sBAAc;AAAA,UACZ,WAAW,MAAM,KAAK,YAAY;AAAA,UAClC,YAAY,MAAM,QAAQ,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;AAAA,QAC1D;AACA,mBAAW,IAAI,OAAO,WAAW;AAAA,MACnC;AACA,YAAM,EAAE,WAAW,UAAU,IAAI;AAGjC,UAAI,WAAW;AACf,iBAAW,KAAK,WAAW;AACzB,YAAI,UAAU,SAAS,CAAC,GAAG;AAAE;AAAY,mBAAS;AAAA,QAAG;AACrD,YAAI,UAAU,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,GAAG;AAAE;AAAY,mBAAS;AAAA,QAAG;AAAA,MACtE;AACA,UAAI,WAAW,EAAG,SAAQ,KAAK,eAAe,QAAQ,GAAG;AAGzD,UAAI,YAAY;AAChB,iBAAW,KAAK,YAAY;AAC1B,YAAI,EAAE,SAAS,MAAM,UAAU,SAAS,CAAC,KAAK,UAAU,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,IAAI;AACnF;AAAA,QACF;AAAA,MACF;AACA,eAAS;AACT,UAAI,YAAY,EAAG,SAAQ,KAAK,gBAAgB,SAAS,GAAG;AAE5D,iBAAW,KAAK,WAAW;AACzB,YAAI,EAAE,SAAS,MAAM,UAAU,SAAS,CAAC,KAAK,UAAU,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,IAAI;AACnF,mBAAS;AACT,kBAAQ,KAAK,iBAAiB,CAAC,EAAE;AAAA,QACnC;AAAA,MACF;AAGA,cAAQ,MAAM,UAAU;AAAA,QACtB,KAAK;AAAY,mBAAS;AAAG,kBAAQ,KAAK,UAAU;AAAG;AAAA,QACvD,KAAK;AAAY,mBAAS;AAAG,kBAAQ,KAAK,eAAe;AAAG;AAAA,QAC5D,KAAK;AAAY,mBAAS;AAAG;AAAA,QAC7B,KAAK;AAAY,mBAAS;AAAG,kBAAQ,KAAK,cAAc;AAAG;AAAA,MAC7D;AAGA,cAAQ,MAAM,MAAM;AAAA,QAClB,KAAK;AAAe,mBAAS;AAAG,kBAAQ,KAAK,UAAU;AAAG;AAAA,QAC1D,KAAK;AAAe,mBAAS;AAAG,kBAAQ,KAAK,YAAY;AAAG;AAAA,QAC5D,KAAK;AAAgB,mBAAS;AAAG,kBAAQ,KAAK,cAAc;AAAG;AAAA,QAC/D,KAAK;AAAe,mBAAS;AAAG,kBAAQ,KAAK,YAAY;AAAG;AAAA,QAC5D,KAAK;AAAe;AAAA,QACpB,KAAK;AAAe;AAAA,MACtB;AAGA,YAAM,WAAW,MAAM,IAAI,KAAK,MAAM,EAAE,EAAE,QAAQ,MAAM,MAAO,KAAK,KAAK;AACzE,UAAI,UAAU,EAAG,UAAS;AAAA,eACjB,UAAU,GAAI,UAAS;AAGhC,UAAI,MAAM,eAAe,UAAa,MAAM,aAAa,KAAK;AAC5D,iBAAS;AACT,gBAAQ,KAAK,gBAAgB;AAAA,MAC/B;AAGA,UAAI,MAAM,cAAc;AACtB,cAAM,oBAAoB,MAAM,IAAI,KAAK,MAAM,YAAY,EAAE,QAAQ,MAAM,MAAO,KAAK;AACvF,YAAI,mBAAmB,EAAG,UAAS;AAAA,MACrC;AAEA,UAAI,QAAQ,GAAG;AACb,eAAO,KAAK;AAAA,UACV,GAAG;AAAA,UACH;AAAA,UACA,aAAa,QAAQ,KAAK,IAAI,KAAK;AAAA,QACrC,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAIvC,UAAM,YAAY;AAClB,UAAM,WAAW,OAAO;AAAA,MACtB,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,aAAa,cAAc,EAAE,aAAa;AAAA,IAC7E;AAGA,SAAK,YAAY,IAAI,SAAS,EAAE,SAAS,KAAK,QAAQ,UAAU,WAAW,MAAM,OAAO,CAAC;AAEzF,WAAO,SAAS,MAAM,GAAG,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9C;AAAA,EAEA,MAAM,OAAO,OAAe,QAAqB,kBAAmC;AAClF,WAAO,KAAK,cAAc,OAAO,YAAY;AAC3C,YAAM,WAAW,KAAK,MAAM,KAAK;AACjC,YAAM,KAAK,KAAK,IAAI;AACpB,UAAI,UAAU;AACd,UAAI;AACF,kBAAU,MAAM,KAAK,QAAQ,OAAO,OAAO,OAAO,QAAQ;AAE1D,aAAK,sBAAsB;AAC3B,cAAM,MAAM,KAAK,IAAI,IAAI;AACzB,aAAK,QAAQ,KAAK,iBAAiB;AAAA,UACjC,WAAW;AAAA,UACX,OAAO;AAAA,UACP;AAAA,UACA,WAAW;AAAA,UACX,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,QAC5D,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,cAAM,MAAM,KAAK,IAAI,IAAI;AACzB,aAAK,QAAQ,KAAK,iBAAiB;AAAA,UACjC,WAAW;AAAA,UACX,OAAO;AAAA,UACP;AAAA,UACA,WAAW;AAAA,UACX,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,OAAO,eAAe,GAAG;AAAA,UACzB,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,QAC5D,CAAC;AACD,cAAM;AAAA,MACR;AACA,UAAI,UAAU,GAAG;AACf,aAAK,QAAQ,KAAK,oBAAoB;AAAA,UACpC;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAkC;AAClC,cAAM,KAAK,aAAa,KAAK;AAC7B,cAAM,KAAK,uBAAuB,KAAK;AAAA,MACzC;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,YAAY,OAAmC;AACnD,WAAO,KAAK,cAAc,OAAO,YAAY;AAC3C,YAAM,WAAW,KAAK,MAAM,KAAK;AACjC,YAAM,KAAK,KAAK,IAAI;AACpB,UAAI,UAAU;AACd,UAAI;AACF,kBAAU,MAAM,KAAK,QAAQ,YAAY,OAAO,QAAQ;AAExD,aAAK,sBAAsB;AAC3B,cAAM,MAAM,KAAK,IAAI,IAAI;AACzB,aAAK,QAAQ,KAAK,iBAAiB;AAAA,UACjC,WAAW;AAAA,UACX,OAAO;AAAA,UACP;AAAA,UACA,WAAW;AAAA,UACX,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,QAC5D,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,cAAM,MAAM,KAAK,IAAI,IAAI;AACzB,aAAK,QAAQ,KAAK,iBAAiB;AAAA,UACjC,WAAW;AAAA,UACX,OAAO;AAAA,UACP;AAAA,UACA,WAAW;AAAA,UACX,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,OAAO,eAAe,GAAG;AAAA,UACzB,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,QAC5D,CAAC;AACD,cAAM;AAAA,MACR;AACA,UAAI,UAAU,GAAG;AACf,aAAK,QAAQ,KAAK,uBAAuB;AAAA,UACvC;AAAA,UACA;AAAA,QACF,CAAqC;AACrC,cAAM,KAAK,aAAa,KAAK;AAC7B,cAAM,KAAK,uBAAuB,KAAK;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAM,OAAoC;AAC9C,QAAI,OAAO;AACT,YAAM,KAAK,cAAc,OAAO,YAAY;AAC1C,cAAM,WAAW,KAAK,MAAM,KAAK;AACjC,cAAM,KAAK,KAAK,IAAI;AACpB,YAAI;AACF,gBAAM,KAAK,QAAQ,MAAM,OAAO,QAAQ;AAExC,eAAK,sBAAsB;AAC3B,gBAAM,MAAM,KAAK,IAAI,IAAI;AACzB,eAAK,QAAQ,KAAK,iBAAiB;AAAA,YACjC,WAAW;AAAA,YACX,OAAO;AAAA,YACP;AAAA,YACA,WAAW;AAAA,YACX,SAAS;AAAA,YACT,YAAY;AAAA,YACZ,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,UAC5D,CAAC;AAAA,QACH,SAAS,KAAK;AACZ,gBAAM,MAAM,KAAK,IAAI,IAAI;AACzB,eAAK,QAAQ,KAAK,iBAAiB;AAAA,YACjC,WAAW;AAAA,YACX,OAAO;AAAA,YACP;AAAA,YACA,WAAW;AAAA,YACX,SAAS;AAAA,YACT,YAAY;AAAA,YACZ,OAAO,eAAe,GAAG;AAAA,YACzB,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,UAC5D,CAAC;AACD,gBAAM;AAAA,QACR;AACA,aAAK,QAAQ,KAAK,kBAAkB,EAAE,MAAM,CAAgC;AAC5E,cAAM,KAAK,aAAa,KAAK;AAC7B,aAAK,kBAAkB,KAAK,IAAI;AAAA,MAClC,CAAC;AACD;AAAA,IACF;AACA,UAAM,QAAQ;AAAA,MACX,CAAC,kBAAkB,kBAAkB,aAAa,EAAoB;AAAA,QAAI,OAAO,MAChF,KAAK,cAAc,GAAG,YAAY;AAChC,gBAAM,WAAW,KAAK,MAAM,CAAC;AAC7B,gBAAM,KAAK,KAAK,IAAI;AACpB,cAAI;AACF,kBAAM,KAAK,QAAQ,MAAM,GAAG,QAAQ;AACpC,kBAAM,MAAM,KAAK,IAAI,IAAI;AACzB,iBAAK,QAAQ,KAAK,iBAAiB;AAAA,cACjC,WAAW;AAAA,cACX,OAAO;AAAA,cACP;AAAA,cACA,WAAW;AAAA,cACX,SAAS;AAAA,cACT,YAAY;AAAA,cACZ,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,YAC5D,CAAC;AAAA,UACH,SAAS,KAAK;AACZ,kBAAM,MAAM,KAAK,IAAI,IAAI;AACzB,iBAAK,QAAQ,KAAK,iBAAiB;AAAA,cACjC,WAAW;AAAA,cACX,OAAO;AAAA,cACP;AAAA,cACA,WAAW;AAAA,cACX,SAAS;AAAA,cACT,YAAY;AAAA,cACZ,OAAO,eAAe,GAAG;AAAA,cACzB,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,YAC5D,CAAC;AACD,kBAAM;AAAA,UACR;AACA,eAAK,QAAQ,KAAK,kBAAkB,EAAE,OAAO,EAAE,CAAgC;AAC/E,gBAAM,KAAK,aAAa,CAAC;AAAA,QAC3B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,SAAK,sBAAsB;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,YAAY,SAA8B;AAIxC,SAAK,UAAU;AACf,WAAO;AAAA,EACT;AAAA,EACA,MAAc,aAAa,OAAmC;AAC5D,QAAI,CAAC,KAAK,iBAAiB,UAAU,iBAAkB;AACvD,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,QAAQ,QAAQ,OAAO,KAAK,MAAM,KAAK,CAAC;AACnE,YAAM,EAAE,WAAAC,YAAW,OAAAC,OAAM,IAAI,MAAM,OAAO,kBAAkB;AAC5D,YAAMA,OAAM,KAAK,WAAW,EAAE,WAAW,KAAK,CAAC;AAC/C,YAAMD,WAAU,GAAG,KAAK,SAAS,IAAI,KAAK,OAAO,SAAS,MAAM;AAAA,IAClE,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAEA,SAAS,QAAQ,OAA4B;AAC3C,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,EACX;AACF;;;AClqBA,YAAYE,SAAQ;AAiDb,IAAM,qBAAN,MAAM,oBAA4C;AAAA,EAC9C,OAAO;AAAA,EAEC;AAAA,EACA;AAAA;AAAA,EAGT,QAAQ,oBAAI,IAAuB;AAAA,EACnC,QAAqB,CAAC;AAAA,EACtB,cAAkC;AAAA,EAClC,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT,gBAAgB,oBAAI,IAAyB;AAAA;AAAA,EAGrD,OAAwB,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO/B,YAA2B,QAAQ,QAAQ;AAAA,EAEnD,YAAY,MAAiC;AAC3C,SAAK,OAAO,IAAI,kBAAkB,EAAE,OAAO,KAAK,MAAM,CAAC;AACvD,SAAK,YAAY,KAAK,aAAa,GAAG,KAAK,MAAM,UAAU;AAAA,EAC7D;AAAA;AAAA,EAIA,MAAM,SAAS,OAAoB,OAAoB,UAAiC;AACtF,UAAM,KAAK,KAAK,SAAS,OAAO,OAAO,QAAQ;AAC/C,UAAM,KAAK,UAAU,KAAK;AAE1B,UAAM,SAAS,KAAK,OAAO,KAAK;AAChC,UAAM,WAAW,KAAK,MAAM,IAAI,MAAM;AACtC,QAAI,UAAU;AACZ,eAAS;AACT,eAAS,QAAQ;AACjB,eAAS,OAAO,MAAM;AACtB,eAAS,OAAO,MAAM;AACtB,eAAS,WAAW,MAAM;AAE1B,WAAK,gBAAgB,QAAQ,KAAK;AAAA,IACpC,OAAO;AACL,WAAK,MAAM,IAAI,QAAQ;AAAA,QACrB,IAAI;AAAA,QACJ;AAAA,QACA,WAAW,MAAM;AAAA,QACjB,OAAO;AAAA,QACP,MAAM,MAAM;AAAA,QACZ,MAAM,MAAM;AAAA,QACZ,UAAU,MAAM;AAAA,MAClB,CAAC;AAGD,WAAK,UAAU,QAAQ,KAAK;AAM5B,YAAM,oBAAoB;AAC1B,YAAM,cAAc,CAAC,GAAG,KAAK,MAAM,OAAO,CAAC,EAAE,MAAM,CAAC,iBAAiB;AACrE,iBAAW,SAAS,aAAa;AAC/B,YAAI,MAAM,OAAO,OAAQ;AACzB,cAAM,MAAM,YAAY,MAAM,MAAM,MAAM,MAAM,IAAI;AACpD,cAAM,SAAS,WAAW,MAAM,QAAQ,CAAC,GAAG,MAAM,QAAQ,CAAC,CAAC;AAC5D,cAAM,SAAS,KAAK,IAAI,KAAK,SAAS,GAAG;AACzC,YAAI,SAAS,MAAM;AACjB,eAAK,MAAM,KAAK;AAAA,YACd,MAAM;AAAA,YACN,IAAI,MAAM;AAAA,YACV,UAAU,OAAO,SAAS,YAAY;AAAA,YACtC;AAAA,YACA,IAAI,MAAM;AAAA,UACZ,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAKA,SAAK,YAAY,KAAK,WAAW,KAAK;AACtC,UAAM,KAAK;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,OAAoB,OAAe,UAAmC;AACjF,UAAM,UAAU,MAAM,KAAK,KAAK,OAAO,OAAO,OAAO,QAAQ;AAC7D,QAAI,UAAU,GAAG;AACf,YAAM,KAAK,UAAU,KAAK;AAE1B,YAAM,IAAI,MAAM,YAAY;AAC5B,YAAM,WAAqB,CAAC;AAC5B,iBAAW,CAAC,IAAI,IAAI,KAAK,KAAK,OAAO;AACnC,YAAI,KAAK,MAAM,KAAK,YAAY,EAAE,SAAS,CAAC,GAAG;AAC7C,mBAAS,KAAK,EAAE;AAAA,QAClB;AAAA,MACF;AACA,iBAAW,MAAM,UAAU;AAEzB,aAAK,oBAAoB,IAAI,KAAK,MAAM,IAAI,EAAE,GAAG,KAAK;AACtD,aAAK,MAAM,OAAO,EAAE;AAAA,MACtB;AACA,WAAK,QAAQ,KAAK,MAAM,OAAO,CAAC,MAAM,CAAC,SAAS,SAAS,EAAE,IAAI,KAAK,CAAC,SAAS,SAAS,EAAE,EAAE,CAAC;AAC5F,WAAK,YAAY,KAAK,WAAW,KAAK;AACtC,YAAM,KAAK;AAAA,IACb;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAQ,OAAoB,UAAmC;AACnE,WAAO,KAAK,KAAK,QAAQ,OAAO,QAAQ;AAAA,EAC1C;AAAA,EAEA,MAAM,KAAK,OAAoB,UAAkB,OAAwC;AACvF,UAAM,KAAK,UAAU,KAAK;AAE1B,UAAM,cAAc,MAAM,KAAK,KAAK,KAAK,OAAO,QAAQ;AACxD,UAAM,UAAU,IAAI,IAAI,KAAK,MAAM,QAAQ,CAAC;AAG5C,UAAM,WAAW,YAAY,IAAI,CAAC,OAAO;AACvC,YAAM,SAAS,KAAK,OAAO,EAAE;AAC7B,YAAM,OAAO,QAAQ,IAAI,MAAM;AAC/B,UAAI,MAAM;AACR,eAAO;AAAA,UACL,GAAG;AAAA,UACH,MAAM,KAAK,QAAQ,GAAG;AAAA,UACtB,MAAM,KAAK,QAAQ,GAAG;AAAA,UACtB,UAAU,KAAK,YAAY,GAAG;AAAA,QAChC;AAAA,MACF;AACA,aAAO;AAAA,IACT,CAAC;AAGD,UAAM,UAAU,IAAI,IAAI,YAAY,IAAI,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC;AAC9D,eAAW,CAAC,IAAI,IAAI,KAAK,KAAK,OAAO;AACnC,UAAI,CAAC,QAAQ,IAAI,EAAE,GAAG;AACpB,iBAAS,KAAK,KAAK,KAAK;AAAA,MAC1B;AAAA,IACF;AAEA,aAAS,KAAK,CAAC,GAAG,MAAM,EAAE,GAAG,cAAc,EAAE,EAAE,CAAC;AAChD,WAAO,QAAQ,SAAS,MAAM,GAAG,KAAK,IAAI;AAAA,EAC5C;AAAA,EAEA,MAAM,OAAO,OAAoB,OAAe,WAAmB,OAAwC;AACzG,UAAM,KAAK,UAAU,KAAK;AAC1B,UAAM,SAAS,MAAM,YAAY,EAAE,MAAM,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,UAAU,oBAAmB,YAAY;AAGzG,UAAM,aAAa,oBAAI,IAAoB;AAC3C,eAAW,QAAQ,QAAQ;AACzB,YAAM,UAAU,KAAK,cAAc,IAAI,IAAI;AAC3C,UAAI,CAAC,QAAS;AACd,iBAAW,UAAU,SAAS;AAC5B,cAAM,OAAO,KAAK,MAAM,IAAI,MAAM;AAClC,YAAI,CAAC,QAAQ,KAAK,MAAM,UAAU,MAAO;AAEzC,mBAAW,IAAI,SAAS,WAAW,IAAI,MAAM,KAAK,KAAK,CAAC;AAAA,MAC1D;AAAA,IACF;AAKA,eAAW,CAAC,QAAQ,IAAI,KAAK,KAAK,OAAO;AACvC,UAAI,KAAK,MAAM,UAAU,MAAO;AAChC,UAAI,WAAW,IAAI,MAAM,EAAG;AAE5B,UAAI,KAAK,aAAa,cAAc,KAAK,aAAa,QAAQ;AAC5D,mBAAW,IAAI,QAAQ,CAAC;AAAA,MAC1B;AAAA,IACF;AAGA,UAAM,SAAkD,CAAC;AACzD,eAAW,CAAC,MAAM,KAAK,YAAY;AACjC,YAAM,OAAO,KAAK,MAAM,IAAI,MAAM;AAClC,UAAI,QAAQ;AAEZ,gBAAU,WAAW,IAAI,MAAM,KAAK,KAAK;AAEzC,iBAAW,QAAQ,QAAQ;AACzB,YAAI,KAAK,MAAM,MAAM,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,IAAI,CAAC,EAAG,UAAS;AAAA,MAC7E;AACA,UAAI,KAAK,aAAa,WAAY,UAAS;AAAA,eAClC,KAAK,aAAa,OAAQ,UAAS;AAC5C,eAAS,KAAK,QAAQ;AACtB,aAAO,KAAK,EAAE,OAAO,KAAK,OAAO,MAAM,CAAC;AAAA,IAC1C;AAEA,WAAO,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACvC,UAAM,UAAU,OAAO,IAAI,CAAC,MAAM,EAAE,KAAK;AACzC,WAAO,QAAQ,QAAQ,MAAM,GAAG,KAAK,IAAI;AAAA,EAC3C;AAAA,EAEA,MAAM,MAAM,OAAoB,UAAiC;AAC/D,UAAM,KAAK,KAAK,MAAM,OAAO,QAAQ;AACrC,SAAK,MAAM,MAAM;AACjB,SAAK,QAAQ,CAAC;AACd,SAAK,gBAAgB,oBAAI,IAAI;AAC7B,SAAK,cAAc;AACnB,SAAK,SAAS;AAEd,QAAI;AAAE,YAAS,WAAO,KAAK,SAAS;AAAA,IAAG,QAAQ;AAAA,IAAW;AAAA,EAC5D;AAAA,EAEA,MAAM,YAAY,OAAoB,UAAmC;AACvE,WAAO,KAAK,KAAK,YAAY,OAAO,QAAQ;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,YAAY,OAAoB,WAAmB,WAAmB,QAAQ,GAA2B;AAC7G,UAAM,KAAK,UAAU,KAAK;AAC1B,UAAM,WAAW,KAAK,OAAO,EAAE,OAAO,MAAM,WAAW,IAAI,GAAG,CAAC;AAC/D,UAAM,UAAU,KAAK,MAClB,OAAO,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,OAAO,QAAQ,EACtD,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,EAClC,MAAM,GAAG,KAAK;AAEjB,UAAM,SAAwB,CAAC;AAC/B,eAAW,QAAQ,SAAS;AAC1B,YAAM,UAAU,KAAK,SAAS,WAAW,KAAK,KAAK,KAAK;AACxD,YAAM,OAAO,KAAK,MAAM,IAAI,OAAO;AACnC,UAAI,KAAM,QAAO,KAAK,KAAK,KAAK;AAAA,IAClC;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,WAAuD;AACrD,WAAO;AAAA,MACL,OAAO,CAAC,GAAG,KAAK,MAAM,OAAO,CAAC;AAAA,MAC9B,OAAO,CAAC,GAAG,KAAK,KAAK;AAAA,IACvB;AAAA,EACF;AAAA;AAAA,EAIQ,OAAO,OAA4B;AAEzC,UAAM,OAAO,MAAM,KAAK,YAAY,EAAE,KAAK,EAAE,QAAQ,QAAQ,GAAG;AAChE,WAAO,GAAG,MAAM,SAAS,KAAK,KAAK,WAAW,IAAI,CAAC;AAAA,EACrD;AAAA,EAEA,MAAc,UAAU,OAAmC;AACzD,QAAI,KAAK,UAAU,KAAK,gBAAgB,MAAO;AAC/C,QAAI;AACF,YAAM,MAAM,MAAS,aAAS,KAAK,WAAW,MAAM;AACpD,YAAM,OAAkE,KAAK,MAAM,GAAG;AACtF,WAAK,QAAQ,IAAI,IAAI,KAAK,KAAK;AAC/B,WAAK,QAAQ,KAAK;AAAA,IACpB,QAAQ;AACN,WAAK,QAAQ,oBAAI,IAAI;AACrB,WAAK,QAAQ,CAAC;AAAA,IAChB;AAEA,SAAK,mBAAmB;AACxB,SAAK,cAAc;AACnB,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,MAAc,WAAW,OAAmC;AAC1D,SAAK,cAAc;AACnB,SAAK,SAAS;AACd,QAAI;AACF,YAAM,OAAO;AAAA,QACX,OAAO,CAAC,GAAG,KAAK,MAAM,QAAQ,CAAC;AAAA,QAC/B,OAAO,KAAK;AAAA,MACd;AACA,YAAM,MAAM,KAAK,UAAU,UAAU,GAAG,KAAK,UAAU,YAAY,GAAG,CAAC;AACvE,YAAS,UAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AAEvC,YAAM,MAAM,GAAG,KAAK,SAAS;AAC7B,YAAS,cAAU,KAAK,KAAK,UAAU,IAAI,CAAC;AAC5C,YAAS,WAAO,KAAK,KAAK,SAAS;AAAA,IACrC,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAuB;AAC3B,UAAM,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,qBAA2B;AACjC,SAAK,gBAAgB,oBAAI,IAAI;AAC7B,eAAW,CAAC,QAAQ,IAAI,KAAK,KAAK,OAAO;AACvC,iBAAW,QAAQ,KAAK,aAAa,KAAK,KAAK,GAAG;AAChD,YAAI,UAAU,KAAK,cAAc,IAAI,IAAI;AACzC,YAAI,CAAC,SAAS;AACZ,oBAAU,oBAAI,IAAI;AAClB,eAAK,cAAc,IAAI,MAAM,OAAO;AAAA,QACtC;AACA,gBAAQ,IAAI,MAAM;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,UAAU,QAAgB,OAA0B;AAC1D,eAAW,QAAQ,KAAK,aAAa,KAAK,GAAG;AAC3C,UAAI,UAAU,KAAK,cAAc,IAAI,IAAI;AACzC,UAAI,CAAC,SAAS;AACZ,kBAAU,oBAAI,IAAI;AAClB,aAAK,cAAc,IAAI,MAAM,OAAO;AAAA,MACtC;AACA,cAAQ,IAAI,MAAM;AAAA,IACpB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,oBAAoB,QAAgB,OAA2B;AACrE,QAAI,CAAC,MAAO;AACZ,eAAW,QAAQ,KAAK,aAAa,KAAK,GAAG;AAC3C,YAAM,UAAU,KAAK,cAAc,IAAI,IAAI;AAC3C,UAAI,SAAS;AACX,gBAAQ,OAAO,MAAM;AACrB,YAAI,QAAQ,SAAS,GAAG;AACtB,eAAK,cAAc,OAAO,IAAI;AAAA,QAChC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,gBAAgB,QAAgB,OAA0B;AAEhE,UAAM,WAAW,KAAK,MAAM,IAAI,MAAM;AACtC,QAAI,UAAU;AACZ,WAAK,oBAAoB,QAAQ,SAAS,KAAK;AAAA,IACjD;AAEA,SAAK,UAAU,QAAQ,KAAK;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,aAAa,OAA8B;AACjD,UAAM,QAAkB,CAAC;AAEzB,UAAM,QAAQ,MAAM,KAAK,YAAY,EAAE,MAAM,KAAK;AAClD,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,UAAU,oBAAmB,cAAc;AAClD,cAAM,KAAK,IAAI;AAAA,MACjB;AAAA,IACF;AAEA,QAAI,MAAM,MAAM;AACd,iBAAW,OAAO,MAAM,MAAM;AAC5B,cAAM,QAAQ,IAAI,YAAY;AAC9B,YAAI,MAAM,UAAU,oBAAmB,gBAAgB,CAAC,MAAM,SAAS,KAAK,GAAG;AAC7E,gBAAM,KAAK,KAAK;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;AAKA,SAAS,YAAY,GAAW,GAAmB;AACjD,QAAM,SAAS,IAAI,IAAI,EAAE,YAAY,EAAE,MAAM,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAC/E,QAAM,SAAS,IAAI,IAAI,EAAE,YAAY,EAAE,MAAM,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAC/E,MAAI,OAAO,SAAS,KAAK,OAAO,SAAS,EAAG,QAAO;AACnD,MAAI,eAAe;AACnB,aAAW,KAAK,QAAQ;AACtB,QAAI,OAAO,IAAI,CAAC,EAAG;AAAA,EACrB;AACA,SAAO,eAAe,KAAK,IAAI,OAAO,MAAM,OAAO,IAAI;AACzD;AAGA,SAAS,WAAW,GAAa,GAAqB;AACpD,MAAI,EAAE,WAAW,KAAK,EAAE,WAAW,EAAG,QAAO;AAC7C,QAAM,OAAO,IAAI,IAAI,CAAC;AACtB,MAAI,SAAS;AACb,aAAW,KAAK,EAAG,KAAI,KAAK,IAAI,CAAC,EAAG;AACpC,SAAO,SAAS,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;AAC7C;AAGA,SAAS,WAAW,GAAmB;AACrC,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,SAAM,KAAK,KAAK,IAAI,EAAE,WAAW,CAAC,IAAK;AAAA,EACzC;AACA,SAAO,KAAK,IAAI,CAAC,EAAE,SAAS,EAAE;AAChC;;;AC5dA,SAAS,cAAc,gBAAgB;AACvC,YAAYC,YAAU;AACtB,SAAS,qBAAqB;AAO9B,IAAM,YAAY,oBAAI,IAAoB;AAG1C,IAAI;AAEG,SAAS,2BAA2B,cAA8B;AACvE,QAAM,SAAS,UAAU,IAAI,YAAY;AACzC,MAAI,WAAW,OAAW,QAAO;AAEjC,MAAI,WAAW;AACf,aAAW,QAAQ,0BAA0B,GAAG;AAC9C,QAAI;AACF,iBAAW,aAAkB,YAAK,MAAM,YAAY,GAAG,MAAM,EAAE,QAAQ;AACvE;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,YAAU,IAAI,cAAc,QAAQ;AACpC,SAAO;AACT;AAEO,SAAS,0BACd,UACA,QACQ;AACR,SAAO,SAAS;AAAA,IAAQ;AAAA,IAAoC,CAAC,OAAO,QAClE,OAAO,OAAO,QAAQ,GAAG,IAAK,OAAO,GAAG,KAAK,KAAM;AAAA,EACrD;AACF;AAEA,SAAS,4BAAsC;AAC7C,MAAI,mBAAmB,OAAW,QAAO;AACzC,QAAM,OAAY,eAAQ,cAAc,YAAY,GAAG,CAAC;AACxD,QAAM,aAAa;AAAA,IACZ,eAAQ,MAAM,oBAAoB;AAAA,IAClC,eAAQ,MAAM,iBAAiB;AAAA,IAC/B,eAAQ,MAAM,cAAc;AAAA,EACnC;AACA,mBAAiB,WAAW,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC;AAC5F,SAAO;AACT;AAEA,SAAS,YAAY,WAA4B;AAC/C,MAAI;AACF,WAAO,SAAS,SAAS,EAAE,YAAY;AAAA,EACzC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACFA,SAAS,yBACP,WACA,YACA,iBACQ;AACR,QAAM,gBACJ,gBAAgB,SAAS,IACrB;AAAA;AAAA;AAAA,EAAiC,gBAC9B,IAAI,CAAC,MAAM,MAAM,EAAE,GAAG,MAAM,GAAG,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAC/C,KAAK,IAAI,CAAC,KACb;AAEN,SAAO,0BAA0B,2BAA2B,4BAA4B,GAAG;AAAA,IACzF,YAAY,OAAO,UAAU;AAAA,IAC7B,SAAS,UAAU,MAAM,GAAG,GAAI;AAAA,IAChC,iBAAiB;AAAA,EACnB,CAAC;AACH;AAIO,IAAM,4BAAN,MAA0D;AAAA,EAC/D,OAAO;AAAA,EACP,QAAQ;AAAA,EAES;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,MAAiC;AAC3C,SAAK,cAAc,KAAK;AACxB,SAAK,WAAW,KAAK;AACrB,SAAK,QAAQ,KAAK;AAClB,SAAK,gBAAgB,KAAK,iBAAiB;AAC3C,SAAK,qBAAqB,KAAK,sBAAsB;AAAA,EACvD;AAAA,EAEA,WAAyB,CAAC,KAAc,WAAsB;AAE5D,QAAI,OAAO,WAAW,OAAQ;AAC9B,QAAI,CAAC,OAAO,aAAa,OAAO,UAAU,KAAK,EAAE,SAAS,GAAI;AAC9D,QAAI,OAAO,aAAa,KAAK,cAAe;AAE5C,UAAM,WAAW,KAAK,YAAY,IAAI;AACtC,QAAI,CAAC,UAAU,SAAU;AAGzB,UAAM,aAAqB,OAAO;AAClC,UAAM,cAAsB,OAAO;AACnC,UAAM,SAA6B,KAAK,SAAS,IAAI;AAKrD,UAAM,YAAY;AAChB,UAAI;AAEF,cAAM,kBAAkB,MAAM,KAAK,YAAY,KAAK,kBAAkB,KAAK,kBAAkB;AAC7F,cAAM,SAAS;AAAA,UACb;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAGA,cAAM,SAAS,YAAY,QAAQ,IAAM;AACzC,cAAM,WAAW,MAAM,SAAS;AAAA,UAC9B;AAAA,YACE,OAAO;AAAA,YACP,QAAQ,CAAC,EAAE,MAAM,QAAQ,MAAM,OAAO,CAAC;AAAA,YACvC,UAAU;AAAA,cACR,EAAE,MAAM,QAAQ,SAAS,2DAA2D;AAAA,YACtF;AAAA,YACA,WAAW;AAAA,UACb;AAAA,UACA,EAAE,OAAO;AAAA,QACX;AAEA,cAAM,OAAO,SAAS,QACnB,OAAO,CAAC,MAA2C,EAAE,SAAS,MAAM,EACpE,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,KAAK,EAAE,EACP,KAAK;AACR,YAAI,CAAC,KAAM;AAGX,cAAM,YAAY,KAAK,MAAM,aAAa;AAC1C,YAAI,CAAC,UAAW;AAEhB,cAAM,SAAgC,KAAK,MAAM,UAAU,CAAC,CAAC;AAC7D,YAAI,CAAC,MAAM,QAAQ,OAAO,UAAU,KAAK,OAAO,WAAW,WAAW,EAAG;AAGzE,YAAI,QAAQ;AACZ,YAAI,SAAS;AACb,YAAI,UAAU;AAEd,mBAAW,MAAM,OAAO,YAAY;AAClC,kBAAQ,GAAG,QAAQ;AAAA,YACjB,KAAK,OAAO;AACV,kBAAI,GAAG,MAAM,KAAK,GAAG;AACnB,sBAAM,KAAK,YAAY,SAAS,GAAG,KAAK,KAAK,GAAG,QAAW;AAAA,kBACzD,MAAM,GAAG;AAAA,kBACT,MAAM,GAAG;AAAA,kBACT,UAAU,GAAG;AAAA,gBACf,CAAC;AACD;AAAA,cACF;AACA;AAAA,YACF;AAAA,YACA,KAAK,QAAQ;AACX,kBAAI,GAAG,SAAS,GAAG,MAAM,KAAK,GAAG;AAC/B,sBAAM,KAAK,YAAY,OAAO,GAAG,KAAK;AACtC,sBAAM,KAAK,YAAY,SAAS,GAAG,KAAK,KAAK,GAAG,QAAW;AAAA,kBACzD,MAAM,GAAG;AAAA,kBACT,MAAM,GAAG;AAAA,kBACT,UAAU,GAAG;AAAA,gBACf,CAAC;AACD;AAAA,cACF;AACA;AAAA,YACF;AAAA,YACA,KAAK,UAAU;AACb,kBAAI,GAAG,OAAO;AACZ,sBAAM,IAAI,MAAM,KAAK,YAAY,OAAO,GAAG,KAAK;AAChD,2BAAW;AAAA,cACb;AACA;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAEA,YAAI,QAAQ,KAAK,SAAS,KAAK,UAAU,GAAG;AAC1C,gBAAM,QAAkB,CAAC;AACzB,cAAI,MAAO,OAAM,KAAK,GAAG,KAAK,QAAQ;AACtC,cAAI,OAAQ,OAAM,KAAK,GAAG,MAAM,SAAS;AACzC,cAAI,QAAS,OAAM,KAAK,GAAG,OAAO,UAAU;AAE5C,kBAAQ,OAAO,MAAM,mCAAmC,MAAM,KAAK,IAAI,CAAC;AAAA,CAAI;AAAA,QAC9E;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF,GAAG;AAAA,EACL;AACF;;;ACjMA,SAAS,qBAAqB,KAAuC;AACnE,QAAM,MAAO,IAAkE;AAC/E,MAAI,CAAC,KAAK,KAAM,QAAO;AACvB,QAAM,MAAuB,EAAE,GAAG,IAAI;AACtC,aAAW,SAAS,KAAK;AACvB,WAAQ,IAAgC,KAAK;AAAA,EAC/C;AACA,SAAQ,IAAgC;AACxC,SAAO;AACT;AAYO,IAAM,qBAAN,MAAgD;AAAA,EAC7C;AAAA,EACA,WAAW,oBAAI,IAA8D;AAAA,EAErF,YAAY,SAAiB;AAC3B,SAAK,UAAU,WAAW,gBAAgB,OAAO,CAAC;AAAA,EACpD;AAAA,EAEA,MAAwB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,WAAmC,KAA6B;AAC9D,WAAO,KAAK,QAAQ,GAAG;AAAA,EACzB;AAAA,EAEA,aAAa,YAAuD;AAClE,UAAM,MAAM,KAAK,QAAQ,aAAa,UAAU;AAChD,WAAO,MAAO,MAA4C;AAAA,EAC5D;AAAA,EAEA,OAAO,SAA4C;AAGjD,UAAM,WAAW,qBAAqB,OAAO;AAK7C,UAAM,OAAO,WAAW,gBAAgB,EAAE,GAAG,KAAK,SAAS,GAAG,SAAS,CAAC,CAAC;AAEzE,QAAI,KAAK,YAAY,GAAG;AACtB,YAAM,IAAI,YAAY;AAAA,QACpB,SAAS,kDAAkD,OAAO,KAAK,OAAO,CAAC;AAAA,QAC/E,MAAM,YAAY;AAAA,QAClB,SAAS,EAAE,OAAO,WAAW,QAAQ,KAAK,QAAQ;AAAA,MACpD,CAAC;AAAA,IACH;AAEA,UAAM,OAAO,KAAK;AAClB,SAAK,UAAU;AAIf,eAAW,KAAK,KAAK,UAAU;AAC7B,UAAI;AACF,UAAE,MAAM,IAAI;AAAA,MACd,SAAS,KAAK;AAKZ,gBAAQ,MAAM,KAAK,UAAU;AAAA,UAC3B,OAAO;AAAA,UACP,OAAO;AAAA,UACP,SAAS,eAAe,GAAG;AAAA,UAC3B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC,CAAC,CAAC;AAAA,MACJ;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,IAA0E;AAC9E,SAAK,SAAS,IAAI,EAAE;AACpB,WAAO,MAAM,KAAK,SAAS,OAAO,EAAE;AAAA,EACtC;AACF;AAEA,IAAM,eAAkD,OAAO,OAAO,CAAC,CAAC;AAExE,SAAS,WAAc,KAAW;AAEhC,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,MAAI,OAAO,SAAS,GAAG,EAAG,QAAO;AAEjC,aAAW,OAAO,OAAO,KAAK,GAAa,GAAG;AAC5C,UAAM,IAAK,IAAgC,GAAG;AAC9C,QAAI,MAAM,QAAQ,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,CAAC,GAAG;AAC9D,iBAAW,CAAC;AAAA,IACd;AAAA,EACF;AACA,SAAO,OAAO,OAAO,GAAG;AAC1B;;;ACpFA,YAAY,YAAY;AACxB,YAAYC,SAAQ;AACpB,YAAYC,YAAU;;;ACbf,SAAS,qBACd,KACA,OACA,MACG;AACH,QAAM,OAAO,MAAM,SAAS,CAAC,QAAgB,QAAQ,KAAK,GAAG;AAI7D,SAAO,KAAK,KAAK,OAAO,CAAC,GAAG,QAAQ;AAClC,QAAI;AACF,aAAO,MAAM,QAAQ,CAAC;AAAA,IACxB,SAAS,KAAK;AACZ;AAAA,QACE,qCAAqC,GAAG,MAAM,eAAe,QAAQ,IAAI,UAAU,GAAG;AAAA,MACxF;AACA,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AACH;AAUA,SAAS,KAAQ,MAAS,OAAoB,WAAkD;AAC9F,MAAI,SAAS,QAAQ,SAAS,OAAW,QAAO;AAChD,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,WAAO,KAAK,IAAI,CAAC,SAAS,KAAK,MAAM,OAAO,SAAS,CAAC;AAAA,EACxD;AACA,QAAM,MAA+B,uBAAO,OAAO,IAAI;AACvD,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAA+B,GAAG;AACpE,QAAI,OAAO,MAAM,YAAY,cAAc,CAAC,GAAG;AAC7C,UAAI,CAAC,IAAI,UAAU,GAAG,CAAC;AAAA,IACzB,WAAW,OAAO,MAAM,YAAY,MAAM,MAAM;AAC9C,UAAI,CAAC,IAAI,KAAK,GAAG,OAAO,SAAS;AAAA,IACnC,OAAO;AACL,UAAI,CAAC,IAAI;AAAA,IACX;AAAA,EACF;AACA,SAAO;AACT;AASA,IAAM,qBACJ;AAIF,IAAM,uBAAuB,oBAAI,IAAI,CAAC,aAAa,YAAY,CAAC;AAEzD,SAAS,cAAc,MAAuB;AACnD,QAAM,KAAK,KAAK,YAAY;AAC5B,MAAI,qBAAqB,IAAI,EAAE,EAAG,QAAO;AACzC,SAAO,mBAAmB,KAAK,EAAE;AACnC;;;ADpBA,eAAe,qBACb,YACA,OACA,MAC6C;AAC7C,MAAI;AACJ,MAAI;AACF,UAAM,MAAS,aAAS,YAAY,MAAM;AAAA,EAC5C,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,UAAU;AACpD,aAAO,kBAAkB,UAAU,KAAM,IAAc,OAAO,EAAE;AAAA,IAClE;AACA,WAAO;AAAA,EACT;AACA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,KAAK;AAEZ,WAAO,aAAa,UAAU,uBAAwB,IAAc,OAAO,EAAE;AAC7E,WAAO;AAAA,EACT;AACA,QAAM,YAAY,qBAAqB,QAAQ,OAAO,OAAO,EAAE,KAAK,IAAI,CAAC,CAAC;AAM1E,QAAM,WAAmC;AAAA,IACvC,WAAW,UAAU,aAAa,CAAC;AAAA,EACrC;AACA,MAAI,OAAO,UAAU,WAAW,SAAU,UAAS,SAAS,UAAU;AACtE,MAAI,OAAO,UAAU,YAAY,SAAU,UAAS,UAAU,UAAU;AACxE,MAAI,OAAO,UAAU,aAAa,YAAY,UAAU;AACtD,aAAS,WAAW,UAAU;AAChC,SAAO;AACT;AAGA,SAAS,kBAAkB,GAAmC;AAC5D,SAAO,KAAK,UAAU;AAAA,IACpB,WAAW,EAAE;AAAA,IACb,QAAQ,EAAE,UAAU;AAAA,IACpB,SAAS,EAAE,WAAW;AAAA,IACtB,UAAU,EAAE,YAAY;AAAA,EAC1B,CAAC;AACH;AAUO,SAAS,oBACd,YACA,OACA,UACA,OAAmC,CAAC,GACb;AACvB,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,OAAO,KAAK;AAClB,QAAM,OAAY,gBAAS,UAAU;AACrC,MAAI;AACJ,MAAI,SAAS;AACb,MAAI;AAIJ,OAAK,qBAAqB,YAAY,OAAO,IAAI,EAAE,KAAK,CAAC,SAAS;AAChE,QAAI,CAAC,UAAU,QAAQ,mBAAmB,QAAW;AACnD,uBAAiB,kBAAkB,IAAI;AAAA,IACzC;AAAA,EACF,CAAC;AAED,MAAI;AACJ,MAAI;AAGF,cAAiB,aAAW,eAAQ,UAAU,GAAG,EAAE,WAAW,MAAM,CAAC;AAAA,EACvE,SAAS,KAAK;AACZ,WAAO,4CAA6C,IAAc,OAAO,EAAE;AAC3E,WAAO,EAAE,OAAO,MAAM;AAAA,IAAC,EAAE;AAAA,EAC3B;AAEA,QAAM,UAAU,MAAY;AAC1B,QAAI,OAAQ;AACZ,QAAI,MAAO,cAAa,KAAK;AAC7B,YAAQ,WAAW,MAAM;AACvB,cAAQ;AACR,WAAK,qBAAqB,YAAY,OAAO,IAAI,EAAE;AAAA,QACjD,CAAC,SAAS;AACR,cAAI,UAAU,CAAC,KAAM;AACrB,gBAAM,aAAa,kBAAkB,IAAI;AACzC,cAAI,eAAe,eAAgB;AACnC,2BAAiB;AACjB,mBAAS,IAAI;AAAA,QACf;AAAA,QACA,MAAM;AAAA,QAEN;AAAA,MACF;AAAA,IACF,GAAG,UAAU;AAAA,EACf;AAEA,UAAQ,GAAG,UAAU,CAAC,WAAmB,aAAqC;AAC5E,UAAM,OAAO,OAAO,aAAa,WAAW,WAAW;AACvD,QAAI,cAAc,YAAY,cAAc,UAAU;AACpD,UAAI,CAAC,QAAQ,SAAS,KAAM,SAAQ;AAAA,IACtC;AAAA,EACF,CAAC;AAED,UAAQ,GAAG,SAAS,CAAC,QAAe;AAClC,WAAO,kCAAkC,IAAI,OAAO,EAAE;AAAA,EACxD,CAAC;AAED,SAAO;AAAA,IACL,OAAO,MAAM;AACX,eAAS;AACT,UAAI,MAAO,cAAa,KAAK;AAC7B,cAAQ,MAAM;AAAA,IAChB;AAAA,EACF;AACF;;;AE9LA,YAAYC,SAAQ;AACpB,YAAYC,YAAU;;;ACqHf,IAAM,4BAA4B;;;ACrFlC,IAAM,iCAAsD;AAE5D,IAAM,uBAAqD,OAAO,OAAO;AAAA,EAC9E;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,EAAE,MAAM,KAAK,MAAM,MAAM,MAAM,IAAI;AAAA,IAC/C,cAAc;AAAA,IACd,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,YAAY;AAAA,EACd;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,EAAE,MAAM,MAAM,MAAM,KAAK,MAAM,KAAK;AAAA,IAChD,cAAc;AAAA,IACd,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,YAAY;AAAA,EACd;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,EAAE,MAAM,MAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IACjD,cAAc;AAAA,IACd,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,YAAY;AAAA,EACd;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY,EAAE,MAAM,MAAM,MAAM,KAAK,MAAM,KAAK;AAAA,IAChD,cAAc;AAAA,IACd,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,YAAY;AAAA,EACd;AACF,CAAC;AAEM,SAAS,yBAA8C;AAC5D,SAAO,qBAAqB,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,YAAY,EAAE,GAAG,EAAE,WAAW,EAAE,EAAE;AACpF;AAQO,SAAS,sBAAsB,IAAuC;AAC3E,SAAO,qBAAqB,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AACrD;;;ACpFO,IAAM,uBAAuB,OAAO,OAAO;AAAA,EAChD,0BAA0B;AAAA,EAC1B,eAAe;AAAA,EACf,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,4BAA4B;AAAA,EAC5B,iBAAiB,OAAO,OAAO,CAAC,CAAC;AAAA,EACjC,eAAe,OAAO,OAAO,CAAC,CAAC;AAAA,EAC/B,iBAAiB;AAAA,EACjB,uBAAuB;AAAA,EACvB,eAAe,OAAO,OAAO;AAAA,IAC3B,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,qBAAqB;AAAA,EACvB,CAAC;AAOH,CAAC;AAGM,IAAM,yBAAyB,OAAO,OAAO;AAAA,EAClD,WAAW;AAAA,EACX,gBAAgB;AAClB,CAAC;AAGM,IAAM,0BAA0B,OAAO,OAAO;AAAA,EACnD,oBAAoB;AACtB,CAAC;AAOM,IAAM,iCAAiC,OAAO,OAAO;AAAA,EAC1D,SAAS;AAAA,EACT,iBAAiB;AACnB,CAAC;AAGM,IAAM,iCAAiC,OAAO,OAAO;AAAA,EAC1D,YAAY;AAAA,EACZ,UAAU;AAAA,IACR,cAAc;AAAA,MACZ,YAAY;AAAA,IACd;AAAA,EACF;AACF,CAAC;;;AH1BD,SAAS,mBAAmB,KAAsB;AAChD,MAAI,eAAe,OAAO;AACxB,UAAM,OAAQ,IAA8B;AAC5C,WAAO,OAAO,GAAG,IAAI,KAAK,IAAI,OAAO,KAAK,IAAI;AAAA,EAChD;AAEA,SAAO,OAAO,GAAG;AACnB;AASA,IAAM,oBAAwD;AAAA,EAC5D,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,IACN,eAAe;AAAA,IACf,eAAe;AAAA,IACf,eAAe;AAAA,IACf,aAAa;AAAA,IACb,WAAW,uBAAuB;AAAA,IAClC,gBAAgB,uBAAuB;AAAA,IACvC,UAAU;AAAA,EACZ;AAAA,EACA,OAAO;AAAA,IACL,0BAA0B,qBAAqB;AAAA,IAC/C,eAAe,qBAAqB;AAAA,IACpC,oBAAoB,qBAAqB;AAAA,IACzC,kBAAkB,qBAAqB;AAAA,IACvC,kBAAkB,qBAAqB;AAAA,IACvC,4BAA4B,qBAAqB;AAAA,IACjD,iBAAiB,qBAAqB;AAAA,IACtC,eAAe,qBAAqB;AAAA,IACpC,iBAAiB,qBAAqB;AAAA,IACtC,uBAAuB,qBAAqB;AAAA,IAC5C,eAAe,qBAAqB;AAAA,EACtC;AAAA,EACA,KAAK,EAAE,OAAO,OAAO;AAAA,EACrB,UAAU;AAAA,IACR,KAAK;AAAA,IACL,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,iBAAiB;AAAA,IACjB,yBAAyB;AAAA,EAC3B;AAAA,EACA,aAAa;AAAA,IACX,SAAS;AAAA,IACT,SAAS;AAAA,MACP,cAAc;AAAA,MACd,WAAW;AAAA,IACb;AAAA,IACA,QAAQ;AAAA,MACN,aAAa;AAAA,MACb,aAAa;AAAA,MACb,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,MACjB,UAAU;AAAA,MACV,kBAAkB,KAAK;AAAA,IACzB;AAAA,IACA,SAAS;AAAA,MACP,kBAAkB;AAAA,MAClB,kBAAkB;AAAA,MAClB,eAAe;AAAA,MACf,+BAA+B;AAAA,IACjC;AAAA,IACA,YAAY;AAAA,MACV,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EACA,QAAQ,EAAE,kBAAkB,KAAK;AAAA,EACjC,YAAY,CAAC;AAAA,EACb,cAAc;AAAA,EACd,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMf,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,OAAO;AAAA,EACP,aAAa;AAAA,EACb,aAAa;AAAA,EACb,UAAU;AAAA,IACR,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,eAAe;AAAA,IACf,YAAY;AAAA,EACd;AAAA,EACA,SAAS,EAAE,GAAG,+BAA+B;AAAA,EAC7C,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMR,aAAa;AAAA,IACb,oBAAoB,wBAAwB;AAAA,IAC5C,0BAA0B;AAAA,IAC1B,oBAAoB;AAAA,IACpB,wBAAwB;AAAA;AAAA;AAAA,IAGxB,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,aAAa;AAAA,IACb,WAAW;AAAA,IACX,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,cAAc;AAAA,EAChB;AAAA,EACA,gBAAgB,EAAE,GAAG,+BAA+B;AAAA,EACpD,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMZ,WAAW,EAAE,MAAM,OAAO;AAAA,IAC1B,OAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,cAAc,OAAkD;AACvE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,eAAkB,OAAa;AACtC,SAAO,gBAAgB,KAAK;AAC9B;AAEA,SAAS,oBACP,QACA,UACsD;AACtD,QAAM,QAAQ,eAAe,MAAM;AACnC,QAAM,UAAU,2BAA2B,OAAO,QAAQ;AAC1D,SAAO,EAAE,OAAO,QAAQ;AAC1B;AAEA,SAAS,2BACP,QACA,UACS;AACT,MAAI,UAAU;AACd,aAAW,CAAC,KAAK,YAAY,KAAK,OAAO,QAAQ,QAAQ,GAAG;AAC1D,QAAI,CAAC,OAAO,OAAO,QAAQ,GAAG,GAAG;AAC/B,aAAO,GAAG,IAAI,eAAe,YAAY;AACzC,gBAAU;AACV;AAAA,IACF;AACA,UAAM,UAAU,OAAO,GAAG;AAC1B,QAAI,cAAc,OAAO,KAAK,cAAc,YAAY,GAAG;AACzD,gBAAU,2BAA2B,SAAS,YAAY,KAAK;AAAA,IACjE;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,QAAQ,GAAoB;AACnC,SAAO,CAAC,sBAAsB,KAAK,EAAE,KAAK,CAAC;AAC7C;AAEA,SAAS,gBAAgB,GAAgC;AACvD,SAAO,MAAM,UAAa,QAAQ,CAAC;AACrC;AAEA,IAAM,aAAa,oBAAI,IAA4B,CAAC,SAAS,QAAQ,QAAQ,SAAS,OAAO,CAAC;AAE9F,SAAS,YAAY,GAAmC;AACtD,SAAO,WAAW,IAAI,CAA2B,IAAK,IAA+B;AACvF;AAEA,IAAM,UAAqE;AAAA,EACzE,qBAAqB,CAAC,GAAG,MAAM;AAC7B,MAAE,WAAW;AACb,QAAI,EAAE,eAAe,OAAW,GAAE,aAAa,oBAAI,IAAI;AACvD,MAAE,WAAW,IAAI,UAAU;AAAA,EAC7B;AAAA,EACA,kBAAkB,CAAC,GAAG,MAAM;AAC1B,MAAE,QAAQ;AACV,QAAI,EAAE,eAAe,OAAW,GAAE,aAAa,oBAAI,IAAI;AACvD,MAAE,WAAW,IAAI,OAAO;AAAA,EAC1B;AAAA,EACA,oBAAoB,CAAC,GAAG,MAAM;AAC5B,MAAE,SAAS;AACX,QAAI,EAAE,eAAe,OAAW,GAAE,aAAa,oBAAI,IAAI;AACvD,MAAE,WAAW,IAAI,QAAQ;AAAA,EAC3B;AAAA,EACA,qBAAqB,CAAC,GAAG,MAAM;AAC7B,MAAE,UAAU;AACZ,QAAI,EAAE,eAAe,OAAW,GAAE,aAAa,oBAAI,IAAI;AACvD,MAAE,WAAW,IAAI,SAAS;AAAA,EAC5B;AAAA,EACA,sBAAsB,CAAC,GAAG,MAAM;AAE9B,QAAI,CAAC,EAAE,IAAK,GAAE,MAAM,EAAE,OAAO,OAAO;AACpC,MAAE,IAAI,QAAQ,YAAY,CAAC;AAAA,EAC7B;AAAA,EACA,2BAA2B,CAAC,GAAG,MAAM;AACnC,MAAE,WAAW,EAAE,GAAG,iBAAiB,GAAG,EAAE,UAAU,gBAAgB,QAAQ,CAAC,EAAE;AAAA,EAC/E;AAAA,EACA,0BAA0B,CAAC,GAAG,MAAM;AAClC,MAAE,WAAW,EAAE,GAAG,iBAAiB,GAAG,EAAE,UAAU,QAAQ,QAAQ,CAAC,EAAE;AAAA,EACvE;AAAA,EACA,wBAAwB,CAAC,GAAG,MAAM;AAChC,MAAE,WAAW,EAAE,GAAG,iBAAiB,GAAG,EAAE,UAAU,eAAe,QAAQ,CAAC,EAAE;AAAA,EAC9E;AACF;AAEA,IAAM,kBAAkB;AAAA,EACtB,gBAAgB;AAAA,EAChB,QAAQ;AAAA,EACR,eAAe;AAAA,EACf,YAAY;AACd;AA8DA,IAAM,0BAA+C,oBAAI,IAAI;AAAA,EAC3D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAoCD,IAAM,0BAA0E;AAAA,EAC9E,EAAE,KAAK,YAAY,QAAQ,yDAAyD;AAAA,EACpF,EAAE,KAAK,UAAU,QAAQ,+CAA+C;AAAA,EACxE,EAAE,KAAK,WAAW,QAAQ,mDAAmD;AAAA,EAC7E,EAAE,KAAK,aAAa,QAAQ,gEAAgE;AAAA,EAC5F,EAAE,KAAK,cAAc,QAAQ,oDAAoD;AAAA,EACjF,EAAE,KAAK,SAAS,QAAQ,kDAAkD;AAAA,EAC1E,EAAE,KAAK,WAAW,QAAQ,0CAA0C;AAAA,EACpE,EAAE,KAAK,QAAQ,QAAQ,kDAAkD;AAAA,EACzE,EAAE,KAAK,QAAQ,QAAQ,gDAAgD;AAAA,EACvE,EAAE,KAAK,cAAc,QAAQ,yDAAyD;AAAA,EACtF,EAAE,KAAK,MAAM,QAAQ,uDAAuD;AAAA,EAC5E,EAAE,KAAK,OAAO,QAAQ,+EAA0E;AAAA,EAChG;AAAA,IACE,KAAK;AAAA,IACL,QACE;AAAA,EACJ;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,QACE;AAAA,EACJ;AACF;AAgBA,IAAM,8BAAmD,oBAAI,IAAI;AAAA,EAC/D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAmBM,SAAS,mCAAyC;AACvD,QAAM,kBAA4B,CAAC;AACnC,aAAW,OAAO,6BAA6B;AAC7C,QAAI,wBAAwB,IAAI,GAAG,EAAG;AACtC,UAAM,SAAS,wBAAwB,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG;AAChE,QAAI,CAAC,OAAQ,iBAAgB,KAAK,GAAG;AAAA,EACvC;AACA,QAAM,eAAe,wBAAwB;AAAA,IAC3C,CAAC,MAAM,CAAC,4BAA4B,IAAI,EAAE,GAAG;AAAA,EAC/C,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG;AAClB,QAAM,YAAY,wBAAwB,OAAO,CAAC,MAAM,wBAAwB,IAAI,EAAE,GAAG,CAAC,EAAE;AAAA,IAC1F,CAAC,MAAM,EAAE;AAAA,EACX;AAEA,QAAM,WAAqB,CAAC;AAC5B,MAAI,gBAAgB,SAAS,GAAG;AAC9B,aAAS;AAAA,MACP,oFACE,gBAAgB,KAAK,IAAI,IACzB;AAAA,IACJ;AAAA,EACF;AACA,MAAI,aAAa,SAAS,GAAG;AAC3B,aAAS;AAAA,MACP,6EACE,aAAa,KAAK,IAAI,IACtB;AAAA,IACJ;AAAA,EACF;AACA,MAAI,UAAU,SAAS,GAAG;AACxB,aAAS;AAAA,MACP,kFACE,UAAU,KAAK,IAAI,IACnB;AAAA,IACJ;AAAA,EACF;AACA,MAAI,SAAS,SAAS,GAAG;AACvB,UAAM,IAAI;AAAA,MACR;AAAA,MAAuD,SAAS,KAAK,QAAQ,CAAC;AAAA,IAChF;AAAA,EACF;AACF;AAEA,IAAI,eAAe;AAeZ,SAAS,2BACd,WACA,YACA,OAA8B,CAAC,QAAQ,QAAQ,KAAK,GAAG,GACxC;AACf,MAAI,CAAC,cAAc;AACjB,qCAAiC;AACjC,mBAAe;AAAA,EACjB;AACA,QAAM,WAAqB,CAAC;AAC5B,QAAM,MAAqB,CAAC;AAC5B,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,SAAS,GAAG;AAC9C,QAAI,wBAAwB,IAAI,CAAC,GAAG;AAClC,MAAC,IAAgC,CAAC,IAAI;AACtC;AAAA,IACF;AACA,aAAS,KAAK,CAAC;AAAA,EACjB;AAaA,QAAM,WAAY,IAAgC,OAAO;AACzD,MAAI,YAAY,OAAO,aAAa,UAAU;AAC5C,UAAM,UAAW,SAAqC,MAAM;AAC5D,QAAI,WAAW,OAAO,YAAY,UAAU;AAC1C,YAAM,WAAW,WAAY;AAC7B,YAAM,YAAY,YAAa;AAC/B,UAAI,YAAY,WAAW;AACzB,cAAM,aAAsC,EAAE,GAAI,QAAoC;AACtF,YAAI,UAAU;AACZ,iBAAO,WAAW,OAAO;AACzB,mBAAS,KAAK,kBAAkB;AAAA,QAClC;AACA,YAAI,WAAW;AAQb,iBAAO,WAAW,QAAQ;AAC1B,mBAAS,KAAK,mBAAmB;AAAA,QACnC;AACA,QAAC,IAAgC,OAAO,IAAI;AAAA,UAC1C,GAAI;AAAA,UACJ,MAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAUA,QAAM,YAAa,IAAgC,QAAQ;AAC3D,MAAI,aAAa,OAAO,cAAc,UAAU;AAC9C,UAAM,YAAY;AAClB,UAAM,aAAa,eAAe,aAAa,iBAAiB;AAChE,QAAI,YAAY;AACd,YAAM,eAAe,EAAE,GAAG,UAAU;AACpC,UAAI,eAAe,cAAc;AAC/B,eAAO,aAAa,WAAW;AAC/B,iBAAS,KAAK,kBAAkB;AAAA,MAClC;AACA,UAAI,iBAAiB,cAAc;AACjC,eAAO,aAAa,aAAa;AACjC,iBAAS,KAAK,oBAAoB;AAAA,MACpC;AACA,MAAC,IAAgC,QAAQ,IAAI;AAAA,IAC/C;AAAA,EACF;AAMA,QAAM,iBAAkB,IAAgC,aAAa;AACrE,MAAI,kBAAkB,OAAO,mBAAmB,UAAU;AACxD,UAAM,UAAW,eAA2C,SAAS;AACrE,QAAI,WAAW,OAAO,YAAY,YAAY,eAAgB,SAAqC;AACjG,YAAM,gBAAgB,EAAE,GAAI,QAAoC;AAChE,aAAO,cAAc,WAAW;AAChC,MAAC,IAAgC,aAAa,IAAI;AAAA,QAChD,GAAI;AAAA,QACJ,SAAS;AAAA,MACX;AACA,eAAS,KAAK,+BAA+B;AAAA,IAC/C;AAAA,EACF;AAEA,MAAI,SAAS,SAAS,GAAG;AACvB;AAAA,MACE,KAAK,UAAU;AAAA,QACb,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM;AAAA,QACN,aAAa;AAAA,QACb,SACE,WAAW,SAAS,MAAM,6CACtB,UAAU,MAAM,SAAS,KAAK,IAAI,CAAC;AAAA,QAIzC,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;AASA,SAAS,SAAS,GAAW,GAAoB;AAC/C,MAAI,KAAU,eAAQ,CAAC;AACvB,MAAI,KAAU,eAAQ,CAAC;AACvB,MAAI,QAAQ,aAAa,WAAW,QAAQ,aAAa,UAAU;AACjE,SAAK,GAAG,YAAY;AACpB,SAAK,GAAG,YAAY;AAAA,EACtB;AACA,SAAO,OAAO;AAChB;AAOA,SAASC,WAAoC,MAAS,OAAkB;AACtE,QAAM,OAAyB,EAAE,WAAW,oBAAoB;AAChE,MAAI,gBAAgB,QAAQ,IAAI,uBAAuB,GAAG;AACxD,SAAK,6BAA6B,CAAC,KAAK,aAAa,aAAa;AAChE,cAAQ;AAAA,QACN,qCAAqC,GAAG,6DACnB,WAAW,oBAAoB,QAAQ;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAkCO,IAAM,sBAAN,MAAkD;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY,oBAAI,IAAkC;AAAA,EAEnE,YAAY,MAA2B;AACrC,SAAK,QAAQ,KAAK;AAClB,SAAK,SAAS,KAAK,UAAU;AAC7B,SAAK,QAAQ,KAAK;AAClB,SAAK,eAAe,KAAK,WAAW,CAAC;AACrC,SAAK,SAAS,KAAK;AACnB,SAAK,UAAU,KAAK;AAAA,EACtB;AAAA,EAEA,MAAM,KACJ,OAA+G,CAAC,GAC/F;AACjB,QAAI,MAAqB,EAAE,GAAG,kBAAkB;AAKhD,UAAM,KAAK,qBAAqB;AAchC,UAAM,oBACJ,SAAS,KAAK,MAAM,iBAAiB,KAAK,MAAM,YAAY,KAC5D,SAAS,KAAK,MAAM,iBAAiB,KAAK,MAAM,kBAAkB;AACpE,UAAM,CAAC,QAAQ,OAAO,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,MACnD,KAAK,SAAS,KAAK,MAAM,YAAY;AAAA,MACrC,KAAK,SAAS,KAAK,MAAM,kBAAkB;AAAA,MAC3C,oBACI,QAAQ,QAAQ,CAAC,CAAkB,IACnC,KAAK,SAAS,KAAK,MAAM,eAAe;AAAA,IAC9C,CAAC;AACD,UAAMA,WAAU,KAAK,MAAM;AAC3B,UAAMA,WAAU,KAAK,KAAK;AAK1B,UAAMA,WAAU,KAAK,2BAA2B,WAAW,KAAK,MAAM,eAAe,CAAC;AAGtF,eAAW,CAAC,KAAK,EAAE,KAAK,OAAO,QAAQ,OAAO,GAAG;AAC/C,YAAM,IAAI,QAAQ,IAAI,GAAG;AACzB,UAAI,EAAG,IAAG,KAAK,CAAC;AAAA,IAClB;AAIA,UAAM,SAAS,CAAC,GAAG,KAAK,YAAY,EAAE,KAAK,CAAC,GAAG,MAAM;AACnD,YAAM,MAAM,EAAE,YAAY,OAAO,EAAE,YAAY;AAC/C,UAAI,OAAO,EAAG,QAAO;AACrB,aAAO,EAAE,KAAK,cAAc,EAAE,IAAI;AAAA,IACpC,CAAC;AACD,eAAW,OAAO,QAAQ;AACxB,UAAI;AACF,cAAM,QAAQ,MAAM,IAAI,KAAK;AAC7B,YAAI,SAAS,OAAO,KAAK,KAAK,EAAE,SAAS,GAAG;AAC1C,gBAAMA,WAAU,KAAK,KAAK;AAAA,QAC5B;AAAA,MACF,SAAS,KAAK;AAEZ,gBAAQ;AAAA,UACN,KAAK,UAAU;AAAA,YACb,OAAO;AAAA,YACP,OAAO;AAAA,YACP,QAAQ,IAAI;AAAA,YACZ,SAAS,eAAe,GAAG;AAAA,YAC3B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAGA,QAAI,KAAK,UAAU;AACjB,YAAMA,WAAU,KAAK,KAAK,QAAQ;AAAA,IACpC;AAGA,QAAI,KAAK,OAAO;AACd,YAAM,qBAAqB,KAAK,KAAK,KAAK;AAAA,IAC5C;AAQA,QAAI,IAAI,WAAW;AACjB,iBAAW,QAAQ,OAAO,OAAO,IAAI,SAAS,GAAG;AAC/C,YAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AACvC,cAAM,UAAW,KAA2C;AAC5D,YAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,QAAQ,WAAW,EAAG;AAKrD,cAAM,OAAO,QAAQ;AAAA,UACnB,CAAC,MACC,CAAC,CAAC,KACF,OAAO,MAAM,YACb,OAAQ,EAAsC,UAAU,YACxD,OAAQ,EAAuC,WAAW;AAAA,QAC9D;AACA,YAAI,KAAK,WAAW,EAAG;AACvB,cAAM,WAAY,KAAyC;AAC3D,YAAI,YAAY,SAAS,SAAS,EAAG;AACrC,cAAM,cAAe,KAA4C;AACjE,cAAM,SAAS,cACV,KAAK,KAAK,CAAC,MAAM,EAAE,UAAU,WAAW,KAAK,KAAK,CAAC,IACpD,KAAK,CAAC;AACV,YAAI,QAAQ,QAAQ;AAClB,UAAC,KAAyC,SAAS,OAAO;AAAA,QAC5D;AAAA,MACF;AAAA,IACF;AAEA,SAAK,iBAAiB,GAAG;AACzB,QAAI,KAAK,UAAU,CAAC,KAAK,wBAAwB;AAC/C,WAAK,iBAAiB,GAAG;AAAA,IAC3B;AAKA,WAAO,OAAO,OAAO,GAAG;AAAA,EAC1B;AAAA,EAEA,MAAc,uBAAsC;AAClD,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI;AACF,YAAM,aAAa,IAAI,YAAY;AACjC,YAAI;AACJ,YAAI;AACF,gBAAM,MAAM,MAAS,aAAS,IAAI,MAAM;AACxC,gBAAM,SAAS,UAAmB,GAAG;AACrC,cAAI,CAAC,OAAO,MAAM,CAAC,cAAc,OAAO,KAAK,GAAG;AAG9C;AAAA,UACF;AACA,mBAAS,OAAO;AAAA,QAClB,SAAS,KAAK;AACZ,cAAK,IAA8B,SAAS,UAAU;AACpD,iBAAK,QAAQ,KAAK,iBAAiB;AAAA,cACjC,WAAW;AAAA,cACX,OAAO;AAAA,cACP,UAAU;AAAA,cACV,WAAW;AAAA,cACX,SAAS;AAAA,cACT,OAAO,mBAAmB,GAAG;AAAA,cAC7B,aAAa;AAAA,cACb,YAAY,KAAK,IAAI,IAAI;AAAA,cACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,YAChE,CAAC;AACD,oBAAQ;AAAA,cACN,KAAK,UAAU;AAAA,gBACb,OAAO;AAAA,gBACP,OAAO;AAAA,gBACP,MAAM;AAAA,gBACN,SAAS,eAAe,GAAG;AAAA,gBAC3B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,cACpC,CAAC;AAAA,YACH;AACA;AAAA,UACF;AACA,mBAAS,CAAC;AAAA,QACZ;AAEA,cAAM,EAAE,OAAO,QAAQ,IAAI;AAAA,UACzB;AAAA,UACA;AAAA,QACF;AACA,YAAI,CAAC,QAAS;AAEd,cAAM,YAAY,IAAI,KAAK,UAAU,OAAO,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AACrE,aAAK,QAAQ,KAAK,iBAAiB;AAAA,UACjC,WAAW;AAAA,UACX,OAAO;AAAA,UACP,UAAU;AAAA,UACV,WAAW;AAAA,UACX,SAAS;AAAA,UACT,YAAY,KAAK,IAAI,IAAI;AAAA,UACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,QAChE,CAAC;AAAA,MACH,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,WAAK,QAAQ,KAAK,iBAAiB;AAAA,QACjC,WAAW;AAAA,QACX,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT,OAAO,mBAAmB,GAAG;AAAA,QAC7B,aAAa;AAAA,QACb,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,cAAQ;AAAA,QACN,KAAK,UAAU;AAAA,UACb,OAAO;AAAA,UACP,OAAO;AAAA,UACP,MAAM;AAAA,UACN,SAAS,eAAe,GAAG;AAAA,UAC3B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,kBAAkB,KAAgC;AACtD,QAAI,UAAU,EAAE,GAAG,IAAI;AACvB,QAAI,KAAK,SAAS,QAAQ,eAAe,CAAC,QAAQ,YAAY,WAAW,MAAM,GAAG;AAGhF,gBAAU,EAAE,GAAG,SAAS,aAAa,KAAK,MAAM,QAAQ,QAAQ,WAAW,EAAE;AAAA,IAC/E;AACA,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI;AACF,YAAM,YAAY,IAAI,KAAK,UAAU,SAAS,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AACvE,WAAK,QAAQ,KAAK,iBAAiB;AAAA,QACjC,WAAW;AAAA,QACX,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,WAAK,QAAQ,KAAK,iBAAiB;AAAA,QACjC,WAAW;AAAA,QACX,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT,OAAO,mBAAmB,GAAG;AAAA,QAC7B,aAAa;AAAA,QACb,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,iBAA6C;AACjD,UAAM,KAAK,KAAK,MAAM;AACtB,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI;AACF,YAAM,MAAM,MAAS,aAAS,IAAI,MAAM;AACxC,YAAM,SAAS,UAAsB,GAAG;AACxC,UAAI,CAAC,OAAO,MAAM,CAAC,OAAO,OAAO;AAC/B,aAAK,QAAQ,KAAK,gBAAgB;AAAA,UAChC,WAAW;AAAA,UACX,OAAO;AAAA,UACP,UAAU;AAAA,UACV,WAAW;AAAA,UACX,SAAS;AAAA,UACT,YAAY,KAAK,IAAI,IAAI;AAAA,UACzB,OAAO;AAAA,UACP,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,QAChE,CAAC;AACD,eAAO;AAAA,MACT;AAGA,UAAI,KAAK,OAAO;AACd,cAAM,YAAY,qBAAqB,EAAE,MAAM,OAAO,MAAM,GAAoB,KAAK,KAAK;AAC1F,cAAM,SAAU,UAAmC,QAAQ;AAC3D,aAAK,QAAQ,KAAK,gBAAgB;AAAA,UAChC,WAAW;AAAA,UACX,OAAO;AAAA,UACP,UAAU;AAAA,UACV,WAAW;AAAA,UACX,SAAS;AAAA,UACT,YAAY,KAAK,IAAI,IAAI;AAAA,UACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,QAChE,CAAC;AACD,eAAO;AAAA,MACT;AACA,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC,WAAW;AAAA,QACX,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,aAAO,OAAO;AAAA,IAChB,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,SAAU,QAAO;AAE7D,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC,WAAW;AAAA,QACX,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,OAAO,mBAAmB,GAAG;AAAA,QAC7B,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,cAAQ;AAAA,QACN,KAAK,UAAU;AAAA,UACb,OAAO;AAAA,UACP,OAAO;AAAA,UACP,SAAS,eAAe,GAAG;AAAA,UAC3B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC,CAAC;AAAA,MACH;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAc,SAAS,MAAsC;AAC3D,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI,UAAyB;AAC7B,QAAI;AACF,YAAMC,SAAO,MAAS,SAAK,IAAI;AAC/B,gBAAUA,OAAK;AACf,YAAM,SAAS,KAAK,UAAU,IAAI,IAAI;AACtC,UAAI,UAAU,OAAO,YAAY,SAAS;AACxC,eAAO,gBAAgB,OAAO,KAAK;AAAA,MACrC;AAAA,IACF,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,UAAU;AACpD,aAAK,UAAU,IAAI,MAAM,EAAE,SAAS,MAAM,OAAO,CAAC,EAAE,CAAC;AACrD,eAAO,CAAC;AAAA,MACV;AACA,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC,WAAW;AAAA,QACX,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,OAAO,mBAAmB,GAAG;AAAA,QAC7B,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,cAAQ;AAAA,QACN,KAAK,UAAU;AAAA,UACb,OAAO;AAAA,UACP,OAAO;AAAA,UACP,MAAM;AAAA,UACN,SAAS,eAAe,GAAG;AAAA,UAC3B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC,CAAC;AAAA,MACH;AACA,aAAO,CAAC;AAAA,IACV;AAEA,QAAI;AACJ,QAAI;AACF,YAAM,MAAS,aAAS,MAAM,MAAM;AAAA,IACtC,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,UAAU;AACpD,aAAK,QAAQ,KAAK,gBAAgB;AAAA,UAChC,WAAW;AAAA,UACX,OAAO;AAAA,UACP,UAAU;AAAA,UACV,WAAW;AAAA,UACX,SAAS;AAAA,UACT,YAAY,KAAK,IAAI,IAAI;AAAA,UACzB,OAAO,mBAAmB,GAAG;AAAA,UAC7B,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,QAChE,CAAC;AACD,gBAAQ;AAAA,UACN,KAAK,UAAU;AAAA,YACb,OAAO;AAAA,YACP,OAAO;AAAA,YACP,MAAM;AAAA,YACN,SAAS,eAAe,GAAG;AAAA,YAC3B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC,CAAC;AAAA,QACH;AAAA,MACF;AACA,WAAK,UAAU,IAAI,MAAM,EAAE,SAAS,MAAM,OAAO,CAAC,EAAE,CAAC;AACrD,aAAO,CAAC;AAAA,IACV;AACA,UAAM,SAAS,UAAyB,GAAG;AAC3C,QAAI,CAAC,OAAO,MAAM,CAAC,OAAO,OAAO;AAC/B,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC,WAAW;AAAA,QACX,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,OAAO;AAAA,QACP,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,cAAQ;AAAA,QACN,KAAK,UAAU;AAAA,UACb,OAAO;AAAA,UACP,OAAO;AAAA,UACP,MAAM;AAAA,UACN,SAAS;AAAA,UACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC,CAAC;AAAA,MACH;AACA,aAAO,CAAC;AAAA,IACV;AACA,SAAK,UAAU,IAAI,MAAM,EAAE,SAAS,OAAO,gBAAgB,OAAO,KAAK,EAAE,CAAC;AAC1E,WAAO,OAAO;AAAA,EAChB;AAAA,EAEQ,iBAAiB,KAA0B;AAEjD,QAAI,IAAI,YAAY;AAClB,YAAM,IAAI,YAAY;AAAA,QACpB,SAAS;AAAA,QACT,MAAM,YAAY;AAAA,QAClB,SAAS,EAAE,OAAO,UAAU;AAAA,MAC9B,CAAC;AAEH,QAAI,IAAI,YAAY;AAClB,YAAM,IAAI,YAAY;AAAA,QACpB,SAAS,+BAA+B,IAAI,OAAO;AAAA,QACnD,MAAM,YAAY;AAAA,QAClB,SAAS,EAAE,OAAO,WAAW,QAAQ,IAAI,QAAQ;AAAA,MACnD,CAAC;AACH,UAAM,IAAI,IAAI;AACd,QAAI,CAAC;AACH,YAAM,IAAI,YAAY;AAAA,QACpB,SAAS;AAAA,QACT,MAAM,YAAY;AAAA,QAClB,SAAS,EAAE,OAAO,UAAU;AAAA,MAC9B,CAAC;AAMH,UAAM,SAAgC,CAAC,iBAAiB,iBAAiB,eAAe;AACxF,eAAW,KAAK,QAAQ;AACtB,YAAM,IAAI,EAAE,CAAC;AACb,UAAI,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,CAAC,GAAG;AAChD,cAAM,IAAI,YAAY;AAAA,UACpB,SAAS,mBAAmB,OAAO,CAAC,CAAC,iCAAiC,OAAO,CAAC;AAAA,UAC9E,MAAM,YAAY;AAAA,UAClB,SAAS,EAAE,OAAO,WAAW,OAAO,CAAC,CAAC,IAAI,YAAY,OAAO,EAAE;AAAA,QACjE,CAAC;AAAA,MACH;AAAA,IACF;AACA,QAAI,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,eAAe;AAC5E,YAAM,IAAI,YAAY;AAAA,QACpB,SAAS;AAAA,QACT,MAAM,YAAY;AAAA,QAClB,SAAS,EAAE,MAAM,EAAE,eAAe,MAAM,EAAE,eAAe,MAAM,EAAE,cAAc;AAAA,MACjF,CAAC;AAAA,IACH;AACA,QAAI,EAAE,SAAS,UAAa,CAAC,sBAAsB,EAAE,IAAI,GAAG;AAI1D,YAAM,QAAQ,uBAAuB,EAClC,IAAI,CAAC,MAAM,EAAE,EAAE,EACf,KAAK,IAAI;AACZ,cAAQ;AAAA,QACN,2CAA2C,EAAE,IAAI,uBAAuB,KAAK,uBACvD,8BAA8B;AAAA,MACtD;AACA,QAAE,OAAO;AAAA,IACX;AAAA,EACF;AAAA,EAEQ,iBAAiB,KAA0B;AACjD,QAAI,CAAC,IAAI,UAAU;AACjB,YAAM,IAAI,YAAY;AAAA,QACpB,SAAS;AAAA,QACT,MAAM,YAAY;AAAA,QAClB,SAAS,EAAE,OAAO,WAAW;AAAA,MAC/B,CAAC;AAAA,IACH;AACA,QAAI,CAAC,IAAI,OAAO;AACd,YAAM,IAAI,YAAY;AAAA,QACpB,SAAS;AAAA,QACT,MAAM,YAAY;AAAA,QAClB,SAAS,EAAE,OAAO,QAAQ;AAAA,MAC5B,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;AIhsCO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,MAKT;AACD,UAAM,KAAK,OAAO;AAClB,SAAK,OAAO;AACZ,SAAK,cAAc,KAAK;AACxB,SAAK,gBAAgB,KAAK;AAC1B,SAAK,cAAc,KAAK;AAAA,EAC1B;AACF;AAkBO,SAAS,oBACd,OACA,eACA,YACiB;AACjB,QAAM,UAAU,OAAO,MAAM,SAAS,MAAM,WAAY,MAAM,SAAS,IAAe;AACtF,MAAI,UAAmC,EAAE,GAAG,MAAM;AAClD,MAAI,iBAAiB;AACrB,QAAM,UAAoB,CAAC;AAC3B,MAAI,gBAAgB;AAEpB,MAAI,QAAQ;AACZ,SAAO,mBAAmB,eAAe;AACvC,QAAI,EAAE,QAAQ,KAAK;AACjB,YAAM,IAAI,qBAAqB;AAAA,QAC7B,SAAS,iDAAiD,OAAO,YAAY,aAAa;AAAA,QAC1F,aAAa;AAAA,QACb;AAAA,QACA,aAAa;AAAA,MACf,CAAC;AAAA,IACH;AACA,UAAM,OAAO,WAAW,KAAK,CAAC,MAAM,EAAE,SAAS,cAAc;AAC7D,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,qBAAqB;AAAA,QAC7B,SAAS,wCAAwC,cAAc,aAAa,aAAa;AAAA,QACzF,aAAa;AAAA,QACb;AAAA,QACA,aAAa;AAAA,MACf,CAAC;AAAA,IACH;AACA,UAAM,MAAwB,EAAE,aAAa,gBAAgB,eAAe,MAAM;AAClF,UAAM,OAAO,KAAK,QAAQ,SAAS,GAAG;AAGtC,QAAI,OAAO,KAAK,SAAS,MAAM,YAAY,KAAK,SAAS,MAAM,KAAK,IAAI;AACtE,WAAK,SAAS,IAAI,KAAK;AAAA,IACzB;AACA,cAAU;AACV,qBAAiB,KAAK;AACtB,YAAQ,KAAK,IAAI,KAAK,IAAI,UAAK,KAAK,EAAE,EAAE;AACxC,oBAAgB,iBAAiB,IAAI,iBAAiB,KAAK,OAAO,KAAK;AAAA,EACzE;AACA,SAAO,EAAE,QAAQ,SAAS,SAAS,cAAc;AACnD;AAiBO,IAAM,4BAAwD,CAAC;;;AC7ItE,YAAYC,UAAS;AACrB,YAAYC,SAAQ;AACpB,YAAYC,YAAU;AAqDtB,IAAM,YAAY;AAClB,IAAM,qBAAqB,KAAK,KAAK,KAAK;AAEnC,IAAM,eAAN,MAAmB;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,MAA2B;AACrC,SAAK,OAAY,YAAK,KAAK,KAAK,SAAS;AACzC,SAAK,MAAM,KAAK,OAAO,QAAQ;AAC/B,SAAK,WAAW,KAAK,YAAe,aAAS;AAC7C,SAAK,WAAW,KAAK,YAAY;AACjC,SAAK,eAAe,KAAK;AACzB,SAAK,QAAQ,KAAK,cAAc;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,iBAAmD;AACvD,UAAM,OAAO,MAAM,KAAK,SAAS;AACjC,QAAI,CAAC,KAAM,QAAO;AAElB,UAAM,QAAQ,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,QAAQ;AAC5D,QAAI,OAAO,MAAM,KAAK,KAAK,QAAQ,GAAG;AAEpC,aAAO;AAAA,IACT;AACA,QAAI,QAAQ,KAAK,SAAU,QAAO;AAOlC,QAAI,KAAK,aAAa,KAAK,YAAY,KAAK,MAAM,KAAK,GAAG,GAAG;AAE3D,aAAO;AAAA,IACT;AAEA,QAAI,eAAe;AACnB,QAAI,KAAK,cAAc;AACrB,UAAI;AACF,cAAM,OAAO,MAAM,KAAK,aAAa,KAAK,KAAK,SAAS;AAKxD,cAAM,UAAU,KAAK,OAAO,cAAc,CAAC,MAAM,EAAE,SAAS,aAAa;AACzE,cAAM,SACJ,WAAW,KACX,CAAC,KAAK,OACH,MAAM,UAAU,CAAC,EACjB;AAAA,UACC,CAAC,MACC,EAAE,SAAS,gBACX,EAAE,SAAS,kBACX,EAAE,SAAS;AAAA,QACf;AACJ,YAAI,OAAQ,QAAO;AACnB,uBAAe,KAAK,SAAS;AAAA,MAC/B,QAAQ;AAGN,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,MACL,WAAW,KAAK;AAAA,MAChB,KAAK,KAAK;AAAA,MACV,WAAW,KAAK;AAAA,MAChB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,MAAM,WAAkC;AAC5C,UAAM,UAAe,eAAQ,KAAK,IAAI,CAAC;AACvC,UAAM,OAAiB;AAAA,MACrB,GAAG;AAAA,MACH;AAAA,MACA,KAAK,KAAK;AAAA,MACV,UAAU,KAAK;AAAA,MACf,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AAMA,QAAI;AACF,YAAU,eAAU,KAAK,MAAM,KAAK,UAAU,IAAI,GAAG,EAAE,MAAM,MAAM,MAAM,IAAM,CAAC;AAAA,IAClF,SAAS,KAAK;AACZ,YAAM,OAAQ,IAA8B;AAC5C,UAAI,SAAS,UAAU;AACrB,cAAM,IAAI,MAAM,+CAA+C;AAAA,MACjE;AAEA,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAuB;AAC3B,QAAI;AACF,YAAU,YAAO,KAAK,IAAI;AAAA,IAC5B,SAAS,KAAK;AACZ,YAAM,OAAQ,IAA8B;AAC5C,UAAI,SAAS,SAAU;AAEvB,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAc,WAAqC;AACjD,QAAI;AACJ,QAAI;AACF,YAAM,MAAU,cAAS,KAAK,MAAM,MAAM;AAAA,IAC5C,SAAS,KAAK;AACZ,YAAM,OAAQ,IAA8B;AAC5C,UAAI,SAAS,SAAU,QAAO;AAC9B,aAAO;AAAA,IACT;AACA,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,UAAI,CAAC,WAAW,MAAM,EAAG,QAAO;AAChC,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEA,SAAS,WAAW,GAA2B;AAC7C,MAAI,OAAO,MAAM,YAAY,MAAM,KAAM,QAAO;AAChD,QAAM,IAAI;AACV,SACE,EAAE,GAAG,MAAM,KACX,OAAO,EAAE,WAAW,MAAM,YAC1B,OAAO,EAAE,KAAK,MAAM,YACpB,OAAO,EAAE,UAAU,MAAM,YACzB,OAAO,EAAE,WAAW,MAAM;AAE9B;AAWA,SAAS,kBAAkB,KAAsB;AAC/C,MAAI,CAAC,OAAO,UAAU,GAAG,KAAK,OAAO,EAAG,QAAO;AAC/C,MAAI;AACF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,UAAM,OAAQ,IAA8B;AAE5C,QAAI,SAAS,QAAS,QAAO;AAC7B,WAAO;AAAA,EACT;AACF;;;ACtPA,YAAYC,SAAQ;AAqBb,IAAM,uBAAN,MAAM,sBAA8C;AAAA,EACxC;AAAA,EACA,aAAa,oBAAI,IAAyB;AAAA,EAC1C,mBAAmB,oBAAI,IAAoB;AAAA,EAC5D,OAAwB,0BAA0B;AAAA,EAElD,YAAY,MAAmC;AAC7C,SAAK,QAAQ,KAAK;AAAA,EACpB;AAAA,EAEA,MAAc,sBAAsB,WAAyC;AAC3E,UAAM,gBAAgB,KAAK;AAI3B,UAAM,UAAU,cAAc;AAC9B,QAAI,CAAC,SAAS;AACZ,aAAO,MAAM,KAAK,MAAM,KAAK,SAAS;AAAA,IACxC;AACA,UAAM,cAAc,kBAAkB,SAAS,WAAW,QAAQ;AAClE,QAAI,UAAyB;AAC7B,QAAI;AACF,YAAMC,SAAO,MAAS,SAAK,WAAW;AACtC,gBAAUA,OAAK;AAAA,IACjB,QAAQ;AACN,WAAK,WAAW,OAAO,SAAS;AAChC,WAAK,iBAAiB,OAAO,SAAS;AACtC,aAAO,MAAM,KAAK,MAAM,KAAK,SAAS;AAAA,IACxC;AAEA,UAAM,cAAc,KAAK,iBAAiB,IAAI,SAAS;AACvD,UAAM,aAAa,KAAK,WAAW,IAAI,SAAS;AAChD,QAAI,cAAc,gBAAgB,SAAS;AACzC,WAAK,WAAW,OAAO,SAAS;AAChC,WAAK,iBAAiB,OAAO,SAAS;AACtC,WAAK,WAAW,IAAI,WAAW,UAAU;AACzC,WAAK,iBAAiB,IAAI,WAAW,OAAO;AAC5C,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,MAAM,KAAK,MAAM,KAAK,SAAS;AAC5C,SAAK,WAAW,OAAO,SAAS;AAChC,SAAK,iBAAiB,OAAO,SAAS;AACtC,SAAK,WAAW,IAAI,WAAW,IAAI;AACnC,SAAK,iBAAiB,IAAI,WAAW,OAAO;AAC5C,WAAO,KAAK,WAAW,OAAO,sBAAqB,yBAAyB;AAC1E,YAAM,SAAS,KAAK,WAAW,KAAK,EAAE,KAAK,EAAE;AAC7C,UAAI,WAAW,OAAW;AAC1B,WAAK,WAAW,OAAO,MAAM;AAC7B,WAAK,iBAAiB,OAAO,MAAM;AAAA,IACrC;AAEA,QAAI,KAAK,SAAS,SAAS;AACzB,oBAAc,iBAAiB,SAAS;AAAA,IAC1C;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,MAAM,IAAkB,CAAC,GAAkC;AAG/D,UAAM,kBAAkB,KAAK;AAW7B,QAAI;AACJ,QAAI,OAAO,gBAAgB,iBAAiB,YAAY;AACtD,YAAM,MAAM,gBAAgB,aAAa;AAAA,QACvC,OAAO,EAAE;AAAA,QACT,OAAO,EAAE;AAAA,QACT,UAAU,EAAE;AAAA,QACZ,OAAO,EAAE;AAAA,QACT,WAAW,EAAE;AAAA,QACb,eAAe,EAAE;AAAA,QACjB,OAAO,EAAE;AAAA,MACX,CAAC;AAAA,IACH,OAAO;AACL,YAAM,UAAU,MAAM,KAAK,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,EAAE,OAAO,GAAG,IAAI,GAAI;AAC7E,YAAM,cAAc,EAAE,eAAe,YAAY;AACjD,YAAM,QAAQ,OAAO,CAAC,MAAM;AAC1B,YAAI,EAAE,SAAS,EAAE,YAAY,EAAE,MAAO,QAAO;AAC7C,YAAI,EAAE,SAAS,EAAE,YAAY,EAAE,MAAO,QAAO;AAC7C,YAAI,EAAE,YAAY,EAAE,aAAa,EAAE,SAAU,QAAO;AACpD,YAAI,EAAE,SAAS,EAAE,UAAU,EAAE,MAAO,QAAO;AAC3C,YAAI,EAAE,cAAc,UAAa,EAAE,aAAa,EAAE,UAAW,QAAO;AACpE,YAAI,eAAe,CAAC,EAAE,MAAM,YAAY,EAAE,SAAS,WAAW,EAAG,QAAO;AACxE,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AACA,UAAM,MAA4B,IAAI,IAAI,CAAC,OAAO;AAAA,MAChD,IAAI,EAAE;AAAA,MACN,OAAO,EAAE;AAAA,MACT,WAAW,EAAE;AAAA,MACb,UAAU,EAAE;AAAA,MACZ,OAAO,EAAE;AAAA,MACT,YAAY,EAAE;AAAA,IAChB,EAAE;AACF,WAAO,EAAE,QAAQ,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI;AAAA,EAC3C;AAAA,EAEA,OAAO,OAAO,WAAgD;AAC5D,UAAM,OAAO,MAAM,KAAK,sBAAsB,SAAS;AACvD,eAAW,KAAK,KAAK,OAAQ,OAAM;AAAA,EACrC;AAAA,EAEA,MAAM,OAAO,GAAuB,WAAgC,cAA0D;AAC5H,UAAM,QAAQ,EAAE,SAAS;AACzB,UAAM,UAAU,aAAa,CAAC;AAC9B,UAAM,eAAe,EAAE,QAAQ,IAAI,IAAI,EAAE,KAAK,IAAI;AAIlD,QAAI;AACJ,QAAI,WAAW;AACb,YAAM,CAAC,SAAS;AAAA,IAClB,OAAO;AAGL,YAAM,kBAAkB,KAAK;AAW7B,UAAI;AACJ,UAAI,OAAO,gBAAgB,iBAAiB,YAAY;AACtD,mBAAW,MAAM,gBAAgB,aAAa;AAAA,UAC5C,OAAO,cAAc;AAAA,UACrB,OAAO,cAAc;AAAA,UACrB,UAAU,cAAc;AAAA,UACxB,OAAO,cAAc;AAAA,UACrB,WAAW,cAAc;AAAA,UACzB,eAAe,cAAc;AAAA,UAC7B,OAAO;AAAA,QACT,CAAC;AAAA,MACH,OAAO;AACL,mBAAW,MAAM,KAAK,MAAM,KAAK,GAAI;AACrC,cAAM,cAAc,cAAc,eAAe,YAAY;AAC7D,mBAAW,SAAS,OAAO,CAAC,MAAM;AAChC,cAAI,cAAc,SAAS,EAAE,YAAY,aAAa,MAAO,QAAO;AACpE,cAAI,cAAc,SAAS,EAAE,YAAY,aAAa,MAAO,QAAO;AACpE,cAAI,cAAc,YAAY,EAAE,aAAa,aAAa,SAAU,QAAO;AAC3E,cAAI,cAAc,SAAS,EAAE,UAAU,aAAa,MAAO,QAAO;AAClE,cAAI,cAAc,cAAc,UAAa,EAAE,aAAa,aAAa,UAAW,QAAO;AAC3F,cAAI,eAAe,CAAC,EAAE,MAAM,YAAY,EAAE,SAAS,WAAW,EAAG,QAAO;AACxE,iBAAO;AAAA,QACT,CAAC;AAAA,MACH;AACA,YAAM,SAAS,IAAI,CAAC,MAAM,EAAE,EAAE;AAAA,IAChC;AAEA,UAAM,OAA2B,CAAC;AAOlC,UAAM,YAAY,KAAK,MAAM,cAAc,KAAK,KAAK,KAAK;AAC1D,QAAI,WAAW;AACb,iBAAW,MAAM,KAAK;AACpB,cAAM,UAAU,MAAM;AAAA,UACpB;AAAA,UACA,CAAC,OAAO;AACN,gBAAI,gBAAgB,CAAC,aAAa,IAAI,GAAG,IAAI,EAAG,QAAO;AACvD,kBAAM,OAAO,UAAU,EAAE;AACzB,gBAAI,SAAS,KAAM,QAAO;AAC1B,mBAAO,QAAQ,IAAI,MAAM;AAAA,UAC3B;AAAA,UACA,EAAE,OAAO,QAAQ,KAAK,OAAO;AAAA,QAC/B;AACA,mBAAW,KAAK,SAAS;AACvB,gBAAM,OAAO,cAAc,UAAU,EAAE,KAAK,CAAC;AAC7C,gBAAM,MAAM,cAAc,QAAQ,IAAI,CAAC;AACvC,eAAK,KAAK;AAAA,YACR,WAAW;AAAA,YACX,YAAY,EAAE;AAAA,YACd,IAAI,EAAE;AAAA,YACN,MAAM,EAAE,MAAM;AAAA,YACd,SAAS,UAAU,MAAM,IAAI,OAAO,IAAI,GAAG;AAAA,UAC7C,CAAC;AACD,cAAI,KAAK,UAAU,MAAO,QAAO;AAAA,QACnC;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAKA,eAAW,MAAM,KAAK;AACpB,UAAI;AACJ,UAAI;AACF,eAAO,MAAM,KAAK,sBAAsB,EAAE;AAAA,MAC5C,QAAQ;AACN;AAAA,MACF;AACA,eAAS,IAAI,GAAG,IAAI,KAAK,OAAO,QAAQ,KAAK;AAC3C,cAAM,KAAK,cAAc,KAAK,OAAO,CAAC,CAAC;AACvC,YAAI,gBAAgB,CAAC,aAAa,IAAI,GAAG,IAAI,EAAG;AAChD,cAAM,OAAO,UAAU,EAAE;AACzB,YAAI,SAAS,KAAM;AACnB,cAAM,MAAM,QAAQ,IAAI;AACxB,YAAI,CAAC,IAAK;AACV,aAAK,KAAK;AAAA,UACR,WAAW;AAAA,UACX,YAAY;AAAA,UACZ,IAAI,GAAG;AAAA,UACP,MAAM,GAAG;AAAA,UACT,SAAS,UAAU,MAAM,IAAI,OAAO,IAAI,GAAG;AAAA,QAC7C,CAAC;AACD,YAAI,KAAK,UAAU,MAAO,QAAO;AAAA,MACnC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,OAAO,WAAmB,MAA6C;AAC3E,UAAM,OAAO,MAAM,KAAK,sBAAsB,SAAS;AACvD,UAAM,eAAe,KAAK,gBAAgB;AAC1C,UAAM,qBAAqB,KAAK,sBAAsB;AAEtD,UAAM,WAAW,KAAK,OAAO,OAAO,CAAC,MAAM;AACzC,UACE,CAAC,iBACA,EAAE,SAAS,cACV,EAAE,SAAS,iBACX,EAAE,SAAS,qBACX,EAAE,SAAS,kBACb;AACA,eAAO;AAAA,MACT;AACA,UACE,CAAC,uBACA,EAAE,SAAS,WAAW,EAAE,SAAS,gBAAgB,EAAE,SAAS,sBAC7D;AACA,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT,CAAC;AAED,QAAI,KAAK,WAAW,QAAQ;AAC1B,aAAO,KAAK,UAAU,EAAE,UAAU,KAAK,UAAU,QAAQ,SAAS,GAAG,MAAM,CAAC;AAAA,IAC9E;AACA,QAAI,KAAK,WAAW,QAAQ;AAC1B,aAAO,gBAAgB,KAAK,UAAU,QAAQ;AAAA,IAChD;AACA,WAAO,eAAe,KAAK,UAAU,QAAQ;AAAA,EAC/C;AAAA,EAEA,MAAM,SAAS,WAA6C;AAC1D,UAAM,OAAO,MAAM,KAAK,sBAAsB,SAAS;AACvD,WAAO,KAAK;AAAA,EACd;AACF;AAEA,SAAS,aACP,GACyD;AACzD,QAAM,KAAK,EAAE,mBAAmB;AAChC,MAAI,EAAE,OAAO;AACX,UAAM,QAAQ,KAAK,MAAM;AACzB,UAAM,WAAW,iBAAiB,EAAE,OAAO,KAAK;AAChD,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,yBAAyB,EAAE,KAAK,MAAM,SAAS,MAAM,EAAE;AAAA,IACzE;AACA,UAAM,KAAK,SAAS;AACpB,WAAO,CAAC,SAAS;AACf,YAAM,IAAI,GAAG,KAAK,IAAI;AACtB,aAAO,IAAI,EAAE,OAAO,EAAE,OAAO,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,IAAI;AAAA,IAC9D;AAAA,EACF;AACA,QAAM,SAAS,KAAK,EAAE,MAAM,YAAY,IAAI,EAAE;AAC9C,SAAO,CAAC,SAAS;AACf,UAAM,MAAM,KAAK,KAAK,YAAY,IAAI;AACtC,UAAM,MAAM,IAAI,QAAQ,MAAM;AAC9B,WAAO,QAAQ,KAAK,OAAO,EAAE,OAAO,KAAK,KAAK,MAAM,OAAO,OAAO;AAAA,EACpE;AACF;AAEA,SAAS,UAAU,GAAgC;AACjD,UAAQ,EAAE,MAAM;AAAA,IACd,KAAK;AACH,aAAO,gBAAgB,EAAE,OAAO;AAAA,IAClC,KAAK;AACH,aAAO,gBAAgB,EAAE,OAAO;AAAA,IAClC,KAAK;AACH,aAAO,GAAG,EAAE,IAAI,IAAI,KAAK,UAAU,EAAE,KAAK,CAAC;AAAA,IAC7C,KAAK;AACH,aAAO,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU,KAAK,UAAU,EAAE,OAAO;AAAA,IAC7E,KAAK;AACH,aAAO,GAAG,EAAE,KAAK,KAAK,EAAE,OAAO;AAAA,IACjC,KAAK;AAAA,IACL,KAAK;AACH,aAAO,GAAG,EAAE,KAAK,IAAI,EAAE,QAAQ;AAAA,IACjC,KAAK;AAAA,IACL,KAAK;AACH,aAAO,EAAE;AAAA,IACX,KAAK;AACH,aAAO,GAAG,EAAE,KAAK,KAAK,EAAE,KAAK;AAAA,IAC/B,KAAK;AAAA,IACL,KAAK;AACH,aAAO,EAAE;AAAA,IACX;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,gBAAgB,SAA0C;AACjE,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,SAAO,QACJ,IAAI,CAAC,MAAM;AACV,YAAQ,EAAE,MAAM;AAAA,MACd,KAAK;AACH,eAAO,EAAE;AAAA,MACX,KAAK;AACH,eAAO,aAAa,EAAE,IAAI,IAAI,KAAK,UAAU,EAAE,KAAK,CAAC;AAAA,MACvD,KAAK;AACH,eAAO,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU,KAAK,UAAU,EAAE,OAAO;AAAA,MAC7E;AACE,eAAO;AAAA,IACX;AAAA,EACF,CAAC,EACA,KAAK,IAAI;AACd;AAEA,IAAM,iBAAiB;AAEvB,SAAS,UAAU,MAAc,OAAe,KAAqB;AACnE,QAAM,OAAO,KAAK,IAAI,GAAG,QAAQ,cAAc;AAC/C,QAAM,KAAK,KAAK,IAAI,KAAK,QAAQ,MAAM,cAAc;AACrD,QAAM,SAAS,OAAO,IAAI,WAAM;AAChC,QAAM,SAAS,KAAK,KAAK,SAAS,WAAM;AACxC,SAAO,SAAS,KAAK,MAAM,MAAM,EAAE,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK,IAAI;AACrE;AAEA,SAAS,eAAe,MAAuB,QAAgC;AAC7E,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,aAAa,KAAK,EAAE,EAAE;AACjC,QAAM,KAAK,EAAE;AACb,MAAI,KAAK,SAAS,KAAK,UAAU;AAC/B,UAAM,KAAK,gBAAgB,KAAK,YAAY,GAAG,IAAI,KAAK,SAAS,GAAG,EAAE;AAAA,EACxE;AACA,QAAM,KAAK,kBAAkB,KAAK,SAAS,EAAE;AAC7C,MAAI,KAAK,QAAS,OAAM,KAAK,gBAAgB,KAAK,OAAO,EAAE;AAC3D,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,KAAK;AAChB,QAAM,KAAK,EAAE;AACb,aAAW,KAAK,QAAQ;AACtB,YAAQ,EAAE,MAAM;AAAA,MACd,KAAK,cAAc;AACjB,cAAM,KAAK,kBAAa,EAAE,EAAE,EAAE;AAC9B,cAAM,KAAK,EAAE;AACb,cAAM,KAAK,gBAAgB,EAAE,OAAO,CAAC;AACrC,cAAM,KAAK,EAAE;AACb;AAAA,MACF;AAAA,MACA,KAAK,gBAAgB;AACnB,cAAM,KAAK,uBAAkB,EAAE,EAAE,EAAE;AACnC,cAAM,KAAK,EAAE;AACb,cAAM,KAAK,gBAAgB,EAAE,OAAO,CAAC;AACrC,YAAI,EAAE,cAAc,EAAE,eAAe,YAAY;AAC/C,gBAAM,KAAK,EAAE;AACb,gBAAM,KAAK,UAAU,EAAE,UAAU,GAAG;AAAA,QACtC;AACA,cAAM,KAAK,EAAE;AACb;AAAA,MACF;AAAA,MACA,KAAK,YAAY;AACf,cAAM,KAAK,oBAAoB,EAAE,IAAI,IAAI;AACzC,cAAM,KAAK,EAAE;AACb,cAAM,KAAK,SAAS;AACpB,cAAM,KAAK,KAAK,UAAU,EAAE,OAAO,MAAM,CAAC,CAAC;AAC3C,cAAM,KAAK,KAAK;AAChB,cAAM,KAAK,EAAE;AACb;AAAA,MACF;AAAA,MACA,KAAK,eAAe;AAClB,cAAM,OAAO,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU,KAAK,UAAU,EAAE,SAAS,MAAM,CAAC;AAC1F,cAAM,KAAK,kBAAkB,EAAE,UAAU,aAAa,EAAE,EAAE;AAC1D,cAAM,KAAK,EAAE;AACb,cAAM,KAAK,KAAK;AAChB,cAAM,KAAK,IAAI;AACf,cAAM,KAAK,KAAK;AAChB,cAAM,KAAK,EAAE;AACb;AAAA,MACF;AAAA,MACA,KAAK,SAAS;AACZ,cAAM,KAAK,gBAAgB,EAAE,KAAK,MAAM,EAAE,OAAO,EAAE;AACnD,cAAM,KAAK,EAAE;AACb;AAAA,MACF;AAAA,MACA,KAAK,cAAc;AACjB,cAAM,KAAK,qBAAqB,EAAE,MAAM,WAAM,EAAE,KAAK,SAAS;AAC9D,cAAM,KAAK,EAAE;AACb;AAAA,MACF;AAAA,MACA;AACE;AAAA,IACJ;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,SAAS,gBAAgB,MAAuB,QAAgC;AAC9E,QAAM,QAAkB,CAAC;AACzB,QAAM;AAAA,IACJ,WAAW,KAAK,EAAE,WAAM,KAAK,YAAY,GAAG,IAAI,KAAK,SAAS,GAAG,mBAAc,KAAK,SAAS;AAAA,EAC/F;AACA,QAAM,KAAK,GAAG,OAAO,IAAI,GAAG,CAAC;AAC7B,aAAW,KAAK,QAAQ;AACtB,YAAQ,EAAE,MAAM;AAAA,MACd,KAAK;AACH,cAAM,KAAK,IAAI,EAAE,EAAE,QAAQ;AAC3B,cAAM,KAAK,gBAAgB,EAAE,OAAO,CAAC;AACrC,cAAM,KAAK,EAAE;AACb;AAAA,MACF,KAAK;AACH,cAAM,KAAK,IAAI,EAAE,EAAE,aAAa;AAChC,cAAM,KAAK,gBAAgB,EAAE,OAAO,CAAC;AACrC,cAAM,KAAK,EAAE;AACb;AAAA,MACF,KAAK;AACH,cAAM,KAAK,IAAI,EAAE,EAAE,cAAc,EAAE,IAAI,IAAI,KAAK,UAAU,EAAE,KAAK,CAAC,EAAE;AACpE;AAAA,MACF,KAAK;AACH,cAAM;AAAA,UACJ,IAAI,EAAE,EAAE,gBAAgB,EAAE,UAAU,aAAa,EAAE,IACjD,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU,KAAK,UAAU,EAAE,OAAO,CACtE;AAAA,QACF;AACA;AAAA,MACF,KAAK;AACH,cAAM,KAAK,IAAI,EAAE,EAAE,YAAY,EAAE,KAAK,MAAM,EAAE,OAAO,EAAE;AACvD;AAAA,MACF;AACE;AAAA,IACJ;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;;;ACxdA,SAAS,cAAAC,mBAAkB;AAC3B,YAAYC,SAAQ;AAgEpB,IAAM,eAAe;AAErB,IAAM,kBAAkB;AAExB,IAAM,kBAAkB;AAcjB,IAAM,mBAAN,MAAuB;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA,cAAc,oBAAI,IAA2B;AAAA,EAE9D,YAAY,MAA+B;AACzC,SAAK,MAAM,KAAK;AAChB,SAAK,SAAS,KAAK;AACnB,SAAK,UAAU,KAAK;AACpB,SAAK,iBAAiB,KAAK,kBAAkB;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,KAAK,WAA0C;AACnD,UAAM,KAAK,KAAK,IAAI;AACpB,UAAM,KAAK,KAAK,SAAS,SAAS;AAClC,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,SAAS,SAAS;AAC1C,YAAM,aAAa,KAAK,IAAI,IAAI;AAChC,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC;AAAA,QACA,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT;AAAA,QACA,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,aAAO,OAAO,KAAK,cAAc,CAAC;AAAA,IACpC,SAAS,KAAK;AACZ,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC;AAAA,QACA,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,OAAO,eAAe,GAAG;AAAA,QACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,SAAS,WAA0C;AACvD,UAAM,MAAM,MAAM,KAAK,KAAK,SAAS;AACrC,WAAO,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,QAAQ;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,IAAI,OAKc;AACtB,UAAM,OAAO,MAAM,KAAK,KAAK;AAC7B,QAAI,KAAK,WAAW,GAAG;AACrB,YAAM,IAAI,gBAAgB;AAAA,QACxB,SAAS;AAAA,QACT,MAAM,YAAY;AAAA,QAClB,WAAW;AAAA,QACX,SAAS,EAAE,OAAO,QAAQ,WAAW,MAAM,UAAU;AAAA,MACvD,CAAC;AAAA,IACH;AACA,QAAI,KAAK,SAAS,iBAAiB;AACjC,YAAM,IAAI,gBAAgB;AAAA,QACxB,SAAS,2BAA2B,eAAe,eAAe,KAAK,MAAM;AAAA,QAC7E,MAAM,YAAY;AAAA,QAClB,WAAW;AAAA,QACX,SAAS,EAAE,OAAO,QAAQ,WAAW,iBAAiB,cAAc,KAAK,OAAO;AAAA,MAClF,CAAC;AAAA,IACH;AACA,QAAI,CAAC,OAAO,UAAU,MAAM,YAAY,KAAK,MAAM,eAAe,GAAG;AACnE,YAAM,IAAI,gBAAgB;AAAA,QACxB,SAAS;AAAA,QACT,MAAM,YAAY;AAAA,QAClB,WAAW;AAAA,QACX,SAAS,EAAE,OAAO,gBAAgB,OAAO,MAAM,aAAa;AAAA,MAC9D,CAAC;AAAA,IACH;AACA,UAAM,aAAyB;AAAA,MAC7B,IAAIC,YAAW;AAAA,MACf,WAAW,MAAM;AAAA,MACjB,cAAc,MAAM;AAAA,MACpB,UAAU,MAAM;AAAA,MAChB,YAAY;AAAA,MACZ;AAAA,MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,UAAU;AAAA,IACZ;AACA,UAAM,KAAK,KAAK,SAAS,MAAM,SAAS;AACxC,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI;AACF,YAAM,KAAK,QAAQ,MAAM,WAAW,YAAY;AAC9C,cAAM,aAAa,IAAI,YAAY;AACjC,gBAAM,MAAM,MAAM,KAAK,KAAK,MAAM,SAAS;AAC3C,cAAI,KAAK,UAAU;AAEnB,cAAI,IAAI,SAAS,KAAK,gBAAgB;AACpC,kBAAM,SAAS,IACZ,IAAI,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,EAAE,EACxB,KAAK,CAAC,GAAG,MAAM;AAGd,kBAAI,EAAE,EAAE,aAAa,EAAE,EAAE,SAAU,QAAO,EAAE,EAAE,WAAW,IAAI;AAC7D,qBAAO,EAAE,EAAE,UAAU,cAAc,EAAE,EAAE,SAAS;AAAA,YAClD,CAAC;AACH,kBAAM,aAAa,IAAI,SAAS,KAAK;AACrC,kBAAM,UAAU,IAAI,IAAI,OAAO,MAAM,GAAG,UAAU,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,CAAC;AACtE,kBAAM,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;AACjD,kBAAM,KAAK,UAAU,MAAM,WAAW,EAAE,SAAS,cAAc,aAAa,KAAK,CAAC;AAClF,kBAAM,aAAa,KAAK,IAAI,IAAI;AAChC,iBAAK,QAAQ,KAAK,iBAAiB;AAAA,cACjC,WAAW,MAAM;AAAA,cACjB,OAAO;AAAA,cACP,UAAU;AAAA,cACV,WAAW;AAAA,cACX,SAAS;AAAA,cACT;AAAA,cACA,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,YAChE,CAAC;AAAA,UACH,OAAO;AACL,kBAAM,KAAK,UAAU,MAAM,WAAW,EAAE,SAAS,cAAc,aAAa,IAAI,CAAC;AACjF,kBAAM,aAAa,KAAK,IAAI,IAAI;AAChC,iBAAK,QAAQ,KAAK,iBAAiB;AAAA,cACjC,WAAW,MAAM;AAAA,cACjB,OAAO;AAAA,cACP,UAAU;AAAA,cACV,WAAW;AAAA,cACX,SAAS;AAAA,cACT;AAAA,cACA,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,YAChE,CAAC;AAAA,UACH;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AACD,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,WAAK,QAAQ,KAAK,iBAAiB;AAAA,QACjC,WAAW,MAAM;AAAA,QACjB,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT,OAAO,eAAe,GAAG;AAAA,QACzB,aAAa;AAAA,QACb,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAAQ,OAIiB;AAC7B,QAAI,UAA6B;AACjC,UAAM,KAAK,KAAK,SAAS,MAAM,SAAS;AACxC,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI;AACF,YAAM,KAAK,QAAQ,MAAM,WAAW,YAAY;AAC9C,cAAM,aAAa,IAAI,YAAY;AACjC,gBAAM,MAAM,MAAM,KAAK,KAAK,MAAM,SAAS;AAC3C,gBAAM,MAAM,IAAI,UAAU,CAAC,MAAM,EAAE,OAAO,MAAM,YAAY;AAC5D,cAAI,QAAQ,IAAI;AACd,sBAAU;AACV;AAAA,UACF;AACA,gBAAM,OAAmB;AAAA,YACvB,GAAG,cAAc,IAAI,GAAG,CAAC;AAAA,YACzB,UAAU;AAAA,YACV,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,YACnC,YAAY,MAAM;AAAA,UACpB;AACA,cAAI,GAAG,IAAI;AACX,gBAAM,KAAK,UAAU,MAAM,WAAW,EAAE,SAAS,cAAc,aAAa,IAAI,CAAC;AACjF,oBAAU;AACV,gBAAM,aAAa,KAAK,IAAI,IAAI;AAChC,eAAK,QAAQ,KAAK,iBAAiB;AAAA,YACjC,WAAW,MAAM;AAAA,YACjB,OAAO;AAAA,YACP,UAAU;AAAA,YACV,WAAW;AAAA,YACX,SAAS;AAAA,YACT;AAAA,YACA,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,UAChE,CAAC;AAAA,QACH,CAAC;AAAA,MACH,CAAC;AACD,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,WAAK,QAAQ,KAAK,iBAAiB;AAAA,QACjC,WAAW,MAAM;AAAA,QACjB,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT,OAAO,eAAe,GAAG;AAAA,QACzB,aAAa;AAAA,QACb,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA,EAIQ,SAAS,WAA2B;AAI1C,WAAO,kBAAkB,KAAK,KAAK,WAAW,mBAAmB;AAAA,EACnE;AAAA,EAEA,MAAc,SAAS,WAAoD;AACzE,UAAM,KAAK,KAAK,SAAS,SAAS;AAClC,QAAI;AACJ,QAAI;AACF,YAAM,MAAS,aAAS,IAAI,MAAM;AAAA,IACpC,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,SAAU,QAAO;AAG7D,YAAM;AAAA,IACR;AACA,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,UAAI,OAAO,YAAY,cAAc;AACnC,eAAO,EAAE,SAAS,cAAc,aAAa,CAAC,EAAE;AAAA,MAClD;AACA,aAAO;AAAA,IACT,QAAQ;AAEN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAc,UAAU,WAAmB,MAAsC;AAC/E,UAAM,KAAK,KAAK,SAAS,SAAS;AAClC,UAAM,YAAY,IAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,QAAQ,WAAmB,IAAwC;AACzE,UAAM,OAAO,KAAK,YAAY,IAAI,SAAS,KAAK,QAAQ,QAAQ;AAChE,UAAM,OAAO,KAAK,KAAK,IAAI,EAAE;AAG7B,SAAK,YAAY;AAAA,MACf;AAAA,MACA,KAAK,MAAM,MAAM,MAAS;AAAA,IAC5B;AACA,WAAO;AAAA,EACT;AACF;;;ACxXA,YAAYC,SAAQ;AACpB,YAAYC,YAAU;;;ACDtB,SAAS,cAAAC,mBAAkB;AA8BpB,SAAS,gBAAgB,OAAwB;AACtD,SAAO,KAAK,UAAU,SAAS,KAAK,CAAC;AACvC;AAEA,SAAS,SAAS,OAAyB;AACzC,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,QAAQ;AACnD,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,UAAM,MAAM;AACZ,UAAM,SAAkC,CAAC;AACzC,eAAW,OAAO,OAAO,KAAK,GAAG,EAAE,KAAK,GAAG;AACzC,aAAO,GAAG,IAAI,SAAS,IAAI,GAAG,CAAC;AAAA,IACjC;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,YAAY,SAA0B;AAEpD,QAAM,UAAU;AAAA,IACd,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ;AAAA,IAChB,UAAU,QAAQ;AAAA,IAClB,OAAO,QAAQ;AAAA,IACf,WAAW,QAAQ;AAAA,IACnB,aAAa,QAAQ;AAAA,IACrB,MAAM,QAAQ;AAAA,IACd,eAAe,QAAQ;AAAA,IACvB,YAAY,QAAQ;AAAA,EACtB;AACA,QAAM,OAAO,gBAAgB,OAAO;AACpC,QAAM,SAASA,YAAW,QAAQ,EAAE,OAAO,MAAM,MAAM,EAAE,OAAO,KAAK;AACrE,SAAO,UAAU,MAAM;AACzB;;;ADhDA,SAASC,oBAAmB,KAAsB;AAChD,MAAI,eAAe,OAAO;AACxB,UAAM,OAAQ,IAA8B;AAC5C,WAAO,OAAO,GAAG,IAAI,KAAK,IAAI,OAAO,KAAK,IAAI;AAAA,EAChD;AAEA,SAAO,OAAO,GAAG;AACnB;AAkCA,IAAMC,gBAAe;AAGrB,IAAM,sBAAsB;AAwBrB,IAAM,iBAAN,MAAqB;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc,oBAAI,IAA2B;AAAA;AAAA,EAE7C,QAAQ,oBAAI,IAA8C;AAAA;AAAA,EAE1D,YAAY,oBAAI,IAAoB;AAAA,EACpC;AAAA,EAEjB,YAAY,MAA6B;AACvC,SAAK,MAAM,KAAK;AAChB,SAAK,SAAS,KAAK;AACnB,SAAK,UAAU,KAAK;AACpB,SAAK,aAAa,KAAK,cAAc;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,OAAO,OAIO;AAClB,UAAM,OAAO,YAAY,MAAM,OAAO;AACtC,UAAM,KAAK,KAAK,SAAS,MAAM,SAAS;AACxC,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI;AACF,YAAM,KAAK,QAAQ,MAAM,WAAW,YAAY;AAC9C,cAAM,aAAa,IAAI,YAAY;AAKjC,gBAAM,QAAQ,MAAM,KAAK,YAAY,MAAM,SAAS;AACpD,cAAI,MAAM,IAAI,IAAI,EAAG;AAErB,gBAAM,QAAqB;AAAA,YACzB;AAAA,YACA,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,YAC3B,SAAS,MAAM;AAAA,YACf,UAAU,MAAM;AAAA,UAClB;AAEA,gBAAM,eAAe,KAAK,UAAU,IAAI,MAAM,SAAS,KAAK;AAC5D,gBAAM,YAAY,eAAe,IAAI,KAAK;AAE1C,cAAI,CAAC,WAAW;AAOd,kBAAM,OAAO,KAAK,UAAU,KAAK,IAAI;AACrC,gBAAIC,UAAS;AACb,gBAAI;AACF,oBAAMC,SAAO,MAAS,SAAK,EAAE;AAC7B,cAAAD,UAASC,OAAK;AAAA,YAChB,SAAS,KAAK;AACZ,kBAAK,IAA8B,SAAS,SAAU,OAAM;AAAA,YAC9D;AACA,kBAAS,eAAW,IAAI,MAAM,MAAM;AACpC,kBAAM,IAAI,MAAM,EAAE,OAAO,QAAAD,SAAQ,QAAQ,OAAO,WAAW,MAAM,MAAM,EAAE,CAAC;AAC1E,iBAAK,UAAU,IAAI,MAAM,WAAW,eAAe,CAAC;AACpD,iBAAK,QAAQ,KAAK,iBAAiB;AAAA,cACjC,WAAW,MAAM;AAAA,cACjB,OAAO;AAAA,cACP,UAAU;AAAA,cACV,WAAW;AAAA,cACX,SAAS;AAAA,cACT,YAAY,KAAK,IAAI,IAAI;AAAA,cACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,YAChE,CAAC;AACD;AAAA,UACF;AAKA,gBAAM,MAAM,MAAM,KAAK,QAAQ,MAAM,SAAS;AAC9C,cAAI,KAAK,KAAK;AACd,gBAAM,OAAO,IAAI,MAAM,CAAC,KAAK,UAAU;AACvC,gBAAM,YAAY,oBAAI,IAAiC;AACvD,cAAI,SAAS;AACb,qBAAW,KAAK,MAAM;AACpB,kBAAM,OAAO,KAAK,UAAU,CAAC,IAAI;AACjC,sBAAU,IAAI,EAAE,MAAM,EAAE,OAAO,GAAG,QAAQ,QAAQ,OAAO,WAAW,MAAM,MAAM,EAAE,CAAC;AACnF,sBAAU,OAAO,WAAW,MAAM,MAAM;AAAA,UAC1C;AACA,eAAK,MAAM,IAAI,MAAM,WAAW,SAAS;AACzC,eAAK,UAAU,IAAI,MAAM,WAAW,KAAK,MAAM;AAC/C,gBAAM,KAAK,SAAS,MAAM,WAAW,MAAM,SAAS;AAAA,QACtD,CAAC;AAAA,MACH,CAAC;AACD,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,WAAK,QAAQ,KAAK,iBAAiB;AAAA,QACjC,WAAW,MAAM;AAAA,QACjB,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT,OAAOF,oBAAmB,GAAG;AAAA,QAC7B,aAAa;AAAA,QACb,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,OAAO,WAAmB,MAA2C;AACzE,UAAM,KAAK,KAAK,SAAS,SAAS;AAClC,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,YAAY,SAAS;AAC9C,YAAM,WAAW,MAAM,IAAI,IAAI;AAC/B,YAAM,QAAQ,WAAW,MAAM,KAAK,aAAa,WAAW,MAAM,QAAQ,IAAI;AAC9E,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC;AAAA,QACA,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC;AAAA,QACA,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,OAAOA,oBAAmB,GAAG;AAAA,QAC7B,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,KAAK,WAA2C;AACpD,UAAM,KAAK,KAAK,SAAS,SAAS;AAClC,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,YAAY,SAAS;AAC9C,YAAM,UAAyB,CAAC;AAChC,iBAAW,CAAC,MAAM,QAAQ,KAAK,OAAO;AACpC,gBAAQ,KAAK,MAAM,KAAK,aAAa,WAAW,MAAM,QAAQ,CAAC;AAAA,MACjE;AACA,YAAM,aAAa,KAAK,IAAI,IAAI;AAChC,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC;AAAA,QACA,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT;AAAA,QACA,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,aAAa,KAAK,IAAI,IAAI;AAChC,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC;AAAA,QACA,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT;AAAA,QACA,OAAOA,oBAAmB,GAAG;AAAA,QAC7B,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OAAgF;AACpF,UAAM,MAAsE,CAAC;AAI7E,UAAM,OAAO,OAAO,KAAa,QAAgB,UAAiC;AAChF,UAAI;AACJ,UAAI;AACF,kBAAU,MAAS,YAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,MACzD,SAAS,KAAK;AACZ,YAAI,UAAU,KAAM,IAA8B,SAAS,UAAU;AAInE,kBAAQ,KAAK,KAAK,UAAU;AAAA,YAC1B,OAAO;AAAA,YACP,OAAO;AAAA,YACP;AAAA,YACA,SAAS,eAAe,GAAG;AAAA,YAC3B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC,CAAC,CAAC;AAAA,QACJ;AACA;AAAA,MACF;AACA,iBAAW,SAAS,SAAS;AAC3B,YAAI,MAAM,KAAK,WAAW,GAAG,EAAG;AAChC,YAAI,MAAM,YAAY,GAAG;AACvB,cAAI,UAAU,EAAG,OAAM,KAAU,YAAK,KAAK,MAAM,IAAI,GAAG,MAAM,MAAM,QAAQ,CAAC;AAC7E;AAAA,QACF;AACA,YAAI,CAAC,MAAM,OAAO,KAAK,CAAC,MAAM,KAAK,SAAS,eAAe,EAAG;AAC9D,cAAM,OAAO,MAAM,KAAK,MAAM,GAAG,CAAC,gBAAgB,MAAM;AACxD,cAAM,YAAY,SAAS,GAAG,MAAM,IAAI,IAAI,KAAK;AACjD,cAAM,KAAU,YAAK,KAAK,MAAM,IAAI;AACpC,YAAI,KAAK;AAAA,UACP;AAAA,UACA,YAAY,MAAM,KAAK,aAAa,EAAE;AAAA,UACtC,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM,KAAK,KAAK,KAAK,IAAI,CAAC;AAC1B,WAAO,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AAAA,EAClE;AAAA;AAAA,EAIQ,SAAS,WAA2B;AAI1C,WAAO,kBAAkB,KAAK,KAAK,WAAW,eAAe;AAAA,EAC/D;AAAA,EAEA,MAAc,aAAa,UAAmC;AAI5D,UAAM,SAAS,MAAS,SAAK,UAAU,GAAG;AAC1C,UAAM,SAAS,OAAO,YAAY,KAAK,IAAI;AAC3C,QAAI,QAAQ;AACZ,QAAII,oBAAmB;AACvB,QAAI;AACF,aAAO,MAAM;AACX,cAAM,EAAE,UAAU,IAAI,MAAM,OAAO,KAAK,QAAQ,GAAG,OAAO,QAAQ,IAAI;AACtE,YAAI,cAAc,EAAG;AACrB,iBAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,gBAAM,KAAK,OAAO,CAAC;AACnB,cAAI,OAAO,IAAI;AACb,gBAAIA,kBAAkB;AACtB,YAAAA,oBAAmB;AACnB;AAAA,UACF;AACA,cAAI,OAAO,MAAM,OAAO,MAAM,OAAO,GAAG;AACtC,YAAAA,oBAAmB;AAAA,UACrB;AAAA,QACF;AAAA,MACF;AAAA,IACF,UAAE;AACA,YAAM,OAAO,MAAM;AAAA,IACrB;AACA,QAAIA,kBAAkB;AACtB,WAAO;AAAA,EACT;AAAA,EAEQ,gBAAgB,MAAkC;AACxD,QAAI;AACF,YAAM,SAAS,UAEb,IAAI;AACN,UAAI,CAAC,OAAO,MAAM,CAAC,OAAO,MAAO,QAAO;AACxC,UAAI,WAAW,OAAO,SAAS,OAAO,MAAM,OAAO;AACjD,eAAO,OAAO,MAAM;AAAA,MACtB;AACA,aAAO,OAAO;AAAA,IAChB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAc,QAAQ,WAA2C;AAC/D,UAAM,KAAK,KAAK,SAAS,SAAS;AAClC,QAAI;AACF,YAAM,MAAM,MAAS,aAAS,IAAI,MAAM;AACxC,YAAM,MAAqB,CAAC;AAC5B,iBAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,YAAI,CAAC,KAAK,KAAK,EAAG;AAClB,cAAM,SAAS,KAAK,gBAAgB,IAAI;AACxC,YAAI,OAAQ,KAAI,KAAK,MAAM;AAAA,MAC7B;AACA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAc,SACZ,WACA,SACA,YAAkC,UACnB;AACf,UAAM,KAAK,KAAK,SAAS,SAAS;AAClC,UAAM,KAAK,KAAK,IAAI;AACpB,UAAM,OAAO,QAAQ,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI,KAAK,QAAQ,SAAS,OAAO;AACzF,UAAM,YAAY,IAAI,IAAI;AAC1B,UAAM,aAAa,KAAK,IAAI,IAAI;AAChC,SAAK,QAAQ,KAAK,iBAAiB;AAAA,MACjC;AAAA,MACA,OAAO;AAAA,MACP,UAAU;AAAA,MACV;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,IAChE,CAAC;AAID,SAAKH;AAAA,EACP;AAAA,EAEA,MAAc,YAAY,WAA8D;AACtF,QAAI,QAAQ,KAAK,MAAM,IAAI,SAAS;AACpC,QAAI,MAAO,QAAO;AAElB,UAAM,KAAK,KAAK,SAAS,SAAS;AAClC,YAAQ,oBAAI,IAAI;AAChB,QAAI;AACF,YAAM,SAAS,MAAS,SAAK,IAAI,GAAG;AACpC,YAAM,QAAQ,KAAK;AACnB,YAAM,SAAS,OAAO,MAAM,KAAK;AACjC,UAAI,WAAW;AACf,UAAI,iBAAiB;AACrB,UAAI,aAAa;AACjB,UAAI;AACF,eAAO,MAAM;AACX,gBAAM,EAAE,UAAU,IAAI,MAAM,OAAO,KAAK,QAAQ,GAAG,OAAO,UAAU;AACpE,cAAI,cAAc,EAAG;AACrB,gBAAM,QAAQ,OAAO,SAAS,GAAG,SAAS,EAAE,SAAS,MAAM;AAC3D,gBAAM,OAAO,WAAW;AACxB,cAAI,YAAY;AAChB,cAAI,aAAa;AACjB,iBAAO,MAAM;AACX,kBAAM,eAAe,KAAK,QAAQ,MAAM,UAAU;AAClD,gBAAI,iBAAiB,GAAI;AACzB,kBAAM,OAAO,KAAK,MAAM,YAAY,YAAY;AAChD,kBAAM,aAAa,OAAO,WAAW,KAAK,MAAM,YAAY,eAAe,CAAC,GAAG,MAAM;AACrF,gBAAI,KAAK,KAAK,GAAG;AACf,oBAAM,QAAQ,KAAK,gBAAgB,IAAI;AACvC,kBAAI,OAAO;AACT,sBAAM,IAAI,MAAM,MAAM,EAAE,QAAQ,WAAW,QAAQ,WAAW,CAAC;AAAA,cACjE;AAAA,YACF;AACA,yBAAa;AACb,yBAAa,eAAe;AAAA,UAC9B;AACA,qBAAW,KAAK,MAAM,UAAU;AAChC,2BAAiB;AACjB,wBAAc;AAAA,QAChB;AACA,YAAI,SAAS,KAAK,GAAG;AACnB,gBAAM,QAAQ,KAAK,gBAAgB,QAAQ;AAC3C,cAAI,OAAO;AACT,kBAAM,IAAI,MAAM,MAAM,EAAE,QAAQ,gBAAgB,QAAQ,OAAO,WAAW,UAAU,MAAM,EAAE,CAAC;AAAA,UAC/F;AAAA,QACF;AAAA,MACF,UAAE;AACA,cAAM,OAAO,MAAM;AAAA,MACrB;AAAA,IACF,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,SAAU,OAAM;AAAA,IAC9D;AAEA,SAAK,MAAM,IAAI,WAAW,KAAK;AAC/B,SAAK,UAAU,IAAI,WAAW,MAAM,IAAI;AACxC,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,aACZ,WACA,MACA,UACsB;AACtB,QAAI,SAAS,MAAO,QAAO,SAAS;AAEpC,UAAM,KAAK,KAAK,SAAS,SAAS;AAClC,UAAM,SAAS,MAAS,SAAK,IAAI,GAAG;AACpC,QAAI;AACF,YAAM,SAAS,OAAO,MAAM,SAAS,MAAM;AAC3C,YAAM,EAAE,UAAU,IAAI,MAAM,OAAO,KAAK,QAAQ,GAAG,SAAS,QAAQ,SAAS,MAAM;AACnF,YAAM,OAAO,OAAO,SAAS,GAAG,SAAS,EAAE,SAAS,MAAM,EAAE,QAAQ;AACpE,YAAM,QAAQ,KAAK,gBAAgB,IAAI;AACvC,UAAI,CAAC,OAAO;AACV,cAAM,IAAI,MAAM,gBAAgB,IAAI,gBAAgB;AAAA,MACtD;AACA,eAAS,QAAQ;AACjB,aAAO;AAAA,IACT,UAAE;AACA,YAAM,OAAO,MAAM;AAAA,IACrB;AAAA,EACF;AAAA,EAEQ,QAAQ,WAAmB,IAAwC;AACzE,UAAM,OAAO,KAAK,YAAY,IAAI,SAAS,KAAK,QAAQ,QAAQ;AAChE,UAAM,OAAO,KAAK,KAAK,IAAI,EAAE;AAC7B,SAAK,YAAY;AAAA,MACf;AAAA,MACA,KAAK,MAAM,MAAM,MAAS;AAAA,IAC5B;AACA,WAAO;AAAA,EACT;AACF;;;AEjgBA,YAAYI,SAAQ;AACpB,YAAYC,YAAU;AAyDf,IAAM,kBAAN,MAAsB;AAAA,EAiK3B,YAA6B,KAAa;AAAb;AAAA,EAAc;AAAA,EAAd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAhJ7B,MAAM,YAAY,WAAiD;AACjE,UAAM,KAAK,KAAK,SAAS,SAAS;AAClC,QAAIC;AACJ,QAAI;AACF,MAAAA,SAAO,MAAS,SAAK,EAAE;AAAA,IACzB,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,SAAU,QAAO;AAE7D,aAAO;AAAA,IACT;AACA,QAAIA,OAAK,SAAS,EAAG,QAAO;AAE5B,QAAI;AACF,YAAM,OAAO,MAAM,4BAA4B,IAAIA,OAAK,IAAI;AAC5D,UAAI,MAAM,SAAS,SAAS,kBAAmB,QAAO;AACtD,aAAO;AAAA,QACL;AAAA,QACA,MAAM;AAAA,QACN,aAAa,KAAK,SAAS;AAAA,QAC3B,SAAS,KAAK,SAAS;AAAA,QACvB,YAAY,KAAK;AAAA,MACnB;AAAA,IAEF,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EAEF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,QAAQ,WAAiD;AAC7D,UAAM,KAAK,KAAK,SAAS,SAAS;AAClC,QAAI;AACJ,QAAI;AACF,YAAM,MAAS,aAAS,IAAI,MAAM;AAAA,IACpC,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,SAAU,QAAO;AAE7D,aAAO;AAAA,IACT;AACA,UAAM,SAAyB,CAAC;AAChC,eAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,UAAI,CAAC,KAAK,KAAK,EAAG;AAClB,UAAI;AACF,eAAO,KAAK,KAAK,MAAM,IAAI,CAAiB;AAAA,MAC9C,QAAQ;AAAA,MAER;AAAA,IACF;AACA,QAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,QAAI,iBAAsC;AAC1C,QAAI,oBAAoB;AACxB,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAI,OAAO,CAAC,GAAG,SAAS,cAAc;AACpC,yBAAiB,cAAc,OAAO,CAAC,CAAC;AACxC,4BAAoB;AAAA,MACtB;AAAA,IACF;AAEA,UAAM,gBACJ,qBAAqB,IAAI,OAAO,MAAM,oBAAoB,CAAC,IAAI;AAGjE,UAAM,iBAAiB,OAAO,SAAS,mBAAmB;AAC1D,UAAM,gBAAgB,gBAAgB,SAAS,oBAAoB,iBAAiB;AACpF,UAAM,UAAU,iBAAiB,cAAc,SAAS,oBACpD,cAAc,UACd;AACJ,WAAO;AAAA,MACL;AAAA,MACA,OAAO,kBAAkB;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,gBAAyC;AAC7C,UAAM,MAAsB,CAAC;AAI7B,UAAM,UAAU,OAAO,KAAa,QAAgB,UAAiC;AACnF,UAAI;AACJ,UAAI;AACF,kBAAU,MAAS,YAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,MAEzD,QAAQ;AACN;AAAA,MACF;AAEA,iBAAW,SAAS,SAAS;AAC3B,YAAI,MAAM,KAAK,WAAW,GAAG,EAAG;AAChC,YACE,MAAM,SAAS,YACf,MAAM,SAAS,eACf,MAAM,SAAS;AAEf;AACF,YAAI,MAAM,YAAY,GAAG;AACvB,cAAI,UAAU,GAAG;AACf,kBAAM,QAAa,YAAK,KAAK,MAAM,IAAI,GAAG,MAAM,MAAM,QAAQ,CAAC;AAAA,UACjE;AACA;AAAA,QACF;AACA,YAAI,CAAC,MAAM,OAAO,KAAK,CAAC,MAAM,KAAK,SAAS,QAAQ,EAAG;AACvD,YAAI,MAAM,SAAS,kBAAkB,MAAM,SAAS,iBAAkB;AACtE,cAAM,OAAO,MAAM,KAAK,MAAM,GAAG,CAAC,SAAS,MAAM;AACjD,YAAI,KAAK,SAAS,SAAS,KAAK,KAAK,SAAS,cAAc,KAAK,KAAK,SAAS,QAAQ;AACrF;AACF,cAAM,YAAY,SAAS,GAAG,MAAM,IAAI,IAAI,KAAK;AACjD,cAAM,QAAQ,MAAM,KAAK,YAAY,SAAS;AAC9C,YAAI,MAAO,KAAI,KAAK,KAAK;AAAA,MAC3B;AAAA,IACF;AACA,UAAM,QAAQ,KAAK,KAAK,IAAI,CAAC;AAC7B,WAAO,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,cAAc,EAAE,WAAW,CAAC;AAAA,EACtE;AAAA;AAAA,EAIQ,SAAS,WAA2B;AAI1C,WAAO,kBAAkB,KAAK,KAAK,WAAW,QAAQ;AAAA,EACxD;AAGF;AAOA,SAAS,oBAAoB,OAAiD;AAC5E,SACE,MAAM,SAAS,qBACf,MAAM,SAAS,mBACf,MAAM,SAAS;AAEnB;AAEA,SAAS,uBAAuB,MAAwC;AACtE,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,KAAK,SAAS,MAAM,CAAC;AAC/C,WAAO,oBAAoB,MAAM,IAAI,SAAS;AAAA,EAChD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,iBAAiB,MAAuB;AAC/C,aAAW,QAAQ,MAAM;AACvB,QAAI,SAAS,KAAQ,SAAS,MAAQ,SAAS,MAAQ,SAAS,GAAM,QAAO;AAAA,EAC/E;AACA,SAAO;AACT;AAQA,eAAe,4BACb,UACA,MACqE;AACrE,QAAM,aAAa,KAAK;AACxB,QAAM,SAAS,MAAS,SAAK,UAAU,GAAG;AAC1C,MAAI,WAAW;AACf,MAAI,oBAAoB,OAAO,MAAM,CAAC;AACtC,MAAI,iBAA2C;AAC/C,MAAI,aAAa;AAEjB,QAAM,cAAc,CAAC,SAA2C;AAC9D,QAAI,CAAC,iBAAiB,IAAI,EAAG,QAAO;AACpC;AACA,WAAO,uBAAuB,IAAI;AAAA,EACpC;AAEA,MAAI;AACF,WAAO,WAAW,GAAG;AACnB,YAAM,SAAS,KAAK,IAAI,YAAY,QAAQ;AAC5C,kBAAY;AACZ,YAAM,QAAQ,OAAO,YAAY,MAAM;AACvC,YAAM,EAAE,UAAU,IAAI,MAAM,OAAO,KAAK,OAAO,GAAG,QAAQ,QAAQ;AAClE,YAAM,OAAO,OAAO,OAAO,CAAC,MAAM,SAAS,GAAG,SAAS,GAAG,iBAAiB,CAAC;AAE5E,UAAI,UAAU,KAAK;AACnB,eAAS,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK;AACzC,YAAI,KAAK,CAAC,MAAM,GAAM;AACtB,cAAMC,YAAW,YAAY,KAAK,SAAS,IAAI,GAAG,OAAO,CAAC;AAC1D,YAAI,CAAC,kBAAkBA,UAAU,kBAAiBA;AAClD,kBAAU;AAAA,MACZ;AACA,0BAAoB,KAAK,SAAS,GAAG,OAAO;AAI5C,UAAI,kBAAkB,eAAe,SAAS,mBAAmB;AAC/D,eAAO,EAAE,UAAU,gBAAgB,WAAW;AAAA,MAChD;AAAA,IACF;AAEA,UAAM,WAAW,YAAY,iBAAiB;AAC9C,QAAI,CAAC,kBAAkB,SAAU,kBAAiB;AAClD,WAAO,iBAAiB,EAAE,UAAU,gBAAgB,WAAW,IAAI;AAAA,EACrE,UAAE;AACA,UAAM,OAAO,MAAM;AAAA,EACrB;AACF;;;AC/SA,SAAS,cAAAC,aAAY,cAAAC,mBAAkB;AACvC,YAAYC,UAAQ;AA4DpB,IAAM,eAAe,IAAI,OAAO,EAAE;AAoBlC,IAAM,sBAAsB;AAErB,IAAM,eAAN,MAAmB;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA,WAAW,oBAAI,IAAoB;AAAA;AAAA,EAEnC,YAAY,oBAAI,IAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOpC,WAAW,oBAAI,IAA+C;AAAA;AAAA,EAE9D,iBAAiB,oBAAI,IAAoB;AAAA,EACzC,cAAc,oBAAI,IAA2B;AAAA,EAC7C;AAAA,EAEjB,YAAY,MAA2B;AACrC,SAAK,MAAM,KAAK;AAChB,SAAK,SAAS,KAAK;AACnB,SAAK,UAAU,KAAK;AACpB,SAAK,aAAa,KAAK,cAAc;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAO,OAOW;AACtB,QAAI;AACJ,UAAM,KAAK,KAAK,SAAS,MAAM,SAAS;AACxC,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI;AACF,YAAM,KAAK,QAAQ,MAAM,WAAW,YAAY;AAC9C,cAAM,aAAa,IAAI,YAAY;AAOjC,gBAAM,MAAM,MAAM,KAAK,iBAAiB,MAAM,WAAW,EAAE;AAC3D,gBAAM,WAAW,IAAI;AACrB,gBAAM,QAAQ,IAAI;AAElB,gBAAM,KAAKC,YAAW;AACtB,gBAAM,MAAK,oBAAI,KAAK,GAAE,YAAY;AAClC,gBAAM,UAAU;AAAA,YACd;AAAA,YACA;AAAA,YACA;AAAA,YACA,UAAU,MAAM;AAAA,YAChB,WAAW,MAAM;AAAA,YACjB,OAAO,MAAM;AAAA,YACb,QAAQ,MAAM;AAAA,YACd,SAAS,MAAM;AAAA,YACf;AAAA,UACF;AACA,gBAAM,OAAOC,YAAW,QAAQ,EAAE,OAAOC,iBAAgB,OAAO,GAAG,MAAM,EAAE,OAAO,KAAK;AACvF,kBAAQ;AAAA,YACN;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,UAAU,MAAM;AAAA,YAChB,WAAW,MAAM;AAAA,YACjB,OAAO,MAAM;AAAA,YACb,QAAQ,MAAM;AAAA,YACd,SAAS,MAAM;AAAA,YACf;AAAA,UACF;AAOA,gBAAS,gBAAW,IAAI,KAAK,UAAU,KAAK,IAAI,MAAM,MAAM;AAK5D,cAAI;AACF,kBAAM,KAAK,MAAS,UAAK,EAAE;AAC3B,iBAAK,SAAS,IAAI,MAAM,WAAW,EAAE,SAAS,GAAG,SAAS,MAAM,GAAG,KAAK,CAAC;AAAA,UAC3E,QAAQ;AAAA,UAER;AACA,eAAK,SAAS,IAAI,MAAM,WAAW,IAAI;AACvC,eAAK,UAAU,IAAI,MAAM,WAAW,QAAQ,CAAC;AAC7C,gBAAM,KAAK,eAAe,MAAM,WAAW,EAAE;AAE7C,gBAAM,aAAa,KAAK,IAAI,IAAI;AAChC,eAAK,QAAQ,KAAK,iBAAiB;AAAA,YACjC,WAAW,MAAM;AAAA,YACjB,OAAO;AAAA,YACP,UAAU;AAAA,YACV,WAAW;AAAA,YACX,SAAS;AAAA,YACT;AAAA,YACA,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,UAChE,CAAC;AAAA,QACH,CAAC;AAAA,MACH,CAAC;AACD,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,WAAK,QAAQ,KAAK,iBAAiB;AAAA,QACjC,WAAW,MAAM;AAAA,QACjB,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT,OAAO,eAAe,GAAG;AAAA,QACzB,aAAa;AAAA,QACb,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,iBACZ,WACA,IACkD;AAClD,UAAM,aAAa,KAAK,SAAS,IAAI,SAAS;AAC9C,UAAM,cAAc,KAAK,UAAU,IAAI,SAAS;AAChD,UAAM,aAAa,KAAK,SAAS,IAAI,SAAS;AAE9C,QAAI,eAAe,UAAa,gBAAgB,UAAa,YAAY;AAIvE,UAAI;AACF,cAAM,KAAK,MAAS,UAAK,EAAE;AAC3B,YAAI,GAAG,YAAY,WAAW,WAAW,GAAG,SAAS,WAAW,MAAM;AACpE,iBAAO,EAAE,UAAU,YAAY,WAAW,YAAY;AAAA,QACxD;AAAA,MACF,SAAS,KAAK;AACZ,YAAK,IAA8B,SAAS,UAAU;AAEpD,eAAK,SAAS,OAAO,SAAS;AAC9B,eAAK,UAAU,OAAO,SAAS;AAC/B,eAAK,SAAS,OAAO,SAAS;AAC9B,iBAAO,EAAE,UAAU,cAAc,WAAW,EAAE;AAAA,QAChD;AAEA,cAAM;AAAA,MACR;AAAA,IACF;AAGA,UAAM,UAAU,MAAM,KAAK,QAAQ,SAAS;AAC5C,UAAM,OAAO,QAAQ,GAAG,EAAE;AAC1B,UAAM,WAAW,MAAM,QAAQ;AAC/B,UAAM,YAAY,OAAO,KAAK,QAAQ,IAAI;AAC1C,SAAK,SAAS,IAAI,WAAW,QAAQ;AACrC,SAAK,UAAU,IAAI,WAAW,SAAS;AACvC,QAAI;AACF,YAAM,KAAK,MAAS,UAAK,EAAE;AAC3B,WAAK,SAAS,IAAI,WAAW,EAAE,SAAS,GAAG,SAAS,MAAM,GAAG,KAAK,CAAC;AAAA,IACrE,QAAQ;AAAA,IAER;AACA,WAAO,EAAE,UAAU,UAAU;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAAO,WAA0C;AACrD,UAAM,KAAK,KAAK,SAAS,SAAS;AAClC,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM,KAAK,QAAQ,SAAS;AAAA,IACxC,SAAS,KAAK;AAIZ,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC;AAAA,QACA,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,OAAO,eAAe,GAAG;AAAA,QACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,aAAO,EAAE,IAAI,MAAM,SAAS,EAAE;AAAA,IAChC;AAIA,UAAM,WAAW,MAAoB;AACnC,UAAI,QAAQ,WAAW,EAAG,QAAO,EAAE,IAAI,MAAM,SAAS,EAAE;AAExD,UAAI,QAAQ,CAAC,GAAG,aAAa,cAAc;AACzC,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,UAAU;AAAA,UACV,QAAQ;AAAA,QACV;AAAA,MACF;AACA,UAAI,WAAW;AACf,eAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,cAAM,IAAI,cAAc,QAAQ,CAAC,CAAC;AAClC,YAAI,EAAE,aAAa,UAAU;AAC3B,iBAAO;AAAA,YACL,IAAI;AAAA,YACJ,UAAU;AAAA,YACV,QAAQ,8BAA8B,CAAC,cAAc,SAAS,MAAM,GAAG,CAAC,CAAC,eAAU,EAAE,SAAS,MAAM,GAAG,CAAC,CAAC;AAAA,UAC3G;AAAA,QACF;AAGA,cAAM,UAAU;AAAA,UACd,IAAI,EAAE;AAAA,UACN,IAAI,EAAE;AAAA,UACN,UAAU,EAAE;AAAA,UACZ,UAAU,EAAE;AAAA,UACZ,WAAW,EAAE;AAAA,UACb,OAAO,EAAE;AAAA,UACT,QAAQ,EAAE;AAAA,UACV,SAAS,EAAE;AAAA,UACX,OAAO,EAAE;AAAA,QACX;AACA,cAAM,eAAeD,YAAW,QAAQ,EACrC,OAAOC,iBAAgB,OAAO,GAAG,MAAM,EACvC,OAAO,KAAK;AACf,YAAI,iBAAiB,EAAE,MAAM;AAC3B,iBAAO;AAAA,YACL,IAAI;AAAA,YACJ,UAAU;AAAA,YACV,QAAQ,0BAA0B,CAAC;AAAA,UACrC;AAAA,QACF;AACA,mBAAW,EAAE;AAAA,MACf;AACA,aAAO,EAAE,IAAI,MAAM,SAAS,QAAQ,OAAO;AAAA,IAC7C,GAAG;AAEH,SAAK,QAAQ,KAAK,gBAAgB;AAAA,MAChC;AAAA,MACA,OAAO;AAAA,MACP,UAAU;AAAA,MACV,WAAW;AAAA,MACX,SAAS,QAAQ,KAAK,YAAY;AAAA,MAClC,YAAY,KAAK,IAAI,IAAI;AAAA,MACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,IAChE,CAAC;AACD,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,KAAK,WAA0C;AACnD,UAAM,KAAK,KAAK,SAAS,SAAS;AAClC,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,QAAQ,SAAS;AAC5C,YAAM,aAAa,KAAK,IAAI,IAAI;AAChC,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC;AAAA,QACA,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT;AAAA,QACA,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,aAAa,KAAK,IAAI,IAAI;AAChC,WAAK,QAAQ,KAAK,gBAAgB;AAAA,QAChC;AAAA,QACA,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,QACT;AAAA,QACA,OAAO,eAAe,GAAG;AAAA,QACzB,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA,EAIQ,SAAS,WAA2B;AAI1C,WAAO,kBAAkB,KAAK,KAAK,WAAW,cAAc;AAAA,EAC9D;AAAA,EAEA,MAAc,QAAQ,WAA0C;AAC9D,UAAM,KAAK,KAAK,SAAS,SAAS;AAClC,QAAI;AACF,YAAM,MAAM,MAAS,cAAS,IAAI,MAAM;AACxC,YAAM,MAAoB,CAAC;AAC3B,iBAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,YAAI,CAAC,KAAK,KAAK,EAAG;AAClB,YAAI;AACF,gBAAM,SAAS,UAAsB,IAAI;AACzC,cAAI,OAAO,MAAM,OAAO,MAAO,KAAI,KAAK,OAAO,KAAK;AAAA,QACtD,QAAQ;AAAA,QAER;AAAA,MACF;AACA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAG9D,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,eAAe,WAAmB,IAA2B;AACzE,UAAM,SAAS,KAAK,eAAe,IAAI,SAAS,KAAK,KAAK;AAC1D,SAAK,eAAe,IAAI,WAAW,KAAK;AACxC,QAAI,KAAK,eAAe,OAAO,qBAAqB,QAAQ,KAAK,eAAe,GAAG;AACjF,YAAM,KAAK,KAAK,WAAW,EAAE;AAAA,IAC/B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,MAAM,WAAkC;AAC5C,UAAM,KAAK,KAAK,WAAW,KAAK,SAAS,SAAS,CAAC;AAAA,EACrD;AAAA,EAEA,MAAc,KAAK,WAAmB,IAA2B;AAC/D,QAAI;AACF,YAAM,KAAK,MAAS,UAAK,IAAI,IAAI;AACjC,UAAI;AACF,cAAM,GAAG,KAAK;AAAA,MAChB,UAAE;AACA,cAAM,GAAG,MAAM;AAAA,MACjB;AAAA,IACF,QAAQ;AAAA,IAER,UAAE;AACA,WAAK,eAAe,IAAI,WAAW,CAAC;AAAA,IACtC;AAAA,EACF;AAAA,EAEQ,QAAQ,WAAmB,IAAwC;AACzE,UAAM,OAAO,KAAK,YAAY,IAAI,SAAS,KAAK,QAAQ,QAAQ;AAChE,UAAM,OAAO,KAAK,KAAK,IAAI,EAAE;AAC7B,SAAK,YAAY;AAAA,MACf;AAAA,MACA,KAAK,MAAM,MAAM,MAAS;AAAA,IAC5B;AACA,WAAO;AAAA,EACT;AACF;AAEA,SAASA,iBAAgB,OAAwB;AAC/C,SAAO,KAAK,UAAUC,UAAS,KAAK,CAAC;AACvC;AAEA,SAASA,UAAS,OAAyB;AACzC,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAIA,SAAQ;AACnD,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,UAAM,MAAM;AACZ,UAAM,SAAkC,CAAC;AACzC,eAAW,OAAO,OAAO,KAAK,GAAG,EAAE,KAAK,GAAG;AACzC,aAAO,GAAG,IAAIA,UAAS,IAAI,GAAG,CAAC;AAAA,IACjC;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;ACzbO,IAAM,kBAAN,MAAsB;AAAA,EAC3B,QAAQ,QAAyC;AAC/C,UAAM,iBAAyC,CAAC;AAChD,UAAM,SAAyB,CAAC;AAChC,UAAM,cAA4B,CAAC;AACnC,UAAM,YAAY,oBAAI,IAAyB;AAC/C,QAAI,YAAY;AAEhB,eAAW,SAAS,QAAQ;AAE1B,UAAI,MAAM,SAAS,mBAAmB,MAAM,SAAS,mBAAmB;AACtE,YAAI,CAAC,UAAW,aAAY,MAAM;AAAA,MACpC;AACA,UAAI,MAAM,SAAS,YAAY;AAC7B,uBAAe,MAAM,IAAI,KAAK,eAAe,MAAM,IAAI,KAAK,KAAK;AAAA,MACnE;AACA,UAAI,MAAM,SAAS,SAAS;AAC1B,eAAO,KAAK,EAAE,IAAI,MAAM,IAAI,OAAO,MAAM,OAAO,SAAS,MAAM,QAAQ,CAAC;AAAA,MAC1E;AACA,UAAI,MAAM,SAAS,gBAAgB;AACjC,oBAAY,KAAK,EAAE,IAAI,MAAM,IAAI,MAAM,MAAM,MAAM,IAAI,MAAM,GAAG,CAAC;AAAA,MACnE;AACA,UAAI,MAAM,SAAS,gBAAgB;AACjC,kBAAU,IAAI,MAAM,QAAQ;AAAA,UAC1B,QAAQ,MAAM;AAAA,UACd,OAAO,MAAM;AAAA,UACb,QAAQ;AAAA,UACR,WAAW,MAAM;AAAA,QACnB,CAAC;AAAA,MACH;AACA,UAAI,MAAM,SAAS,gBAAgB;AACjC,cAAM,IAAI,UAAU,IAAI,MAAM,MAAM;AACpC,YAAI,EAAG,GAAE,SAAS,MAAM;AAAA,MAC1B;AACA,UAAI,MAAM,SAAS,kBAAkB;AACnC,cAAM,IAAI,UAAU,IAAI,MAAM,MAAM;AACpC,YAAI,GAAG;AACL,YAAE,SAAS;AACX,YAAE,cAAc,MAAM;AAAA,QACxB,OAAO;AACL,oBAAU,IAAI,MAAM,QAAQ;AAAA,YAC1B,QAAQ,MAAM;AAAA,YACd,OAAO,MAAM;AAAA,YACb,QAAQ;AAAA,YACR,WAAW,MAAM;AAAA,YACjB,aAAa,MAAM;AAAA,UACrB,CAAC;AAAA,QACH;AAAA,MACF;AACA,UAAI,MAAM,SAAS,eAAe;AAChC,cAAM,IAAI,UAAU,IAAI,MAAM,MAAM;AACpC,YAAI,GAAG;AACL,YAAE,SAAS;AACX,YAAE,cAAc,MAAM;AAAA,QACxB,OAAO;AACL,oBAAU,IAAI,MAAM,QAAQ;AAAA,YAC1B,QAAQ,MAAM;AAAA,YACd,OAAO,MAAM;AAAA,YACb,QAAQ;AAAA,YACR,WAAW,MAAM;AAAA,YACjB,aAAa,MAAM;AAAA,UACrB,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,MACA,eAAe,KAAK,aAAa,MAAM;AAAA,MACvC;AAAA,MACA,YAAY,OAAO;AAAA,MACnB;AAAA,MACA,OAAO,MAAM,KAAK,UAAU,OAAO,CAAC;AAAA,IACtC;AAAA,EACF;AAAA,EAEA,MAAM,QAAwB,QAAqC;AACjE,WAAO,OAAO,OAAO,CAAC,MAAM;AAC1B,UAAI,OAAO,YAAY,UAAU,CAAC,OAAO,WAAW,SAAS,EAAE,IAAI,EAAG,QAAO;AAC7E,UAAI,OAAO,WAAW,UAAU,EAAE,SAAS,YAAY;AACrD,cAAM,YAAY;AAClB,YAAI,CAAC,OAAO,UAAU,SAAS,UAAU,IAAI,EAAG,QAAO;AAAA,MACzD;AACA,UAAI,OAAO,WAAW;AACpB,cAAM,KAAK,IAAI,KAAK,EAAE,EAAE,EAAE,QAAQ;AAClC,cAAM,QAAQ,IAAI,KAAK,OAAO,UAAU,KAAK,EAAE,QAAQ;AACvD,cAAM,MAAM,IAAI,KAAK,OAAO,UAAU,GAAG,EAAE,QAAQ;AACnD,YAAI,KAAK,SAAS,KAAK,IAAK,QAAO;AAAA,MACrC;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEQ,aAAa,QAAgC;AACnD,QAAI,OAAO,SAAS,EAAG,QAAO;AAC9B,UAAM,aAAa,OAAO,CAAC;AAC3B,UAAM,YAAY,OAAO,OAAO,SAAS,CAAC;AAE1C,QAAI,CAAC,cAAc,CAAC,UAAW,QAAO;AACtC,UAAM,QAAQ,IAAI,KAAK,WAAW,EAAE,EAAE,QAAQ;AAC9C,UAAM,OAAO,IAAI,KAAK,UAAU,EAAE,EAAE,QAAQ;AAC5C,WAAO,OAAO;AAAA,EAChB;AACF;;;ACvIA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AACtB,SAAS,cAAAC,mBAAkB;AAgF3B,IAAM,gBAAgB;AACtB,IAAM,wBAAwB;AAC9B,IAAM,mBAAmB;AAGzB,IAAM,mBAAmB;AAIzB,IAAM,gBAAgB;AAGtB,IAAM,gBAAgB;AACtB,IAAM,eAAe;AACrB,IAAM,sBAAsB;AAI5B,SAAS,SAAS,KAAsB;AACtC,MAAI;AACF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAUA,SAAS,cAAc,KAAmD;AACxE,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC3E,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO,CAAC;AACV;AAIO,IAAM,kBAAN,MAAsB;AAAA,EACV;AAAA,EACT,iBAAwD;AAAA,EACxD,mBAAkC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMlC,YAAyC;AAAA,EAEjD,YAAY,YAAoB;AAC9B,SAAK,WAAgB,YAAK,YAAY,aAAa;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,SACJ,OAGe;AAOf,QAAI,KAAK,gBAAgB;AACvB,oBAAc,KAAK,cAAc;AACjC,WAAK,iBAAiB;AAAA,IACxB;AACA,SAAK,mBAAmB,MAAM;AAC9B,UAAM,OAA6B;AAAA,MACjC,GAAG;AAAA,MACH,QAAQ;AAAA,MACR,kBAAiB,oBAAI,KAAK,GAAE,YAAY;AAAA,MACxC,YAAY,MAAM,QAAQ,UAAU;AAAA,MACpC,QAAQ,MAAM,UAAU,CAAC;AAAA,IAC3B;AACA,SAAK,YAAY;AACjB,UAAM,KAAK,aAAa,CAAC,aAAa;AAGpC,YAAM,MAAM,KAAK,IAAI;AACrB,iBAAW,CAAC,IAAI,QAAQ,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACrD,YAAI,SAAS,QAAQ,MAAM,KAAK;AAK9B,cAAI,OAAO,MAAM,UAAW,QAAO,SAAS,EAAE;AAC9C;AAAA,QACF;AACA,cAAM,eAAe,MAAM,IAAI,KAAK,SAAS,eAAe,EAAE,QAAQ;AACtE,YAAI,eAAe,oBAAoB,CAAC,SAAS,SAAS,GAAG,GAAG;AAC9D,iBAAO,SAAS,EAAE;AAAA,QACpB;AAAA,MACF;AACA,eAAS,MAAM,SAAS,IAAI;AAAA,IAC9B,CAAC;AAID,SAAK,iBAAiB,YAAY,MAAM;AACtC,WAAK,KAAK,UAAU;AAAA,IACtB,GAAG,qBAAqB;AAExB,QAAI,KAAK,eAAe,MAAO,MAAK,eAAe,MAAM;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,aAAa,QAAqC;AACtD,QAAI,CAAC,KAAK,iBAAkB;AAE5B,UAAM,aAAa,OAAO,KAAK,CAAC,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW,WAAW;AACxF,UAAM,aAAa,OAAO,KAAK,CAAC,MAAM,EAAE,WAAW,cAAc;AACjE,UAAM,WAAW,OAAO,KAAK,CAAC,MAAM,EAAE,WAAW,OAAO;AACxD,UAAM,SAA4B,cAAc,cAAc,WAAW,WAAW;AACpF,UAAM,UAAS,oBAAI,KAAK,GAAE,YAAY;AAGtC,QAAI,KAAK,WAAW;AAClB,WAAK,UAAU,SAAS;AACxB,WAAK,UAAU,aAAa,OAAO;AACnC,WAAK,UAAU,SAAS;AACxB,WAAK,UAAU,kBAAkB;AAAA,IACnC;AAEA,UAAM,KAAK,aAAa,CAAC,aAAa;AACpC,UAAI,QAAQ,SAAS,KAAK,gBAAiB;AAC3C,UAAI,CAAC,OAAO;AAGV,YAAI,CAAC,KAAK,UAAW;AACrB,gBAAQ,EAAE,GAAG,KAAK,UAAU;AAC5B,iBAAS,KAAK,gBAAiB,IAAI;AAAA,MACrC;AACA,YAAM,SAAS;AACf,YAAM,aAAa,OAAO;AAC1B,YAAM,SAAS;AACf,YAAM,kBAAkB;AAAA,IAC1B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAA6B;AACjC,QAAI,KAAK,gBAAgB;AACvB,oBAAc,KAAK,cAAc;AACjC,WAAK,iBAAiB;AAAA,IACxB;AACA,QAAI,CAAC,KAAK,iBAAkB;AAC5B,UAAM,KAAK,aAAa,CAAC,aAAa;AACpC,YAAM,QAAQ,SAAS,KAAK,gBAAiB;AAC7C,UAAI,CAAC,MAAO;AACZ,YAAM,SAAS;AACf,YAAM,mBAAkB,oBAAI,KAAK,GAAE,YAAY;AAAA,IACjD,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AAChC,QAAI,KAAK,gBAAgB;AACvB,oBAAc,KAAK,cAAc;AACjC,WAAK,iBAAiB;AAAA,IACxB;AACA,QAAI,CAAC,KAAK,iBAAkB;AAC5B,UAAM,MAAM,KAAK;AACjB,SAAK,mBAAmB;AACxB,UAAM,KAAK,aAAa,CAAC,aAAa;AACpC,aAAO,SAAS,GAAG;AAAA,IACrB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAwC;AAC5C,UAAM,WAAW,MAAM,KAAK,aAAa;AACzC,WAAO,OAAO,OAAO,QAAQ;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,IAAI,WAA8D;AACtE,UAAM,WAAW,MAAM,KAAK,aAAa;AACzC,WAAO,SAAS,SAAS;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAcC,cAAsD;AACxE,UAAM,MAAM,MAAM,KAAK,KAAK;AAC5B,WAAO,IAAI,OAAO,CAAC,MAAM,EAAE,gBAAgBA,YAAW;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,eAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAIA,MAAc,YAA2B;AACvC,QAAI,CAAC,KAAK,iBAAkB;AAC5B,QAAI;AACF,YAAM,YAAY,KAAK;AACvB,YAAM,UAAS,oBAAI,KAAK,GAAE,YAAY;AACtC,YAAM,KAAK,aAAa,CAAC,aAAa;AACpC,cAAM,QAAQ,SAAS,SAAS;AAChC,YAAI,OAAO;AACT,gBAAM,kBAAkB;AAExB,cAAI,MAAM,WAAW,WAAW;AAC9B,kBAAM,cAAc,MAAM,UAAU,CAAC,GAAG;AAAA,cACtC,CAAC,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW;AAAA,YAChD;AACA,kBAAM,SAAS,aAAa,WAAW;AAAA,UACzC;AACA;AAAA,QACF;AACA,YAAI,KAAK,WAAW;AAIlB,mBAAS,SAAS,IAAI,EAAE,GAAG,KAAK,WAAW,iBAAiB,OAAO;AAAA,QACrE;AAAA,MACF,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAc,eAA8D;AAC1E,QAAI;AACF,YAAM,MAAM,MAAS,cAAS,KAAK,UAAU,MAAM;AACnD,YAAM,WAAW,cAAc,GAAG;AAClC,YAAM,MAAM,KAAK,IAAI;AACrB,UAAI,SAAS;AAEb,iBAAW,CAAC,IAAI,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG;AAClD,cAAM,eAAe,MAAM,IAAI,KAAK,MAAM,eAAe,EAAE,QAAQ;AAGnE,YAAI,MAAM,WAAW,aAAa,eAAe,kBAAkB;AACjE,iBAAO,SAAS,EAAE;AAClB,mBAAS;AACT;AAAA,QACF;AAKA,YAAI,eAAe,kBAAkB;AACnC,gBAAM,QAAQ,SAAS,MAAM,GAAG;AAChC,cAAI,OAAO;AAET,gBAAI,MAAM,WAAW,UAAU,MAAM,WAAW,aAAa,MAAM,WAAW,SAAS;AACrF,oBAAM,SAAS;AACf,uBAAS;AAAA,YACX;AAEA,gBAAI,MAAM,WAAW,UAAU,eAAe,mBAAmB,eAAe;AAC9E,qBAAO,SAAS,EAAE;AAClB,uBAAS;AAAA,YACX;AAAA,UACF,OAAO;AAEL,kBAAM,SAAS;AACf,kBAAM,aAAa,MAAM,IAAI,KAAK,MAAM,SAAS,EAAE,QAAQ;AAC3D,gBAAI,aAAa,IAAI,KAAQ;AAC3B,qBAAO,SAAS,EAAE;AAClB,uBAAS;AAAA,YACX;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,UAAI,QAAQ;AAEV,cAAM,KAAK,YAAY,QAAQ,EAAE,MAAM,MAAM,MAAS;AAAA,MAExD;AAEA,aAAO;AAAA,IACT,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAc,aACZ,IACe;AACf,UAAM,WAAW,GAAG,KAAK,QAAQ;AACjC,UAAM,aAAa;AACnB,UAAM,eAAe;AAErB,aAAS,UAAU,GAAG,UAAU,YAAY,WAAW;AACrD,UAAI;AAEF,cAAS,WAAW,eAAQ,KAAK,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAG/D,YAAI,aAAa,MAAS,UAAK,UAAU,IAAI,EAAE,MAAM,MAAM,IAAI;AAC/D,YAAI,CAAC,YAAY;AAMf,cAAI,MAAM,KAAK,eAAe,QAAQ,GAAG;AAEvC,yBAAa,MAAS,UAAK,UAAU,IAAI,EAAE,MAAM,MAAM,IAAI;AAAA,UAE7D;AACA,cAAI,CAAC,YAAY;AACf,kBAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,gBAAgB,UAAU,EAAE,CAAC;AACpE;AAAA,UACF;AAAA,QACF;AAEA,YAAI;AAGF,gBAAM,WAAW,UAAU,OAAO,QAAQ,GAAG,CAAC,EAAE,MAAM,MAAM,MAAS;AAErE,gBAAM,MAAM,MAAS,cAAS,KAAK,UAAU,MAAM,EAAE,MAAM,MAAM,IAAI;AAGrE,gBAAM,WAAW,cAAc,GAAG;AAClC,aAAG,QAAQ;AACX,gBAAM,KAAK,kBAAkB,QAAQ;AACrC;AAAA,QACF,UAAE;AACA,gBAAM,WAAW,MAAM;AAEvB,gBAAS,YAAO,QAAQ,EAAE,MAAM,MAAM,MAAS;AAAA,QAEjD;AAAA,MACF,QAAQ;AAGN;AAAA,MACF;AAAA,IACF;AAAA,EAEF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,eAAe,UAAoC;AAC/D,QAAI;AACF,YAAM,CAACC,QAAM,OAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,QACrC,UAAK,QAAQ;AAAA;AAAA,QAEb,cAAS,UAAU,MAAM,EAAE,MAAM,MAAM,EAAE;AAAA;AAAA,MAE9C,CAAC;AACD,YAAM,QAAQ,KAAK,IAAI,IAAIA,OAAK;AAChC,YAAM,WAAW,OAAO,SAAS,QAAQ,KAAK,GAAG,EAAE;AACnD,YAAM,YACJ,OAAO,UAAU,QAAQ,KAAK,WAAW,KAAK,aAAa,QAAQ,OAAO,CAAC,SAAS,QAAQ;AAC9F,UAAI,aAAa,QAAQ,eAAe;AAEtC,cAAS,YAAO,QAAQ,EAAE,MAAM,MAAM,MAAS;AAE/C,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT,QAAQ;AAGN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAc,kBAAkB,UAA+D;AAC7F,UAAM,KAAK,oBAAoB;AAC/B,UAAM,KAAK,gBAAgB,QAAQ;AAAA,EACrC;AAAA;AAAA,EAGA,MAAc,YAAY,UAA+D;AACvF,UAAM,KAAK,oBAAoB;AAC/B,UAAM,KAAK,gBAAgB,QAAQ;AAAA,EACrC;AAAA,EAEA,MAAc,gBAAgB,UAA+D;AAC3F,UAAM,MAAW;AAAA,MACV,eAAQ,KAAK,QAAQ;AAAA,MAC1B,IAAS,gBAAS,KAAK,QAAQ,CAAC,IAAIF,YAAW,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,IAC9D;AACA,QAAI;AAIF,YAAM,SAAS,MAAS,UAAK,KAAK,GAAG;AACrC,UAAI;AACF,cAAM,OAAO,UAAU,KAAK,UAAU,UAAU,MAAM,CAAC,GAAG,MAAM;AAChE,cAAM,OAAO,KAAK,EAAE,MAAM,MAAM,MAAS;AAAA,MAC3C,UAAE;AACA,cAAM,OAAO,MAAM;AAAA,MACrB;AACA,YAAS,YAAO,KAAK,KAAK,QAAQ;AAAA,IACpC,SAAS,KAAK;AAEZ,YAAS,YAAO,GAAG,EAAE,MAAM,MAAM,MAAS;AAC1C,YAAM;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAc,sBAAqC;AACjD,QAAI;AACF,YAAM,MAAW,eAAQ,KAAK,QAAQ;AACtC,YAAM,OAAY,gBAAS,KAAK,QAAQ;AACxC,YAAM,MAAM,KAAK,IAAI;AACrB,YAAM,QAAkD,CAAC;AAEzD,iBAAW,QAAQ,MAAS,aAAQ,GAAG,GAAG;AACxC,cAAM,UACH,KAAK,WAAW,GAAG,IAAI,GAAG,KAAK,KAAK,WAAW,IAAI,IAAI,GAAG,MAAM,KAAK,SAAS,MAAM;AACvF,YAAI,CAAC,OAAQ;AAEb,cAAME,SAAO,MAAS,UAAU,YAAK,KAAK,IAAI,CAAC,EAAE,MAAM,MAAM,IAAI;AACjE,YAAI,CAACA,OAAM;AAEX,YAAI,MAAMA,OAAK,UAAU,aAAc,OAAM,KAAK,EAAE,MAAM,SAASA,OAAK,QAAQ,CAAC;AAAA,MACnF;AAEA,YAAM,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,OAAO;AAC1C,YAAM,QAAQ;AAAA,QACZ,MAAM,MAAM,mBAAmB,EAAE,IAAI,OAAO,EAAE,KAAK,MAAM;AAEvD,gBAAS,YAAY,YAAK,KAAK,IAAI,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,QAE7D,CAAC;AAAA,MACH;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAGA,IAAI,YAAoC;AAEjC,SAAS,mBAAmB,YAAsC;AACvE,MAAI,CAAC,aAAa,YAAY;AAC5B,gBAAY,IAAI,gBAAgB,UAAU;AAAA,EAC5C;AACA,MAAI,CAAC,WAAW;AAEd,UAAM,IAAI,MAAM,6EAA6E;AAAA,EAC/F;AACA,SAAO;AACT;AAEO,SAAS,qBAA8B;AAC5C,SAAO,cAAc;AACvB;;;ACtjBA,IAAM,gBAAgB;AAEtB,IAAM,0BAA0B;AAEhC,IAAM,mBAAmB;AAEzB,IAAM,4BAA4B;AAElC,SAAS,SAAS,KAAqB;AACrC,MAAI,CAAC,OAAO,SAAS,GAAG,EAAG,QAAO;AAClC,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,GAAG,CAAC;AACvC;AAiBO,IAAM,qBAAN,MAAyB;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGT,SAAS,oBAAI,IAAwB;AAAA;AAAA;AAAA,EAGrC,aAA2B,CAAC;AAAA;AAAA,EAG5B,eAAgC;AAAA,EAChC;AAAA,EACA,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB;AAAA,EACA;AAAA,EACA,oBAAoB;AAAA,EACpB;AAAA,EAEA,gBAAmC,CAAC;AAAA,EAC3B;AAAA,EACT,aAAoD;AAAA,EACpD,eAAqD;AAAA,EAE7D,YAAY,MAAiC;AAC3C,SAAK,SAAS,KAAK;AACnB,SAAK,WAAW,KAAK;AACrB,SAAK,YAAY,KAAK;AACtB,SAAK,aAAa,KAAK,cAAc;AACrC,SAAK,WAAW,KAAK;AAAA,EACvB;AAAA;AAAA,EAGA,YAA0B;AACxB,WAAO,KAAK,WAAW,SAAS,IAAI,CAAC,GAAG,KAAK,UAAU,IAAI,CAAC;AAAA,EAC9D;AAAA,EAEA,QAAc;AACZ,UAAM,KAAK,CACT,SACA,OAEA,KAAK,OAAO,UAAU,SAAS,CAAC,OAAO,YAAY;AACjD,UAAI,CAAC,KAAK,eAAe,OAAO,EAAG;AACnC,SAAG,OAAO,OAAO;AAAA,IACnB,CAAC;AAGH,SAAK,cAAc;AAAA,MACjB,GAAG,qBAAqB,CAAC,QAAQ,YAAY;AAC3C,cAAM,IAAI;AACV,aAAK,kBAAkB,GAAG,EAAE;AAC5B,aAAK,qBAAqB,GAAG,GAAG;AAChC,YAAI,GAAG,MAAO,MAAK,cAAc,EAAE;AACnC,aAAK,eAAe;AACpB,aAAK;AACL,aAAK,MAAM;AAAA,MACb,CAAC;AAAA,IACH;AAGA,SAAK,cAAc;AAAA,MACjB,GAAG,qBAAqB,CAAC,IAAI,YAAY;AACvC,cAAM,IAAI;AACV,cAAM,MAAM,GAAG;AACf,aAAK,kBAAkB;AACvB,aAAK,eAAe;AACpB,YAAI,OAAO,GAAG,UAAU,UAAU;AAChC,eAAK,mBAAmB,KAAK,IAAI,KAAK,kBAAkB,EAAE,QAAQ,CAAC;AAAA,QACrE;AACA,YAAI,CAAC,KAAK;AACR,eAAK,MAAM;AACX;AAAA,QACF;AACA,aAAK,qBAAqB,GAAG;AAC7B,aAAK,MAAM;AAAA,MACb,CAAC;AAAA,IACH;AAEA,SAAK,cAAc;AAAA,MACjB,GAAG,uBAAuB,CAAC,QAAQ,YAAY;AAC7C,cAAM,IAAI;AACV,aAAK,qBAAqB,GAAG,GAAG;AAChC,aAAK,eAAe,GAAG,WAAW,WAAW,UAAU;AACvD,aAAK,oBAAoB;AACzB,aAAK,oBAAoB;AACzB,YAAI,KAAK,iBAAiB,OAAQ,MAAK,kBAAkB;AACzD,aAAK,MAAM;AAAA,MACb,CAAC;AAAA,IACH;AAEA,SAAK,cAAc;AAAA,MACjB,GAAG,mBAAmB,CAAC,QAAQ,YAAY;AACzC,cAAM,IAAI;AACV,aAAK,qBAAqB,GAAG,GAAG;AAChC,aAAK,eAAe;AACpB,aAAK,oBAAoB;AACzB,aAAK,oBAAoB;AACzB,aAAK,MAAM;AAAA,MACb,CAAC;AAAA,IACH;AAGA,SAAK,cAAc;AAAA,MACjB,GAAG,gBAAgB,CAAC,QAAQ,YAAY;AACtC,cAAM,IAAI;AACV,YAAI,GAAG,MAAM;AACX,eAAK,kBAAkB;AACvB,eAAK,oBAAoB,EAAE;AAC3B,eAAK;AAAA,QACP;AACA,aAAK,eAAe;AACpB,aAAK,MAAM;AAAA,MACb,CAAC;AAAA,IACH;AAEA,SAAK,cAAc;AAAA,MACjB,GAAG,iBAAiB,MAAM;AACxB,aAAK,oBAAoB;AACzB,aAAK,MAAM;AAAA,MACb,CAAC;AAAA,IACH;AAGA,SAAK,cAAc;AAAA,MACjB,GAAG,mBAAmB,MAAM;AAC1B,aAAK,kBAAkB;AACvB,aAAK,eAAe;AACpB,aAAK,MAAM;AAAA,MACb,CAAC;AAAA,IACH;AAGA,SAAK,cAAc;AAAA,MACjB,GAAG,sBAAsB,MAAM;AAC7B,aAAK,kBAAkB;AACvB,aAAK,eAAe;AAEpB,aAAK,oBAAoB;AACzB,aAAK,MAAM;AAAA,MACb,CAAC;AAAA,IACH;AAKA,SAAK,cAAc;AAAA,MACjB,GAAG,uBAAuB,CAAC,IAAI,YAAY;AACzC,cAAM,IAAI;AACV,cAAM,OAAO,GAAG;AAChB,YAAI,CAAC,KAAM;AACX,aAAK,kBAAkB;AACvB,aAAK,qBAAqB,GAAG,GAAG;AAChC,aAAK,eAAe;AACpB,cAAM,OAAO,KAAK,oBAAoB;AACtC,aAAK,oBACH,KAAK,SAAS,mBAAmB,KAAK,MAAM,KAAK,SAAS,gBAAgB,IAAI;AAChF,aAAK,qBAAqB;AAAA,MAC5B,CAAC;AAAA,IACH;AAEA,SAAK,cAAc;AAAA,MACjB,GAAG,qBAAqB,CAAC,IAAI,YAAY;AACvC,cAAM,IAAI;AACV,aAAK,qBAAqB,GAAG,GAAG;AAChC,aAAK,MAAM;AAAA,MACb,CAAC;AAAA,IACH;AAEA,SAAK,cAAc;AAAA,MACjB,GAAG,qBAAqB,CAAC,IAAI,YAAY;AACvC,cAAM,IAAI;AACV,YAAI,GAAG,IAAI,OAAO;AAChB,eAAK,cAAc,EAAE,GAAG,aACpB,GAAG,EAAE,GAAG,UAAU,IAAI,EAAE,GAAG,KAAK,KAChC,EAAE,GAAG;AACT,eAAK,MAAM;AAAA,QACb;AAAA,MACF,CAAC;AAAA,IACH;AAEA,SAAK,cAAc;AAAA,MACjB,GAAG,WAAW,CAAC,IAAI,YAAY;AAC7B,cAAM,IAAI;AACV,YAAI,OAAO,GAAG,SAAS,YAAY,OAAO,SAAS,EAAE,IAAI,GAAG;AAC1D,eAAK,eAAe,SAAS,KAAK,MAAM,EAAE,OAAO,GAAG,CAAC;AACrD,eAAK,MAAM;AAAA,QACb;AAAA,MACF,CAAC;AAAA,IACH;AAGA,SAAK,cAAc;AAAA,MACjB,GAAG,mBAAmB,CAAC,IAAI,YAAY;AACrC,cAAM,IAAI;AAGV,YAAI,CAAC,EAAG;AACR,aAAK,kBAAkB,EAAE,OAAO,SAAS;AACzC,aAAK,mBAAmB,EAAE,OAAO,UAAU;AAC3C,aAAK,iBAAiB,EAAE,MAAM,SAAS;AACvC,aAAK,MAAM;AAAA,MACb,CAAC;AAAA,IACH;AAOA,UAAM,QAAQ,CAAC,OAA2B;AACxC,UAAI,QAAQ,KAAK,OAAO,IAAI,EAAE;AAC9B,UAAI,CAAC,OAAO;AACV,cAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,gBAAQ,EAAE,IAAI,MAAM,IAAI,QAAQ,QAAQ,YAAY,GAAG,WAAW,GAAG,WAAW,KAAK,gBAAgB,IAAI;AACzG,aAAK,OAAO,IAAI,IAAI,KAAK;AAAA,MAC3B;AACA,YAAM,kBAAiB,oBAAI,KAAK,GAAE,YAAY;AAC9C,aAAO;AAAA,IACT;AAEA,SAAK,cAAc;AAAA,MACjB,GAAG,oBAAoB,CAAC,IAAI,YAAY;AACtC,cAAM,IAAI;AACV,YAAI,CAAC,GAAG,WAAY;AACpB,cAAM,QAAQ,MAAM,EAAE,UAAU;AAChC,cAAM,OAAO,EAAE,MAAM,KAAK,KAAK,MAAM;AACrC,YAAI,EAAE,MAAO,OAAM,QAAQ,EAAE;AAE7B,YAAI,CAAC,MAAM,UAAW,OAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AAC/D,cAAM,SAAS;AACf,aAAK,MAAM;AAAA,MACb,CAAC;AAAA,IACH;AAEA,SAAK,cAAc;AAAA,MACjB,GAAG,oBAAoB,CAAC,IAAI,YAAY;AACtC,cAAM,IAAI;AACV,YAAI,CAAC,GAAG,WAAY;AACpB,cAAM,QAAQ,MAAM,EAAE,UAAU;AAChC,YAAI,OAAO,EAAE,SAAS,SAAU,OAAM,SAAS,SAAS,KAAK,MAAM,EAAE,OAAO,GAAG,CAAC;AAChF,aAAK,MAAM;AAAA,MACb,CAAC;AAAA,IACH;AAEA,SAAK,cAAc;AAAA,MACjB,GAAG,yBAAyB,CAAC,IAAI,YAAY;AAC3C,cAAM,IAAI;AACV,YAAI,CAAC,GAAG,WAAY;AACpB,cAAM,QAAQ,MAAM,EAAE,UAAU;AAChC,cAAM,SAAS;AAEf,YAAI,CAAC,MAAM,UAAW,OAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AAC/D,cAAM;AACN,aAAK,MAAM;AAAA,MACb,CAAC;AAAA,IACH;AAEA,SAAK,cAAc;AAAA,MACjB,GAAG,0BAA0B,CAAC,IAAI,YAAY;AAC5C,cAAM,IAAI;AACV,YAAI,CAAC,GAAG,WAAY;AACpB,cAAM,QAAQ,MAAM,EAAE,UAAU;AAChC,cAAM,SAAS;AAEf,YAAI,CAAC,MAAM,UAAW,OAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AAC/D,cAAM,cAAc,EAAE;AACtB,cAAM;AACN,aAAK,MAAM;AAAA,MACb,CAAC;AAAA,IACH;AAEA,SAAK,cAAc;AAAA,MACjB,GAAG,8BAA8B,CAAC,IAAI,YAAY;AAChD,cAAM,IAAI;AAUV,YAAI,CAAC,GAAG,WAAY;AACpB,cAAM,QAAQ,MAAM,EAAE,UAAU;AAChC,cAAM,SAAS;AAEf,YAAI,CAAC,MAAM,UAAW,OAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AAC/D,YAAI,OAAO,EAAE,cAAc,SAAU,OAAM,aAAa,EAAE;AAC1D,YAAI,OAAO,EAAE,cAAc,SAAU,OAAM,YAAY,EAAE;AACzD,YAAI,OAAO,EAAE,YAAY,SAAU,OAAM,UAAU,EAAE;AACrD,YAAI,EAAE,YAAa,OAAM,cAAc,EAAE;AAGzC,YAAI,OAAO,EAAE,gBAAgB,UAAU;AACrC,gBAAM,cACJ,EAAE,YAAY,SAAS,mBACnB,EAAE,YAAY,MAAM,EAAE,YAAY,SAAS,gBAAgB,IAC3D,EAAE;AAAA,QACV;AACA,aAAK,MAAM;AAAA,MACb,CAAC;AAAA,IACH;AAEA,SAAK,cAAc;AAAA,MACjB,GAAG,2BAA2B,CAAC,IAAI,YAAY;AAC7C,cAAM,IAAI;AAGV,YAAI,CAAC,GAAG,WAAY;AAGpB,cAAM,QAAQ,KAAK,OAAO,IAAI,EAAE,UAAU;AAC1C,YAAI,CAAC,MAAO;AACZ,cAAM,SAAS,EAAE,WAAW,YAAY,EAAE,WAAW,YAAY,UAAU;AAC3E,cAAM,cAAc;AACpB,cAAM,cAAc;AACpB,YAAI,OAAO,EAAE,eAAe,SAAU,OAAM,aAAa,EAAE;AAC3D,YAAI,OAAO,EAAE,cAAc,SAAU,OAAM,YAAY,EAAE;AACzD,cAAM,kBAAiB,oBAAI,KAAK,GAAE,YAAY;AAC9C,aAAK,MAAM;AAAA,MACb,CAAC;AAAA,IACH;AAEA,SAAK,cAAc;AAAA,MACjB,GAAG,oBAAoB,CAAC,IAAI,YAAY;AACtC,cAAM,IAAI;AACV,YAAI,CAAC,GAAG,WAAY;AACpB,YAAI,KAAK,OAAO,OAAO,EAAE,UAAU,EAAG,MAAK,MAAM;AAAA,MACnD,CAAC;AAAA,IACH;AACA,SAAK,cAAc;AAAA,MACjB,GAAG,oBAAoB,CAAC,IAAI,YAAY;AACtC,cAAM,IAAI;AACV,YAAI,CAAC,GAAG,WAAY;AACpB,YAAI,KAAK,OAAO,OAAO,EAAE,UAAU,EAAG,MAAK,MAAM;AAAA,MACnD,CAAC;AAAA,IACH;AAKA,SAAK,aAAa,YAAY,MAAM,KAAK,MAAM,GAAG,uBAAuB;AACzE,QAAI,OAAO,KAAK,WAAW,UAAU,WAAY,MAAK,WAAW,MAAM;AAAA,EACzE;AAAA,EAEA,OAAa;AACX,eAAW,SAAS,KAAK,eAAe;AACtC,UAAI;AAAE,cAAM;AAAA,MAAG,QAAQ;AAAA,MAAe;AAAA,IACxC;AACA,SAAK,gBAAgB,CAAC;AACtB,QAAI,KAAK,YAAY;AACnB,oBAAc,KAAK,UAAU;AAC7B,WAAK,aAAa;AAAA,IACpB;AACA,QAAI,KAAK,cAAc;AACrB,mBAAa,KAAK,YAAY;AAC9B,WAAK,eAAe;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,uBAA6B;AACnC,QAAI,KAAK,aAAc;AACvB,SAAK,eAAe,WAAW,MAAM;AACnC,WAAK,eAAe;AACpB,WAAK,MAAM;AAAA,IACb,GAAG,yBAAyB;AAC5B,QAAI,OAAO,KAAK,aAAa,UAAU,WAAY,MAAK,aAAa,MAAM;AAAA,EAC7E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,QAAc;AACpB,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,UAAU;AACd,eAAW,CAAC,IAAI,CAAC,KAAK,KAAK,QAAQ;AACjC,YAAM,WAAW,EAAE,WAAW,aAAa,EAAE,WAAW,eAAe,EAAE,WAAW;AACpF,YAAM,MAAM,MAAM,KAAK,MAAM,EAAE,cAAc;AAC7C,UAAI,YAAY,OAAO,SAAS,GAAG,KAAK,MAAM,eAAe;AAC3D,aAAK,OAAO,OAAO,EAAE;AACrB,kBAAU;AAAA,MACZ;AAAA,IACF;AACA,QAAI,QAAS,MAAK,MAAM;AAAA,EAC1B;AAAA,EAEQ,QAAc;AACpB,UAAM,cAA0B;AAAA,MAC9B,IAAI;AAAA,MACJ,MAAM,KAAK;AAAA,MACX,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK;AAAA,MACb,aAAa,KAAK;AAAA,MAClB,YAAY,KAAK;AAAA,MACjB,WAAW,KAAK;AAAA,MAChB,SAAS,KAAK;AAAA,MACd,UAAU,KAAK;AAAA,MACf,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK;AAAA,MACb,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK,qBAAqB;AAAA,MACvC,iBAAgB,oBAAI,KAAK,GAAE,YAAY;AAAA,IACzC;AAEA,UAAM,YAAY,CAAC,aAAa,GAAG,KAAK,OAAO,OAAO,CAAC;AACvD,SAAK,aAAa;AAIlB,QAAI;AACF,WAAK,OAAO,KAAK,0BAA0B;AAAA,QACzC,WAAW,KAAK,iBAAiB;AAAA,QACjC,QAAQ;AAAA,MACV,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAGA,SAAK,SACF,aAAa,SAAS,EACtB,KAAK,MAAM;AACV,UAAI;AACF,aAAK,WAAW;AAAA,MAClB,QAAQ;AAAA,MAER;AAAA,IACF,CAAC,EACA,MAAM,MAAM,MAAS;AAAA,EAC1B;AAAA,EAEQ,mBAAuC;AAC7C,WAAO,OAAO,KAAK,cAAc,aAAa,KAAK,UAAU,IAAI,KAAK;AAAA,EACxE;AAAA,EAEQ,eAAe,SAA2B;AAChD,UAAM,WAAW,KAAK,iBAAiB;AACvC,QAAI,CAAC,SAAU,QAAO;AACtB,QAAI,OAAO,YAAY,YAAY,YAAY,KAAM,QAAO;AAC5D,UAAM,SAAU,QAAoC;AACpD,WAAO,OAAO,WAAW,YAAY,OAAO,WAAW,KAAK,WAAW;AAAA,EACzE;AAAA,EAEQ,kBAAkB,WAA0B;AAClD,QACE,KAAK,oBACJ,KAAK,iBAAiB,aACrB,KAAK,iBAAiB,eACtB,KAAK,iBAAiB,iBACxB;AACA;AAAA,IACF;AACA,SAAK,kBAAkB,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,EAC7D;AAAA,EAEQ,qBAAqB,KAAoB;AAC/C,QAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM;AAC7C,UAAM,IAAI;AAMV,QAAI,OAAO,EAAE,UAAU,YAAY,EAAE,MAAM,SAAS,EAAG,MAAK,cAAc,EAAE;AAE5E,UAAM,YAAY,EAAE,OAAO,qBAAqB;AAChD,UAAM,cAAc,EAAE,UAAU,cAAc;AAC9C,UAAM,aACJ,OAAO,cAAc,YAAY,YAAY,IACzC,YACA,OAAO,gBAAgB,YAAY,cAAc,IAC/C,cACA;AACR,QACE,OAAO,EAAE,sBAAsB,YAC/B,EAAE,oBAAoB,KACtB,eAAe,QACf;AACA,WAAK,eAAe,SAAS,KAAK,MAAO,EAAE,oBAAoB,aAAc,GAAG,CAAC;AAAA,IACnF;AAAA,EACF;AACF;;;ACvgBA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAEtB,IAAM,iBAAiB;AACvB,IAAM,mBAAmB;AACzB,IAAM,cAAc;AACpB,IAAM,kBAAkB;AASxB,SAASC,UAAS,KAAsB;AACtC,MAAI,CAAC,OAAO,UAAU,GAAG,KAAK,OAAO,EAAG,QAAO;AAC/C,MAAI;AACF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,WAAQ,IAA8B,SAAS;AAAA,EACjD;AACF;AAGA,SAAS,SAAS,MAAsB;AACtC,QAAM,WAAgB,eAAQ,IAAI;AAClC,SAAO,QAAQ,aAAa,UAAU,SAAS,YAAY,IAAI;AACjE;AAaO,IAAM,gBAAN,MAAoB;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,QAA+C;AAAA,EAC/C,QAA8C;AAAA,EAC9C,WAAW;AAAA,EAEnB,YAAY,MAA4B;AACtC,SAAK,UAAU,KAAK;AACpB,SAAK,cAAc,SAAS,KAAK,WAAW;AAC5C,SAAK,UAAU,KAAK,WAAW,QAAQ;AACvC,SAAK,SAAS,KAAK,QAAQ;AAAA,EAC7B;AAAA;AAAA,EAGA,SAAe;AACb,QAAI,KAAK,YAAY,KAAK,MAAO;AACjC,SAAK,QAAQ,WAAW,MAAM;AAC5B,WAAK,QAAQ;AACb,WAAK,KAAK,MAAM;AAAA,IAClB,GAAG,WAAW;AAEd,QAAI,OAAO,KAAK,MAAM,UAAU,WAAY,MAAK,MAAM,MAAM;AAAA,EAC/D;AAAA;AAAA,EAGA,MAAM,YAA+B;AACnC,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,KAAK,SAAS,MAAM,KAAK,MAAM,KAAK,iBAAkB,QAAO,KAAK,MAAM;AAC5E,UAAM,OAAO,MAAM,KAAK,SAAS;AACjC,SAAK,QAAQ,EAAE,IAAI,KAAK,KAAK;AAC7B,WAAO;AAAA,EACT;AAAA,EAEA,UAAgB;AACd,SAAK,WAAW;AAChB,QAAI,KAAK,OAAO;AACd,mBAAa,KAAK,KAAK;AACvB,WAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA,EAEA,MAAc,QAAuB;AACnC,UAAM,OAAO,MAAM,KAAK,UAAU;AAClC,UAAM,QAAQ,IAAI,KAAK,IAAI,CAAC,MAAM,KAAK,OAAO,CAAC,EAAE,MAAM,MAAM,MAAS,CAAC,CAAC;AAAA,EAC1E;AAAA,EAEA,MAAc,WAA8B;AAC1C,QAAI;AACF,YAAM,MAAM,MAAS,cAAc,YAAK,KAAK,SAAS,cAAc,GAAG,MAAM;AAC7E,YAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,YAAM,OAAO,MAAM,QAAQ,MAAM,SAAS,IAAI,KAAK,YAAY,CAAC;AAChE,aAAO,KACJ,OAAO,CAAC,MAAM,KAAK,OAAO,EAAE,aAAa,QAAQ,EACjD,OAAO,CAAC,MAAM,EAAE,QAAQ,KAAK,OAAO,EACpC,OAAO,CAAC,MAAM,SAAS,EAAE,WAAW,MAAM,KAAK,WAAW,EAC1D,OAAO,CAAC,MAAMA,UAAS,EAAE,GAAG,CAAC,EAC7B,IAAI,CAAC,MAAM;AACV,cAAM,OAAO,EAAE,SAAS,aAAa,EAAE,SAAS,QAAQ,CAAC,EAAE,OAAO,cAAc,EAAE;AAClF,eAAO,UAAU,IAAI,IAAI,EAAE,QAAQ;AAAA,MACrC,CAAC;AAAA,IACL,QAAQ;AAEN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AACF;AAEA,eAAe,YAAY,KAA4B;AACrD,QAAM,MAAM,KAAK;AAAA,IACf,QAAQ;AAAA,IACR,QAAQ,YAAY,QAAQ,eAAe;AAAA,EAC7C,CAAC;AACH;;;AC7IA,YAAYC,UAAS;AACrB,YAAYC,YAAU;AAiBf,IAAM,yBAAN,MAAwD;AAAA,EAC7D,YAA6B,aAAsC,aAAqB;AAA3D;AAAsC;AAAA,EAAsB;AAAA,EAA5D;AAAA,EAAsC;AAAA,EAE3D,YAAY,WAA2B;AAC7C,WAAO,kBAAkB,KAAK,aAAa,WAAW,QAAQ;AAAA,EAChE;AAAA,EAEA,MAAM,gBAAgB,WAA8C;AAClE,UAAM,OAAO,KAAK,YAAY,SAAS;AACvC,UAAM,MAAM,MAAU,cAAS,MAAM,MAAM;AAC3C,UAAM,SAAS,YAAY,GAAG;AAG9B,UAAM,eAAe,oBAAI,IAAoB;AAC7C,eAAW,SAAS,QAAQ;AAC1B,UAAI,MAAM,SAAS,iBAAiB;AAClC,cAAM,IAAI;AACV,qBAAa,IAAI,EAAE,cAAc,aAAa,IAAI,EAAE,WAAW,KAAK,KAAK,EAAE,MAAM,MAAM;AAAA,MACzF;AAAA,IACF;AAEA,UAAM,cAAgC,CAAC;AACvC,eAAW,SAAS,QAAQ;AAC1B,UAAI,MAAM,SAAS,cAAc;AAC/B,cAAM,IAAI;AACV,oBAAY,KAAK;AAAA,UACf,aAAa,EAAE;AAAA,UACf,eAAe,EAAE;AAAA,UACjB,IAAI,EAAE;AAAA,UACN,WAAW,aAAa,IAAI,EAAE,WAAW,KAAK;AAAA,QAChD,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,mBACJ,WACA,iBAC+B;AAC/B,UAAM,OAAO,KAAK,YAAY,SAAS;AACvC,UAAM,MAAM,MAAU,cAAS,MAAM,MAAM;AAC3C,UAAM,SAAS,YAAY,GAAG;AAE9B,QAAI,YAAY;AAChB,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,YAAM,QAAQ,cAAc,OAAO,CAAC,CAAC;AACrC,UAAI,MAAM,SAAS,cAAc;AAC/B,cAAM,kBAAkB;AACxB,YAAI,gBAAgB,gBAAgB,iBAAiB;AACnD,sBAAY;AACZ;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,cAAc,IAAI;AACpB,YAAM,IAAI,aAAa;AAAA,QACrB,SAAS,cAAc,eAAe;AAAA,QACtC,MAAM,YAAY;AAAA,QAClB,SAAS,EAAE,gBAAgB;AAAA,MAC7B,CAAC;AAAA,IACH;AAEA,UAAM,oBAA2E,CAAC;AAClF,aAAS,IAAI,YAAY,GAAG,IAAI,OAAO,QAAQ,KAAK;AAClD,YAAM,QAAQ,cAAc,OAAO,CAAC,CAAC;AACrC,UAAI,MAAM,SAAS,cAAc;AAC/B;AAAA,MACF;AACA,UAAI,MAAM,SAAS,iBAAiB;AAClC,cAAM,gBAAgB;AACtB,YAAI,cAAc,eAAe,iBAAiB;AAChD,4BAAkB,KAAK,EAAE,aAAa,cAAc,aAAa,OAAO,cAAc,MAAM,CAAC;AAAA,QAC/F;AAAA,MACF;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,gBAAgB,mBAAmB,KAAK,WAAW;AACxE,UAAM,gBAAgB,OAAO,SAAS,YAAY;AAClD,WAAO,EAAE,GAAG,QAAQ,eAAe,iBAAiB,cAAc;AAAA,EACpE;AAAA,EAEA,MAAM,YAAY,WAAmB,GAA0C;AAC7E,UAAM,OAAO,KAAK,YAAY,SAAS;AACvC,UAAM,MAAM,MAAU,cAAS,MAAM,MAAM;AAC3C,UAAM,SAAS,YAAY,GAAG;AAE9B,UAAM,cAA0D,CAAC;AACjE,eAAW,SAAS,QAAQ;AAC1B,UAAI,MAAM,SAAS,cAAc;AAC/B,oBAAY,KAAK,EAAE,aAAa,MAAM,aAAa,IAAI,MAAM,GAAG,CAAC;AAAA,MACnE;AAAA,IACF;AAEA,QAAI,YAAY,WAAW,GAAG;AAC5B,aAAO,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,GAAG,eAAe,GAAG,eAAe,EAAE;AAAA,IAC7E;AAEA,gBAAY,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,EAAE,WAAW;AACxD,UAAM,cAAc,YAAY,CAAC,GAAG,eAAe;AAEnD,UAAM,oBAA2E,CAAC;AAClF,QAAI,eAAe;AAEnB,eAAW,SAAS,QAAQ;AAC1B,UAAI,MAAM,SAAS,gBAAgB,MAAM,gBAAgB,aAAa;AACpE,uBAAe;AACf;AAAA,MACF;AACA,UAAI,gBAAgB,MAAM,SAAS,iBAAiB;AAClD,0BAAkB,KAAK,EAAE,aAAa,MAAM,aAAa,OAAO,MAAM,MAAM,CAAC;AAAA,MAC/E;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,gBAAgB,mBAAmB,KAAK,WAAW;AACxE,WAAO,EAAE,GAAG,QAAQ,eAAe,aAAa,eAAe,kBAAkB,OAAO;AAAA,EAC1F;AAAA,EAEA,MAAM,cAAc,WAAkD;AACpE,UAAM,OAAO,KAAK,YAAY,SAAS;AACvC,UAAM,MAAM,MAAU,cAAS,MAAM,MAAM;AAC3C,UAAM,SAAS,YAAY,GAAG;AAE9B,UAAM,eAAsE,CAAC;AAC7E,eAAW,SAAS,QAAQ;AAC1B,UAAI,MAAM,SAAS,iBAAiB;AAClC,qBAAa,KAAK,EAAE,aAAa,MAAM,aAAa,OAAO,MAAM,MAAM,CAAC;AAAA,MAC1E;AAAA,IACF;AAEA,QAAI,aAAa,WAAW,GAAG;AAC7B,aAAO,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,GAAG,eAAe,GAAG,eAAe,EAAE;AAAA,IAC7E;AAEA,UAAM,SAAS,MAAM,gBAAgB,cAAc,KAAK,WAAW;AACnE,WAAO,EAAE,GAAG,QAAQ,eAAe,GAAG,eAAe,aAAa,OAAO;AAAA,EAC3E;AACF;AAEA,SAAS,YAAY,KAA6B;AAChD,QAAM,QAAQ,IAAI,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;AACpD,QAAM,SAAyB,CAAC;AAEhC,aAAW,QAAQ,OAAO;AACxB,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,UACE,WAAW,QACX,OAAO,WAAW,YAClB,OAAQ,OAA0C,SAAS,YAC3D,OAAQ,OAAwC,OAAO,UACvD;AACA,eAAO,KAAK,MAAsB;AAAA,MACpC;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAe,gBACb,WACA,aACuB;AACvB,QAAM,gBAA0B,CAAC;AACjC,QAAM,SAAmB,CAAC;AAK1B,aAAW,YAAY,CAAC,GAAG,SAAS,EAAE,QAAQ,GAAG;AAC/C,eAAW,QAAQ,CAAC,GAAG,SAAS,KAAK,EAAE,QAAQ,GAAG;AAChD,UAAI;AAKF,cAAM,UAAe,eAAQ,KAAK,IAAI;AACtC,cAAM,OAAY,eAAQ,WAAW;AACrC,cAAM,MAAW,gBAAS,MAAM,OAAO;AACvC,YAAI,IAAI,WAAW,IAAI,KAAU,kBAAW,GAAG,GAAG;AAChD,iBAAO,KAAK,GAAG,KAAK,IAAI,sDAAiD;AACzE;AAAA,QACF;AAEA,YAAI,KAAK,WAAW,WAAW;AAE7B,cAAI,KAAK,WAAW,MAAM;AAExB,kBAAM,YAAY,KAAK,MAAM,KAAK,QAAQ,EAAE,MAAM,IAAM,CAAC;AACzD,0BAAc,KAAK,KAAK,IAAI;AAAA,UAC9B;AAAA,QACF,WAAW,KAAK,WAAW,WAAW;AAEpC,gBAAU,YAAO,KAAK,IAAI;AAC1B,wBAAc,KAAK,KAAK,IAAI;AAAA,QAC9B,WAAW,KAAK,WAAW,YAAY;AAErC,cAAI,KAAK,WAAW,MAAM;AAExB,kBAAM,YAAY,KAAK,MAAM,KAAK,QAAQ,EAAE,MAAM,IAAM,CAAC;AACzD,0BAAc,KAAK,KAAK,IAAI;AAAA,UAC9B;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,eAAO,KAAK,GAAG,KAAK,IAAI,KAAK,eAAe,GAAG,CAAC,EAAE;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,eAAe,OAAO;AACjC;;;AC3OA,YAAYC,UAAS;AA6BrB,eAAsB,oBACpB,UACA,QACA,SAC4B;AAC5B,QAAM,KAAK,KAAK,IAAI;AACpB,MAAI;AACJ,MAAI;AACF,UAAM,MAAU,cAAS,UAAU,MAAM;AAAA,EAC3C,SAAS,KAAK;AACZ,YAAQ,KAAK,iBAAiB;AAAA,MAC5B,WAAW,WAAW;AAAA,MACtB,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,OAAO,eAAe,GAAG;AAAA,MACzB,aAAa;AAAA,IACf,CAAC;AACD,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,QAAQ,YAAY,KAAK,CAAC,MAAM,QAAQ,OAAO,KAAK,GAAG;AACzD,cAAQ,KAAK,gBAAgB;AAAA,QAC3B,WAAW,WAAW;AAAA,QACtB,OAAO;AAAA,QACP;AAAA,QACA,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,OAAO;AAAA,QACP,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,MACzC,CAAC;AACD,aAAO;AAAA,IACT;AACA,YAAQ,KAAK,gBAAgB;AAAA,MAC3B,WAAW,WAAW;AAAA,MACtB,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,MACzB,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD,WAAO,OAAO,MAAM;AAAA,MAClB,CAAC,MACC,CAAC,CAAC,KACF,OAAO,EAAE,OAAO,YAChB,OAAO,EAAE,YAAY,YACrB,OAAO,EAAE,WAAW,aACnB,EAAE,eAAe,UAAa,OAAO,EAAE,eAAe;AAAA,IAC3D;AAAA,EACF,QAAQ;AACN,YAAQ,KAAK,gBAAgB;AAAA,MAC3B,WAAW,WAAW;AAAA,MACtB,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,MACzB,OAAO;AAAA,MACP,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,oBACpB,UACA,WACA,OACA,QACA,SACe;AACf,QAAM,KAAK,KAAK,IAAI;AACpB,QAAM,UAA+B;AAAA,IACnC,SAAS;AAAA,IACT;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,OAAO,CAAC,GAAG,KAAK;AAAA,EAClB;AACA,MAAI;AACF,UAAM,YAAY,UAAU,KAAK,UAAU,SAAS,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AAC7E,YAAQ,KAAK,iBAAiB;AAAA,MAC5B,WAAW,WAAW;AAAA,MACtB,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,MACzB,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,YAAQ,KAAK,iBAAiB;AAAA,MAC5B,WAAW,WAAW;AAAA,MACtB,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,OAAO,eAAe,GAAG;AAAA,MACzB,aAAa;AAAA,IACf,CAAC;AACD,YAAQ,KAAK,KAAK,UAAU;AAAA,MAC1B,OAAO;AAAA,MACP,OAAO;AAAA,MACP,SAAS,eAAe,GAAG;AAAA,MAC3B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,CAAC,CAAC;AAAA,EACJ;AACF;AAUO,SAAS,sBACd,OACA,UACA,WACA,QACA,SACuB;AACvB,MAAI,QAA+B;AACnC,MAAI,UAAsC;AAC1C,MAAI,aAA4B,QAAQ,QAAQ;AAEhD,QAAM,eAAe,CAAC,UAA+B;AACnD,iBAAa,WACV,KAAK,MAAM,oBAAoB,UAAU,WAAW,OAAO,QAAQ,OAAO,CAAC,EAE3E,MAAM,CAAC,QAAQ;AAGd,YAAM,MAAM,eAAe,GAAG;AAC9B,cAAQ,MAAM,KAAK,UAAU;AAAA,QAC3B,OAAO;AAAA,QACP,OAAO;AAAA,QACP;AAAA,QACA,SAAS;AAAA,QACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC,CAAC;AAAA,IACJ,CAAC;AAEH,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,MAAM;AAClB,YAAQ;AACR,QAAI,SAAS;AACX,YAAM,QAAQ;AACd,gBAAU;AACV,aAAO,aAAa,KAAK;AAAA,IAC3B;AAEA,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,MAAM,SAAS,CAAC,WAAW;AAC7C,QAAI,OAAO,SAAS,iBAAkB;AACtC,cAAU,OAAO;AACjB,QAAI,MAAO,cAAa,KAAK;AAC7B,YAAQ,WAAW,MAAM;AACvB,WAAK,MAAM;AAAA,IACb,GAAG,GAAG;AAAA,EACR,CAAC;AACD,SAAO,YAAY;AACjB,gBAAY;AACZ,QAAI,OAAO;AACT,mBAAa,KAAK;AAGlB,YAAM,MAAM;AAAA,IACd,OAAO;AACL,YAAM;AAAA,IACR;AAAA,EACF;AACF;;;ACtNA,YAAYC,WAAS;AAarB,eAAsB,4BACpB,UACA,QACA,SACyC;AACzC,QAAM,KAAK,KAAK,IAAI;AACpB,MAAI;AACJ,MAAI;AACF,UAAM,MAAU,eAAS,UAAU,MAAM;AAAA,EAC3C,SAAS,KAAK;AACZ,YAAQ,KAAK,iBAAiB;AAAA,MAC5B,WAAW,WAAW;AAAA,MACtB,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,OAAO,eAAe,GAAG;AAAA,MACzB,aAAa;AAAA,IACf,CAAC;AACD,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,QAAQ,YAAY,KAAK,CAAC,MAAM,QAAQ,OAAO,aAAa,GAAG;AACjE,cAAQ,KAAK,gBAAgB;AAAA,QAC3B,WAAW,WAAW;AAAA,QACtB,OAAO;AAAA,QACP;AAAA,QACA,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,OAAO;AAAA,QACP,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,MACzC,CAAC;AACD,aAAO;AAAA,IACT;AACA,YAAQ,KAAK,gBAAgB;AAAA,MAC3B,WAAW,WAAW;AAAA,MACtB,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,MACzB,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD,WAAO,OAAO,cAAc,OAAO,uBAAuB;AAAA,EAC5D,QAAQ;AACN,YAAQ,KAAK,gBAAgB;AAAA,MAC3B,WAAW,WAAW;AAAA,MACtB,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,MACzB,OAAO;AAAA,MACP,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,4BACpB,UACA,WACA,eACA,QACA,SACe;AACf,QAAM,KAAK,KAAK,IAAI;AACpB,QAAM,UAAuC;AAAA,IAC3C,SAAS;AAAA,IACT;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,eAAe,CAAC,GAAG,aAAa;AAAA,EAClC;AACA,MAAI;AACF,UAAM,YAAY,UAAU,KAAK,UAAU,SAAS,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AAC7E,YAAQ,KAAK,iBAAiB;AAAA,MAC5B,WAAW,WAAW;AAAA,MACtB,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,MACzB,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,YAAQ,KAAK,iBAAiB;AAAA,MAC5B,WAAW,WAAW;AAAA,MACtB,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,OAAO,eAAe,GAAG;AAAA,MACzB,aAAa;AAAA,IACf,CAAC;AACD,YAAQ,KAAK,KAAK,UAAU;AAAA,MAC1B,OAAO;AAAA,MACP,OAAO;AAAA,MACP,SAAS,eAAe,GAAG;AAAA,MAC3B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,CAAC,CAAC;AAAA,EACJ;AACF;AAEA,SAAS,wBAAwB,OAAgD;AAC/E,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,OAAO;AACb,SACE,OAAO,KAAK,QAAQ,YACpB,OAAO,KAAK,WAAW,YACvB,CAAC,QAAQ,QAAQ,QAAQ,gBAAgB,QAAQ,EAAE,SAAS,KAAK,MAAM,KACvE,OAAO,KAAK,YAAY,YACxB,OAAO,KAAK,gBAAgB,aAC3B,KAAK,aAAa,UAAa,OAAO,KAAK,aAAa;AAE7D;;;ACjIA,YAAYC,WAAS;AACrB,SAAS,cAAAC,mBAAkB;AAmC3B,eAAsB,SAAS,UAAkB,QAA6C;AAC5F,QAAM,KAAK,KAAK,IAAI;AACpB,MAAI;AACJ,MAAI;AACF,UAAM,MAAU,eAAS,UAAU,MAAM;AAAA,EAC3C,SAAS,KAAK;AACZ,YAAQ,KAAK,iBAAiB;AAAA,MAC5B,WAAW;AAAA,MACX,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,OAAO,eAAe,GAAG;AAAA,MACzB,aAAa;AAAA,IACf,CAAC;AACD,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,QAAQ,YAAY,KAAK,CAAC,MAAM,QAAQ,OAAO,KAAK,GAAG;AACzD,cAAQ,KAAK,gBAAgB;AAAA,QAC3B,WAAW;AAAA,QACX,OAAO;AAAA,QACP;AAAA,QACA,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,OAAO;AAAA,MACT,CAAC;AACD,aAAO;AAAA,IACT;AACA,YAAQ,KAAK,gBAAgB;AAAA,MAC3B,WAAW;AAAA,MACX,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,IAC3B,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AACN,YAAQ,KAAK,gBAAgB;AAAA,MAC3B,WAAW;AAAA,MACX,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,MACzB,OAAO;AAAA,IACT,CAAC;AACD,WAAO;AAAA,EACT;AACF;AASA,eAAsB,SAAS,UAAkB,MAAgB,QAAqC;AACpG,QAAM,KAAK,KAAK,IAAI;AACpB,MAAI;AACF,UAAM,YAAY,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AAC1E,YAAQ,KAAK,iBAAiB;AAAA,MAC5B,WAAW;AAAA,MACX,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,IAC3B,CAAC;AACD,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ,KAAK,iBAAiB;AAAA,MAC5B,WAAW;AAAA,MACX,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,OAAO,eAAe,GAAG;AAAA,MACzB,aAAa;AAAA,IACf,CAAC;AACD,YAAQ;AAAA,MACN;AAAA,MACA,eAAe,GAAG;AAAA,IACpB;AACA,WAAO;AAAA,EACT;AACF;AAGO,SAAS,UAAU,WAAmB,OAA0B;AACrE,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,OAAO,CAAC;AAAA,EACV;AACF;AAEO,SAAS,YACd,MACA,OACA,SACoC;AACpC,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,QAAM,OAAiB;AAAA,IACrB,IAAI,QAAQ,KAAK,IAAI,CAAC,IAAIC,YAAW,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,IAClD;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,WAAW;AAAA,EACb;AACA,SAAO;AAAA,IACL,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,GAAG,KAAK,OAAO,IAAI,GAAG,WAAW,IAAI;AAAA,IAC9D;AAAA,EACF;AACF;AAEO,SAAS,eAAe,MAAgB,WAA6B;AAC1E,QAAM,MAAM,WAAW,MAAM,SAAS;AACtC,MAAI,QAAQ,GAAI,QAAO;AACvB,SAAO;AAAA,IACL,GAAG;AAAA,IACH,OAAO,KAAK,MAAM,OAAO,CAAC,GAAG,MAAM,MAAM,GAAG;AAAA,IAC5C,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC;AACF;AAEO,SAAS,kBACd,MACA,WACA,QACU;AACV,QAAM,MAAM,WAAW,MAAM,SAAS;AACtC,MAAI,QAAQ,GAAI,QAAO;AACvB,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,QAAM,QAAQ,KAAK,MAAM;AAAA,IAAI,CAAC,IAAI,MAChC,MAAM,MAAM,EAAE,GAAG,IAAI,QAAQ,WAAW,IAAI,IAAI;AAAA,EAClD;AACA,SAAO,EAAE,GAAG,MAAM,OAAO,WAAW,IAAI;AAC1C;AAEO,SAAS,UAAU,MAA0B;AAClD,SAAO,EAAE,GAAG,MAAM,OAAO,CAAC,GAAG,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE;AACnE;AAGO,SAAS,WAAW,MAAwB;AACjD,MAAI,KAAK,MAAM,WAAW,EAAG,QAAO;AACpC,QAAM,QAAkB,CAAC;AACzB,MAAI,KAAK,MAAO,OAAM,KAAK,KAAK,KAAK,KAAK,EAAE;AAC5C,OAAK,MAAM,QAAQ,CAAC,IAAI,MAAM;AAC5B,UAAM,OAAO,GAAG,WAAW,SAAS,QAAQ,GAAG,WAAW,gBAAgB,QAAQ;AAClF,UAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,GAAG,KAAK,EAAE;AAC1C,QAAI,GAAG,SAAS;AACd,iBAAW,QAAQ,GAAG,QAAQ,MAAM,IAAI,EAAG,OAAM,KAAK,QAAQ,IAAI,EAAE;AAAA,IACtE;AAAA,EACF,CAAC;AACD,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,SAAS,WAAW,MAAgB,WAA2B;AAC7D,QAAM,QAAQ,OAAO,SAAS,WAAW,EAAE;AAC3C,MAAI,CAAC,OAAO,MAAM,KAAK,KAAK,SAAS,KAAK,SAAS,KAAK,MAAM,OAAQ,QAAO,QAAQ;AACrF,QAAM,OAAO,KAAK,MAAM,UAAU,CAAC,OAAO,GAAG,OAAO,SAAS;AAC7D,MAAI,SAAS,GAAI,QAAO;AACxB,QAAM,QAAQ,UAAU,YAAY;AACpC,SAAO,KAAK,MAAM,UAAU,CAAC,OAAO,GAAG,MAAM,YAAY,EAAE,SAAS,KAAK,CAAC;AAC5E;AASO,SAAS,wBACd,MACA,WACA,UACmM;AACnM,QAAM,MAAM,WAAW,MAAM,SAAS;AACtC,MAAI,QAAQ,GAAI,QAAO;AAEvB,QAAM,OAAO,KAAK,MAAM,GAAG;AAE3B,MAAI,CAAC,KAAM,QAAO;AAGlB,MAAI,cAAc;AAClB,MAAI,KAAK,WAAW,QAAQ;AAC1B,kBAAc,kBAAkB,MAAM,WAAW,aAAa;AAAA,EAChE;AAEA,QAAM,QAAyK,CAAC;AAGhL,QAAM,KAAK;AAAA,IACT,IAAI,QAAQ,KAAK,IAAI,CAAC;AAAA,IACtB,SAAS,KAAK;AAAA,IACd,QAAQ;AAAA,IACR,YAAY,KAAK;AAAA,IACjB,kBAAkB,KAAK;AAAA,EACzB,CAAC;AAGD,MAAI,YAAY,SAAS,SAAS,GAAG;AACnC,eAAW,MAAM,UAAU;AACzB,YAAM,KAAK;AAAA,QACT,IAAI,QAAQ,KAAK,IAAI,CAAC,IAAIA,YAAW,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,QAClD,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,kBAAkB,KAAK;AAAA,MACzB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,aAAa,MAAM;AACpC;AAMA,eAAsB,WACpB,UACA,WACA,IACmB;AACnB,SAAO,aAAa,UAAU,YAAY;AACxC,UAAM,OAAQ,MAAM,SAAS,QAAQ,KAAM,UAAU,SAAS;AAC9D,UAAM,UAAU,MAAM,GAAG,IAAI;AAC7B,UAAM,YAAY,MAAM,SAAS,UAAU,OAAO;AAClD,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,aAAa;AAAA,QACrB,SAAS,6BAA6B,QAAQ;AAAA,QAC9C,MAAM;AAAA,QACN;AAAA,QACA,SAAS,EAAE,UAAU,WAAW,aAAa;AAAA,MAC/C,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT,CAAC;AACH;AAQO,SAAS,qBACd,QACA,WACA,YACY;AACZ,SAAO,MAAM;AACf;;;ACtRA,IAAM,YAA0C;AAAA,EAC9C,eAAe;AAAA,IACb,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,OAAO;AAAA,MACL,EAAE,OAAO,oCAAoC,SAAS,gDAAgD;AAAA,MACtG,EAAE,OAAO,yBAAyB,SAAS,8BAA8B;AAAA,MACzE,EAAE,OAAO,wBAAwB,SAAS,yCAAoC;AAAA,MAC9E,EAAE,OAAO,kBAAkB,SAAS,+BAA+B;AAAA,MACnE,EAAE,OAAO,yBAAyB,SAAS,sCAAsC;AAAA,MACjF,EAAE,OAAO,wBAAwB,SAAS,8BAA8B;AAAA,MACxE,EAAE,OAAO,eAAe,SAAS,uCAAuC;AAAA,MACxE,EAAE,OAAO,oBAAoB,SAAS,wBAAwB;AAAA,IAChE;AAAA,EACF;AAAA,EACA,WAAW;AAAA,IACT,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,OAAO;AAAA,MACL,EAAE,OAAO,qBAAqB,SAAS,4BAA4B;AAAA,MACnE,EAAE,OAAO,uBAAuB,SAAS,+BAA+B;AAAA,MACxE,EAAE,OAAO,sBAAsB,SAAS,4BAA4B;AAAA,MACpE,EAAE,OAAO,iBAAiB,SAAS,2BAA2B;AAAA,MAC9D,EAAE,OAAO,cAAc,SAAS,4CAA4C;AAAA,MAC5E,EAAE,OAAO,mBAAmB,SAAS,iCAAiC;AAAA,MACtE,EAAE,OAAO,yBAAyB,SAAS,iCAAiC;AAAA,IAC9E;AAAA,EACF;AAAA,EACA,YAAY;AAAA,IACV,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,OAAO;AAAA,MACL,EAAE,OAAO,+BAA+B,SAAS,mDAAmD;AAAA,MACpG,EAAE,OAAO,wBAAwB,SAAS,4CAA4C;AAAA,MACtF,EAAE,OAAO,gCAAgC,SAAS,yCAAyC;AAAA,MAC3F,EAAE,OAAO,qBAAqB,SAAS,gCAAgC;AAAA,MACvE,EAAE,OAAO,uBAAuB,SAAS,sBAAsB;AAAA,MAC/D,EAAE,OAAO,qBAAqB,SAAS,uBAAuB;AAAA,MAC9D,EAAE,OAAO,eAAe,SAAS,qCAAqC;AAAA,IACxE;AAAA,EACF;AAAA,EACA,WAAW;AAAA,IACT,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,OAAO;AAAA,MACL,EAAE,OAAO,gBAAgB,SAAS,gCAAgC;AAAA,MAClE,EAAE,OAAO,oBAAoB,SAAS,iCAAiC;AAAA,MACvE,EAAE,OAAO,uBAAuB,SAAS,2BAA2B;AAAA,MACpE,EAAE,OAAO,mBAAmB,SAAS,mCAAmC;AAAA,MACxE,EAAE,OAAO,uBAAuB,SAAS,4BAA4B;AAAA,MACrE,EAAE,OAAO,qBAAqB,SAAS,uBAAuB;AAAA,MAC9D,EAAE,OAAO,4BAA4B,SAAS,6BAA6B;AAAA,MAC3E,EAAE,OAAO,oBAAoB,SAAS,qCAAqC;AAAA,IAC7E;AAAA,EACF;AAAA,EACA,kBAAkB;AAAA,IAChB,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,OAAO;AAAA,MACL,EAAE,OAAO,oBAAoB,SAAS,qCAAqC;AAAA,MAC3E,EAAE,OAAO,eAAe,SAAS,yCAAyC;AAAA,MAC1E,EAAE,OAAO,yBAAyB,SAAS,8BAA8B;AAAA,MACzE,EAAE,OAAO,0BAA0B,SAAS,qCAAqC;AAAA,MACjF,EAAE,OAAO,yBAAyB,SAAS,2CAA2C;AAAA,MACtF,EAAE,OAAO,0BAA0B,SAAS,4BAA4B;AAAA,MACxE,EAAE,OAAO,0BAA0B,SAAS,iCAAiC;AAAA,IAC/E;AAAA,EACF;AAAA,EACA,cAAc;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,OAAO;AAAA,MACL,EAAE,OAAO,qBAAqB,SAAS,4BAA4B;AAAA,MACnE,EAAE,OAAO,2BAA2B,SAAS,kCAAkC;AAAA,MAC/E,EAAE,OAAO,qBAAqB,SAAS,qBAAqB;AAAA,MAC5D,EAAE,OAAO,0BAA0B,SAAS,gCAAgC;AAAA,MAC5E,EAAE,OAAO,gBAAgB,SAAS,gCAAgC;AAAA,MAClE,EAAE,OAAO,4BAA4B,SAAS,mCAAmC;AAAA,MACjF,EAAE,OAAO,qBAAqB,SAAS,sBAAsB;AAAA,IAC/D;AAAA,EACF;AACF;AAEO,SAAS,oBAAoC;AAClD,SAAO,OAAO,OAAO,SAAS;AAChC;AAEO,SAAS,gBAAgB,MAAwC;AACtE,SAAO,UAAU,IAAI;AACvB;AAEO,SAAS,sBAA8B;AAC5C,QAAM,OAAO,oBAAI,IAA8C;AAC/D,aAAW,KAAK,OAAO,OAAO,SAAS,GAAG;AACxC,UAAM,MAAM,KAAK,IAAI,EAAE,QAAQ,KAAK,CAAC;AACrC,QAAI,KAAK,CAAC;AACV,SAAK,IAAI,EAAE,UAAU,GAAG;AAAA,EAC1B;AAEA,QAAM,QAAkB,CAAC,2BAA2B;AACpD,aAAW,CAAC,KAAK,KAAK,KAAK,MAAM;AAC/B,UAAM,KAAK;AAAA,EAAK,GAAG,GAAG;AACtB,eAAW,KAAK,OAAO;AACrB,YAAM,KAAK,KAAK,EAAE,KAAK,OAAO,EAAE,CAAC,WAAM,EAAE,WAAW,EAAE;AAAA,IACxD;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;;;ACpIA,YAAYC,WAAS;AAuBd,SAAS,cAAc,WAA6B;AACzD,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,OAAO,CAAC;AAAA,EACV;AACF;AAGA,eAAsB,UACpB,UACA,QACA,SAC0B;AAC1B,QAAM,KAAK,KAAK,IAAI;AACpB,MAAI;AACJ,MAAI;AACF,UAAM,MAAU,eAAS,UAAU,MAAM;AAAA,EAC3C,SAAS,KAAK;AACZ,YAAQ,KAAK,iBAAiB;AAAA,MAC5B,WAAW,WAAW;AAAA,MACtB,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,OAAO,eAAe,GAAG;AAAA,MACzB,aAAa;AAAA,IACf,CAAC;AACD,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,QAAQ,YAAY,KAAK,CAAC,MAAM,QAAQ,OAAO,KAAK,GAAG;AACzD,cAAQ,KAAK,gBAAgB;AAAA,QAC3B,WAAW,WAAW;AAAA,QACtB,OAAO;AAAA,QACP;AAAA,QACA,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,OAAO;AAAA,QACP,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,MACzC,CAAC;AACD,aAAO;AAAA,IACT;AACA,YAAQ,KAAK,gBAAgB;AAAA,MAC3B,WAAW,WAAW;AAAA,MACtB,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,MACzB,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AACN,YAAQ,KAAK,gBAAgB;AAAA,MAC3B,WAAW,WAAW;AAAA,MACtB,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,MACzB,OAAO;AAAA,MACP,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD,WAAO;AAAA,EACT;AACF;AAYA,eAAsB,UACpB,UACA,OACA,QACA,SACkB;AAClB,QAAM,KAAK,KAAK,IAAI;AACpB,MAAI;AACF,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,UAAM,YAAY,UAAU,KAAK,UAAU,OAAO,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AAC3E,YAAQ,KAAK,iBAAiB;AAAA,MAC5B,WAAW,WAAW;AAAA,MACtB,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,MACzB,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ,KAAK,iBAAiB;AAAA,MAC5B,WAAW,WAAW;AAAA,MACtB,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,OAAO,eAAe,GAAG;AAAA,MACzB,aAAa;AAAA,MACb,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD,YAAQ;AAAA,MACN;AAAA,MACA,eAAe,GAAG;AAAA,IACpB;AACA,WAAO;AAAA,EACT;AACF;AAWA,eAAsB,YACpB,UACA,WACA,IACA,QACA,SACmB;AACnB,SAAO,aAAa,UAAU,YAAY;AACxC,UAAM,OAAQ,MAAM,UAAU,UAAU,QAAQ,OAAO,KAAM,cAAc,SAAS;AACpF,UAAM,UAAU,MAAM,GAAG,IAAI;AAC7B,UAAM,YAAY,MAAM,UAAU,UAAU,SAAS,QAAQ,OAAO;AACpE,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,aAAa;AAAA,QACrB,SAAS,8BAA8B,QAAQ;AAAA,QAC/C,MAAM;AAAA,QACN;AAAA,QACA,SAAS,EAAE,UAAU,WAAW,cAAc;AAAA,MAChD,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT,CAAC;AACH;;;AC7KA,YAAYC,WAAS;AACrB,SAAS,YAAAC,iBAAgB;AA6EzB,eAAsB,kBAAkB,UAAyD;AAC/F,MAAI;AACJ,MAAI;AACF,UAAM,MAAU,eAAS,UAAU,MAAM;AAAA,EAC3C,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,QAAQ,YAAY,EAAG,QAAO;AAClC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAkBA,eAAsB,yBACpB,UACA,YAAY,QAAQ,KACF;AAClB,MAAI;AACJ,MAAI;AACF,eAAW,MAAU,eAAS,UAAU,MAAM;AAAA,EAChD,QAAQ;AAAA,EAER;AAEA,MAAI,UAAU;AACZ,QAAI;AACF,YAAMC,QAAO,KAAK,MAAM,QAAQ;AAEhC,UAAI;AACF,gBAAQ,KAAKA,MAAK,KAAK,CAAC;AAGxB,eAAO;AAAA,MACT,QAAQ;AAAA,MAER;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,OAA0B;AAAA,IAC9B,KAAK;AAAA,IACL,UAAUC,UAAS;AAAA,IACnB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC;AACA,QAAM,YAAY,UAAU,KAAK,UAAU,IAAI,GAAG,EAAE,MAAM,IAAM,CAAC;AACjE,SAAO;AACT;AAMA,eAAsB,yBAAyB,UAAiC;AAC9E,MAAI;AACF,UAAU,aAAO,QAAQ;AAAA,EAC3B,QAAQ;AAAA,EAER;AACF;AAaO,IAAM,0BAAN,MAA8B;AAAA,EAC3B;AAAA,EACS;AAAA,EACA;AAAA,EACT,QAA+B;AAAA,EACtB;AAAA,EACT,UAAU;AAAA,EACV,mBAAmB;AAAA,EAE3B,YACE,UACA,MAYA,aAAa,KACb;AACA,SAAK,WAAW;AAEhB,SAAK,WAAW,GAAG,QAAQ;AAC3B,SAAK,aAAa;AAClB,SAAK,WAAW;AAAA,MACd,SAAS;AAAA,MACT,eAAe,KAAK;AAAA,MACpB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,YAAY;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,YAAY,KAAK;AAAA,MACjB,eAAe,KAAK;AAAA,MACpB,gBAAgB,KAAK;AAAA,MACrB,WAAW,CAAC;AAAA,MACZ,OAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,cAAgC;AACpC,WAAO,yBAAyB,KAAK,QAAQ;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,cAA6B;AACjC,WAAO,yBAAyB,KAAK,QAAQ;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,UAAuC;AAC5C,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,UAAiC;AAC/B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,YAAY,KAA4B,YAA0B;AAChE,SAAK,WAAW;AAAA,MACd,GAAG,KAAK;AAAA,MACR;AAAA,MACA,WAAW,CAAC,GAAG,KAAK,SAAS,UAAU,OAAO,CAAC,MAAM,EAAE,OAAO,IAAI,EAAE,GAAG,GAAG;AAAA,IAC5E;AACA,SAAK,cAAc;AACnB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,mBAAmB,MAA+B;AAChD,UAAM,SAAS,KAAK,SAAS,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,KAAK,MAAM;AACvE,SAAK,WAAW;AAAA,MACd,GAAG,KAAK;AAAA,MACR,OAAO,SACH,KAAK,SAAS,MAAM,IAAI,CAAC,MAAO,EAAE,WAAW,KAAK,SAAS,EAAE,GAAG,GAAG,GAAG,KAAK,IAAI,CAAE,IACjF,CAAC,GAAG,KAAK,SAAS,OAAO,IAAI;AAAA,IACnC;AACA,SAAK,cAAc;AACnB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,iBACE,QACA,OACM;AACN,SAAK,WAAW;AAAA,MACd,GAAG,KAAK;AAAA,MACR,OAAO,KAAK,SAAS,MAAM,IAAI,CAAC,MAAO,EAAE,WAAW,SAAS,EAAE,GAAG,GAAG,GAAG,MAAM,IAAI,CAAE;AAAA,IACtF;AACA,SAAK,cAAc;AACnB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,mBAAmB,QAAgB,UAA2C;AAC5E,UAAM,SAAS,KAAK,SAAS,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,MAAM;AAClE,SAAK,WAAW;AAAA,MACd,GAAG,KAAK;AAAA,MACR,OAAO,SACH,KAAK,SAAS,MAAM,IAAI,CAAC,MAAO,EAAE,WAAW,SAAS,EAAE,GAAG,GAAG,SAAS,IAAI,CAAE,IAC7E;AAAA,QACE,GAAG,KAAK,SAAS;AAAA,QACjB;AAAA,UACE;AAAA,UACA,YAAY,SAAS;AAAA,UACrB,QAAQ;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACN;AACA,SAAK,cAAc;AACnB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,SAAS,OAAsB;AAC7B,SAAK,WAAW,EAAE,GAAG,KAAK,UAAU,MAAM;AAC1C,SAAK,cAAc;AACnB,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,QAAI,KAAK,OAAO;AACd,mBAAa,KAAK,KAAK;AACvB,WAAK,QAAQ;AAAA,IACf;AACA,UAAM,KAAK,QAAQ;AAKnB,WAAO,KAAK,kBAAkB;AAC5B,WAAK,mBAAmB;AACxB,YAAM,KAAK,QAAQ;AAAA,IACrB;AAAA,EAEF;AAAA,EAEQ,gBAAsB;AAC5B,SAAK,WAAW,EAAE,GAAG,KAAK,UAAU,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE;AAAA,EAC1E;AAAA,EAEQ,WAAiB;AACvB,QAAI,KAAK,MAAO;AAChB,SAAK,QAAQ,WAAW,MAAM;AAC5B,WAAK,QAAQ;AACb,WAAK,KAAK,QAAQ;AAAA,IACpB,GAAG,KAAK,UAAU;AAAA,EACpB;AAAA,EAEA,MAAc,UAAyB;AACrC,QAAI,KAAK,SAAS;AAIhB,WAAK,mBAAmB;AACxB;AAAA,IACF;AACA,SAAK,UAAU;AACf,QAAI;AACF,YAAM,YAAY,KAAK,UAAU,KAAK,UAAU,KAAK,UAAU,MAAM,CAAC,GAAG;AAAA,QACvE,MAAM;AAAA,MACR,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,cAAQ;AAAA,QACN,KAAK,UAAU;AAAA,UACb,OAAO;AAAA,UACP,OAAO;AAAA,UACP,SAAS,eAAe,GAAG;AAAA,UAC3B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC,CAAC;AAAA,MACH;AAAA,IACF,UAAE;AACA,WAAK,UAAU;AAEf,UAAI,KAAK,kBAAkB;AACzB,aAAK,mBAAmB;AACxB,aAAK,SAAS;AAAA,MAChB;AAAA,IAEF;AAAA,EACF;AACF;;;AC/WA,YAAYC,WAAS;AAwFd,IAAM,sBAAsB;AAgB5B,SAAS,aAAa,aAA6B;AACxD,SAAO,mBAAmB,EAAE,YAAY,CAAC,EAAE;AAC7C;AAEA,eAAsB,SAAS,UAAkB,QAA6C;AAC5F,QAAM,KAAK,KAAK,IAAI;AACpB,MAAI;AACJ,MAAI;AACF,UAAM,MAAU,eAAS,UAAU,MAAM;AAAA,EAC3C,SAAS,KAAK;AACZ,UAAM,OAAQ,IAA8B;AAC5C,QAAI,SAAS,UAAU;AACrB,cAAQ,KAAK,gBAAgB;AAAA,QAC3B,WAAW;AAAA,QACX,OAAO;AAAA,QACP;AAAA,QACA,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,MAC3B,CAAC;AACD,aAAO;AAAA,IACT;AACA,YAAQ,KAAK,iBAAiB;AAAA,MAC5B,WAAW;AAAA,MACX,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,OAAO,eAAe,GAAG;AAAA,MACzB,aAAa;AAAA,IACf,CAAC;AACD,UAAM;AAAA,EACR;AACA,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,QAAQ,YAAY,KAAK,OAAO,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,OAAO,OAAO,GAAG;AAC9F,cAAQ,KAAK,KAAK,UAAU;AAAA,QAC1B,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC,CAAC;AACF,cAAQ,KAAK,gBAAgB;AAAA,QAC3B,WAAW;AAAA,QACX,OAAO;AAAA,QACP;AAAA,QACA,WAAW;AAAA,QACX,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,OAAO;AAAA,MACT,CAAC;AACD,aAAO;AAAA,IACT;AACA,YAAQ,KAAK,gBAAgB;AAAA,MAC3B,WAAW;AAAA,MACX,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,IAC3B,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AACN,YAAQ,KAAK,KAAK,UAAU;AAAA,MAC1B,OAAO;AAAA,MACP,OAAO;AAAA,MACP,MAAM;AAAA,MACN,SAAS;AAAA,MACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,CAAC,CAAC;AACF,YAAQ,KAAK,gBAAgB;AAAA,MAC3B,WAAW;AAAA,MACX,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,MACzB,OAAO;AAAA,IACT,CAAC;AACD,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,SAAS,UAAkB,MAAgB,QAAkC;AACjG,QAAM,KAAK,KAAK,IAAI;AACpB,MAAI;AACF,UAAM,YAAY,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AAC1E,YAAQ,KAAK,iBAAiB;AAAA,MAC5B,WAAW;AAAA,MACX,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,IAC3B,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,YAAQ,KAAK,iBAAiB;AAAA,MAC5B,WAAW;AAAA,MACX,OAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,OAAO,eAAe,GAAG;AAAA,MACzB,aAAa;AAAA,IACf,CAAC;AACD,UAAM,IAAI,QAAQ;AAAA,MAChB,SAAS,eAAe,GAAG;AAAA,MAC3B,MAAM,YAAY;AAAA,MAClB,MAAM;AAAA,MACN,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;AAuDO,SAAS,UAAU,MAAwB;AAChD,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA,OAAO;AAAA,IACP,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,WAAW;AAAA,IACX,cAAc,CAAC;AAAA,IACf,SAAS,CAAC;AAAA,EACZ;AACF;AAMO,SAAS,YACd,MACA,UACA,MACU;AACV,QAAM,UAAU,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,QAAQ,CAAC;AACnD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,UAAU;AAAA,IACV,cAAc,QAAQ,UAAU;AAAA,EAClC;AACF;AAMO,SAAS,cAAc,MAAgB,OAAyD;AACrG,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,MAAK,oBAAI,KAAK,GAAE,YAAY;AAClC,QAAM,OAAqB,EAAE,GAAG,OAAO,WAAW,GAAG;AACrD,QAAM,UAAU,CAAC,GAAG,KAAK,SAAS,IAAI;AACtC,QAAM,UAAU,QAAQ,SAAS,sBAC7B,QAAQ,MAAM,QAAQ,SAAS,mBAAmB,IAClD;AACJ,SAAO;AAAA,IACL,GAAG;AAAA,IACH,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,SAAS;AAAA,EACX;AACF;AAKO,SAAS,eAAe,MAK7B;AACA,MAAI,eAAe;AACnB,MAAI,mBAAmB;AACvB,MAAI,oBAAoB;AACxB,MAAI,sBAAsB;AAC1B,aAAW,KAAK,KAAK,SAAS;AAC5B,QAAI,OAAO,EAAE,YAAY,SAAU,iBAAgB,EAAE;AACrD,QAAI,EAAE,QAAQ;AACZ,0BAAoB,EAAE,OAAO;AAC7B,2BAAqB,EAAE,OAAO;AAAA,IAChC;AACA,QAAI,OAAO,EAAE,YAAY,YAAY,EAAE,OAAQ;AAAA,EACjD;AACA,SAAO,EAAE,cAAc,kBAAkB,mBAAmB,oBAAoB;AAClF;AAEA,IAAM,SAAS;AAGR,SAAS,WAAW,MAAgB,eAAe,IAAY;AACpE,QAAM,QAAkB,CAAC;AAGzB,QAAM,cAAc,KAAK,eAAe,KAAK;AAC7C,QAAM,KAAK,MAAM,KAAK,MAAM,IAAI,OAAO,WAAW;AAClD,MAAI,KAAK,eAAe,KAAK,gBAAgB,KAAK,MAAM;AACtD,UAAM,UAAU,KAAK,KAAK,SAAS,KAAK,KAAK,KAAK,MAAM,GAAG,EAAE,IAAI,WAAM,KAAK;AAC5E,UAAM,KAAK,MAAM,IAAI,mBAAmB,UAAU,IAAI,CAAC;AAAA,EACzD;AAGA,MAAI,OAAO,KAAK,aAAa,UAAU;AACrC,UAAM,MAAM,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,QAAQ,CAAC,CAAC;AAChE,UAAM,SAAS,KAAK,MAAM,MAAM,CAAC;AACjC,UAAM,QAAQ,KAAK;AACnB,UAAM,MAAM,MAAM,MAAM,SAAI,OAAO,MAAM,CAAC,IAAI,MAAM,IAAI,SAAI,OAAO,KAAK,CAAC;AACzE,UAAM,KAAK,eAAe,MAAM,MAAM,MAAM,KAAK,MAAM,GAAG,CAAC;AAC3D,QAAI,KAAK,cAAc;AACrB,YAAM,KAAK,OAAO,MAAM,IAAI,KAAK,YAAY,CAAC;AAAA,IAChD;AAEA,QAAI,KAAK,eAAe;AACtB,YAAM,YAAY,KAAK,kBAAkB,iBAAiB,cACtD,KAAK,kBAAkB,aAAa,iBACpC;AACJ,YAAM,KAAK,cAAc,YAAY,MAAM,KAAK,aAAa;AAAA,IAC/D;AAAA,EACF;AAGA,MAAI,KAAK,gBAAgB,KAAK,aAAa,SAAS,GAAG;AACrD,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,MAAM,KAAK,eAAe,CAAC;AACtC,eAAW,KAAK,KAAK,cAAc;AACjC,YAAM,OAAO,wBAAwB,KAAK,CAAC;AAC3C,YAAM,SAAS,OAAO,MAAM,MAAM,QAAG,IAAI,MAAM,IAAI,QAAG;AACtD,YAAM,KAAK,OAAO,SAAS,MAAM,CAAC;AAAA,IACpC;AAAA,EACF;AAEA,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,UAAU,KAAK,KAAK;AAC/B,QAAM,KAAK,oBAAoB,KAAK,cAAc;AAClD,QAAM,KAAK,iBAAiB,KAAK,UAAU;AAC3C,QAAM,aAAa,KAAK,aAAa;AACrC,QAAM,KAAK,YAAY,cAAc,KAAK,aAAa,IAAI,kBAAkB,KAAK,aAAa,MAAM,GAAG;AACxG,QAAM,KAAK,aAAa,KAAK,WAAW;AACxC,QAAM,QAAQ,eAAe,IAAI;AACjC,MAAI,MAAM,sBAAsB,GAAG;AACjC,UAAM,QAAQ,YAAY,SAAS,MAAM,aAAa,QAAQ,CAAC,IAC3D,WAAW,MAAM,mBAAmB,YAAY,MAAM,oBACtD,oBAAoB,MAAM,sBAAsB;AACpD,UAAM,KAAK,KAAK;AAAA,EAClB;AACA,MAAI,KAAK,QAAQ,SAAS,GAAG;AAC3B,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,0BAA0B,KAAK,IAAI,cAAc,KAAK,QAAQ,MAAM,IAAI,IAAI;AACvF,UAAM,OAAO,KAAK,QAAQ,MAAM,CAAC,YAAY;AAC7C,eAAW,KAAK,MAAM;AACpB,YAAM,OAAO,EAAE,WAAW,YAAY,WAAM,EAAE,WAAW,YAAY,WAAM,EAAE,WAAW,YAAY,WAAM;AAC1G,YAAM,OAAO,EAAE,OAAO,aAAQ,EAAE,OAAO;AACvC,YAAM,OAAO,OAAO,EAAE,YAAY,WAAW,OAAO,SAAS,EAAE,QAAQ,QAAQ,CAAC,IAAI,MAAM;AAC1F,YAAM,KAAK,QAAQ,EAAE,YAAY,MAAM,OAAO,OAAO,EAAE,SAAS,OAAO,EAAE,OAAO,OAAO,IAAI;AAAA,IAC7F;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAiBO,SAAS,sBAAsB,MAA0D;AAC9F,QAAM,KAAK;AACX,QAAM,IAAI,KAAK,MAAM,EAAE;AACvB,MAAI,CAAC,EAAG,QAAO;AAGf,QAAM,WAAW,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,OAAO,SAAS,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;AAC5E,QAAM,OAAO,EAAE,CAAC,GAAG,KAAK,KAAK;AAC7B,SAAO,SAAS,SAAY,EAAE,SAAS,IAAI,EAAE,UAAU,KAAK;AAC9D;AAQO,SAAS,eACd,MACA,UACA,MACU;AACV,QAAM,UAAU,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,QAAQ,CAAC;AACnD,QAAM,UAAU,CAAC,GAAI,KAAK,mBAAmB,CAAC,GAAI,EAAE,KAAI,oBAAI,KAAK,GAAE,YAAY,GAAG,UAAU,SAAS,KAAK,CAAC;AAE3G,QAAM,UAAU,QAAQ,SAAS,MAAM,QAAQ,MAAM,IAAI,IAAI;AAE7D,SAAO;AAAA,IACL,GAAG;AAAA,IACH,UAAU;AAAA,IACV,cAAc,QAAQ,GAAG,OAAO;AAAA,IAChC,iBAAiB;AAAA,IACjB,eAAe,aAAa,OAAO;AAAA,EACrC;AACF;AAGO,IAAM,uBAAuB;AAEpC,SAAS,aAAa,SAAiF;AACrG,MAAI,QAAQ,SAAS,EAAG,QAAO;AAC/B,QAAM,SAAS,QAAQ,MAAM,EAAE;AAC/B,QAAM,SAAmB,CAAC;AAC1B,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,WAAO,MAAM,OAAO,CAAC,GAAG,YAAY,MAAM,OAAO,IAAI,CAAC,GAAG,YAAY,EAAE;AAAA,EACzE;AAEA,MAAI,OAAO,SAAS,EAAG,QAAO;AAC9B,QAAM,WAAW,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,OAAO;AAC5D,MAAI,WAAW,EAAG,QAAO;AACzB,MAAI,WAAW,GAAI,QAAO;AAC1B,SAAO;AACT;;;ACreA,SAAS,cAAAC,mBAAkB;AAC3B,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAkBtB,IAAM,iBAAiB;AAQhB,SAAS,eAAe,SAAyB;AACtD,SAAOC,YAAW,QAAQ,EAAE,OAAO,SAAS,MAAM,EAAE,OAAO,KAAK;AAClE;AAQO,SAAS,mBAAmB,KAAkC;AACnE,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,IAAI,MAAM,YAAY,OAAO,EAAE,OAAO,MAAM,SAAU,QAAO;AAE1E,QAAM,QAAQ,EAAE,OAAO;AACvB,QAAM,UAAU,OAAO,EAAE,SAAS,MAAM,WAAW,EAAE,SAAS,IAAI;AAClE,QAAM,MAAM,OAAO,EAAE,WAAW,MAAM,WAAW,EAAE,WAAW,KAAI,oBAAI,KAAK,CAAC,GAAE,YAAY;AAC1F,QAAM,OAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,IAC/B,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,OAAO,MAAM,QAAQ,IAC9C,CAAC;AAEL,SAAO;AAAA,IACL,IAAI,EAAE,IAAI;AAAA,IACV,MAAM,OAAO,EAAE,MAAM,MAAM,YAAY,EAAE,MAAM,EAAE,SAAS,IAAI,EAAE,MAAM,IAAI,QAAQ,KAAK;AAAA,IACvF;AAAA,IACA,aAAa,OAAO,EAAE,aAAa,MAAM,WAAW,EAAE,aAAa,IAAI;AAAA,IACvE;AAAA,IACA,UACE,OAAO,EAAE,UAAU,MAAM,YAAY,EAAE,UAAU,EAAE,SAAS,IACxD,EAAE,UAAU,IACZ;AAAA,IACN;AAAA,IACA,QAAQ,eAAe,EAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,IAAI;AAAA,IACpD,UAAU,EAAE,UAAU,MAAM;AAAA,IAC5B,WAAW,mBAAmB,EAAE,WAAW,CAAC;AAAA,IAC5C,QAAQ,OAAO,EAAE,QAAQ,MAAM,WAAW,EAAE,QAAQ,IAAI;AAAA,IACxD,SAAS,OAAO,EAAE,SAAS,MAAM,WAAW,EAAE,SAAS,IAAI;AAAA,IAC3D,SAAS,OAAO,EAAE,SAAS,MAAM,WAAW,EAAE,SAAS,IAAI;AAAA,IAC3D,UAAU,OAAO,EAAE,UAAU,MAAM,WAAW,EAAE,UAAU,IAAI;AAAA,IAC9D,YAAY,OAAO,EAAE,YAAY,MAAM,WAAW,EAAE,YAAY,IAAI;AAAA,IACpE,WAAW;AAAA,IACX,WAAW,OAAO,EAAE,WAAW,MAAM,WAAW,EAAE,WAAW,IAAI;AAAA,EACnE;AACF;AAEA,SAAS,eAAe,GAAwC;AAC9D,SAAO,MAAM,aAAa,MAAM,UAAU,MAAM,aAAa,MAAM;AACrE;AAEA,SAAS,mBAAmB,GAA0C;AACpE,MAAI,CAAC,MAAM,QAAQ,CAAC,EAAG,QAAO;AAC9B,QAAM,MAAwB,CAAC;AAC/B,aAAW,QAAQ,GAAG;AACpB,QACE,QACA,OAAO,SAAS,YAChB,OAAQ,KAAiC,MAAM,MAAM,UACrD;AACA,YAAM,IAAI;AACV,YAAM,WACJ,MAAM,QAAQ,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ,IACnE,EAAE,MAAM,IACT;AACN,UAAI,KAAK;AAAA,QACP,MAAM,EAAE,MAAM;AAAA,QACd,aAAa,OAAO,EAAE,aAAa,MAAM,WAAW,EAAE,aAAa,IAAI;AAAA,QACvE,SAAS,OAAO,EAAE,SAAS,MAAM,WAAW,EAAE,SAAS,IAAI;AAAA,QAC3D,UAAU,EAAE,UAAU,MAAM,OAAO,OAAO;AAAA,QAC1C,MAAM,YAAY,SAAS,SAAS,IAAI,WAAW;AAAA,QACnD,WAAW,EAAE,WAAW,MAAM,OAAO,OAAO;AAAA,MAC9C,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO,IAAI,SAAS,IAAI,MAAM;AAChC;AAQO,IAAM,qBAAN,MAAgD;AAAA,EACpC;AAAA,EAEjB,YAAY,YAAkC;AAC5C,SAAK,MAAM,OAAO,eAAe,WAAW,aAAa,WAAW;AAAA,EACtE;AAAA,EAEA,MAAM,OAA+B;AACnC,UAAM,UAAU,KAAK,GAAG;AACxB,UAAM,UAAyB,CAAC;AAChC,QAAI;AACF,YAAM,QAAQ,MAAS,aAAQ,KAAK,GAAG;AACvC,iBAAW,QAAQ,OAAO;AACxB,YAAI,CAAC,KAAK,SAAS,OAAO,EAAG;AAC7B,YAAI;AACF,gBAAM,MAAqB,KAAK;AAAA,YAC9B,MAAS,cAAc,YAAK,KAAK,KAAK,IAAI,GAAG,MAAM;AAAA,UACrD;AACA,gBAAM,WAAW,mBAAmB,IAAI,KAAK;AAC7C,cAAI,SAAU,SAAQ,KAAK,QAAQ;AAAA,QACrC,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AACA,WAAO,QAAQ;AAAA,MACb,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ;AAAA,IAC5E;AAAA,EACF;AAAA,EAEA,MAAM,IAAI,IAAyC;AACjD,UAAM,OAAY,YAAK,KAAK,KAAK,GAAG,EAAE,OAAO;AAC7C,QAAI;AACF,YAAM,MAAqB,KAAK,MAAM,MAAS,cAAS,MAAM,MAAM,CAAC;AACrE,aAAO,mBAAmB,IAAI,KAAK;AAAA,IACrC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,OAAmC;AAC5C,UAAM,UAAU,KAAK,GAAG;AACxB,UAAM,OAAY,YAAK,KAAK,KAAK,GAAG,MAAM,EAAE,OAAO;AACnD,UAAM,MAAqB,EAAE,SAAS,gBAAgB,MAAM;AAC5D,UAAM,YAAY,MAAM,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC;AAAA,EACtD;AAAA,EAEA,MAAM,OAAO,IAA8B;AACzC,UAAM,OAAY,YAAK,KAAK,KAAK,GAAG,EAAE,OAAO;AAC7C,QAAI;AACF,YAAS,YAAO,IAAI;AACpB,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,OAAuC;AAChD,UAAM,MAAM,MAAM,KAAK,KAAK;AAC5B,UAAM,QAAQ,MAAM,YAAY;AAChC,WAAO,IAAI;AAAA,MACT,CAAC,MACC,EAAE,MAAM,YAAY,EAAE,SAAS,KAAK,KACpC,EAAE,YAAY,YAAY,EAAE,SAAS,KAAK,KAC1C,EAAE,QAAQ,YAAY,EAAE,SAAS,KAAK,KACtC,EAAE,SAAS,YAAY,EAAE,SAAS,KAAK,KACvC,EAAE,KAAK,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,KAAK,CAAC;AAAA,IACtD;AAAA,EACF;AAAA;AAAA,EAGA,UACE,OACA,SACA,OAAiB,CAAC,GAClB,QAEI,CAAC,GACQ;AACb,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,WAAO;AAAA,MACL,IAAI,KAAK;AAAA,MACT,MAAM,MAAM,QAAQ,MAAM,KAAK,SAAS,IAAI,MAAM,OAAO,QAAQ,KAAK;AAAA,MACtE;AAAA,MACA,aAAa,MAAM,eAAe;AAAA,MAClC;AAAA,MACA,UAAU,MAAM,YAAY;AAAA,MAC5B;AAAA,MACA,QAAQ,MAAM,UAAU;AAAA,MACxB,UAAU,MAAM,YAAY;AAAA,MAC5B,WAAW,MAAM;AAAA,MACjB,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,MACf,SAAS,MAAM;AAAA,MACf,UAAU,MAAM;AAAA,MAChB,YAAY,MAAM;AAAA,MAClB,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AAAA,EACF;AACF;;;ACrNA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAoBf,IAAM,mBAAN,MAAuB;AAAA,EAC5B,YAA6B,MAAc;AAAd;AAAA,EAAe;AAAA,EAAf;AAAA,EAE7B,MAAM,OAA6C;AACjD,QAAI;AACF,YAAM,MAAoB,KAAK,MAAM,MAAS,cAAS,KAAK,MAAM,MAAM,CAAC;AACzE,UAAI,OAAO,OAAO,QAAQ,YAAY,IAAI,SAAS,OAAO,IAAI,UAAU,UAAU;AAChF,eAAO,IAAI;AAAA,MACb;AAAA,IACF,QAAQ;AAAA,IAER;AACA,WAAO,CAAC;AAAA,EACV;AAAA,EAEA,MAAM,OAAO,MAAc,MAAa,oBAAI,KAAK,GAAE,YAAY,GAAyB;AACtF,UAAM,QAAQ,MAAM,KAAK,KAAK;AAC9B,UAAM,OAAO,MAAM,IAAI;AACvB,UAAM,OAAoB,EAAE,QAAQ,MAAM,SAAS,KAAK,GAAG,YAAY,GAAG;AAC1E,UAAM,IAAI,IAAI;AACd,UAAM,UAAe,eAAQ,KAAK,IAAI,CAAC;AACvC,UAAM,YAAY,KAAK,MAAM,KAAK,UAAU,EAAE,SAAS,GAAG,MAAM,GAA0B,MAAM,CAAC,CAAC;AAClG,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,IAAI,MAAgD;AACxD,YAAQ,MAAM,KAAK,KAAK,GAAG,IAAI;AAAA,EACjC;AAAA;AAAA,EAGA,MAAM,OAAO,QAAQ,IAAqD;AACxE,UAAM,QAAQ,MAAM,KAAK,KAAK;AAC9B,WAAO,OAAO,QAAQ,KAAK,EACxB,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,OAAO,EAAE,EAAE,EACvC;AAAA,MACC,CAAC,GAAG,MACF,IAAI,KAAK,EAAE,MAAM,UAAU,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,MAAM,UAAU,EAAE,QAAQ,KAC9E,EAAE,MAAM,QAAQ,EAAE,MAAM;AAAA,IAC5B,EACC,MAAM,GAAG,KAAK;AAAA,EACnB;AAAA;AAAA,EAGA,MAAM,IAAI,QAAQ,IAAqD;AACrE,UAAM,QAAQ,MAAM,KAAK,KAAK;AAC9B,WAAO,OAAO,QAAQ,KAAK,EACxB,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,OAAO,EAAE,EAAE,EACvC;AAAA,MACC,CAAC,GAAG,MACF,EAAE,MAAM,QAAQ,EAAE,MAAM,SACxB,IAAI,KAAK,EAAE,MAAM,UAAU,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,MAAM,UAAU,EAAE,QAAQ;AAAA,IAClF,EACC,MAAM,GAAG,KAAK;AAAA,EACnB;AACF;;;AC3EA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AAcf,IAAM,4BAA4B;AAgBlC,IAAM,oBAAN,MAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM7B,YACmB,MACA,UACA,aAAqB,2BACtC;AAHiB;AACA;AACA;AAAA,EAChB;AAAA,EAHgB;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOnB,MAAM,OAA0B;AAC9B,QAAI;AACF,YAAM,MAAwB,KAAK,MAAM,MAAS,cAAS,KAAK,MAAM,MAAM,CAAC;AAC7E,UACE,OACA,OAAO,QAAQ,YACf,MAAM,QAAQ,IAAI,OAAO,KACzB,IAAI,QAAQ,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ,GAC9C;AACA,eAAO,IAAI,QAAQ,MAAM,GAAG,KAAK,UAAU;AAAA,MAC7C;AAAA,IACF,QAAQ;AAAA,IAER;AACA,WAAO,CAAC;AAAA,EACV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,KAAK,SAAkC;AAC3C,UAAM,UAAU,KAAK,eAAe,OAAO;AAC3C,UAAM,UAAe,eAAQ,KAAK,IAAI,CAAC;AACvC,UAAM,UAA4B;AAAA,MAChC,SAAS;AAAA,MACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,SAAS;AAAA,IACX;AACA,UAAM,YAAY,KAAK,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,EAC/D;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,UAAM,UAAe,eAAQ,KAAK,IAAI,CAAC;AACvC,UAAM,UAA4B,EAAE,SAAS,GAAG,YAAW,oBAAI,KAAK,GAAE,YAAY,GAAG,SAAS,CAAC,EAAE;AACjG,UAAM,YAAY,KAAK,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,eAAe,SAA6B;AAClD,UAAM,MAAgB,CAAC;AACvB,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAW,KAAK,SAAS,MAAM,KAAK;AAC1C,UAAI,KAAK,cAAc,QAAQ,EAAG;AAClC,UAAI,KAAK,QAAQ;AAAA,IACnB;AACA,WAAO,IAAI,MAAM,GAAG,KAAK,UAAU;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,cAAc,MAAuB;AAC3C,WAAO,KAAK,SAAS,gBAAgB;AAAA,EACvC;AACF;;;AChHA,YAAYC,UAAQ;AACpB,YAAYC,YAAU;AACtB,SAAS,cAAAC,mBAAkB;AAIpB,IAAM,sBAAsC,CAAC,YAAY,UAAU,WAAW,UAAU,SAAS;AAuBjG,IAAM,YAAN,MAAgB;AAAA,EAIrB,YACmB,OACA,WACA,WACjB;AAHiB;AACA;AACA;AAEjB,SAAK,YAAiB,YAAK,MAAM,YAAY,iBAAiB;AAAA,EAChE;AAAA,EALmB;AAAA,EACA;AAAA,EACA;AAAA,EANF;AAAA,EACT,QAA8B;AAAA;AAAA,EAYtC,MAAM,SAA0B;AAC9B,UAAM,MAAM,KAAK,UAAU;AAC3B,QAAI,CAAC,KAAK,SAAS;AACjB,aAAO;AAAA,IACT;AACA,UAAM,OAAO,KAAK,OAAO;AACzB,UAAM,QAAQ,OAAO,QAAQ,IAAI,IAAI;AACrC,WAAO;AAAA,MACL;AAAA,MACA,iBAAiB,IAAI,IAAI;AAAA,MACzB,iBAAiB,IAAI,WAAW,KAAK,IAAI,CAAC;AAAA,MAC1C,iBAAiB,KAAK;AAAA,IACxB,EAAE,KAAK,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,OAAe,aAA8C;AAExE,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,UAA2B;AAC/B,UAAM,MAAM,KAAK,UAAU;AAC3B,QAAI,CAAC,IAAK,QAAO;AACjB,UAAM,OAAO,EAAE,GAAG,KAAK,SAAS,MAAM;AACtC,UAAM,KAAK,UAAU,IAAI;AACzB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,KAAK,OAAoC;AAC7C,UAAM,MAAM,KAAK,UAAU;AAC3B,QAAI,CAAC,KAAK,QAAS,QAAO,EAAE,IAAI,OAAO,QAAQ,QAAQ,YAAY,CAAC,GAAG,SAAS,eAAe;AAE/F,UAAM,QAAQ,IAAI,KAAK,MAAM,GAAG;AAChC,UAAM,QAAQ,cAAc,MAAM,CAAC,CAAC;AACpC,UAAM,WAAW,cAAc,MAAM,CAAC,CAAC;AACvC,UAAM,SAAS;AACf,UAAM,cAAc,KAAK,OAAO;AAEhC,UAAM,EAAE,aAAa,IAAI,IAAI,MAAM,KAAK,eAAe,IAAI,UAAU;AACrE,UAAM,aAAa,MAAM,KAAK,cAAc,OAAO,OAAO,UAAU,aAAa,WAAW;AAE5F,UAAM,YAAY,MAAM,KAAK;AAAA,MAC3B;AAAA,MAAO;AAAA,MAAO;AAAA,MAAU;AAAA,MACxB;AAAA,MACA,QAAQ,IAAI,WAAW,KAAK,IAAI,CAAC,YAAM,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA,IACjE;AAEA,QAAI;AACF,YAAM,KAAK,UAAU,OAAO,OAAO,UAAU,QAAQ,SAAS;AAAA,IAChE,SAAS,KAAK;AAEZ,UAAI,eAAe,SAAS,IAAI,QAAQ,SAAS,KAAK,GAAG;AACvD,cAAM,SAAS,MAAM,KAAK,OAAO,OAAO,OAAO,UAAU,MAAM;AAC/D,cAAM,aAAa,OAAO,OAAO;AACjC,cAAM,mBAAmB,MAAM,KAAK;AAAA,UAClC;AAAA,UAAO;AAAA,UAAO;AAAA,UAAU;AAAA,UACxB;AAAA,UACA,QAAQ,IAAI,WAAW,KAAK,IAAI,CAAC,YAAM,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA,QACjE;AACA,cAAM,KAAK,UAAU,OAAO,OAAO,UAAU,QAAQ,gBAAgB;AAAA,MACvE,OAAO;AACL,cAAM;AAAA,MACR;AAAA,IACF;AAEA,UAAM,YAA2B;AAAA,MAC/B,SAAS;AAAA,MACT,KAAK;AAAA,MACL,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,MACrC,UAAU;AAAA,IACZ;AACA,UAAS,eAAU,KAAK,WAAW,KAAK,UAAU,WAAW,MAAM,CAAC,GAAG,MAAM;AAC7E,SAAK,QAAQ;AAEb,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,YAAY,IAAI;AAAA,MAChB,aAAa;AAAA,MACb,SAAS,UAAU,IAAI,WAAW,KAAK,IAAI,CAAC,OAAO,IAAI,IAAI,aAAa,UAAU,MAAM,GAAG,CAAC,CAAC;AAAA,IAC/F;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,OAAoC;AAC7C,UAAM,MAAM,KAAK,UAAU;AAC3B,QAAI,CAAC,KAAK,QAAS,QAAO,EAAE,IAAI,OAAO,QAAQ,QAAQ,YAAY,CAAC,GAAG,SAAS,eAAe;AAE/F,UAAM,YAAY,IAAI,KAAK,MAAM,GAAG;AACpC,UAAM,QAAQ,cAAc,UAAU,CAAC,CAAC;AACxC,UAAM,WAAW,cAAc,UAAU,CAAC,CAAC;AAE3C,UAAM,aAAa,MAAM,KAAK,OAAO,OAAO,OAAO,UAAU,MAAM;AACnE,UAAM,aAAa,WAAW,OAAO;AAErC,UAAM,aAAa,MAAM,KAAK,UAAU,OAAO,OAAO,UAAU,UAAU;AAC1E,UAAM,UAAU,WAAW,KAAK;AAEhC,UAAM,cAAc,MAAM,KAAK,eAAe,OAAO,OAAO,UAAU,OAAO;AAE7E,eAAW,SAAS,aAAa;AAC/B,UAAI,MAAM,SAAS,OAAQ;AAG3B,YAAM,WAAW,MAAM,KAAK,MAAM,GAAG;AACrC,UAAI,SAAS,CAAC,MAAM,UAAU,CAAC,SAAS,CAAC,EAAG;AAC5C,YAAM,MAAM,SAAS,CAAC;AACtB,UAAI,CAAC,CAAC,YAAY,UAAU,WAAW,UAAU,SAAS,EAAE,SAAS,GAAG,EAAG;AAE3E,YAAM,YAAY,KAAK,eAAe,GAAG;AACzC,UAAI,CAAC,UAAW;AAMhB,YAAM,MAAM,SAAS,MAAM,CAAC,EAAE,KAAK,GAAG;AACtC,YAAM,WAAW,0BAA0B,KAAK,WAAW,KAAK,MAAM,IAAI;AAE1E,YAAM,WAAW,MAAM,KAAK,QAAQ,OAAO,OAAO,UAAU,MAAM,GAAG;AACrE,YAAS,WAAW,eAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAC1D,YAAS,eAAU,UAAU,OAAO,KAAK,UAAU,QAAQ,CAAC;AAAA,IAC9D;AAEA,UAAM,WAAW,MAAM,KAAK,oBAAoB,IAAI,UAAU;AAC9D,UAAM,YAA2B;AAAA,MAC/B,SAAS;AAAA,MACT,KAAK;AAAA,MACL,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,MACrC;AAAA,IACF;AACA,UAAS,eAAU,KAAK,WAAW,KAAK,UAAU,WAAW,MAAM,CAAC,GAAG,MAAM;AAC7E,SAAK,QAAQ;AAEb,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,YAAY,IAAI;AAAA,MAChB,aAAa;AAAA,MACb,SAAS,UAAU,IAAI,WAAW,KAAK,IAAI,CAAC,SAAS,IAAI,IAAI,aAAa,WAAW,MAAM,GAAG,CAAC,CAAC;AAAA,IAClG;AAAA,EACF;AAAA,EAEA,MAAM,kBAAoC;AACxC,QAAI,CAAC,KAAK,MAAO,QAAO;AACxB,UAAM,MAAM,KAAK,UAAU;AAC3B,QAAI,CAAC,IAAK,QAAO;AACjB,UAAM,UAAU,MAAM,KAAK,oBAAoB,IAAI,UAAU;AAC7D,WAAO,YAAY,KAAK,MAAM;AAAA,EAChC;AAAA,EAEA,MAAM,YAA2B;AAC/B,QAAI;AACF,YAAM,MAAM,MAAS,cAAS,KAAK,WAAW,MAAM;AACpD,WAAK,QAAQ,KAAK,MAAM,GAAG;AAAA,IAC7B,QAAQ;AACN,WAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA;AAAA,EAIA,MAAc,YACZ,OACA,OACA,MACA,QACA,aACA,MACkB;AAClB,UAAM,MAAM,gCAAgC,KAAK,IAAI,IAAI,GAAG,WAAW;AACvE,UAAM,OAAoB;AAAA,MACxB,QAAQ,YAAY,QAAQ,IAAM;AAAA,MAClC;AAAA,MACA,SAAS;AAAA,QACP,eAAe,UAAU,KAAK;AAAA,QAC9B,QAAQ;AAAA,QACR,wBAAwB;AAAA,QACxB,gBAAgB;AAAA,MAClB;AAAA,IACF;AACA,QAAI,SAAS,OAAW,MAAK,OAAO,KAAK,UAAU,IAAI;AACvD,UAAM,MAAM,MAAM,MAAM,KAAK,IAAI;AAEjC,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,UAAU,MAAM,IAAI,KAAK;AAC/B,YAAM,IAAI,gBAAgB;AAAA,QACxB,SAAS,cAAc,MAAM,IAAI,WAAW,YAAY,IAAI,MAAM,MAAM,OAAO;AAAA,QAC/E,MAAM,YAAY;AAAA,QAClB,WAAW;AAAA,QACX,SAAS,EAAE,QAAQ,aAAa,QAAQ,IAAI,QAAQ,MAAM,GAAG,KAAK,IAAI,IAAI,GAAG;AAAA,MAC/E,CAAC;AAAA,IACH;AAEA,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,WAAO,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,EACpC;AAAA,EAEA,MAAc,OAAO,OAAe,OAAe,MAAc,KAAa;AAC5E,WAAQ,MAAM,KAAK,YAAY,OAAO,OAAO,MAAM,OAAO,mBAAmB,GAAG,EAAE;AAAA,EAGpF;AAAA,EAEA,MAAc,UAAU,OAAe,OAAe,MAAc,KAAa,KAAa;AAC5F,UAAM,KAAK,YAAY,OAAO,OAAO,MAAM,SAAS,mBAAmB,GAAG,IAAI;AAAA,MAC5E;AAAA,MACA,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,UAAU,OAAe,OAAe,MAAc,KAAa;AAC/E,WAAQ,MAAM,KAAK,YAAY,OAAO,OAAO,MAAM,OAAO,gBAAgB,GAAG,EAAE;AAAA,EAIjF;AAAA,EAEA,MAAc,eAAe,OAAe,OAAe,MAAc,SAAiB;AACxF,WAAQ,MAAM,KAAK,YAAY,OAAO,OAAO,MAAM,OAAO,cAAc,OAAO,cAAc;AAAA,EAK/F;AAAA,EAEA,MAAc,aACZ,OAAe,OAAe,MAC9B,SAAiB,WAAgC,UAAU,QAC3D;AACA,UAAM,OAAgC,EAAE,SAAS,MAAM,QAAQ;AAC/D,QAAI,UAAW,MAAK,UAAU,CAAC,SAAS;AACxC,UAAM,SAAU,MAAM,KAAK,YAAY,OAAO,OAAO,MAAM,QAAQ,gBAAgB,IAAI;AACvF,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,MAAc,cACZ,OAAe,OAAe,MAC9B,SACA,aACiB;AACjB,UAAM,OAAO,QAAQ,IAAI,CAAC,OAAO;AAAA,MAC/B,MAAM,EAAE;AAAA,MACR,MAAM,EAAE;AAAA,MACR,MAAM;AAAA,MACN,SAAS,EAAE;AAAA,IACb,EAAE;AACF,UAAM,OAAgC,EAAE,KAAK;AAC7C,QAAI,YAAa,MAAK,YAAY;AAClC,UAAM,SAAU,MAAM,KAAK,YAAY,OAAO,OAAO,MAAM,QAAQ,cAAc,IAAI;AACrF,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,MAAc,QAAQ,OAAe,OAAe,MAAc,KAA8B;AAC9F,UAAM,SAAU,MAAM,KAAK,YAAY,OAAO,OAAO,MAAM,OAAO,cAAc,GAAG,EAAE;AACrF,WAAO,OAAO;AAAA,EAChB;AAAA;AAAA,EAIA,MAAc,eAAe,YAG1B;AACD,UAAM,UAAkE,CAAC;AACzE,UAAM,SAAmB,CAAC;AAE1B,eAAW,OAAO,YAAY;AAC5B,YAAM,YAAY,KAAK,eAAe,GAAG;AACzC,UAAI,CAAC,UAAW;AAChB,UAAI;AACF,cAAMC,SAAO,MAAS,UAAK,SAAS;AACpC,YAAIA,OAAK,YAAY,GAAG;AACtB,gBAAM,QAAQ,MAAM,KAAK,QAAQ,WAAW,SAAS;AACrD,qBAAW,QAAQ,OAAO;AACxB,kBAAM,UAAU,MAAS,cAAS,MAAM,MAAM;AAC9C,kBAAM,MAAW,gBAAS,WAAW,IAAI,EAAE,QAAQ,OAAO,GAAG;AAC7D,oBAAQ,KAAK,EAAE,MAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,SAAS,MAAM,SAAS,CAAC;AACpE,mBAAO,KAAK,OAAO;AAAA,UACrB;AAAA,QACF,OAAO;AACL,gBAAM,UAAU,MAAS,cAAS,WAAW,MAAM;AACnD,kBAAQ,KAAK,EAAE,MAAM,QAAQ,GAAG,IAAI,SAAS,MAAM,SAAS,CAAC;AAC7D,iBAAO,KAAK,OAAO;AAAA,QACrB;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,UAAM,MAAMC,YAAW,QAAQ,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAClF,WAAO,EAAE,aAAa,SAAS,IAAI;AAAA,EACrC;AAAA,EAEA,MAAc,oBAAoB,YAA6C;AAC7E,UAAM,SAAmB,CAAC;AAC1B,eAAW,OAAO,YAAY;AAC5B,YAAM,YAAY,KAAK,eAAe,GAAG;AACzC,UAAI,CAAC,UAAW;AAChB,UAAI;AACF,cAAMD,SAAO,MAAS,UAAK,SAAS;AACpC,YAAIA,OAAK,YAAY,GAAG;AACtB,gBAAM,QAAQ,MAAM,KAAK,QAAQ,WAAW,SAAS;AACrD,qBAAW,QAAQ,OAAO;AACxB,kBAAM,UAAU,MAAS,cAAS,IAAI;AACtC,mBAAO,KAAK,QAAQ,SAAS,QAAQ,IAAI,IAAI;AAAA,UAC/C;AAAA,QACF,OAAO;AACL,gBAAM,UAAU,MAAS,cAAS,SAAS;AAC3C,iBAAO,KAAK,QAAQ,SAAS,QAAQ,IAAI,SAAS;AAAA,QACpD;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAOC,YAAW,QAAQ,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAAA,EAC/E;AAAA,EAEQ,eAAe,KAAkC;AACvD,YAAQ,KAAK;AAAA,MACX,KAAK;AAAY,eAAO,KAAK,MAAM;AAAA,MACnC,KAAK;AAAY,eAAO,KAAK,MAAM;AAAA,MACnC,KAAK;AAAY,eAAO,KAAK,MAAM;AAAA,MACnC,KAAK;AAAY,eAAO,KAAK,MAAM;AAAA,MACnC,KAAK;AAAY,eAAO,KAAK,MAAM;AAAA;AAAA,MAEnC;AAAiB,eAAO;AAAA,IAC1B;AAAA,EACF;AAAA,EAEA,MAAc,QAAQ,KAAa,MAAiC;AAClE,UAAM,UAAoB,CAAC;AAC3B,UAAM,UAAU,MAAS,aAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAC7D,eAAW,SAAS,SAAS;AAC3B,YAAM,OAAY,YAAK,KAAK,MAAM,IAAI;AACtC,UAAI,MAAM,YAAY,GAAG;AACvB,gBAAQ,KAAK,GAAI,MAAM,KAAK,QAAQ,MAAM,IAAI,CAAE;AAAA,MAClD,OAAO;AACL,gBAAQ,KAAK,IAAI;AAAA,MACnB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,0BACP,KACA,WACA,KACA,YACQ;AACR,QAAM,kBAAkB,QAAQ,YAAY,QAAQ;AACpD,MAAI,CAAC,iBAAiB;AACpB,QAAI,IAAK,OAAM,IAAI,QAAQ;AAAA,MACzB,SAAS,qDAAqD,UAAU;AAAA,MACxE,MAAM,YAAY;AAAA,MAClB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,wBAAwB,UAAU,IAAI;AAAA,IAC3D,CAAC;AACD,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,gBAAqB,iBAAU,GAAG;AACxC,QAAM,cAAc,kBAAkB,QAAQ,cAAc,WAAW,KAAU,UAAG,EAAE;AACtF,MAAS,kBAAW,aAAa,KAAK,aAAa;AACjD,UAAM,IAAI,QAAQ;AAAA,MAChB,SAAS,sCAAsC,UAAU;AAAA,MACzD,MAAM,YAAY;AAAA,MAClB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,kBAAkB,cAAc;AAAA,IACrD,CAAC;AAAA,EACH;AAEA,QAAM,OAAY,eAAQ,WAAW,aAAa;AAClD,QAAM,OAAY,eAAQ,SAAS;AACnC,QAAMC,YAAgB,gBAAS,MAAM,IAAI;AAEzC,MAAIA,UAAS,WAAW,IAAI,KAAU,kBAAWA,SAAQ,GAAG;AAC1D,UAAM,IAAI,QAAQ;AAAA,MAChB,SAAS,kDAAkD,UAAU;AAAA,MACrE,MAAM,YAAY;AAAA,MAClB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,yBAAyB,UAAU,IAAI;AAAA,IAC5D,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAEA,SAAS,QAAQ,KAAqB;AACpC,QAAM,OAAO,KAAK,IAAI,IAAI,IAAI,KAAK,GAAG,EAAE,QAAQ;AAChD,QAAM,OAAO,KAAK,MAAM,OAAO,GAAM;AACrC,MAAI,OAAO,EAAG,QAAO;AACrB,MAAI,OAAO,GAAI,QAAO,GAAG,IAAI;AAC7B,QAAM,MAAM,KAAK,MAAM,OAAO,EAAE;AAChC,MAAI,MAAM,GAAI,QAAO,GAAG,GAAG;AAC3B,QAAM,OAAO,KAAK,MAAM,MAAM,EAAE;AAChC,SAAO,GAAG,IAAI;AAChB;;;AClYA,IAAM,0BAA0B,oBAAI,IAA0B;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAMD,IAAM,wBAAwB,oBAAI,IAA0B;AAAA,EAC1D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAMD,IAAM,mBAAmB,oBAAI,IAA0B;AAAA,EACrD;AACF,CAAC;AAMD,SAAS,UAAU,MAA4B,OAA4B;AACzE,MAAI,wBAAwB,IAAI,IAAI,EAAG,QAAO;AAC9C,MAAI,UAAU,UAAW,QAAO;AAEhC,MAAI,sBAAsB,IAAI,IAAI,EAAG,QAAO;AAC5C,MAAI,UAAU,WAAY,QAAO;AAGjC,MAAI,iBAAiB,IAAI,IAAI,GAAG;AAC9B,WAAO,UAAU;AAAA,EACnB;AAGA,SAAO;AACT;AAeO,SAAS,yBACd,QAKA,QAAoB,YACpB,UAAqC,CAAC,GAClB;AAEpB,MAAI,eAA2B,SAAS;AAMxC,QAAM,gBACJ,OAAO,WAAW,aAAa,SAAS,MAAM;AAIhD,QAAM,mBAAmB,oBAAI,IAAoB;AAEjD,QAAM,qBAAqB,QAAQ,UAAU,gBAAgB,CAAC;AAC9D,QAAM,4BAA4B,mBAAmB,cAAc;AAMnE,WAAS,aAAa,OAA8B;AAClD,QAAI,MAAM,SAAS,gBAAiB,QAAO;AAE3C,UAAM,YAAY;AAClB,UAAM,YAAY,UAAU,OAAO;AAGnC,QAAI,cAAc,aAAa,cAAc,YAAY,cAAc,gBAAgB;AACrF,aAAO;AAAA,IACT;AAGA,QAAI,cAAc,SAAS,cAAc,kBAAkB;AACzD,YAAM,MAAM,UAAU,MAAM,UAAU;AACtC,YAAM,SAAS,iBAAiB,IAAI,GAAG,KAAK,KAAK;AACjD,uBAAiB,IAAI,KAAK,KAAK;AAG/B,aAAO,UAAU,KAAM,QAAQ,8BAA8B;AAAA,IAC/D;AAEA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,IAAI,QAAQ;AACV,aAAO;AAAA,IACT;AAAA,IAEA,cAAc,MAAM;AAClB,qBAAe,QAAQ;AAAA,IACzB;AAAA,IAEA,OAAO,MAAM;AACX,aAAO,UAAU,MAAM,YAAY;AAAA,IACrC;AAAA,IAEA,MAAM,OAAO,OAAO;AAClB,YAAM,SAAS,cAAc;AAC7B,UAAI,CAAC,OAAQ;AACb,UAAI,CAAC,UAAU,MAAM,MAAM,YAAY,EAAG;AAG1C,UAAI,CAAC,aAAa,KAAK,EAAG;AAE1B,UAAI;AACF,cAAM,OAAO,OAAO,KAAK;AAAA,MAC3B,SAAS,MAAM;AAAA,MAKf;AAAA,IACF;AAAA,IAEA,MAAM,YAAY,QAAQ;AACxB,YAAM,SAAS,cAAc;AAC7B,UAAI,CAAC,UAAU,OAAO,WAAW,EAAG;AACpC,YAAM,UAAU,OAAO;AAAA,QACrB,CAAC,MAAM,UAAU,EAAE,MAAM,YAAY,KAAK,aAAa,CAAC;AAAA,MAC1D;AACA,UAAI,QAAQ,WAAW,EAAG;AAC1B,UAAI;AACF,cAAM,OAAO,YAAY,OAAO;AAAA,MAClC,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;AASO,SAAS,kBACd,KACY;AACZ,QAAM,MAAM,KAAK,SAAS;AAC1B,MAAI,QAAQ,aAAa,QAAQ,cAAc,QAAQ,QAAQ;AAC7D,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAMO,SAAS,4BACd,KAaA;AACA,QAAM,UAAU,KAAK,WAAW,CAAC;AAEjC,QAAM,aAAa,kBAAkB,GAAG;AAExC,QAAM,yBACJ,QAAQ,UAAU,cAAc,cAAc;AAEhD,SAAO;AAAA,IACL;AAAA,IACA,UAAU;AAAA,MACR,cAAc;AAAA,QACZ,YAAY,KAAK,IAAI,GAAG,KAAK,MAAM,sBAAsB,CAAC;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
|
-
"names": ["createHash", "fsp", "path", "randomBytes", "path", "open", "path", "slugify", "projectHash", "randomBytes", "stat", "resolve", "path", "createHash", "fsp", "path", "createHash", "relative", "stat", "resolve", "createHash", "fsp", "path", "isInside", "relative", "stat", "createHash", "createHash", "stat", "entry", "fsp", "path", "randomBytes", "fsp", "path", "randomBytes", "randomUUID", "fs", "path", "stat", "randomUUID", "
|
|
3
|
+
"sources": ["../../src/storage/session-store.ts", "../../src/utils/atomic-write.ts", "../../src/utils/child-env.ts", "../../src/utils/term.ts", "../../src/utils/color.ts", "../../src/utils/deep-merge.ts", "../../src/utils/error.ts", "../../src/utils/expect-defined.ts", "../../src/utils/message-invariants.ts", "../../src/utils/regex-guard.ts", "../../src/utils/safe-json.ts", "../../src/utils/session-scoped-path.ts", "../../src/utils/slug.ts", "../../src/utils/ulid.ts", "../../src/utils/wstack-paths.ts", "../../src/types/errors.ts", "../../src/storage/file-session-writer.ts", "../../src/storage/session-helpers.ts", "../../src/storage/session-checkpoint-cas.ts", "../../src/storage/storage-concurrency.ts", "../../src/storage/session-resume-validation.ts", "../../src/storage/session-id.ts", "../../src/storage/session-summary.ts", "../../src/storage/session-tool-call-ends.ts", "../../src/storage/queue-store.ts", "../../src/storage/attachment-store.ts", "../../src/storage/memory-backend.ts", "../../src/types/memory.ts", "../../src/storage/memory-graph-backend.ts", "../../src/utils/instruction-file.ts", "../../src/storage/memory-consolidator.ts", "../../src/storage/config-store.ts", "../../src/storage/provider-config-watcher.ts", "../../src/security/config-secrets.ts", "../../src/storage/config-loader.ts", "../../src/types/config.ts", "../../src/types/context-window.ts", "../../src/types/default-config.ts", "../../src/storage/config-migration.ts", "../../src/storage/recovery-lock.ts", "../../src/storage/session-reader.ts", "../../src/storage/annotations-store.ts", "../../src/storage/replay-log-store.ts", "../../src/replay/hash.ts", "../../src/storage/session-recovery.ts", "../../src/storage/tool-audit-log.ts", "../../src/storage/session-analyzer.ts", "../../src/session-registry.ts", "../../src/agent-status-tracker.ts", "../../src/fleet-notifier.ts", "../../src/storage/session-rewinder.ts", "../../src/storage/todos-checkpoint.ts", "../../src/storage/completed-work-checkpoint.ts", "../../src/storage/plan-store.ts", "../../src/storage/plan-templates.ts", "../../src/storage/task-store.ts", "../../src/storage/director-state.ts", "../../src/storage/goal-store.ts", "../../src/storage/goal-kanban.ts", "../../src/storage/prompt-store.ts", "../../src/storage/prompt-usage-store.ts", "../../src/storage/input-history-store.ts", "../../src/storage/cloud-sync.ts", "../../src/storage/session-event-bridge.ts"],
|
|
4
|
+
"sourcesContent": ["import { createHash } from 'node:crypto';\nimport { createReadStream, type Dirent } from 'node:fs';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { createInterface } from 'node:readline';\nimport type { EventBus } from '../kernel/events.js';\nimport type { Logger } from '../types/logger.js';\nimport type { ContentBlock } from '../types/blocks.js';\nimport type { Message } from '../types/messages.js';\nimport type { SecretScrubber } from '../types/secret-scrubber.js';\nimport type {\n ForkedSession,\n ResumedSession,\n SessionData,\n SessionEvent,\n SessionForkOptions,\n SessionMetadata,\n SessionStore,\n SessionSummary,\n SessionWriter,\n WorkspaceCheckpointRef,\n WorkspaceMaterializationResult,\n} from '../types/session.js';\nimport { atomicWrite, ensureDir, withFileLock } from '../utils/atomic-write.js';\nimport { repairToolUseAdjacency } from '../utils/message-invariants.js';\nimport { toErrorMessage } from '../utils/index.js';\nimport { sessionScopedPath } from '../utils/session-scoped-path.js';\nimport { FileSessionWriter } from './file-session-writer.js';\nimport { SessionCheckpointCas } from './session-checkpoint-cas.js';\nimport {\n formatResumeValidationNotice,\n validateResumeFileObservations,\n} from './session-resume-validation.js';\nimport { userInputTitle } from './session-helpers.js';\nimport { generateSessionId } from './session-id.js';\nimport { compareSessionSummaries, matchesSessionFilter } from './session-summary.js';\nimport { extractToolCallEnds } from './session-tool-call-ends.js';\nimport { mapWithConcurrency } from './storage-concurrency.js';\n\nexport interface SessionStoreOptions {\n dir: string;\n /**\n * Active project root used to revalidate persisted file-observation hashes\n * during resume. Omit for stores that only inspect/archive transcripts.\n */\n projectRoot?: string | undefined;\n /** Optional EventBus for emitting session diagnostics. */\n events?: EventBus | undefined;\n /**\n * Optional secret scrubber. When set, `user_input` and `llm_response` event\n * content is scrubbed before being persisted to the JSONL log and the\n * summary sidecar \u00E2\u20AC\u201D so a secret a user pastes or the model echoes does not\n * sit in cleartext on disk (and does not ride along in history cloud-sync).\n * Tool output is already scrubbed upstream by the executor; this closes the\n * conversation-turn gap (finding F-06).\n */\n secretScrubber?: SecretScrubber | undefined;\n /**\n * Optional guard consulted by {@link DefaultSessionStore.delete} before\n * removing a session. Returns `true` if the session is currently in use by\n * any live process (e.g. it is the active session of another terminal, TUI,\n * or WebUI in this project). The store ALWAYS also checks `active.json`\n * directly; this callback widens the check to cross-process live sessions\n * via the SessionRegistry. When omitted, only the `active.json` check runs.\n * Resolves to a human-readable reason when in use, or `null` when safe.\n */\n isSessionInUse?: ((sessionId: string) => Promise<string | null>) | undefined;\n /** Logger for structured warnings. Falls back to console.warn when omitted. */\n logger?: Logger | undefined;\n}\n\n/**\n * Cache entry for load() \u00E2\u20AC\u201D stores the parsed SessionData along with the\n * file's mtimeMs and size at the time of loading. On subsequent calls,\n * if the file's mtimeMs+size match, we return the cached data without\n * re-reading or re-parsing the JSONL.\n */\ninterface LoadCacheEntry {\n mtimeMs: number;\n size: number;\n data: SessionData;\n}\n\ninterface IndexCacheEntry {\n mtimeMs: number;\n size: number;\n summaries: SessionSummary[];\n}\n\ninterface SessionFileRef {\n id: string;\n filePath: string;\n}\n\ninterface DirectorySummaryCandidate {\n summary: SessionSummary;\n needsBackfill: boolean;\n}\n\ninterface ShardManifestEntry {\n summaries: SessionSummary[];\n ids: string[];\n}\n\nfunction isReplayableMessage(value: unknown): value is Message {\n if (!value || typeof value !== 'object') return false;\n const candidate = value as Partial<Message>;\n return (\n (candidate.role === 'user' ||\n candidate.role === 'assistant' ||\n candidate.role === 'system') &&\n (typeof candidate.content === 'string' || Array.isArray(candidate.content))\n );\n}\n\nfunction applyContextSnapshot(\n target: Message[],\n openToolUses: Set<string>,\n snapshot: unknown,\n): boolean {\n if (!Array.isArray(snapshot) || !snapshot.every(isReplayableMessage)) return false;\n target.length = 0;\n openToolUses.clear();\n for (const raw of snapshot) {\n const { _estTokens: _ignored, ...message } = raw;\n target.push(message);\n if (!Array.isArray(message.content)) continue;\n for (const block of message.content) {\n if (block.type === 'tool_use') openToolUses.add(block.id);\n else if (block.type === 'tool_result') openToolUses.delete(block.tool_use_id);\n }\n }\n return true;\n}\n\nfunction inheritsIntoFork(event: SessionEvent): boolean {\n switch (event.type) {\n case 'session_start':\n case 'session_resumed':\n case 'session_forked':\n case 'session_end':\n case 'in_flight_start':\n case 'in_flight_end':\n case 'rewound':\n return false;\n // Parent snapshots describe mutations owned by the parent journal. A\n // child that shares the current workspace must not inherit authority to\n // rewind those historical side effects.\n case 'file_snapshot':\n return false;\n default:\n return true;\n }\n}\n\nexport class DefaultSessionStore implements SessionStore {\n private readonly dir: string;\n private readonly events?: EventBus | undefined;\n private readonly secretScrubber?: SecretScrubber | undefined;\n private readonly projectRoot?: string | undefined;\n private readonly checkpointCas?: SessionCheckpointCas | undefined;\n private readonly isSessionInUse?: ((sessionId: string) => Promise<string | null>) | undefined;\n private readonly logger: Logger | undefined;\n\n /**\n * In-memory cache for load() results, keyed by session ID. The cache is\n * invalidated when the file's mtimeMs or size changes (indicating the\n * file was written to). This eliminates redundant full-file reads and\n * JSON parses when the same session is loaded multiple times within the\n * store's lifetime (e.g., webui session detail views, list() fallbacks).\n *\n * Max size is capped to prevent unbounded memory growth in long-running\n * processes. When the limit is reached, the oldest entry is evicted.\n */\n private readonly _loadCache = new Map<string, LoadCacheEntry>();\n private _indexCache: IndexCacheEntry | null = null;\n private readonly shardManifestCache = new Map<string, ShardManifestEntry>();\n private static readonly LOAD_CACHE_MAX_ENTRIES = 50;\n private static readonly LIST_SCAN_CONCURRENCY = 32;\n\n constructor(opts: SessionStoreOptions) {\n this.dir = opts.dir;\n this.projectRoot = opts.projectRoot ? path.resolve(opts.projectRoot) : undefined;\n this.checkpointCas = this.projectRoot\n ? new SessionCheckpointCas({\n rootDir: path.join(this.dir, '_cas'),\n projectRoot: this.projectRoot,\n })\n : undefined;\n this.events = opts.events;\n this.secretScrubber = opts.secretScrubber;\n this.isSessionInUse = opts.isSessionInUse;\n this.logger = opts.logger;\n }\n\n /**\n * Emit a structured warning. Uses the configured Logger when available;\n * falls back to console.warn(JSON) so warnings are never silently dropped.\n */\n private logWarn(msg: string, ctx?: Record<string, unknown>): void {\n if (this.logger) {\n this.logger.warn(msg, ctx);\n } else {\n console.warn(JSON.stringify({ ...ctx, message: msg, timestamp: new Date().toISOString() }));\n }\n }\n\n /**\n * Clear the load() cache. Useful for testing or when the caller knows\n * the file has changed externally (e.g., another process wrote to it).\n */\n clearLoadCache(sessionId?: string): void {\n if (sessionId !== undefined) {\n this._loadCache.delete(sessionId);\n } else {\n this._loadCache.clear();\n }\n }\n\n // \u00E2\u201D\u20AC\u00E2\u201D\u20AC Storage event helpers \u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\n\n private emitRead(\n sessionId: string,\n filePath: string,\n operation: 'load' | 'load_events_only' | 'list' | 'summary' | 'index_read',\n outcome: 'success' | 'failure',\n durationMs: number,\n error?: string,\n ): void {\n this.events?.emit('storage.read', {\n sessionId,\n store: 'session',\n filePath,\n operation,\n outcome,\n durationMs,\n ...(error !== undefined ? { error } : {}),\n });\n }\n\n private emitWrite(\n sessionId: string,\n filePath: string,\n operation: 'create' | 'resume' | 'append' | 'flush' | 'close' | 'index_append' | 'compact' | 'checkpoint',\n outcome: 'success' | 'failure',\n durationMs: number,\n eventCount?: number,\n error?: string,\n ): void {\n this.events?.emit('storage.write', {\n sessionId,\n store: 'session',\n filePath,\n operation,\n outcome,\n durationMs,\n ...(eventCount !== undefined ? { eventCount } : {}),\n ...(error !== undefined ? { error } : {}),\n });\n }\n\n private emitError(\n sessionId: string,\n filePath: string,\n operation: string,\n error: string,\n recoverable: boolean,\n ): void {\n this.events?.emit('storage.error', {\n sessionId,\n store: 'session',\n filePath,\n operation,\n error,\n recoverable,\n });\n }\n\n /** Absolute path to the session index file. */\n private get indexFile(): string {\n return path.join(this.dir, '_index.jsonl');\n }\n\n /** Join session ID to its absolute path within the store directory. */\n private sessionPath(id: string, ext: '.jsonl' | '.summary.json'): string {\n return sessionScopedPath(this.dir, id, ext);\n }\n\n private shardManifestPath(shardKey: string): string {\n return shardKey ? path.join(this.dir, shardKey, '_manifest.json') : path.join(this.dir, '_manifest.json');\n }\n\n private shardKeyForSessionId(id: string): string {\n const dirName = path.dirname(id);\n return dirName === '.' ? '' : dirName;\n }\n\n private invalidateShardManifestBySessionId(id: string): void {\n this.shardManifestCache.delete(this.shardKeyForSessionId(id));\n }\n\n /**\n * Ensure the directory implied by the session ID exists. When the ID\n * contains a date prefix like `2026-06-06/...`, this creates the date\n * subdirectory so sessions group naturally by day.\n */\n private async ensureShardDir(id: string): Promise<string> {\n const dirPath = path.dirname(sessionScopedPath(this.dir, id, ''));\n await ensureDir(dirPath);\n return dirPath;\n }\n\n async create(meta: Omit<SessionMetadata, 'startedAt'>): Promise<SessionWriter> {\n const startedAt = new Date().toISOString();\n const id =\n meta.id && meta.id.length > 0\n ? meta.id\n : generateSessionId(startedAt);\n const shardDir = await this.ensureShardDir(id);\n const file = this.sessionPath(id, '.jsonl');\n const t0 = Date.now();\n let handle: fsp.FileHandle;\n try {\n handle = await fsp.open(file, 'a', 0o600);\n } catch (err) {\n this.emitError(id, file, 'create', toErrorMessage(err), false);\n throw new Error(\n `Failed to open session file: ${toErrorMessage(err)}`,\n { cause: err },\n );\n }\n try {\n const writer = new FileSessionWriter(id, handle, startedAt, meta, this.events, {\n dir: shardDir,\n filePath: file,\n secretScrubber: this.secretScrubber,\n checkpointCas: this.checkpointCas,\n onClose: (s) => this.appendToIndex(s),\n });\n this.emitWrite(id, file, 'create', 'success', Date.now() - t0);\n return writer;\n /* v8 ignore start -- defensive: FileSessionWriter ctor does not throw in practice */\n } catch (err) {\n await handle.close().catch((e) => this.logWarn(\n 'Session handle close failed',\n { event: 'session_store.handle_close_failed', message: e instanceof Error ? e.message : String(e) },\n ));\n this.emitError(id, file, 'create', toErrorMessage(err), true);\n throw err;\n }\n /* v8 ignore stop */\n }\n\n async fork(id: string, opts: SessionForkOptions = {}): Promise<ForkedSession> {\n const parent = await this.load(id);\n let boundary = parent.events.length - 1;\n let targetCheckpoint: Extract<SessionEvent, { type: 'checkpoint' }> | undefined;\n if (opts.checkpointPromptIndex !== undefined) {\n boundary = -1;\n for (let i = 0; i < parent.events.length; i++) {\n const event = parent.events[i];\n if (\n event?.type === 'checkpoint' &&\n event.promptIndex === opts.checkpointPromptIndex\n ) {\n // Prefer the latest matching checkpoint if a legacy/non-truncated\n // journal reused prompt indices after a rewind.\n boundary = i;\n targetCheckpoint = event;\n }\n }\n if (boundary === -1) {\n throw new Error(\n `Checkpoint ${opts.checkpointPromptIndex} not found in session \"${id}\"`,\n );\n }\n }\n\n const parentPrefix = parent.events.slice(0, boundary + 1);\n const workspaceCheckpoint = targetCheckpoint?.workspaceCheckpoint;\n const checkpointHash = createHash('sha256')\n .update(parentPrefix.map((event) => JSON.stringify(event)).join('\\n') + '\\n', 'utf8')\n .digest('hex');\n const inherited = parentPrefix.filter(inheritsIntoFork);\n const writer = await this.create({\n id: '',\n title: parent.metadata.title,\n model: parent.metadata.model,\n provider: parent.metadata.provider,\n });\n\n try {\n await writer.append({\n type: 'session_forked',\n ts: new Date().toISOString(),\n parentSessionId: id,\n parentCheckpointPromptIndex: opts.checkpointPromptIndex,\n parentCheckpointHash: checkpointHash,\n workspace: 'shared-current',\n workspaceCheckpointHash: workspaceCheckpoint?.manifestHash,\n });\n const batchSize = 250;\n for (let offset = 0; offset < inherited.length; offset += batchSize) {\n await writer.appendBatch(inherited.slice(offset, offset + batchSize));\n }\n await writer.flush();\n await writer.close();\n const data = await this.load(writer.id);\n return {\n id: writer.id,\n data,\n parentSessionId: id,\n checkpointPromptIndex: opts.checkpointPromptIndex,\n checkpointHash,\n workspace: 'shared-current',\n workspaceCheckpoint,\n };\n } catch (err) {\n await writer.close().catch(() => undefined);\n await this.delete(writer.id).catch(() => undefined);\n throw err;\n }\n }\n\n async materializeWorkspaceCheckpoint(\n checkpoint: WorkspaceCheckpointRef,\n targetRoot: string,\n ): Promise<WorkspaceMaterializationResult> {\n if (!this.checkpointCas) {\n throw new Error('Workspace checkpoint materialization requires a projectRoot-aware session store');\n }\n return this.checkpointCas.materialize(checkpoint, targetRoot);\n }\n\n async resume(id: string): Promise<ResumedSession> {\n const file = this.sessionPath(id, '.jsonl');\n const t0 = Date.now();\n const data = await this.load(id);\n let resumedData = data;\n if (this.projectRoot) {\n try {\n const resumeValidation = await validateResumeFileObservations(data.events, this.projectRoot);\n const notice = formatResumeValidationNotice(resumeValidation, this.projectRoot);\n resumedData = {\n ...data,\n resumeValidation,\n ...(notice\n ? {\n messages: [\n ...data.messages,\n { role: 'system' as const, content: notice, ts: resumeValidation.checkedAt },\n ],\n }\n : {}),\n };\n } catch (err) {\n // Validation is a safety signal, not a reason to make an otherwise\n // readable session impossible to resume. Surface diagnostics and\n // continue with the replay if an unexpected filesystem error occurs.\n this.emitError(id, file, 'resume_validation', toErrorMessage(err), true);\n }\n }\n let handle: fsp.FileHandle;\n try {\n handle = await fsp.open(file, 'a', 0o600);\n /* v8 ignore start -- defensive: load() above already validated the file is readable */\n } catch (err) {\n this.emitError(id, file, 'resume', toErrorMessage(err), false);\n throw new Error(\n `Failed to open session \"${id}\" for append: ${toErrorMessage(err)}`,\n { cause: err },\n );\n }\n /* v8 ignore stop */\n try {\n const writer = new FileSessionWriter(\n id,\n handle,\n new Date().toISOString(),\n {\n id,\n model: data.metadata.model,\n provider: data.metadata.provider,\n },\n this.events,\n {\n resumed: true,\n // Shard directory (sessions/<date>/) \u00E2\u20AC\u201D must match create() so the\n // .summary.json sidecar lands next to the JSONL instead of the\n // sessions root (where summaryFor() would never find it).\n dir: path.dirname(file),\n filePath: file,\n secretScrubber: this.secretScrubber,\n checkpointCas: this.checkpointCas,\n onClose: (s) => this.appendToIndex(s),\n },\n );\n this.emitWrite(id, file, 'resume', 'success', Date.now() - t0);\n return { writer, data: resumedData };\n /* v8 ignore start -- defensive: FileSessionWriter ctor does not throw in practice */\n } catch (err) {\n await handle.close().catch((e) => this.logWarn(\n 'Session handle close failed',\n { event: 'session_store.handle_close_failed', message: e instanceof Error ? e.message : String(e) },\n ));\n this.emitError(id, file, 'resume', toErrorMessage(err), true);\n throw err;\n }\n /* v8 ignore stop */\n }\n\n async load(id: string): Promise<SessionData> {\n return this.loadInternal(id, { full: true });\n }\n\n /**\n * Fast-path loader that skips message reconstruction and adjacency repair.\n *\n * Use this for callers that only need the raw event stream + session\n * metadata \u2014 e.g. session listers, analytics, audit, and the TUI's\n * \"events only\" views. It avoids the message array build and\n * repairToolUseAdjacency cost on large session files (a long agent\n * run can have 50k+ events; rebuilding messages is O(events) and\n * allocates per-block, so skipping it is a meaningful win).\n *\n * The returned data.messages is an empty array; data.toolCallEnds\n * is computed from the raw events. usage is the sum across all\n * llm_response events \u2014 same as full load().\n */\n async loadEventsOnly(id: string): Promise<SessionData> {\n return this.loadInternal(id, { full: false });\n }\n\n private async loadInternal(\n id: string,\n mode: { full: true } | { full: false },\n ): Promise<SessionData> {\n const file = this.sessionPath(id, '.jsonl');\n const t0 = Date.now();\n let outcome: 'success' | 'failure' = 'success';\n let errorMsg: string | undefined;\n let cacheHit = false;\n try {\n // Stat the file first to check the cache. The stat is cheap (no content\n // read) and lets us skip the full readFile + JSON parse when the file\n // hasn't changed since the last load.\n const s = await fsp.stat(file);\n const stat: { mtimeMs: number; size: number } = { mtimeMs: s.mtimeMs, size: s.size };\n\n // Check cache: if mtimeMs AND size match, the file hasn't changed.\n const cached = this._loadCache.get(id);\n if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {\n cacheHit = true;\n // Update insertion order to prevent frequent-access sessions from being\n // evicted by the LRU eviction logic.\n this._loadCache.delete(id);\n this._loadCache.set(id, cached);\n if (mode.full) return cached.data;\n return { ...cached.data, messages: [] };\n }\n\n // Cache miss \u00E2\u20AC\u201D do the full read + parse.\n // Fused single pass: parse events + build messages + extract metadata together.\n // Streams the file line-by-line so we don't materialize the whole JSONL\n // (multi-MB for long sessions). Events-only requests skip the message\n // build and the adjacency repair entirely.\n const events: SessionEvent[] = [];\n\n // Metadata extracted in the same single pass over the raw lines.\n let sessionStartEvent: SessionEvent | undefined;\n let sessionEndEvent: SessionEvent | undefined;\n let sessionModel: string | undefined;\n let sessionProvider: string | undefined;\n let sessionPendingToolUses: string[] | undefined;\n let sessionForkedEvent: Extract<SessionEvent, { type: 'session_forked' }> | undefined;\n\n // Message builder state \u2014 only allocated when mode.full.\n const messages: Message[] | undefined = mode.full ? [] : undefined;\n const openToolUses: Set<string> | undefined = mode.full ? new Set<string>() : undefined;\n let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };\n\n const stream = createReadStream(file, { encoding: 'utf8' });\n const rl = createInterface({ input: stream, crlfDelay: Infinity });\n\n try {\n for await (const line of rl) {\n if (!line.trim()) continue;\n try {\n const parsed: unknown = JSON.parse(line);\n if (\n parsed !== null &&\n typeof parsed === 'object' &&\n typeof (parsed as { type?: unknown | undefined }).type === 'string' &&\n typeof (parsed as { ts?: unknown | undefined }).ts === 'string'\n ) {\n const ev = parsed as SessionEvent;\n events.push(ev);\n\n // Track metadata in the same pass.\n if (ev.type === 'session_start' && !sessionStartEvent) {\n sessionStartEvent = ev;\n sessionModel = ev.model;\n sessionProvider = ev.provider;\n }\n if (ev.type === 'session_end') {\n sessionEndEvent = ev;\n sessionPendingToolUses = ev.pendingToolUses;\n }\n if (ev.type === 'session_forked' && !sessionForkedEvent) {\n sessionForkedEvent = ev;\n }\n\n // Build messages in the same pass (replay() logic inlined).\n // Skipped entirely when mode.full is false.\n if (mode.full && messages !== undefined && openToolUses !== undefined) {\n if (ev.type === 'context_snapshot') {\n if (!applyContextSnapshot(messages, openToolUses, ev.messages)) {\n this.events?.emit('session.damaged', {\n sessionId: id,\n detail: 'Ignored malformed context_snapshot event',\n });\n }\n } else if (ev.type === 'user_input') {\n openToolUses.clear();\n messages.push({ role: 'user', content: ev.content, ts: ev.ts });\n } else if (ev.type === 'llm_response') {\n messages.push({ role: 'assistant', content: ev.content, ts: ev.ts });\n for (const b of ev.content) {\n if (b.type === 'tool_use') openToolUses.add(b.id);\n }\n usage = {\n input: usage.input + (ev.usage.input ?? 0),\n output: usage.output + (ev.usage.output ?? 0),\n cacheRead: (usage.cacheRead ?? 0) + (ev.usage.cacheRead ?? 0),\n cacheWrite: (usage.cacheWrite ?? 0) + (ev.usage.cacheWrite ?? 0),\n };\n } else if (ev.type === 'tool_result') {\n if (!openToolUses.has(ev.id)) {\n this.events?.emit('session.damaged', {\n sessionId: id,\n detail: `Orphan tool_result \"${ev.id}\" has no matching tool_use`,\n });\n continue;\n }\n openToolUses.delete(ev.id);\n const resultBlock: ContentBlock = {\n type: 'tool_result',\n tool_use_id: ev.id,\n content: typeof ev.content === 'string' ? ev.content : JSON.stringify(ev.content),\n is_error: ev.isError,\n };\n const last = messages[messages.length - 1];\n const lastIsToolResultUser =\n last?.role === 'user' &&\n Array.isArray(last.content) &&\n last.content.every((b) => (b as ContentBlock).type === 'tool_result');\n if (lastIsToolResultUser && Array.isArray(last.content)) {\n last.content.push(resultBlock);\n } else {\n messages.push({ role: 'user', content: [resultBlock], ts: ev.ts });\n }\n }\n } else if (ev.type === 'llm_response') {\n // events-only path still accumulates usage.\n usage = {\n input: usage.input + (ev.usage.input ?? 0),\n output: usage.output + (ev.usage.output ?? 0),\n cacheRead: (usage.cacheRead ?? 0) + (ev.usage.cacheRead ?? 0),\n cacheWrite: (usage.cacheWrite ?? 0) + (ev.usage.cacheWrite ?? 0),\n };\n }\n }\n } catch {\n // skip malformed JSON\n }\n }\n } finally {\n rl.close();\n stream.close();\n }\n\n let finalMessages: Message[] = [];\n if (mode.full && messages !== undefined && openToolUses !== undefined) {\n // Repair tool adjacency after the single parse + replay loop.\n if (openToolUses.size > 0) {\n this.events?.emit('session.damaged', {\n sessionId: id,\n detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`,\n });\n }\n const repaired = repairToolUseAdjacency(messages);\n if (repaired.report.changed) {\n this.events?.emit('session.damaged', {\n sessionId: id,\n detail:\n `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ` +\n `${repaired.report.removedToolResults.length} tool_result, ` +\n `${repaired.report.removedMessages} empty messages`,\n });\n }\n finalMessages = repaired.messages;\n }\n\n // Build metadata from the extracted session_start/end events.\n const meta: SessionMetadata = {\n id,\n startedAt: sessionStartEvent?.ts ?? new Date(0).toISOString(),\n endedAt: sessionEndEvent?.ts,\n model: sessionModel,\n provider: sessionProvider,\n pendingToolUses: sessionPendingToolUses,\n forkedFrom: sessionForkedEvent\n ? {\n sessionId: sessionForkedEvent.parentSessionId,\n checkpointPromptIndex: sessionForkedEvent.parentCheckpointPromptIndex,\n checkpointHash: sessionForkedEvent.parentCheckpointHash,\n workspace: sessionForkedEvent.workspace,\n workspaceCheckpointHash: sessionForkedEvent.workspaceCheckpointHash,\n }\n : undefined,\n };\n\n // Extract tool_call_end events for TUI tool entry rendering on resume.\n const toolCallEnds = extractToolCallEnds(events);\n const data: SessionData = { metadata: meta, events, messages: finalMessages, usage, toolCallEnds };\n\n // Update the cache. Evict oldest entry if at capacity.\n // Only full loads populate the cache; events-only loads always read\n // through (they're cheap, and a hot loop on events-only would\n // otherwise evict full-load entries that callers also need).\n if (mode.full) {\n if (this._loadCache.size >= DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {\n // Map iteration order is insertion order \u00E2\u20AC\u201D delete the first key.\n const oldest = this._loadCache.keys().next().value;\n if (oldest !== undefined) {\n this._loadCache.delete(oldest);\n }\n }\n this._loadCache.set(id, { mtimeMs: stat.mtimeMs, size: stat.size, data });\n }\n\n return data;\n } catch (err) {\n outcome = 'failure';\n errorMsg = toErrorMessage(err);\n throw err;\n } finally {\n this.emitRead(id, file, mode.full ? 'load' : 'load_events_only', outcome, Date.now() - t0, errorMsg);\n if (cacheHit) {\n this.events?.emit('storage.cache_hit', {\n sessionId: id,\n store: 'session',\n filePath: file,\n operation: mode.full ? 'load' : 'load_events_only',\n durationMs: Date.now() - t0,\n });\n }\n }\n }\n\n /**\n * Streaming search over a session's JSONL. Walks the file once, parses\n * each event lazily, and yields only the events that match `predicate`.\n * Stops as soon as `opts.limit` matches are collected.\n *\n * Why this exists: `load()` parses the entire file into memory and\n * rebuilds `messages`/`toolCallEnds` for every caller. `search()` only\n * needs to know which events contain matching text \u2014 a per-line\n * predicate is enough. The full parse work (and the `_loadCache` poll)\n * is wasted in that case.\n *\n * Memory: O(hits) regardless of file size. Disk: one linear scan,\n * terminated at `limit` if the caller asked for one.\n *\n * Errors: missing file yields []. Corrupt lines are skipped (same\n * policy as `load()`). Aborting via `signal` rejects with `AbortError`.\n */\n async searchEvents(\n id: string,\n predicate: (event: SessionEvent, eventIndex: number, ts: string) => boolean,\n opts?: { limit?: number | undefined; signal?: AbortSignal | undefined },\n ): Promise<Array<{ event: SessionEvent; eventIndex: number; ts: string }>> {\n const file = this.sessionPath(id, '.jsonl');\n const limit = opts?.limit;\n const signal = opts?.signal;\n const out: Array<{ event: SessionEvent; eventIndex: number; ts: string }> = [];\n\n // Try to stat first so a missing file returns [] instead of throwing\n // \u2014 matches `load()` ENOENT semantics that callers already depend on.\n let stat: import('node:fs').Stats;\n try {\n stat = await fsp.stat(file);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw err;\n }\n if (stat.size === 0) return [];\n\n let fh: fsp.FileHandle | undefined;\n try {\n fh = await fsp.open(file, 'r');\n // Read in 64KB chunks; lines can straddle a chunk boundary so we\n // carry the trailing partial line forward between iterations.\n const CHUNK = 64 * 1024;\n const buf = Buffer.alloc(CHUNK);\n let leftover = '';\n let eventIndex = 0;\n for (let position = 0; ; position += buf.byteLength) {\n if (signal?.aborted) {\n const reason = signal.reason ?? new DOMException('Aborted', 'AbortError');\n throw reason;\n }\n const { bytesRead } = await fh.read(buf, 0, CHUNK, position);\n if (bytesRead === 0) break;\n const text = leftover + buf.subarray(0, bytesRead).toString('utf8');\n // Split into lines; the last element is either '' (file ended on a\n // newline) or a partial line \u2014 keep it as the new leftover.\n const parts = text.split('\\n');\n leftover = parts.pop() ?? '';\n for (const line of parts) {\n if (!line) continue;\n let ev: SessionEvent;\n try {\n const parsed: unknown = JSON.parse(line);\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n typeof (parsed as { type?: unknown }).type !== 'string' ||\n typeof (parsed as { ts?: unknown }).ts !== 'string'\n ) {\n // Skip lines that don't match the SessionEvent shape \u2014 same\n // tolerance as `load()` (which silently drops non-events).\n continue;\n }\n ev = parsed as SessionEvent;\n } catch {\n // Skip malformed JSON, matching `load()` behavior.\n continue;\n }\n if (predicate(ev, eventIndex, ev.ts)) {\n out.push({ event: ev, eventIndex, ts: ev.ts });\n if (limit !== undefined && out.length >= limit) {\n return out;\n }\n }\n eventIndex++;\n }\n }\n // Flush a trailing line that lacks a final newline.\n if (leftover.trim()) {\n try {\n const parsed: unknown = JSON.parse(leftover);\n if (\n parsed !== null &&\n typeof parsed === 'object' &&\n typeof (parsed as { type?: unknown }).type === 'string' &&\n typeof (parsed as { ts?: unknown }).ts === 'string'\n ) {\n const ev = parsed as SessionEvent;\n if (predicate(ev, eventIndex, ev.ts)) {\n out.push({ event: ev, eventIndex, ts: ev.ts });\n }\n }\n } catch {\n /* partial trailing line \u2014 drop */\n }\n }\n return out;\n } finally {\n if (fh) await fh.close().catch(() => undefined);\n }\n }\n\n async list(limit = 20): Promise<SessionSummary[]> {\n try {\n await ensureDir(this.dir);\n // Try the index first; fall back to directory scan if the index is\n // missing, empty, or unreadable.\n const indexed = await this.readIndex();\n if (indexed.length > 0) {\n // `readIndex()` already sorted the array by startedAt DESC, id\n // ASC, so we just slice the prefix.\n return indexed.slice(0, limit);\n }\n // Index unavailable \u2014 fall back to a directory scan. Prefer summary\n // sidecars and only backfill full JSONL-derived summaries for the page\n // we are about to return.\n return await this.listFromDirectoryScan(limit);\n } catch {\n return [];\n }\n }\n\n /**\n * List sessions matching filter criteria, using the cached index.\n * Filters are applied BEFORE sorting and slicing, so the caller gets\n * exactly `limit` matching sessions \u2014 not a slice of a larger fetch.\n *\n * This avoids the DefaultSessionReader pattern of fetching 1000 sessions\n * then linear-filtering: the index is already in memory (readIndex\n * caches it), and the filter runs over the cached array without any\n * additional disk I/O.\n */\n async listFiltered(criteria: {\n since?: string | undefined;\n until?: string | undefined;\n provider?: string | undefined;\n model?: string | undefined;\n minTokens?: number | undefined;\n titleContains?: string | undefined;\n limit?: number | undefined;\n }): Promise<SessionSummary[]> {\n const limit = criteria.limit ?? 100;\n try {\n await ensureDir(this.dir);\n const indexed = await this.readIndex();\n if (indexed.length === 0) {\n // No index \u2014 fall back to list() + in-process filter.\n const raw = await this.list(Math.max(limit, 100));\n return raw.filter((s) => matchesSessionFilter(s, criteria)).slice(0, limit);\n }\n const filtered = indexed.filter((s) => matchesSessionFilter(s, criteria));\n filtered.sort((a, b) => {\n if (a.startedAt < b.startedAt) return 1;\n if (a.startedAt > b.startedAt) return -1;\n return a.id.localeCompare(b.id);\n });\n return filtered.slice(0, limit);\n } catch {\n return [];\n }\n }\n\n // \u00E2\u201D\u20AC\u00E2\u201D\u20AC Session index (_index.jsonl) \u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\n //\n // One JSON line per closed session, appended atomically on close().\n // When a session is deleted, a tombstone {action:\"delete\",id:\"...\"} is\n // appended. On read, tombstones filter out matching session entries.\n // This keeps listing O(lines-in-index) instead of O(files-on-disk).\n //\n // The index auto-compacts every N appends to prevent unbounded growth\n // from tombstones and duplicate entries (resume cycles).\n\n private indexAppendCount = 0;\n private static readonly COMPACT_EVERY = 30;\n\n /** Append a session summary to the index. */\n private async appendToIndex(summary: SessionSummary): Promise<void> {\n // Note: storage.write for this operation is emitted by FileSessionWriter.doClose()\n // so it can include the traceId. Do NOT emit here to avoid duplicates.\n try {\n await ensureDir(this.dir);\n // Serialize the append (and any compaction it triggers) under the index\n // file lock. The lock is per-FILE, so it also guards against a SECOND\n // wstack process in the same project appending/compacting concurrently \u2014\n // without it, a compact() rewrite racing an append() silently drops the\n // appended line (the source-of-truth .jsonl survives, but the listing\n // cache loses the entry until rebuildIndex()).\n let shouldCompact = false;\n await withFileLock(this.indexFile, async () => {\n const line = JSON.stringify(summary) + '\\n';\n await fsp.appendFile(this.indexFile, line, 'utf8');\n this._indexCache = null;\n this.invalidateShardManifestBySessionId(summary.id);\n this.indexAppendCount++;\n // Auto-compact periodically to remove tombstones and duplicates.\n // compactIndexInner() is called WHILE the lock is held \u2014 it must not\n // re-acquire it (withFileLock is not reentrant) or it would deadlock.\n if (this.indexAppendCount >= DefaultSessionStore.COMPACT_EVERY) {\n shouldCompact = true;\n this.indexAppendCount = 0;\n }\n });\n if (shouldCompact) {\n await withFileLock(this.indexFile, () => this.compactIndexInner());\n }\n } catch {\n // best-effort \u00E2\u20AC\u201D error surfaced via the storage.write event in doClose()\n }\n }\n\n /** Append a tombstone entry for a deleted session. */\n private async writeTombstone(id: string): Promise<void> {\n try {\n await ensureDir(this.dir);\n await withFileLock(this.indexFile, async () => {\n const line = JSON.stringify({ action: 'delete', id }) + '\\n';\n await fsp.appendFile(this.indexFile, line, 'utf8');\n this._indexCache = null;\n this.invalidateShardManifestBySessionId(id);\n this.indexAppendCount++;\n });\n } catch {\n // best-effort\n }\n }\n\n /**\n * Compact the index: read all entries, drop tombstones, deduplicate\n * (keep latest per session), and rewrite atomically. Acquires the index\n * file lock so a concurrent append (this process or another wstack in the\n * same project) can't be overwritten by the rewrite.\n */\n private async compactIndex(): Promise<void> {\n const t0 = Date.now();\n let outcome: 'success' | 'failure' = 'success';\n let errorMsg: string | undefined;\n try {\n await withFileLock(this.indexFile, () => this.compactIndexInner());\n } catch (err) {\n outcome = 'failure';\n errorMsg = toErrorMessage(err);\n } finally {\n // Compact is internal \u00E2\u20AC\u201D use 'session' as the session ID placeholder.\n this.emitWrite('~compact~', this.indexFile, 'compact', outcome, Date.now() - t0, undefined, errorMsg);\n }\n }\n\n /**\n * Lock-free compaction body. The caller MUST already hold the index file\n * lock (via withFileLock(this.indexFile, ...)). Uses atomicWrite for the\n * rewrite so the temp file gets a random suffix (no collision between two\n * compactions) and the Windows transient-EPERM rename retry.\n */\n private async compactIndexInner(): Promise<void> {\n const entries = await this.readIndex();\n if (entries.length === 0) return;\n const lines = entries.map((s) => JSON.stringify(s)).join('\\n') + '\\n';\n await atomicWrite(this.indexFile, lines, { mode: 0o600 });\n this._indexCache = null;\n }\n\n /**\n * Read the index file and return deduplicated session summaries.\n * Entries with a matching tombstone are filtered out.\n * Returns empty array when the index doesn't exist or is corrupt.\n */\n private async readIndex(): Promise<SessionSummary[]> {\n let stat: { mtimeMs: number; size: number };\n try {\n const s = await fsp.stat(this.indexFile);\n stat = { mtimeMs: s.mtimeMs, size: s.size };\n } catch {\n this._indexCache = null;\n return [];\n }\n\n if (\n this._indexCache !== null &&\n this._indexCache.mtimeMs === stat.mtimeMs &&\n this._indexCache.size === stat.size\n ) {\n return [...this._indexCache.summaries];\n }\n\n let raw: string;\n try {\n raw = await fsp.readFile(this.indexFile, 'utf8');\n } catch {\n this._indexCache = null;\n return [];\n }\n const deleted = new Set<string>();\n const seen = new Map<string, SessionSummary>();\n for (const line of raw.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const entry = JSON.parse(line) as { action?: string | undefined; id?: string | undefined } & SessionSummary;\n if (entry.action === 'delete' && entry.id) {\n deleted.add(entry.id);\n seen.delete(entry.id);\n continue;\n }\n if (entry.id && !deleted.has(entry.id)) {\n // Keep the latest entry for each session (multiple appends on resume).\n seen.set(entry.id, entry as SessionSummary);\n }\n } catch {\n // skip corrupt lines\n }\n }\n const summaries = Array.from(seen.values());\n // Sort once when the index is (re)loaded so `list()` callers can\n // take a prefix without re-sorting the whole array per request.\n // Sort key mirrors the original `list()` comparator:\n // startedAt DESC, then id ASC for tie-breaks.\n summaries.sort((a, b) => {\n if (a.startedAt < b.startedAt) return 1;\n if (a.startedAt > b.startedAt) return -1;\n return a.id.localeCompare(b.id);\n });\n this._indexCache = { ...stat, summaries };\n return [...summaries];\n }\n\n /**\n * Rebuild the index from disk by scanning all sessions and writing a\n * fresh _index.jsonl. Useful after manual cleanup or index corruption.\n */\n async rebuildIndex(): Promise<number> {\n const ids = await this.collectSessionIds(this.dir);\n /* v8 ignore next -- summaryFor() never rejects for a collected id (its .jsonl exists) */\n const summaries = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null))); /* best-effort */\n const valid = summaries.filter((s): s is SessionSummary => s !== null);\n // Atomic rewrite under the index lock so it can't clobber a concurrent\n // append (or be clobbered by a concurrent compaction). atomicWrite gives\n // a random temp suffix (no collision with compactIndexInner's temp) and\n // the Windows transient-EPERM rename retry. The expensive disk scan above\n // runs OUTSIDE the lock to avoid holding it for the whole rebuild.\n const lines = valid.map((s) => JSON.stringify(s)).join('\\n') + '\\n';\n await withFileLock(this.indexFile, async () => {\n await atomicWrite(this.indexFile, lines, { mode: 0o600 });\n this._indexCache = null;\n });\n return valid.length;\n }\n\n private async listFromDirectoryScan(limit: number): Promise<SessionSummary[]> {\n const shardKeys = await this.collectShardKeys();\n const shardEntries = await mapWithConcurrency(\n shardKeys,\n DefaultSessionStore.LIST_SCAN_CONCURRENCY,\n async (shardKey) => await this.readOrBuildShardManifest(shardKey),\n );\n\n const out: DirectorySummaryCandidate[] = [];\n for (const entry of shardEntries) {\n for (const summary of entry.summaries) {\n out.push({ summary, needsBackfill: false });\n }\n }\n out.sort((a, b) => compareSessionSummaries(a.summary, b.summary));\n\n const selected = out.slice(0, limit);\n const summaries = await mapWithConcurrency(\n selected,\n Math.min(DefaultSessionStore.LIST_SCAN_CONCURRENCY, Math.max(1, limit)),\n async (candidate): Promise<SessionSummary | null> => candidate.summary,\n );\n return summaries.filter((s): s is SessionSummary => s !== null);\n }\n\n private async collectShardKeys(): Promise<string[]> {\n let entries: Dirent[];\n try {\n entries = await fsp.readdir(this.dir, { withFileTypes: true });\n } catch {\n return [''];\n }\n\n const shardKeys = [''];\n for (const entry of entries) {\n if (entry.name.startsWith('.') && entry.name !== '.wrongstack') continue;\n if (entry.name === 'shared' || entry.name === 'subagents' || entry.name === 'attachments') continue;\n if (entry.isDirectory()) shardKeys.push(entry.name);\n }\n return shardKeys;\n }\n\n private async readOrBuildShardManifest(shardKey: string): Promise<ShardManifestEntry> {\n const cached = this.shardManifestCache.get(shardKey);\n if (cached) return cached;\n\n const manifestPath = this.shardManifestPath(shardKey);\n try {\n const raw = await fsp.readFile(manifestPath, 'utf8');\n const parsed = JSON.parse(raw) as ShardManifestEntry;\n const entry: ShardManifestEntry = {\n summaries: Array.isArray(parsed.summaries) ? parsed.summaries : [],\n ids: Array.isArray(parsed.ids) ? parsed.ids : [],\n };\n this.shardManifestCache.set(shardKey, entry);\n return entry;\n } catch {\n // build below\n }\n\n const refs = await this.collectSessionFilesInShard(shardKey);\n const candidates = await mapWithConcurrency(\n refs,\n DefaultSessionStore.LIST_SCAN_CONCURRENCY,\n async (ref): Promise<DirectorySummaryCandidate | null> => {\n const manifest = await this.readSummaryManifest(ref.id);\n if (manifest) return { summary: manifest, needsBackfill: false };\n const summary = await this.summaryHeaderFor(ref);\n if (!summary) return null;\n const hydrated = await this.summaryFor(summary.id).catch(() => summary);\n return { summary: hydrated, needsBackfill: false };\n },\n );\n const summaries = candidates\n .filter((candidate): candidate is DirectorySummaryCandidate => candidate !== null)\n .map((candidate) => candidate.summary);\n summaries.sort(compareSessionSummaries);\n const entry: ShardManifestEntry = { summaries, ids: summaries.map((summary) => summary.id) };\n this.shardManifestCache.set(shardKey, entry);\n await atomicWrite(manifestPath, JSON.stringify(entry), { mode: 0o600 }).catch(() => undefined);\n return entry;\n }\n\n private async collectSessionFilesInShard(shardKey: string): Promise<SessionFileRef[]> {\n const dir = shardKey ? path.join(this.dir, shardKey) : this.dir;\n const entries = await this.collectSessionFiles(dir, shardKey);\n return shardKey\n ? entries.filter((entry) => entry.id.startsWith(`${shardKey}/`))\n : entries.filter((entry) => !entry.id.includes('/'));\n }\n\n private async collectSessionFiles(\n dir: string,\n prefix = '',\n depth = 0,\n ): Promise<SessionFileRef[]> {\n let entries: Dirent[];\n try {\n entries = await fsp.readdir(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n\n const dirEntries: Dirent[] = [];\n const files: SessionFileRef[] = [];\n for (const entry of entries) {\n if (entry.name.startsWith('.') && entry.name !== '.wrongstack') continue;\n if (entry.name === 'shared' || entry.name === 'subagents' || entry.name === 'attachments')\n continue;\n if (entry.isDirectory()) {\n dirEntries.push(entry);\n } else if (entry.isFile() && entry.name.endsWith('.jsonl')) {\n if (entry.name === '_index.jsonl') continue;\n const base = entry.name.replace(/\\.jsonl$/, '');\n const id = prefix ? `${prefix}/${base}` : base;\n files.push({ id, filePath: path.join(dir, entry.name) });\n }\n }\n\n const childFileArrays = await Promise.all(\n dirEntries.map((entry) => {\n const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;\n return this.collectSessionFiles(path.join(dir, entry.name), childPrefix, depth + 1);\n }),\n );\n\n return [...childFileArrays.flat(), ...files];\n }\n\n /** Recursively collect session IDs from date-shard subdirectories.\n * IDs include the date-prefix path (e.g. \"2026-06-06/17-46-57Z_\u00E2\u20AC\u00A6\").\n * Skips `.jsonl`/`.summary.json` root files, dot-files, and\n * sub-directories that belong to fleet/subagent sessions. */\n private async collectSessionIds(\n dir: string,\n prefix = '',\n depth = 0,\n ): Promise<string[]> {\n let entries: Dirent[];\n try {\n entries = await fsp.readdir(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n\n // Separate dirs and files in one pass \u00E2\u20AC\u201D avoids a second iteration.\n const dirEntries: Dirent[] = [];\n const fileIds: string[] = [];\n for (const entry of entries) {\n if (entry.name.startsWith('.') && entry.name !== '.wrongstack') continue;\n if (entry.name === 'shared' || entry.name === 'subagents' || entry.name === 'attachments')\n continue;\n if (entry.isDirectory()) {\n dirEntries.push(entry);\n } else if (entry.isFile() && entry.name.endsWith('.jsonl')) {\n if (entry.name === '_index.jsonl') continue;\n const base = entry.name.replace(/\\.jsonl$/, '');\n fileIds.push(prefix ? `${prefix}/${base}` : base);\n }\n }\n\n // At depth 0 the date-shard directories are independent \u00E2\u20AC\u201D parallelize across\n // them. Deeper recursion (intra-shard) is sequential since shards are small.\n const childIdArrays = await Promise.all(\n dirEntries.map((entry) => {\n const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;\n return this.collectSessionIds(path.join(dir, entry.name), childPrefix, depth + 1);\n }),\n );\n\n return [...childIdArrays.flat(), ...fileIds];\n }\n\n private async summaryFor(id: string): Promise<SessionSummary> {\n const manifest = this.sessionPath(id, '.summary.json');\n const t0 = Date.now();\n let outcome: 'success' | 'failure' = 'success';\n let errorMsg: string | undefined;\n const fromManifest = await this.readSummaryManifest(id, t0);\n if (fromManifest) return fromManifest;\n\n try {\n const full = this.sessionPath(id, '.jsonl');\n const stat = await fsp.stat(full);\n const summary = await this.summarize(id, stat.mtime.toISOString());\n await atomicWrite(manifest, JSON.stringify(summary), { mode: 0o600 }).catch((err) => {\n const msg = toErrorMessage(err);\n this.emitError(id, manifest, 'summary_fallback', msg, true);\n this.logWarn(\n 'Session manifest write failed',\n { event: 'session_store.manifest_write_failed', sessionId: id, message: msg },\n );\n });\n outcome = 'failure';\n errorMsg = 'summary fallback \u00E2\u20AC\u201D manifest rebuilt';\n this.emitRead(id, manifest, 'summary', outcome, Date.now() - t0, errorMsg);\n return summary;\n } catch (err) {\n outcome = 'failure';\n errorMsg = toErrorMessage(err);\n this.emitRead(id, manifest, 'summary', outcome, Date.now() - t0, errorMsg);\n return {\n id,\n title: '(damaged)',\n startedAt: new Date().toISOString(),\n model: 'unknown',\n provider: 'unknown',\n tokenTotal: 0,\n };\n }\n }\n\n private async readSummaryManifest(\n id: string,\n startTime = Date.now(),\n ): Promise<SessionSummary | null> {\n const manifest = this.sessionPath(id, '.summary.json');\n try {\n const raw = await fsp.readFile(manifest, 'utf8');\n this.emitRead(id, manifest, 'summary', 'success', Date.now() - startTime);\n return JSON.parse(raw) as SessionSummary;\n } catch {\n return null;\n }\n }\n\n private async summaryHeaderFor(ref: SessionFileRef): Promise<SessionSummary | null> {\n let mtime = new Date(0).toISOString();\n try {\n const stat = await fsp.stat(ref.filePath);\n if (!stat.isFile()) {\n return {\n id: ref.id,\n title: '(damaged)',\n startedAt: stat.mtime.toISOString(),\n model: 'unknown',\n provider: 'unknown',\n tokenTotal: 0,\n };\n }\n mtime = stat.mtime.toISOString();\n } catch {\n return null;\n }\n\n try {\n for await (const event of this.iterSessionEvents(ref.filePath)) {\n if (event.type === 'session_start') {\n return {\n id: ref.id,\n title: '(empty session)',\n startedAt: event.ts,\n model: event.model ?? 'unknown',\n provider: event.provider ?? 'unknown',\n tokenTotal: 0,\n };\n }\n }\n return {\n id: ref.id,\n title: '(empty session)',\n startedAt: new Date(0).toISOString(),\n model: 'unknown',\n provider: 'unknown',\n tokenTotal: 0,\n };\n } catch {\n return {\n id: ref.id,\n title: '(damaged)',\n startedAt: mtime,\n model: 'unknown',\n provider: 'unknown',\n tokenTotal: 0,\n };\n }\n }\n\n /**\n * Delete a session and all associated files: JSONL, summary, plan/todos\n * sidecars, and the session directory (fleet.json, shared/, subagents/).\n *\n * Individual file deletions are best-effort (logged as structured warnings),\n * but a tombstone is always written so readIndex() filters this session out.\n * If the session directory itself can't be removed, the error is surfaced\n * to the caller so prune() can report it.\n */\n private async deleteSession(id: string): Promise<void> {\n const jsonlPath = this.sessionPath(id, '.jsonl');\n const summaryPath = this.sessionPath(id, '.summary.json');\n const shardDir = path.dirname(jsonlPath);\n const base = path.basename(id);\n const sessDir = path.join(shardDir, base);\n\n const deletions: Array<Promise<void>> = [\n fsp.unlink(jsonlPath),\n fsp.unlink(summaryPath),\n fsp.unlink(sessionScopedPath(this.dir, id, '.plan.json')),\n fsp.unlink(sessionScopedPath(this.dir, id, '.tasks.json')),\n fsp.unlink(sessionScopedPath(this.dir, id, '.todos.json')),\n ];\n\n const results = await Promise.allSettled(deletions);\n for (const r of results) {\n if (r.status === 'rejected') {\n const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);\n // ENOENT is expected (file may not exist \u00E2\u20AC\u201D sidecars are optional).\n if ((r.reason as NodeJS.ErrnoException)?.code !== 'ENOENT') {\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'session_store.delete_failed',\n sessionId: id,\n message: msg,\n timestamp: new Date().toISOString(),\n }));\n }\n }\n }\n\n // Remove the session directory (may contain fleet.json, shared/, subagents/).\n /* v8 ignore start -- defensive: rm with force:true rarely rejects */\n await fsp.rm(sessDir, { recursive: true, force: true }).catch((err) => {\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'session_store.rmdir_failed',\n sessionId: id,\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }));\n });\n /* v8 ignore stop */\n\n // Write an index tombstone so readIndex() filters this session out.\n await this.writeTombstone(id);\n }\n\n /**\n * Read the session id currently marked active in `active.json`, or `null`\n * when the lock is absent/unreadable. Shared by {@link delete} and\n * {@link prune} to avoid clobbering a session a live process is writing to.\n */\n private async readActiveSessionId(): Promise<string | null> {\n try {\n const raw = await fsp.readFile(path.join(this.dir, 'active.json'), 'utf8');\n const active = JSON.parse(raw) as { sessionId?: string | undefined };\n return active.sessionId ?? null;\n } catch {\n // no active.json \u2014 nothing to protect\n return null;\n }\n }\n\n async delete(id: string): Promise<void> {\n // Guard 1: never delete the session another process in this project is\n // actively writing to. active.json is the per-project RecoveryLock; every\n // CLI/TUI/WebUI writes it on session start. Without this check, deleting\n // a session that a parallel surface holds open would silently drop every\n // subsequent append (the JSONL is gone) while the writer keeps buffering \u2014\n // a data-loss + recovery-inconsistency bug.\n const activeId = await this.readActiveSessionId();\n if (activeId && id === activeId) {\n throw new Error(\n `Session ${id} is currently active in this project and cannot be deleted. Resume or start another session first.`,\n );\n }\n // Guard 2: cross-process live-session registry. active.json only tracks\n // the *latest* active session per project; the registry lists every live\n // process (multiple terminals/TUIs/WebUIs can each have their own active\n // session). When wired, this catches a delete targeting a session that a\n // *different* surface is using, even though it isn't in our active.json.\n if (this.isSessionInUse) {\n const reason = await this.isSessionInUse(id);\n if (reason) {\n throw new Error(`Session ${id} is in use (${reason}) and cannot be deleted.`);\n }\n }\n await this.deleteSession(id);\n }\n\n async rename(id: string, name: string): Promise<SessionSummary> {\n const trimmed = name.trim();\n const manifest = this.sessionPath(id, '.summary.json');\n const jsonlPath = this.sessionPath(id, '.jsonl');\n // Refuse to name a session that has no JSONL on disk. `summaryFor`\n // would otherwise synthesize a '(damaged)' summary and persist it,\n // creating a phantom entry. ENOENT \u2192 throw a typed error so callers\n // can surface \"session not found\" cleanly.\n try {\n await fsp.stat(jsonlPath);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException)?.code;\n if (code === 'ENOENT') {\n throw new Error(`Session not found: ${id}`);\n }\n throw err;\n }\n\n // Load the current summary (sidecar first, rebuild on miss) and apply\n // the name mutation. summaryFor() already emits read/failure events.\n // Build the updated summary with explicit name handling so we stay\n // compatible with exactOptionalPropertyTypes: a cleared name is omitted\n // from the object entirely (not set to undefined).\n const summary = await this.summaryFor(id);\n const { name: _drop, ...rest } = summary;\n const updated: SessionSummary = trimmed ? { ...rest, name: trimmed } : rest;\n\n const t0 = Date.now();\n let outcome: 'success' | 'failure' = 'success';\n let errorMsg: string | undefined;\n try {\n await atomicWrite(manifest, JSON.stringify(updated), { mode: 0o600 });\n } catch (err) {\n outcome = 'failure';\n errorMsg = toErrorMessage(err);\n this.emitError(id, manifest, 'rename', errorMsg, false);\n throw err;\n } finally {\n this.emitWrite(id, manifest, 'close', outcome, Date.now() - t0, undefined, errorMsg);\n }\n\n // Mirror the change into the index so list() reflects it immediately.\n // appendToIndex dedupes by id (\"latest wins\"), so the stale entry is\n // shadowed without a full compact.\n await this.appendToIndex(updated);\n this.invalidateShardManifestBySessionId(id);\n this._indexCache = null;\n this.clearLoadCache(id);\n return updated;\n }\n\n async prune(maxAgeDays = 30): Promise<number> {\n const cutoff = Date.now() - maxAgeDays * 86_400_000;\n let deleted = 0;\n\n // Read the active session lock to avoid pruning the current session.\n const activeSessionId = await this.readActiveSessionId();\n\n const isPrunableJsonl = (name: string): boolean =>\n name.endsWith('.jsonl') &&\n name !== '_index.jsonl' &&\n name !== '_mailbox.jsonl' &&\n !name.endsWith('.replay.jsonl') &&\n !name.endsWith('.audit.jsonl');\n\n const pruneFile = async (dir: string, name: string, prefix: string): Promise<void> => {\n const jsonlPath = path.join(dir, name);\n try {\n const stat = await fsp.stat(jsonlPath);\n if (stat.mtimeMs >= cutoff) return;\n /* v8 ignore start -- defensive: file vanished between readdir and stat */\n } catch {\n return;\n }\n /* v8 ignore stop */\n const base = name.replace(/\\.jsonl$/, '');\n const id = prefix ? `${prefix}/${base}` : base;\n // Never prune the currently active session.\n if (activeSessionId && id === activeSessionId) return;\n await this.deleteSession(id);\n deleted++;\n };\n\n /* v8 ignore next -- defensive: store dir is ensured before prune runs */\n const entries = await fsp.readdir(this.dir, { withFileTypes: true }).catch(() => []);\n for (const entry of entries) {\n if (entry.isFile()) {\n // Flat legacy sessions at the sessions root \u00E2\u20AC\u201D pre-shard layout.\n // A shard-only scan left these accumulating forever.\n if (isPrunableJsonl(entry.name)) await pruneFile(this.dir, entry.name, '');\n continue;\n }\n /* v8 ignore next -- defensive: root entries are only files or directories */\n if (!entry.isDirectory()) continue;\n // entry.name is a date-shard like \"2026-06-06\"\n const dateDir = path.join(this.dir, entry.name);\n /* v8 ignore next -- defensive: dateDir came from readdir and is readable */\n const files = await fsp.readdir(dateDir, { withFileTypes: true }).catch(() => []);\n for (const file of files) {\n if (!file.isFile() || !isPrunableJsonl(file.name)) continue;\n await pruneFile(dateDir, file.name, entry.name);\n }\n }\n if (deleted > 0) {\n // Compact the index to remove tombstones for deleted sessions.\n /* v8 ignore next -- best-effort: compactIndex swallows its own errors */\n await this.compactIndex().catch(() => undefined); /* best-effort */\n }\n // Clean up empty date-shard directories left behind after pruning.\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const dateDir = path.join(this.dir, entry.name);\n try {\n const remaining = await fsp.readdir(dateDir);\n if (remaining.length === 0) {\n /* v8 ignore next -- best-effort: rmdir of a confirmed-empty dir does not reject */\n await fsp.rmdir(dateDir).catch(() => undefined);\n }\n } catch {\n // best-effort\n }\n }\n return deleted;\n }\n\n async clearHistory(id: string): Promise<void> {\n await this.ensureShardDir(id);\n const file = this.sessionPath(id, '.jsonl');\n const meta = this.sessionPath(id, '.summary.json');\n const record = `${JSON.stringify({\n type: 'session_start',\n ts: new Date().toISOString(),\n id,\n model: 'unknown',\n provider: 'unknown',\n })}\\n`;\n await fsp.writeFile(file, record, 'utf8');\n await fsp.unlink(meta).catch(() => undefined);\n }\n\n private async summarize(id: string, mtime: string): Promise<SessionSummary> {\n try {\n const file = this.sessionPath(id, '.jsonl');\n let title = '(empty session)';\n let startedAt = new Date(0).toISOString();\n let endedAt: string | undefined;\n let model = 'unknown';\n let provider = 'unknown';\n let tokenIn = 0;\n let tokenOut = 0;\n let iterationCount = 0;\n let toolCallCount = 0;\n let toolErrorCount = 0;\n let fileChangeCount = 0;\n const toolBreakdown: Record<string, number> = {};\n let outcome: SessionSummary['outcome'] ;\n let lastEventType: SessionEvent['type'] | undefined;\n let hasError = false;\n let sawStart = false;\n\n for await (const e of this.iterSessionEvents(file)) {\n lastEventType = e.type;\n if (e.type === 'session_start') {\n if (!sawStart) {\n sawStart = true;\n startedAt = e.ts;\n model = e.model ?? 'unknown';\n provider = e.provider ?? 'unknown';\n }\n } else if (e.type === 'session_end') {\n endedAt = e.ts;\n } else if (e.type === 'user_input') {\n if (title === '(empty session)') title = userInputTitle(e.content);\n } else if (e.type === 'llm_response') {\n tokenIn += e.usage.input ?? 0;\n tokenOut += e.usage.output ?? 0;\n } else if (e.type === 'in_flight_start') iterationCount++;\n else if (e.type === 'tool_call_start') {\n toolCallCount++;\n toolBreakdown[e.name] = (toolBreakdown[e.name] ?? 0) + 1;\n } else if (e.type === 'tool_result' && e.isError) toolErrorCount++;\n else if (e.type === 'file_snapshot') fileChangeCount += e.files.length;\n else if (e.type === 'error' || e.type === 'provider_error') hasError = true;\n }\n\n // Determine outcome from the last event.\n if (lastEventType === 'session_end') {\n outcome = 'completed';\n } else if (lastEventType === 'in_flight_start') {\n outcome = 'aborted';\n } else if (hasError) {\n outcome = 'error';\n }\n\n return {\n id,\n title,\n startedAt,\n endedAt,\n model,\n provider,\n tokenTotal: tokenIn + tokenOut,\n iterationCount: iterationCount > 0 ? iterationCount : undefined,\n toolCallCount: toolCallCount > 0 ? toolCallCount : undefined,\n toolErrorCount: toolErrorCount > 0 ? toolErrorCount : undefined,\n fileChangeCount: fileChangeCount > 0 ? fileChangeCount : undefined,\n toolBreakdown: Object.keys(toolBreakdown).length > 0 ? toolBreakdown : {},\n outcome,\n };\n } catch {\n return {\n id,\n title: '(damaged)',\n startedAt: mtime,\n model: 'unknown',\n provider: 'unknown',\n tokenTotal: 0,\n };\n }\n }\n\n private async *iterSessionEvents(file: string): AsyncGenerator<SessionEvent> {\n const stream = createReadStream(file, { encoding: 'utf8' });\n const lines = createInterface({ input: stream, crlfDelay: Infinity });\n try {\n for await (const line of lines) {\n if (!line.trim()) continue;\n try {\n const parsed: unknown = JSON.parse(line);\n if (\n parsed !== null &&\n typeof parsed === 'object' &&\n typeof (parsed as { type?: unknown | undefined }).type === 'string' &&\n typeof (parsed as { ts?: unknown | undefined }).ts === 'string'\n ) {\n yield parsed as SessionEvent;\n }\n } catch {\n // skip malformed JSON\n }\n }\n } finally {\n lines.close();\n stream.destroy();\n }\n }\n\n\n}\n", "import { randomBytes } from 'node:crypto';\nimport * as fs from 'node:fs/promises';\nimport { watch as watchDir } from 'node:fs';\nimport type { FSWatcher } from 'node:fs';\nimport * as path from 'node:path';\nimport { FsError } from '../types/errors.js';\n\nexport interface AtomicWriteOptions {\n mode?: number | undefined;\n encoding?: BufferEncoding | undefined;\n}\n\nexport interface FileLockOptions {\n timeoutMs?: number | undefined;\n staleMs?: number | undefined;\n}\n\nexport async function atomicWrite(\n targetPath: string,\n content: string | Uint8Array,\n opts: AtomicWriteOptions = {},\n): Promise<void> {\n const dir = path.dirname(targetPath);\n await fs.mkdir(dir, { recursive: true });\n const tmp = path.join(dir, `.${path.basename(targetPath)}.${randomBytes(6).toString('hex')}.tmp`);\n\n // Write content to tmp first; 'wx' ensures exclusive creation (fails if\n // tmp already exists \u2014 extremely unlikely with 6-byte random suffix).\n try {\n if (typeof content === 'string') {\n await fs.writeFile(tmp, content, { flag: 'wx', encoding: opts.encoding ?? 'utf8' });\n } else {\n await fs.writeFile(tmp, content, { flag: 'wx' });\n }\n try {\n const fh = await fs.open(tmp, 'r+');\n try {\n await fh.sync();\n } finally {\n await fh.close();\n }\n } catch {\n // fsync best-effort\n }\n // Now safely read mode from target (if it exists) and apply to tmp before rename.\n // Prefer opts.mode for new files; for existing files preserve their mode.\n let mode: number | undefined;\n try {\n const stat = await fs.stat(targetPath);\n mode = stat.mode & 0o777;\n } catch {\n mode = opts.mode;\n }\n if (mode !== undefined) {\n await fs.chmod(tmp, mode);\n }\n await renameWithRetry(tmp, targetPath);\n // P3 #20 (before-release.md): on Windows, fs.rename (MoveFileExW) does\n // not preserve Unix permission bits \u2014 the chmod above applies to the tmp\n // file, but the rename may reset the destination's mode to the Windows\n // default. Re-apply the mode after rename on win32 so an edited file\n // keeps its executable bit (or any non-default permission). On POSIX,\n // rename preserves metadata so this is a no-op (chmod is idempotent and\n // cheap), but we gate it on win32 to avoid the extra stat+chmod on the\n // common path.\n if (mode !== undefined && process.platform === 'win32') {\n try {\n await fs.chmod(targetPath, mode);\n } catch {\n // Best-effort: a transient EPERM (antivirus lock) should not fail\n // the write \u2014 the content is already on disk.\n }\n }\n } catch (err) {\n try {\n await fs.unlink(tmp);\n } catch {\n // ignore cleanup error\n }\n throw err;\n }\n}\n\nexport async function ensureDir(dir: string): Promise<void> {\n await fs.mkdir(dir, { recursive: true });\n}\n\nexport async function withFileLock<T>(\n targetPath: string,\n fn: () => Promise<T>,\n opts: FileLockOptions = {},\n): Promise<T> {\n const dir = path.dirname(targetPath);\n await fs.mkdir(dir, { recursive: true });\n const lockPath = path.join(dir, `.${path.basename(targetPath)}.lock`);\n const timeoutMs = opts.timeoutMs ?? 5_000;\n const staleMs = opts.staleMs ?? 30_000;\n const started = Date.now();\n let handle: fs.FileHandle | undefined;\n\n for (;;) {\n try {\n handle = await fs.open(lockPath, 'wx');\n await handle.writeFile(`${process.pid}:${Date.now()}`);\n break;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n // ENOENT means the directory was deleted (e.g. by concurrent cleanup).\n // Recreate it and retry acquiring the lock.\n if (code === 'ENOENT') {\n await fs.mkdir(dir, { recursive: true });\n continue;\n }\n if (code !== 'EEXIST' && code !== 'EPERM') throw err;\n try {\n const stat = await fs.stat(lockPath);\n if (Date.now() - stat.mtimeMs > staleMs) {\n await fs.unlink(lockPath);\n continue;\n }\n } catch {\n continue;\n }\n const elapsed = Date.now() - started;\n if (elapsed >= timeoutMs) {\n throw new FsError({\n message: `Timed out waiting for file lock: ${targetPath}`,\n code: 'FS_ATOMIC_WRITE_FAILED',\n path: targetPath,\n context: { timeoutMs },\n });\n }\n // Wait for the lock to be released, using a filesystem watcher for\n // nearly-instant wake-up instead of polling. The watcher is best-effort:\n // a safety timeout fires at most every 100ms so we don't busy-wait.\n await waitForLockRelease(lockPath, timeoutMs - elapsed);\n }\n }\n\n try {\n return await fn();\n } finally {\n try {\n await handle?.close();\n } catch {\n // ignore\n }\n try {\n await fs.unlink(lockPath);\n } catch {\n // ignore\n }\n }\n}\n\n/**\n * Watch a lock file's parent directory for the file being removed (unlinked),\n * which signals that the lock holder has released it. A safety timeout caps\n * the wait so the overall `withFileLock` timeout is always respected.\n *\n * Uses a bounded safety interval (up to 100ms) so even if `fs.watch` is\n * unavailable or misses the event, we never busy-wait at 25ms fixed polling.\n */\nasync function waitForLockRelease(lockPath: string, remainingMs: number): Promise<void> {\n const parentDir = path.dirname(lockPath);\n const lockName = path.basename(lockPath);\n const intervalMs = Math.min(remainingMs, 100);\n\n return new Promise<void>((resolve) => {\n let settled = false;\n let watcher: FSWatcher | null = null;\n\n // Safety timer \u2014 always fires, even if fs.watch is unavailable.\n const timer = setTimeout(() => {\n settled = true;\n watcher?.close();\n resolve();\n }, intervalMs);\n\n try {\n watcher = watchDir(parentDir, (eventType, filename) => {\n if (settled) return;\n // 'rename' fires on unlink on most platforms; 'change' is a\n // conservative fallback for environments that only emit 'change'.\n if (filename === lockName && (eventType === 'rename' || eventType === 'change')) {\n settled = true;\n clearTimeout(timer);\n watcher?.close();\n resolve();\n }\n });\n } catch {\n // fs.watch not supported (e.g. some container environments, network\n // filesystems). Clear the safety timer and fall back to a single\n // short delay \u2014 the caller's loop will retry on the next iteration.\n clearTimeout(timer);\n if (!settled) {\n settled = true;\n setTimeout(resolve, Math.min(remainingMs, 25));\n }\n return;\n }\n\n // Re-check lock existence after setting up the watch to close the race\n // where the lock was released between our last EEXIST check and now.\n fs.access(lockPath).then(\n () => {\n // Lock still exists \u2014 the watch (or safety timer) will resolve.\n },\n () => {\n // Lock was already released \u2014 respond immediately.\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n watcher?.close();\n resolve();\n }\n },\n );\n });\n}\n\n// On Windows, fs.rename over an existing file can fail with EPERM/EBUSY/EACCES\n// when antivirus, file indexers, editor file watchers, or a concurrent writer\n// briefly hold a handle on the destination. These are transient \u2014 retry with a\n// short backoff before giving up. POSIX renames are atomic and won't hit this.\nconst TRANSIENT_RENAME_CODES = new Set(['EPERM', 'EBUSY', 'EACCES', 'ENOTEMPTY']);\n\nasync function renameWithRetry(from: string, to: string): Promise<void> {\n if (process.platform !== 'win32') {\n await fs.rename(from, to);\n return;\n }\n const delays = [10, 25, 60, 120, 250];\n let lastErr: unknown;\n for (let i = 0; i <= delays.length; i++) {\n try {\n await fs.rename(from, to);\n return;\n } catch (err) {\n lastErr = err;\n const code = (err as NodeJS.ErrnoException)?.code;\n if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {\n throw err;\n }\n await new Promise((resolve) => setTimeout(resolve, delays[i]));\n }\n }\n throw lastErr;\n}\n", "/**\n * Build a sanitized child-process environment.\n *\n * The bash/exec tools and MCP stdio transports execute LLM-generated or\n * configured commands. The parent process carries provider API keys\n * (ANTHROPIC_API_KEY, OPENAI_API_KEY, ...), VCS tokens (GITHUB_TOKEN),\n * and cloud credentials. Forwarding those to a child is an exfiltration\n * vector even with `permission: 'confirm'` \u2014 a compromised MCP server\n * or a cleverly composed shell pipeline can leak secrets.\n *\n * Strategy: copy a small, explicit allowlist of variables that real builds\n * need, then copy anything else that does NOT look secret-bearing. This\n * preserves user-friendly behavior (locale, terminal, npm config) while\n * blocking the obvious leak channels. Two value-side guards back up the\n * name-based filter:\n * - any value carrying an embedded URI credential (`scheme://user:pass@host`,\n * e.g. `DATABASE_URL`/`REDIS_URL`/`*_DSN`) is dropped (WS-01);\n * - `NODE_OPTIONS` is forwarded but with module-preload directives\n * (`--require`/`--import`/`--loader`) stripped so a parent-set value can't\n * inject code into node children (WS-02).\n *\n * Override with `WRONGSTACK_CHILD_ENV_PASSTHROUGH=1` to forward the full\n * parent environment unchanged (opt-in for advanced users who understand\n * the risk).\n */\n\nconst ALLOWED_KEYS = new Set<string>([\n 'PATH',\n 'HOME',\n 'USER',\n 'USERNAME',\n 'LOGNAME',\n 'SHELL',\n 'LANG',\n 'LC_ALL',\n 'LC_CTYPE',\n 'TERM',\n 'TZ',\n 'TMPDIR',\n 'TEMP',\n 'TMP',\n 'PWD',\n 'OLDPWD',\n 'COMSPEC',\n 'SYSTEMROOT',\n 'SYSTEMDRIVE',\n 'WINDIR',\n 'PROGRAMFILES',\n 'PROGRAMFILES(X86)',\n 'PROGRAMDATA',\n 'APPDATA',\n 'LOCALAPPDATA',\n 'USERPROFILE',\n 'PUBLIC',\n 'PATHEXT',\n]);\n\n// Substring match against env-var names (case-insensitive). Bias toward\n// false-positives \u2014 a missing var is recoverable, an exfiltrated key is not.\n// Only consulted for vars NOT on the curated allowlist; PWD/PASSWD-style\n// false positives there are avoided by checking allowlist first.\nconst SECRET_NAME_PARTS = [\n 'TOKEN',\n 'SECRET',\n 'PASSWORD',\n 'PASSWD',\n 'AUTH',\n 'CRED',\n 'BEARER',\n 'COOKIE',\n 'PRIVATE',\n];\n\nfunction looksSecret(name: string): boolean {\n const upper = name.toUpperCase();\n for (const p of SECRET_NAME_PARTS) {\n if (upper.includes(p)) return true;\n }\n // KEY is tricky \u2014 PUBLIC_KEY is fine to forward but most _KEY vars are\n // secrets. Require word boundary so KEYBOARD_LAYOUT etc. are not flagged.\n if (/(?:^|_)KEY(?:$|_|S$)/i.test(upper)) return true;\n if (/API[_-]?KEY/i.test(upper)) return true;\n if (/ACCESS[_-]?KEY/i.test(upper)) return true;\n if (/SESSION[_-]?ID/i.test(upper) === false && /SESSION/i.test(upper)) {\n // SESSION_ID is metadata (we set our own); other SESSION_* often holds\n // session cookies. Be conservative.\n return true;\n }\n return false;\n}\n\n/**\n * Value-side secret detection (WS-01). The name-based `looksSecret` filter\n * misses connection-string variables whose NAME is innocuous but whose VALUE\n * embeds a password \u2014 e.g. `DATABASE_URL=postgres://user:pass@host`,\n * `REDIS_URL=redis://:pass@host`, `MONGO_URI`, `AMQP_URL`, `*_DSN`. Forwarding\n * these to a child (bash/exec/MCP server) leaks the embedded credential.\n *\n * Matches a URI userinfo component that contains a password, i.e.\n * `scheme://[user]:<password>@host`. Deliberately precise: a credential-free\n * URL (`https://api.example.com`, `https://user@host` with no password) is NOT\n * matched, so non-secret `*_URL` knobs (registries, endpoints) still forward.\n */\nfunction valueHasEmbeddedCredential(value: string): boolean {\n // scheme:// then optional user, a ':' , a non-empty password, then '@'.\n // Userinfo chars stop at '/', whitespace, ':' (separator) and '@'.\n return /\\b[a-z][a-z0-9+.-]*:\\/\\/[^/\\s:@]*:[^/\\s@]+@/i.test(value);\n}\n\n/**\n * Code-injection directives that turn `NODE_OPTIONS` into an RCE channel by\n * preloading an arbitrary module into every node child process (WS-02).\n */\nconst NODE_OPTIONS_INJECTION_FLAG =\n /^(?:--require|-r|--import|--loader|--experimental-loader)$/;\nconst NODE_OPTIONS_INJECTION_FLAG_EQ =\n /^(?:--require|-r|--import|--loader|--experimental-loader)=/;\n\n/**\n * Strip module-preload directives from a `NODE_OPTIONS` value while preserving\n * benign flags (`--no-warnings`, `--max-old-space-size=\u2026`, etc.). Handles both\n * the `--require=./x.js` and space-separated `--require ./x.js` forms. Returns\n * the sanitized string (possibly empty).\n */\nexport function sanitizeNodeOptions(value: string): string {\n const tokens = value.split(/\\s+/).filter(Boolean);\n const kept: string[] = [];\n for (let i = 0; i < tokens.length; i++) {\n const tok = tokens[i] as string;\n if (NODE_OPTIONS_INJECTION_FLAG_EQ.test(tok)) continue; // --require=./x\n if (NODE_OPTIONS_INJECTION_FLAG.test(tok)) {\n i++; // also drop the following path token (--require ./x)\n continue;\n }\n kept.push(tok);\n }\n return kept.join(' ');\n}\n\nexport interface BuildChildEnvOptions {\n /** Session ID to inject as WRONGSTACK_SESSION_ID. */\n sessionId?: string | undefined;\n /** Additional env vars to merge (takes priority over filtered parent env). */\n extra?: NodeJS.ProcessEnv | undefined;\n}\n\n/**\n * Commit identity applied to every git-touching child process via the\n * `GIT_AUTHOR_*` / `GIT_COMMITTER_*` env vars. Env-var identity outranks\n * every `git config` layer (repo/global/system), so commits made by any\n * tool (git tool, bash/exec, worktree manager, plugins) carry this\n * name/email without touching the user's git config \u2014 hand-made commits\n * in a normal terminal are unaffected.\n */\nexport interface GitIdentity {\n name?: string | undefined;\n email?: string | undefined;\n}\n\nlet gitIdentity: GitIdentity | null = null;\n\n/**\n * Set (or clear with `null`) the git commit identity injected by\n * `buildChildEnv()`. Wired at boot from the user-level `config.git.identity`\n * (the config loader strips `git` from repo-committed in-project configs \u2014\n * identity spoofing must not be repo-controllable) and re-applied at runtime\n * by the `/gitid` slash command.\n */\nexport function configureChildEnvGitIdentity(identity: GitIdentity | null | undefined): void {\n const name = identity?.name?.trim();\n const email = identity?.email?.trim();\n gitIdentity = name || email ? { name: name || undefined, email: email || undefined } : null;\n}\n\n/** Current configured git identity, or null when none is set. */\nexport function getChildEnvGitIdentity(): Readonly<GitIdentity> | null {\n return gitIdentity;\n}\n\n/**\n * Build a filtered child-process environment suitable for bash, exec, and\n * MCP server subprocesses. Strips API keys, tokens, and other credentials\n * while preserving system/tooling variables.\n */\nexport function buildChildEnv(optsOrSessionId?: BuildChildEnvOptions | string): NodeJS.ProcessEnv {\n const opts: BuildChildEnvOptions =\n typeof optsOrSessionId === 'string'\n ? { sessionId: optsOrSessionId }\n : (optsOrSessionId ?? {});\n\n // WRONGSTACK_CHILD_ENV_PASSTHROUGH may NOT be set via config file.\n // It is a privileged override that opt-outs the entire credential filter\n // and must only be set by the operator's shell environment (real env var,\n // not something a config file injects into process.env). Config-file\n // sources do NOT go through process.env \u2014 only the actual shell environment\n // does \u2014 so checking Object.prototype.hasOwnProperty.call(process.env, ...)\n // is sufficient to exclude config-driven values.\n const hasOwn = Object.hasOwn(process.env, 'WRONGSTACK_CHILD_ENV_PASSTHROUGH');\n const legacyHasOwn = Object.hasOwn(process.env, 'WRONGSTACK_BASH_ENV_PASSTHROUGH');\n const passthrough = (hasOwn && process.env['WRONGSTACK_CHILD_ENV_PASSTHROUGH'] === '1')\n || (legacyHasOwn && process.env['WRONGSTACK_BASH_ENV_PASSTHROUGH'] === '1');\n if (passthrough && !process.env['CI']) {\n console.warn(\n '[agent] WARNING: WRONGSTACK_*_ENV_PASSTHROUGH=1 is active \u2014\\n' +\n ' all parent env vars (including API keys) forwarded to child processes.\\n' +\n ' Do not use on shared or multi-tenant systems.'\n );\n }\n const out: NodeJS.ProcessEnv = {};\n\n // The CLI entry defaults NODE_ENV=production (so React/Ink resolve their\n // production builds \u2014 see cli-main) and marks the injection with this\n // flag. The injected value must NOT reach children: NODE_ENV=production\n // makes `pnpm install` skip devDependencies and flips test-runner\n // behavior. Strip both vars whenever the flag says wrongstack set them \u2014\n // a NODE_ENV genuinely exported by the operator's shell (flag absent)\n // is forwarded unchanged. Applies in passthrough mode too: passthrough\n // means \"the operator's real environment\", which this value is not.\n const nodeEnvDefaulted = process.env['WRONGSTACK_NODE_ENV_DEFAULTED'] === '1';\n\n for (const [k, v] of Object.entries(process.env)) {\n if (v === undefined) continue;\n if (nodeEnvDefaulted && (k === 'NODE_ENV' || k === 'WRONGSTACK_NODE_ENV_DEFAULTED')) continue;\n if (passthrough) {\n out[k] = v;\n continue;\n }\n const upper = k.toUpperCase();\n // 0. Strip any value with an embedded URI credential (user:pass@host),\n // regardless of the variable name (WS-01). Applied before the allowlist\n // so even a \"system\" name carrying a connection string is caught.\n if (valueHasEmbeddedCredential(v)) continue;\n // 1. Forward names on the explicit allowlist \u2014 these are well-known\n // non-secret system variables (PATH, HOME, LANG, ...).\n if (ALLOWED_KEYS.has(upper)) {\n out[k] = v;\n continue;\n }\n // 2. Strip anything that looks like a secret.\n if (looksSecret(upper)) continue;\n // NODE_OPTIONS is forwarded (builds rely on flags like --no-warnings) but\n // module-preload directives (--require/--import/--loader) are stripped \u2014\n // they would let a parent-set NODE_OPTIONS inject code into every node\n // child (WS-02 defense-in-depth).\n if (upper === 'NODE_OPTIONS') {\n const sanitized = sanitizeNodeOptions(v);\n if (sanitized) out[k] = sanitized;\n continue;\n }\n // 3. Forward tooling-prefixed vars that builds commonly need, unless\n // they already failed the secret check above.\n if (\n upper.startsWith('NODE_') ||\n upper.startsWith('NPM_') ||\n upper.startsWith('PNPM_') ||\n upper.startsWith('YARN_') ||\n upper.startsWith('GIT_') ||\n upper.startsWith('CI') ||\n upper.startsWith('XDG_') ||\n // Our own non-secret knobs (WRONGSTACK_HOME, WRONGSTACK_SESSION_ID, \u2026).\n // Secrets never live in WRONGSTACK_* env vars (they're in the encrypted\n // vault). Forwarding keeps child wstack processes \u2014 e.g. ones spawned\n // by the test suite \u2014 inside the same redirected global root.\n upper.startsWith('WRONGSTACK_') ||\n upper === 'EDITOR' ||\n upper === 'VISUAL' ||\n upper === 'PAGER'\n ) {\n out[k] = v;\n }\n }\n\n // Configured commit identity. Applied in passthrough mode too \u2014 it is the\n // operator's explicit intent, not a parent-env leak. Placed BEFORE the\n // extras merge so a caller-provided GIT_* override still wins.\n if (gitIdentity) {\n if (gitIdentity.name) {\n out['GIT_AUTHOR_NAME'] = gitIdentity.name;\n out['GIT_COMMITTER_NAME'] = gitIdentity.name;\n }\n if (gitIdentity.email) {\n out['GIT_AUTHOR_EMAIL'] = gitIdentity.email;\n out['GIT_COMMITTER_EMAIL'] = gitIdentity.email;\n }\n }\n\n // Merge explicit extras AFTER filtering. Callers MUST treat `opts.extra`\n // as a small, user-authored allowlist (e.g. MCP server tokens, LSP env\n // overrides from config). Do NOT pass `process.env` or any object derived\n // from it \u2014 that would defeat the parent-env scrub above. The secret\n // filter is intentionally skipped here so legitimate secret-bearing\n // tokens the user explicitly configured can still reach the child.\n if (opts.extra) {\n Object.assign(out, opts.extra);\n }\n\n if (opts.sessionId) out['WRONGSTACK_SESSION_ID'] = opts.sessionId;\n return out;\n}\n", "/**\n * TTY detection helpers \u2014 the single source of truth for \"is this process\n * running against a real terminal?\". Replaces ad-hoc `process.stdin.isTTY`\n * / `process.stdout.isTTY` checks scattered across the codebase so that:\n *\n * 1. test code can mock a single module instead of stubbing `isTTY` on\n * every ReadStream/WriteStream the test happens to touch;\n * 2. a future TTY-detection source (an env var override, a Windows\n * ConPTY workaround, \u2026) lands in one place;\n * 3. `isInteractive()` encodes the rule the project already used inline\n * (\"both streams are TTYs AND we're not running under CI\") in one\n * testable helper instead of the same 3-condition check in two\n * different files.\n *\n * Scope: detection only. Raw-mode control (`setRawMode`), resize\n * subscriptions, and write-injection belong to a future, larger TTY\n * abstraction; this module is the smallest pull that gives us a\n * testable seam and dedups 20+ call sites.\n */\n\nconst hasStdout = (): boolean => typeof process !== 'undefined' && !!process.stdout;\nconst hasStdin = (): boolean => typeof process !== 'undefined' && !!process.stdin;\n\n/** True when `process.stdout` is attached to a terminal (not a pipe/file). */\nexport function isStdoutTTY(): boolean {\n return hasStdout() && Boolean(process.stdout.isTTY);\n}\n\n/** True when `process.stdin` is attached to a terminal (not a pipe/file). */\nexport function isStdinTTY(): boolean {\n return hasStdin() && Boolean(process.stdin.isTTY);\n}\n\n/**\n * True when the current process is an interactive session: both stdin and\n * stdout are TTYs. Callers that also need a \"not a single-shot invocation\"\n * or \"not under CI\" check should layer that on top \u2014 keeping this helper\n * minimal preserves the original inline checks it replaces.\n */\nexport function isInteractive(): boolean {\n return isStdinTTY() && isStdoutTTY();\n}\n\n/** Current terminal size in characters, with a 24\u00D780 fallback for non-TTYs. */\nexport function getTermSize(): { rows: number; cols: number } {\n if (!hasStdout()) return { rows: 24, cols: 80 };\n return {\n rows: process.stdout.rows ?? 24,\n cols: process.stdout.columns ?? 80,\n };\n}\n\n/**\n * Subscribe to terminal resize events. `cb` is called with the new size each\n * time the underlying stream emits `resize`. Returns a cleanup function the\n * caller MUST call on dispose to remove the listener \u2014 leaving a stale\n * `resize` listener on a disposed component leaks the closure (and the\n * component itself, transitively) until the process exits.\n *\n * The stream argument defaults to `process.stdout`. Pass an explicit\n * `NodeJS.WriteStream` when the caller already owns one (e.g. a status line\n * that targets an injected `out` for testability). For non-TTY streams no\n * listener is registered and the returned cleanup is a no-op.\n */\nexport function onResize(\n cb: (size: { rows: number; cols: number }) => void,\n stream: NodeJS.WriteStream = process.stdout,\n): () => void {\n if (!stream || typeof stream.on !== 'function') return () => {};\n const handler = (): void => {\n cb({\n rows: stream.rows ?? 24,\n cols: stream.columns ?? 80,\n });\n };\n stream.on('resize', handler);\n return () => {\n stream.off('resize', handler);\n };\n}\n\n/**\n * Toggle raw mode on a TTY stdin stream. Returns `true` when the toggle was\n * applied, `false` when the stream is null, not a TTY, or doesn't expose\n * `setRawMode` (pipes, file descriptors, Windows ConPTY edge cases). Callers\n * that need to restore the previous mode should snapshot `input.isRaw`\n * BEFORE the call and pass the value to a second call to flip back.\n *\n * Use this helper to drop the now-redundant\n * `if (input.isTTY) input.setRawMode(...)` ceremony at every call site.\n */\nexport function setRawMode(input: NodeJS.ReadStream, mode: boolean): boolean {\n if (input?.isTTY !== true) return false;\n if (typeof input.setRawMode !== 'function') return false;\n input.setRawMode(mode);\n return true;\n}\n\n/**\n * Bracket installed by the interactive input reader while a `readline`\n * prompt is on screen. Out-of-band terminal writes \u2014 logger WARN/INFO\n * lines, async activity from the Telegram bridge, etc. \u2014 go to the same\n * physical terminal as the half-typed prompt but readline has no idea they\n * happened, so it never repaints. The result is the classic corruption the\n * user sees: every async line strands the in-progress draft as a fresh\n * scrollback row (sometimes with its cursor underline).\n *\n * The guard closes that gap. `suspend()` wipes the draft row so the message\n * prints clean; `resume()` repaints the prompt + draft (cursor preserved).\n * When no prompt is active the guard is `null` and writes pass straight\n * through \u2014 so agent-turn output (spinner, renderer) is untouched.\n */\nexport interface OutputLineGuard {\n /** Clear the current input row right before an out-of-band write. */\n suspend(): void;\n /** Repaint the prompt + in-progress draft right after the write. */\n resume(): void;\n}\n\nlet activeOutputGuard: OutputLineGuard | null = null;\n\n/**\n * Register (or clear, with `null`) the guard that brackets out-of-band\n * writes. Installed by {@link writeOut}/{@link writeErr} consumers \u2014 in\n * practice the CLI's readline input reader \u2014 only while a prompt is live.\n * Idempotent; the most recent caller wins.\n */\nexport function setOutputLineGuard(guard: OutputLineGuard | null): void {\n activeOutputGuard = guard;\n}\n\n/**\n * Stream-agnostic write primitive. Returns `false` when the stream is\n * missing or doesn't expose `write` so callers can degrade silently under\n * hostile host environments (closed pipe, mock injects `null`, test\n * replaces the stream with a stub).\n *\n * When an {@link OutputLineGuard} is installed (a readline prompt is on\n * screen) the write is bracketed by `suspend()`/`resume()` so the user's\n * half-typed input survives the interruption instead of being stranded in\n * scrollback. The guard's own redraw uses raw stream writes \u2014 never\n * `writeOut`/`writeErr` \u2014 so there is no re-entrancy here.\n *\n * **Not exported in the public API.** Exposed only inside `term.ts` for\n * `writeOut` / `writeErr` to share a single implementation. If a caller\n * needs to write to an arbitrary stream, they should call `writeOut` (or\n * `writeErr`) with an explicit `stream` argument \u2014 the named functions\n * are the public surface so the \"this is the standard error stream\"\n * intent stays visible at every call site.\n */\nfunction writeTo(\n s: string,\n stream: NodeJS.WriteStream | undefined,\n): boolean {\n if (!stream || typeof stream.write !== 'function') return false;\n const guard = activeOutputGuard;\n if (!guard) {\n stream.write(s);\n return true;\n }\n // A prompt is live \u2014 wipe the draft row, emit the message, repaint.\n guard.suspend();\n stream.write(s);\n guard.resume();\n return true;\n}\n\n/**\n * Write `s` to `stream` (defaults to `process.stdout`). Returns `false`\n * when the stream is missing or doesn't expose `write` so callers can\n * degrade silently under hostile host environments (closed pipe, mock\n * injects `null`, test replaces the stream with a stub).\n *\n * Why a helper:\n * 1. **Single seam for output capture in tests** \u2014 stub `writeOut` once\n * and assert on what the rest of the codebase intended to print,\n * without spying on `process.stdout.write` (which is brittle and\n * leaks across parallel test files).\n * 2. **Stream swap without grep** \u2014 routing the CLI's output to a\n * logger or `out.log` becomes a one-line change at process boot.\n * 3. **Defensive default** \u2014 closes the \"what if `process.stdout` is\n * `null`\" gap that currently exists at ~50 call sites that just\n * call `process.stdout.write(s)` and crash on certain Windows\n * redirect invocations.\n *\n * Call-site migration is staged: this commit introduces the helper, a\n * follow-up commit replaces the 50+ `process.stdout.write(...)` sites\n * with `writeOut(...)`. Until that migration lands, both forms coexist\n * and `writeOut` is the preferred form for new code.\n */\nexport function writeOut(\n s: string,\n stream: NodeJS.WriteStream = process.stdout,\n): boolean {\n return writeTo(s, stream);\n}\n\n/**\n * Symmetric partner of `writeOut` for the standard error stream. Same shape,\n * same defensive contract, same single-seam-for-tests story \u2014 just defaults to\n * `process.stderr` instead of `process.stdout`.\n *\n * Use this in code paths that emit error/diagnostic/warning text. Keeping\n * these two helpers split (rather than a single `writeTo(s, stream)`) means\n * the call site reads as a clear intent signal: \"I am writing an error\" vs.\n * \"I am writing a result\" \u2014 which matters for callers that decide between\n * stdout/stderr routing (e.g. `--quiet` flags, log-level filtering,\n * structured-log rewriters that fork on stream).\n *\n * Stderr writes from the core logger (see `infrastructure/logger.ts`) and from\n * the TUI guard (see `tui/run-tui.ts`) used to call `process.stderr.write`\n * directly. Routing them through this helper lets tests stub the stream at\n * one boundary and lets future logging middleware (e.g. a JSON-line rewriter)\n * swap the destination for the entire process in one place.\n */\nexport function writeErr(\n s: string,\n stream: NodeJS.WriteStream = process.stderr,\n): boolean {\n return writeTo(s, stream);\n}\n", "import { isStdoutTTY } from './term.js';\n\nconst isColorTty = (): boolean => {\n if (envFlag(process.env.NO_COLOR)) return false;\n if (envFlag(process.env.FORCE_COLOR)) return true;\n return isStdoutTTY();\n};\n\nfunction envFlag(value: string | undefined): boolean {\n if (value === undefined) return false;\n if (value.trim() === '') return false;\n return !/^(0|false|no|off)$/i.test(value.trim());\n}\n\nconst COLOR = isColorTty();\n\nconst wrap =\n (open: string, close: string) =>\n (s: string): string =>\n COLOR ? `\\x1b[${open}m${s}\\x1b[${close}m` : s;\n\nexport const color = {\n reset: wrap('0', '0'),\n bold: wrap('1', '22'),\n dim: wrap('2', '22'),\n italic: wrap('3', '23'),\n underline: wrap('4', '24'),\n red: wrap('31', '39'),\n green: wrap('32', '39'),\n yellow: wrap('33', '39'),\n blue: wrap('34', '39'),\n magenta: wrap('35', '39'),\n cyan: wrap('36', '39'),\n gray: wrap('90', '39'),\n amber: wrap('38;5;214', '39'),\n pink: wrap('38;5;205', '39'),\n bgRed: wrap('41', '49'),\n bgGreen: wrap('42', '49'),\n};\n\nexport function stripAnsi(s: string): string {\n return s.replace(/\\x1b\\[[0-9;]*[A-Za-z]/g, '');\n}\n", "/**\n * Deep merge utility \u2014 safely merges nested objects with configurable\n * conflict resolution, array merging, and prototype-pollution guarding.\n *\n * Used by:\n * - config-loader (config layer merging with primitive-array concatenation)\n * - secret-vault (config patching)\n * - json-path (json_merge tool with prefer-base / prefer-patch semantics)\n *\n * @module utils/deep-merge\n */\n\n// ---------------------------------------------------------------------------\n// Prototype-pollution guard \u2014 shared set of forbidden __proto__ keys\n// ---------------------------------------------------------------------------\n\nexport const FORBIDDEN_PROTO_KEYS = new Set([\n '__proto__',\n 'constructor',\n 'prototype',\n '__defineGetter__',\n '__defineSetter__',\n '__lookupGetter__',\n '__lookupSetter__',\n]);\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** True when every element is a primitive or null (no nested objects/arrays). */\nexport function isPrimitiveArray(a: unknown[]): boolean {\n return a.every((v) => v === null || (typeof v !== 'object' && typeof v !== 'function'));\n}\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface DeepMergeOptions {\n /**\n * Which side wins on collision for scalars and arrays.\n *\n * - `'prefer-patch'` (default): patch value replaces base value.\n * - `'prefer-base'`: base value is kept, patch value is ignored.\n */\n conflictResolution?: 'prefer-base' | 'prefer-patch';\n\n /**\n * How to handle array values.\n *\n * - `'replace'` (default): patch array replaces base array entirely.\n * - `'concat-primitives'`: when both values are primitive arrays,\n * they are concatenated and deduped (via Set). Non-primitive\n * arrays still replace the base wholesale.\n */\n arrayMode?: 'replace' | 'concat-primitives';\n\n /**\n * Skip prototype-pollution keys (`__proto__`, `constructor`, etc.).\n * Enabled by default. Only disable when you control both inputs\n * and the keyset (e.g. when merging trusted JSON schemas).\n */\n protectProto?: boolean;\n\n /**\n * Optional callback fired when a non-primitive (object) array is\n * replaced wholesale (only relevant with `arrayMode: 'concat-primitives'`).\n * Receives the key name, existing array length, and patch array length.\n * Used by config-loader for debug logging.\n */\n onNonPrimitiveArrayReplace?: (\n key: string,\n existingLen: number,\n patchLen: number,\n ) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively merge `patch` into `base`, returning a new object.\n *\n * - Nested plain objects are merged recursively.\n * - Arrays are handled per `options.arrayMode`.\n * - Scalar collisions are resolved per `options.conflictResolution`.\n * - `null` and non-object values in `patch` replace the base value\n * (unless `conflictResolution` is `'prefer-base'`).\n * - Keys in `base` that are absent from `patch` are preserved.\n * - `FORBIDDEN_PROTO_KEYS` are skipped in the patch (unless\n * `options.protectProto` is set to `false`).\n *\n * The function is generic over `T extends Record<string, unknown>` for\n * callers that pass typed config objects, but the runtime signature\n * also accepts `unknown` inputs (used by the json-path plugin).\n */\nexport function deepMerge<T extends Record<string, unknown>>(\n base: T,\n patch: Record<string, unknown>,\n options?: DeepMergeOptions,\n): T;\n\nexport function deepMerge(\n base: unknown,\n patch: unknown,\n options?: DeepMergeOptions,\n): unknown;\n\nexport function deepMerge(\n base: unknown,\n patch: unknown,\n options: DeepMergeOptions = {},\n): unknown {\n const {\n conflictResolution = 'prefer-patch',\n arrayMode = 'replace',\n protectProto = true,\n onNonPrimitiveArrayReplace,\n } = options;\n\n // Non-object / null handling \u2014 delegate to conflict resolution.\n if (typeof base !== 'object' || base === null) {\n return conflictResolution === 'prefer-patch' ? patch : base;\n }\n if (typeof patch !== 'object' || patch === null) {\n return conflictResolution === 'prefer-patch' ? patch : base;\n }\n\n // Arrays \u2014 handled *before* the object merge so array-of-objects\n // aren't accidentally treated as plain records.\n if (Array.isArray(base) && Array.isArray(patch)) {\n if (\n arrayMode === 'concat-primitives' &&\n isPrimitiveArray(base) &&\n isPrimitiveArray(patch)\n ) {\n return [...new Set([...base, ...patch])];\n }\n return conflictResolution === 'prefer-patch' ? patch : base;\n }\n\n // If only one side is an array, treat as scalar collision.\n if (Array.isArray(base) || Array.isArray(patch)) {\n return conflictResolution === 'prefer-patch' ? patch : base;\n }\n\n // Plain object merge.\n const baseObj = base as Record<string, unknown>;\n const patchObj = patch as Record<string, unknown>;\n const out: Record<string, unknown> = { ...baseObj };\n\n for (const [k, v] of Object.entries(patchObj)) {\n if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;\n\n const existing = out[k];\n if (\n v !== null &&\n typeof v === 'object' &&\n !Array.isArray(v) &&\n existing !== null &&\n typeof existing === 'object' &&\n !Array.isArray(existing)\n ) {\n // Recursive merge for nested plain objects.\n out[k] = deepMerge(existing, v, options);\n } else if (Array.isArray(v) && Array.isArray(existing)) {\n // Delegate to top-level array handling so arrayMode\n // (e.g. 'concat-primitives') applies to nested arrays too.\n // Fire debug hook when a non-primitive array replaces an existing\n // array (for non-primitive arrays, concat-primitives is a no-op and\n // the result is always a wholesale replacement).\n if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {\n onNonPrimitiveArrayReplace(k, existing.length, v.length);\n }\n out[k] = deepMerge(existing, v, options);\n } else if (v !== undefined) {\n // Fire debug hook when a non-primitive (object) array replaces an\n // existing value in concat-primitives mode.\n if (\n onNonPrimitiveArrayReplace &&\n Array.isArray(v) &&\n !isPrimitiveArray(v)\n ) {\n const existingLen = Array.isArray(existing) ? existing.length : 0;\n onNonPrimitiveArrayReplace(k, existingLen, v.length);\n }\n out[k] = v;\n }\n // When v === undefined, leave the existing value untouched\n // (this matches config-loader's behaviour: undefined in patch\n // means \"don't change this key\").\n }\n\n return out;\n}\n", "/**\n * Converts an unknown error value to a human-readable string.\n * Used in 40+ files across the codebase to normalize error messaging.\n */\nexport function toErrorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n", "/** Assert a value is neither null nor undefined. Throws if it is.\n * Useful after optional chaining and indexed access when the\n * control flow guarantees the value exists but TypeScript can't\n * prove it (e.g. after a check on a related field). */\nexport function expectDefined<T>(value: T | null | undefined, label?: string): T {\n if (value === null || value === undefined) {\n const err = new Error(label ? `Expected ${label} to be defined` : 'Expected value to be defined');\n err.name = 'ExpectDefinedError';\n throw err;\n }\n return value;\n}\n", "import { expectDefined } from './expect-defined.js';\nimport type { ContentBlock, ToolResultBlock, ToolUseBlock } from '../types/blocks.js';\nimport type { Message } from '../types/messages.js';\nexport interface MessageRepairReport {\n changed: boolean;\n removedToolUses: string[];\n removedToolResults: string[];\n removedMessages: number;\n}\n\nexport interface MessageRepairResult {\n messages: Message[];\n report: MessageRepairReport;\n}\n\n/**\n * Repair provider-level tool-call adjacency invariants.\n *\n * Anthropic requires every assistant `tool_use` block to have a matching\n * `tool_result` block in the immediately following user message. Manual\n * context surgery (summary/prune) can cut through the middle of such an\n * exchange. This function removes only the now-orphaned protocol blocks,\n * preserving surrounding text/images/thinking blocks where possible.\n */\nexport function repairToolUseAdjacency(messages: Message[]): MessageRepairResult {\n const removedToolUses: string[] = [];\n const removedToolResults: string[] = [];\n let removedMessages = 0;\n let changed = false;\n const out: Message[] = [];\n\n for (let i = 0; i < messages.length; i++) {\n const original = expectDefined(messages[i]);\n let msg = original;\n\n if (hasToolUse(msg)) {\n const nextIds = toolResultIds(messages[i + 1]);\n const filtered = mapContent(msg, (blocks) => {\n const next: ContentBlock[] = [];\n for (const block of blocks) {\n if (block.type === 'tool_use' && !nextIds.has(block.id)) {\n removedToolUses.push(block.id);\n changed = true;\n continue;\n }\n next.push(block);\n }\n return next;\n });\n msg = filtered ?? msg;\n }\n\n if (hasToolResult(msg)) {\n const allowed = toolUseIds(out[out.length - 1]);\n const filtered = mapContent(msg, (blocks) => {\n const next: ContentBlock[] = [];\n for (const block of blocks) {\n if (block.type === 'tool_result' && !allowed.has(block.tool_use_id)) {\n removedToolResults.push(block.tool_use_id);\n changed = true;\n continue;\n }\n next.push(block);\n }\n return next;\n });\n msg = filtered ?? msg;\n }\n\n if (isEmptyMessage(msg)) {\n removedMessages++;\n changed = true;\n continue;\n }\n out.push(msg);\n }\n\n return {\n messages: changed ? out : messages,\n report: { changed, removedToolUses, removedToolResults, removedMessages },\n };\n}\n\nfunction hasToolUse(msg: Message | undefined): boolean {\n return contentBlocks(msg).some((b): b is ToolUseBlock => b.type === 'tool_use');\n}\n\nfunction hasToolResult(msg: Message | undefined): boolean {\n return contentBlocks(msg).some((b): b is ToolResultBlock => b.type === 'tool_result');\n}\n\nfunction toolUseIds(msg: Message | undefined): Set<string> {\n const ids = new Set<string>();\n if (msg?.role !== 'assistant') return ids;\n for (const block of contentBlocks(msg)) {\n if (block.type === 'tool_use') ids.add(block.id);\n }\n return ids;\n}\n\nfunction toolResultIds(msg: Message | undefined): Set<string> {\n const ids = new Set<string>();\n if (msg?.role !== 'user') return ids;\n for (const block of contentBlocks(msg)) {\n if (block.type === 'tool_result') ids.add(block.tool_use_id);\n }\n return ids;\n}\n\nfunction contentBlocks(msg: Message | undefined): ContentBlock[] {\n return msg && Array.isArray(msg.content) ? msg.content : [];\n}\n\nfunction mapContent(\n msg: Message,\n fn: (blocks: ContentBlock[]) => ContentBlock[],\n): Message | null {\n if (!Array.isArray(msg.content)) return msg;\n const next = fn(msg.content);\n if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {\n return msg;\n }\n return { ...msg, content: next };\n}\n\nfunction isEmptyMessage(msg: Message): boolean {\n if (typeof msg.content === 'string') return msg.content.trim().length === 0;\n return msg.content.length === 0;\n}\n", "/**\n * Compile a user-supplied regex with conservative bounds against ReDoS.\n *\n * Duplicated from @wrongstack/tools/_regex.ts to avoid a circular\n * dependency (tools depends on core, not vice versa). Keep both copies\n * in sync if the heuristics change.\n *\n * V8's regex engine is backtracking-based and cannot interrupt a\n * synchronous match \u2014 a pattern like `(a+)+$` against a sufficiently\n * long line will pin a worker for seconds.\n */\n\nconst MAX_PATTERN_LEN = 512;\n\n// Heuristics for catastrophic-backtracking constructs.\nconst DANGEROUS_PATTERNS: ReadonlyArray<RegExp> = [\n /(\\([^)]*[+*][^)]*\\))[+*]/, // (a+)+, (.*)+, etc\n /(\\(\\?:[^)]*[+*][^)]*\\))[+*]/, // same, with non-capturing group\n];\n\nexport interface CompileResult {\n ok: true;\n regex: RegExp;\n}\n\nexport interface CompileFail {\n ok: false;\n reason: string;\n}\n\nexport function compileUserRegex(pattern: string, flags: string): CompileResult | CompileFail {\n if (typeof pattern !== 'string') {\n return { ok: false, reason: 'pattern must be a string' };\n }\n if (pattern.length === 0) {\n return { ok: false, reason: 'pattern is empty' };\n }\n if (pattern.length > MAX_PATTERN_LEN) {\n return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };\n }\n for (const rx of DANGEROUS_PATTERNS) {\n if (rx.test(pattern)) {\n return {\n ok: false,\n reason:\n 'pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers',\n };\n }\n }\n try {\n return { ok: true, regex: new RegExp(pattern, flags) };\n } catch (err) {\n return {\n ok: false,\n reason: err instanceof Error ? err.message : 'invalid regex',\n };\n }\n}\n", "import { toErrorMessage } from './error.js';\n\nexport interface SafeParseResult<T> {\n ok: boolean;\n value?: T | undefined;\n error?: string | undefined;\n}\n\nexport function safeParse<T = unknown>(input: string, maxBytes = 5_000_000): SafeParseResult<T> {\n if (input.length > maxBytes) {\n return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };\n }\n try {\n return { ok: true, value: JSON.parse(input) as T };\n } catch (err) {\n return {\n ok: false,\n error: toErrorMessage(err),\n };\n }\n}\n\nexport function safeStringify(value: unknown, pretty = false): string {\n const seen = new WeakSet();\n const replacer = (_k: string, v: unknown): unknown => {\n if (typeof v === 'bigint') return v.toString();\n if (v instanceof Error) {\n return { name: v.name, message: v.message, stack: v.stack };\n }\n if (typeof v === 'object' && v !== null) {\n if (seen.has(v as object)) return '[Circular]';\n seen.add(v as object);\n }\n return v;\n };\n try {\n return JSON.stringify(value, replacer, pretty ? 2 : undefined) ?? 'null';\n } catch (err) {\n return JSON.stringify({\n __serialization_error: toErrorMessage(err),\n });\n }\n}\n\n/**\n * Attempt to parse JSON5-style input and return a valid JSON string.\n * Handles trailing commas, single-line comments, and unquoted keys\n * that are common in provider output.\n *\n * Returns the sanitized string if it parses successfully as JSON,\n * or `null` if the input cannot be made valid. Callers use this to\n * decide whether to proceed with the parsed result or fall back to\n * raw handling.\n */\nexport function sanitizeJsonString(s: string): string | null {\n let out = s.trim();\n\n // Stage 1: strip single-line comments (// to end of line) that appear\n // outside of string values. This is a heuristic: comments inside strings\n // are preserved because we only strip // when preceded by a char that\n // strongly suggests we're not in a string (quote count modulo 2 is even).\n out = stripSingleLineComments(out);\n\n // Stage 2: strip trailing commas before } or ]\n out = out.replace(/,(\\s*[}\\]])/g, '$1');\n\n // Stage 3: escape literal control characters that appear *inside* string\n // values. Models frequently emit raw newlines/tabs inside a code payload\n // (e.g. edit's old_string/new_string) instead of the required \\n / \\t, which\n // makes JSON.parse throw. This is the single most common malformed-args case.\n out = escapeControlCharsInStrings(out);\n\n // Stage 4: attempt full parse; return null if it fails so callers can\n // distinguish \"already valid JSON\" from \"unrecoverable\".\n try {\n JSON.parse(out);\n return out;\n } catch {\n return null; // stripped but still not valid JSON; caller handles it\n }\n}\n\n/**\n * Strip a Markdown code-fence wrapper from a payload.\n *\n * Models occasionally return tool-call arguments wrapped in ```json fences\n * (or embedded in prose around one) instead of bare JSON. Returns the inner\n * content when the input starts with a fence (closing fence optional, so a\n * truncated stream still unwraps) or contains one complete fenced block;\n * returns null when no fence is present. Callers should only invoke this\n * after a direct parse failed, so fences inside legitimate string values are\n * never touched.\n */\nexport function stripCodeFences(s: string): string | null {\n const trimmed = s.trim();\n // Whole-payload fence: ```lang? \u2026 ```? (closer optional for truncation)\n const opener = /^```[\\w+-]*[ \\t]*\\r?\\n?/.exec(trimmed);\n if (opener) {\n const inner = trimmed.slice(opener[0].length).replace(/(\\r?\\n)?[ \\t]*```[ \\t]*$/, '');\n return inner.trim();\n }\n // Fence embedded in prose: extract the first complete fenced block.\n const embedded = /```[\\w+-]*[ \\t]*\\r?\\n([\\s\\S]*?)\\r?\\n[ \\t]*```/.exec(trimmed);\n if (embedded) return (embedded[1] ?? '').trim();\n return null;\n}\n\n/**\n * Walk the string tracking whether we are inside a JSON string literal and\n * replace raw control characters (U+0000\u2013U+001F) that appear inside strings\n * with their valid JSON escape sequences. Characters outside strings are left\n * untouched (insignificant whitespace stays as-is). Already-escaped sequences\n * are not double-escaped because we only act on *literal* control bytes.\n */\nfunction escapeControlCharsInStrings(s: string): string {\n let inString = false;\n let out = '';\n for (let i = 0; i < s.length; i++) {\n const c = s.charAt(i);\n if (c === '\"' && (i === 0 || s[i - 1] !== '\\\\')) {\n inString = !inString;\n out += c;\n continue;\n }\n const code = c.charCodeAt(0);\n if (inString && code < 0x20) {\n switch (c) {\n case '\\n':\n out += '\\\\n';\n break;\n case '\\r':\n out += '\\\\r';\n break;\n case '\\t':\n out += '\\\\t';\n break;\n case '\\b':\n out += '\\\\b';\n break;\n case '\\f':\n out += '\\\\f';\n break;\n default:\n out += `\\\\u${code.toString(16).padStart(4, '0')}`;\n }\n continue;\n }\n out += c;\n }\n return out;\n}\n\nfunction stripSingleLineComments(s: string): string {\n let inString = false;\n const chars: string[] = [];\n let i = 0;\n while (i < s.length) {\n const c = s.charAt(i);\n if (c === '\"' && (i === 0 || s.charAt(i - 1) !== '\\\\')) {\n inString = !inString;\n chars.push(c);\n } else if (c === '/' && s.charAt(i + 1) === '/' && !inString) {\n // skip to end of line\n while (i < s.length && s.charAt(i) !== '\\n') i++;\n } else {\n chars.push(c);\n }\n i++;\n }\n return chars.join('');\n}\n", "import * as path from 'node:path';\nimport { ERROR_CODES, FsError } from '../types/errors.js';\n\n/**\n * Resolve `<dir>/<sessionId><suffix>` for per-session sidecar files\n * (annotations, audit chain, replay log, the session JSONL itself).\n *\n * Modern session ids are date-sharded (\"2026-06-11/sess_<ULID>\"),\n * so a forward slash is a legitimate shard separator \u2014 NOT traversal.\n * Escape attempts are blocked two ways: an explicit ban on `..` and\n * backslashes, plus a resolved-path containment check that rejects any\n * id whose resolved target leaves `dir`. Character bans alone are how\n * several stores ended up throwing on every modern session id.\n */\nexport function sessionScopedPath(dir: string, sessionId: string, suffix: string): string {\n if (!sessionId || sessionId.includes('\\\\') || sessionId.includes('..')) {\n throw invalid(sessionId);\n }\n const resolved = path.resolve(dir, `${sessionId}${suffix}`);\n const rel = path.relative(path.resolve(dir), resolved);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw invalid(sessionId);\n }\n return resolved;\n}\n\nfunction invalid(sessionId: string): FsError {\n return new FsError({\n message: `Invalid sessionId: ${sessionId}`,\n code: ERROR_CODES.FS_DELETE_FAILED,\n path: sessionId,\n context: { reason: 'path_traversal' },\n });\n}\n", "/**\n * Turn an arbitrary string into a filesystem- and URL-safe lowercase slug.\n *\n * Collapses every run of non-alphanumeric characters into a single hyphen,\n * trims leading/trailing hyphens, and caps the length. Returns `fallback`\n * when the input slugifies to the empty string.\n *\n * Used as the stable dedup + registry key for prompts. (Distinct from the\n * project-folder slug in `wstack-paths.ts`, which has its own `'project'`\n * fallback and shorter cap.)\n */\nexport function slugify(name: string, fallback = 'prompt', maxLen = 64): string {\n return (\n name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, maxLen)\n .replace(/-+$/g, '') || fallback\n );\n}\n", "import { randomBytes } from 'node:crypto';\n\n/**\n * Crockford base32 alphabet (excludes I, L, O, U to avoid ambiguity).\n */\nconst ENCODING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';\nconst ENCODING_LEN = ENCODING.length;\nconst TIME_LEN = 10;\nconst RANDOM_LEN = 16;\n\nfunction encodeTime(now: number, len: number): string {\n let mod: number;\n let str = '';\n for (let i = len - 1; i >= 0; i--) {\n mod = now % ENCODING_LEN;\n str = ENCODING[mod] + str;\n now = (now - mod) / ENCODING_LEN;\n }\n return str;\n}\n\nfunction encodeRandom(len: number): string {\n const bytes = randomBytes(len);\n let str = '';\n for (let i = 0; i < len; i++) {\n str += ENCODING[(bytes[i] as number) % ENCODING_LEN];\n }\n return str;\n}\n\n/**\n * Generate a ULID \u2014 a 26-char Crockford-base32 identifier whose first 10 chars\n * encode the millisecond timestamp (so IDs sort lexicographically by creation\n * time) followed by 16 chars of randomness. Zero runtime dependencies.\n *\n * The codebase convention is \"IDs are ULIDs\"; use this for any new store key.\n */\nexport function ulid(seedTime: number = Date.now()): string {\n return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN);\n}\n\n/** True for a well-formed 26-char Crockford-base32 ULID. */\nexport function isUlid(value: string): boolean {\n if (value.length !== TIME_LEN + RANDOM_LEN) return false;\n for (const ch of value) {\n if (!ENCODING.includes(ch)) return false;\n }\n return true;\n}\n", "import { createHash } from 'node:crypto';\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\n/**\n * Path layout. All developer-level state lives in ~/.wrongstack/.\n * Per-project state is keyed by the canonical project root under\n * ~/.wrongstack/projects/<slug>/. Linked Git worktrees resolve to their main\n * checkout so coordination and durable state are shared across worktrees.\n *\n * The ONLY thing inside the project tree is the optional\n * .wrongstack/AGENTS.md (committed) and .wrongstack/skills/ (committed).\n */\n\nexport interface WstackPaths {\n /** ~/.wrongstack \u2014 global root. */\n globalRoot: string;\n /** Absolute project root. */\n projectRoot: string;\n /** Home directory (~) \u2014 base for foreign tool state (~/.codex, ~/.agents, \u2026). */\n homeDir: string;\n /**\n * ~/.wrongstack \u2014 directory for user-global stateful config files\n * (mode.json, theme.json, \u2026). Currently an alias for `globalRoot`;\n * separate name lets us split out per-OS XDG_CONFIG_HOME later\n * without rewriting callers.\n */\n configDir: string;\n /** ~/.wrongstack/config.json */\n globalConfig: string;\n /** ~/.wrongstack/.key \u2014 32 random bytes, mode 0600, AES-GCM key for the secret vault. */\n secretsKey: string;\n /** ~/.wrongstack/memory.md \u2014 user-global memory. */\n globalMemory: string;\n /** ~/.wrongstack/skills \u2014 user-global skills. */\n globalSkills: string;\n /** ~/.claude/skills \u2014 user-global skills from foreign coding agents (Claude Code, Codex, \u2026). Read-only. */\n globalClaudeSkills: string;\n /** ~/.wrongstack/design-kits \u2014 user-global Design Studio kits. */\n globalDesignKits: string;\n /** ~/.wrongstack/prompts \u2014 user-global prompt library. */\n globalPrompts: string;\n /** ~/.wrongstack/instructions \u2014 user-global system instruction overrides. */\n globalInstructions: string;\n /** ~/.wrongstack/prompt-usage.json \u2014 per-slug insert counts (recent/popular). */\n promptUsage: string;\n /** ~/.wrongstack/cache \u2014 fetched data (models.dev, etc.). */\n cacheDir: string;\n /** ~/.wrongstack/cache/models.dev.json */\n modelsCache: string;\n /** ~/.wrongstack/cache/models-overlay.json \u2014 cached curated overlay. */\n modelsOverlayCache: string;\n /**\n * Per-project codebase symbol index (SQLite). Lives under the global project\n * dir \u2014 NOT inside the repo \u2014 so it never clutters the working tree or needs\n * gitignoring. `~/.wrongstack/projects/<hash>/codebase-index`.\n */\n projectCodebaseIndex: string;\n /** ~/.wrongstack/history \u2014 REPL line history. */\n historyFile: string;\n /** ~/.wrongstack/logs/wrongstack.log */\n logFile: string;\n /** ~/.wrongstack/projects/<hash> */\n projectDir: string;\n /** ~/.wrongstack/projects/<hash>/memory.md */\n projectMemory: string;\n /** ~/.wrongstack/projects/<hash>/sessions */\n projectSessions: string;\n /** ~/.wrongstack/projects/<hash>/trust.json */\n projectTrust: string;\n /** ~/.wrongstack/projects/<hash>/meta.json */\n projectMeta: string;\n /** ~/.wrongstack/projects/<hash>/config.local.json \u2014 optional override */\n projectLocalConfig: string;\n /** <project>/.wrongstack/config.json \u2014 per-project settings (safe fields only).\n * This lives inside the project root so it can be gitignored or shared. */\n inProjectConfig: string;\n /** <project>/.wrongstack/AGENTS.md \u2014 committed project memory. */\n inProjectAgentsFile: string;\n /** <project>/.wrongstack/skills \u2014 committed project skills. */\n inProjectSkills: string;\n /** <project>/.claude/skills \u2014 project skills authored for foreign coding agents (Claude Code, \u2026). Read-only. */\n inProjectClaudeSkills: string;\n /** <project>/.wrongstack/prompts \u2014 committed project prompt library. */\n inProjectPrompts: string;\n /** <project>/.wrongstack/instructions \u2014 committed project instruction overrides. */\n inProjectInstructions: string;\n /** <project>/.wrongstack/design-kits \u2014 committed project Design Studio kits. */\n inProjectDesignKits: string;\n /** <project>/.wrongstack/worktrees \u2014 git worktrees for per-phase isolation (gitignored). */\n inProjectWorktrees: string;\n /** Stable hash for the canonical project root (shared by linked Git worktrees). */\n projectHash: string;\n /** Human-readable canonical project slug, shared by linked Git worktrees. */\n projectSlug: string;\n /** ~/.wrongstack/projects/<hash>/goal.json \u2014 goal persistence */\n projectGoal: string;\n /** ~/.wrongstack/projects/<hash>/input-history.json \u2014 TUI prompt input history */\n projectInputHistory: string;\n /** ~/.wrongstack/projects/<hash>/specs \u2014 SDD spec files */\n projectSpecs: string;\n /** ~/.wrongstack/projects/<hash>/task-graphs \u2014 SDD task graphs */\n projectTaskGraphs: string;\n /** ~/.wrongstack/projects/<hash>/sdd-session.json \u2014 SDD session state */\n projectSddSession: string;\n /** ~/.wrongstack/projects/<hash>/plan.json \u2014 plan persistence */\n projectPlan: string;\n /** ~/.wrongstack/projects/<hash>/autophase \u2014 AutoPhase phase-graph JSON files */\n projectAutophase: string;\n /** ~/.wrongstack/projects/<hash>/sdd-boards \u2014 live SDD board snapshots + JSONL event logs */\n projectSddBoards: string;\n /** ~/.wrongstack/sync.json \u2014 CloudSync configuration */\n syncConfig: string;\n /** Function to get the status.json path for a project given its hash. */\n projectStatus: (projectHash: string) => string;\n}\n\n/**\n * Resolve the stable project identity root used by global WrongStack state.\n *\n * A linked Git worktree has its own checkout path and a `.git` *file* that\n * points into `<main>/.git/worktrees/<name>`. Its `commondir` points back to\n * the main checkout's `.git` directory. Treating the linked checkout path as\n * the project identity would split one repository into multiple session,\n * registry, and mailbox directories \u2014 agents in different worktrees would be\n * unable to see or message each other.\n *\n * Project-local paths still use the caller's actual checkout. Only global\n * state identity is canonicalized. Non-Git projects, normal checkouts, Git\n * submodules, and separate-git-dir layouts keep their existing identity.\n */\nexport function canonicalProjectRoot(absRoot: string): string {\n const checkoutRoot = path.resolve(absRoot);\n const dotGit = path.join(checkoutRoot, '.git');\n\n try {\n if (!fs.statSync(dotGit).isFile()) return checkoutRoot;\n\n const gitDirLine = fs.readFileSync(dotGit, 'utf8').trim();\n const match = /^gitdir:\\s*(.+)$/i.exec(gitDirLine);\n if (!match?.[1]) return checkoutRoot;\n\n const gitDir = path.resolve(checkoutRoot, match[1].trim());\n const commonDirFile = path.join(gitDir, 'commondir');\n if (!fs.statSync(commonDirFile).isFile()) return checkoutRoot;\n\n const commonDir = path.resolve(gitDir, fs.readFileSync(commonDirFile, 'utf8').trim());\n // Linked worktrees created by Git share the main checkout's `.git` dir.\n // A submodule or --separate-git-dir layout may point elsewhere; do not\n // guess a working-tree root for those shapes.\n if (path.basename(commonDir).toLowerCase() !== '.git') return checkoutRoot;\n return path.dirname(commonDir);\n } catch {\n // Missing/malformed administrative files must never make path resolution\n // fail. Falling back preserves the pre-canonicalization behavior.\n return checkoutRoot;\n }\n}\n\nexport function projectHash(absRoot: string): string {\n return createHash('sha256').update(canonicalProjectRoot(absRoot)).digest('hex').slice(0, 12);\n}\n\n/**\n * Human-readable project directory name: slugified folder name + short hash\n * suffix for uniqueness. e.g. `wrongstack-a1b2c3` instead of `3024e5e6fa58`.\n */\nexport function projectSlug(absRoot: string): string {\n const identityRoot = canonicalProjectRoot(absRoot);\n const base = slugify(path.basename(identityRoot));\n const hash = createHash('sha256').update(identityRoot).digest('hex').slice(0, 6);\n return `${base}-${hash}`;\n}\n\n/** Turn a folder name into a filesystem-safe lowercase slug. */\nfunction slugify(name: string): string {\n return (\n name\n .toLowerCase()\n // Collapse any run of non-alphanumeric chars into a single hyphen.\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 40) || 'project'\n );\n}\n\nexport interface WstackPathOptions {\n userHome?: string | undefined;\n projectRoot: string;\n /** Override the global root (e.g. for tests). Default: `${userHome}/.wrongstack`. */\n globalRoot?: string | undefined;\n}\n\n/**\n * The global `~/.wrongstack` root, honoring the `WRONGSTACK_HOME` env\n * override. The override exists so tests (and sandboxed runs) can redirect\n * ALL global state \u2014 config, secrets, logs, projects/, mailboxes \u2014 away from\n * the real user home. Before it existed, `pnpm test` booted runtimes against\n * the real `~/.wrongstack`: it read the user's real config.json (starting a\n * second live Telegram poller), appended to the real wrongstack.log, and left\n * ~20k orphaned fixture dirs under projects/.\n *\n * Every code path that wants the global dir must come through here (or\n * through `resolveWstackPaths`) instead of `path.join(os.homedir(), '.wrongstack')`.\n */\nexport function wstackGlobalRoot(): string {\n const fromEnv = process.env['WRONGSTACK_HOME'];\n if (fromEnv && fromEnv.trim().length > 0) return path.resolve(fromEnv);\n return path.join(os.homedir(), '.wrongstack');\n}\n\nexport function resolveWstackPaths(opts: WstackPathOptions): WstackPaths {\n // Precedence: explicit globalRoot > explicit userHome (callers/tests that\n // pass one expect paths under it) > WRONGSTACK_HOME env > real home dir.\n const globalRoot =\n opts.globalRoot ?? (opts.userHome ? path.join(opts.userHome, '.wrongstack') : wstackGlobalRoot());\n // Home dir for FOREIGN tool state (Claude Code's ~/.claude). Independent of\n // WRONGSTACK_HOME, which redirects only WrongStack state: a real user's\n // Claude skills live in their real home, but tests pass `userHome` to keep\n // both `.wrongstack` and `.claude` under a temp dir.\n const homeDir = opts.userHome ?? os.homedir();\n const hash = projectHash(opts.projectRoot);\n const slug = projectSlug(opts.projectRoot);\n const projectDir = path.join(globalRoot, 'projects', slug);\n return {\n globalRoot,\n projectRoot: opts.projectRoot,\n homeDir,\n configDir: globalRoot,\n globalConfig: path.join(globalRoot, 'config.json'),\n secretsKey: path.join(globalRoot, '.key'),\n globalMemory: path.join(globalRoot, 'memory.md'),\n globalSkills: path.join(globalRoot, 'skills'),\n globalClaudeSkills: path.join(homeDir, '.claude', 'skills'),\n globalDesignKits: path.join(globalRoot, 'design-kits'),\n globalPrompts: path.join(globalRoot, 'prompts'),\n globalInstructions: path.join(globalRoot, 'instructions'),\n promptUsage: path.join(globalRoot, 'prompt-usage.json'),\n cacheDir: path.join(globalRoot, 'cache'),\n modelsCache: path.join(globalRoot, 'cache', 'models.dev.json'),\n modelsOverlayCache: path.join(globalRoot, 'cache', 'models-overlay.json'),\n historyFile: path.join(globalRoot, 'history'),\n logFile: path.join(globalRoot, 'logs', 'wrongstack.log'),\n projectDir,\n projectCodebaseIndex: path.join(projectDir, 'codebase-index'),\n projectMemory: path.join(projectDir, 'memory.md'),\n projectSessions: path.join(projectDir, 'sessions'),\n projectTrust: path.join(projectDir, 'trust.json'),\n projectMeta: path.join(projectDir, 'meta.json'),\n projectLocalConfig: path.join(projectDir, 'config.local.json'),\n inProjectConfig: path.join(opts.projectRoot, '.wrongstack', 'config.json'),\n inProjectAgentsFile: path.join(opts.projectRoot, '.wrongstack', 'AGENTS.md'),\n inProjectSkills: path.join(opts.projectRoot, '.wrongstack', 'skills'),\n inProjectClaudeSkills: path.join(opts.projectRoot, '.claude', 'skills'),\n inProjectPrompts: path.join(opts.projectRoot, '.wrongstack', 'prompts'),\n inProjectInstructions: path.join(opts.projectRoot, '.wrongstack', 'instructions'),\n inProjectDesignKits: path.join(opts.projectRoot, '.wrongstack', 'design-kits'),\n inProjectWorktrees: path.join(opts.projectRoot, '.wrongstack', 'worktrees'),\n projectHash: hash,\n projectSlug: slug,\n projectGoal: path.join(projectDir, 'goal.json'),\n projectInputHistory: path.join(projectDir, 'input-history.json'),\n projectSpecs: path.join(projectDir, 'specs'),\n projectTaskGraphs: path.join(projectDir, 'task-graphs'),\n projectSddSession: path.join(projectDir, 'sdd-session.json'),\n projectPlan: path.join(projectDir, 'plan.json'),\n projectAutophase: path.join(projectDir, 'autophase'),\n projectSddBoards: path.join(projectDir, 'sdd-boards'),\n syncConfig: path.join(globalRoot, 'sync.json'),\n projectStatus: (projectHash: string) => path.join(globalRoot, 'projects', projectHash, 'status.json'),\n };\n}\n", "import { toErrorMessage } from '../utils/index.js';\n\n/**\n * WrongStack error hierarchy.\n *\n * Every error thrown by the framework is a `WrongStackError` with a\n * machine-readable `code`, a `subsystem` tag, and a `severity` level.\n * This lets consumers (CLI, TUI, plugins, tests) branch on structured\n * data instead of parsing error messages.\n */\n\n// \u2500\u2500 Error codes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Machine-readable error codes as frozen constants.\n *\n * Use `ERROR_CODES.X` instead of raw string literals for:\n * - IDE autocomplete and compile-time validation\n * - Safe refactoring (rename updates all usages)\n * - Plugin extensibility (extend the object to add custom codes)\n *\n * The `ErrorCode` type is derived from this object, so adding a new\n * code here automatically updates the type without extra changes.\n */\nexport const ERROR_CODES = {\n // Provider\n PROVIDER_RATE_LIMITED: 'PROVIDER_RATE_LIMITED',\n PROVIDER_AUTH_FAILED: 'PROVIDER_AUTH_FAILED',\n PROVIDER_OVERLOADED: 'PROVIDER_OVERLOADED',\n PROVIDER_INVALID_REQUEST: 'PROVIDER_INVALID_REQUEST',\n PROVIDER_SERVER_ERROR: 'PROVIDER_SERVER_ERROR',\n PROVIDER_NETWORK_ERROR: 'PROVIDER_NETWORK_ERROR',\n PROVIDER_CONTEXT_OVERFLOW: 'PROVIDER_CONTEXT_OVERFLOW',\n // Tool\n TOOL_NOT_FOUND: 'TOOL_NOT_FOUND',\n TOOL_PERMISSION_DENIED: 'TOOL_PERMISSION_DENIED',\n TOOL_EXECUTION_FAILED: 'TOOL_EXECUTION_FAILED',\n TOOL_TIMEOUT: 'TOOL_TIMEOUT',\n TOOL_INPUT_INVALID: 'TOOL_INPUT_INVALID',\n // Config\n CONFIG_INVALID: 'CONFIG_INVALID',\n CONFIG_NOT_FOUND: 'CONFIG_NOT_FOUND',\n CONFIG_PARSE_FAILED: 'CONFIG_PARSE_FAILED',\n CONFIG_MIGRATION_NEEDED: 'CONFIG_MIGRATION_NEEDED',\n // Plugin\n PLUGIN_LOAD_FAILED: 'PLUGIN_LOAD_FAILED',\n PLUGIN_API_MISMATCH: 'PLUGIN_API_MISMATCH',\n PLUGIN_MISSING_DEPENDENCY: 'PLUGIN_MISSING_DEPENDENCY',\n // Agent\n AGENT_ITERATION_LIMIT: 'AGENT_ITERATION_LIMIT',\n AGENT_CONTEXT_OVERFLOW: 'AGENT_CONTEXT_OVERFLOW',\n AGENT_ABORTED: 'AGENT_ABORTED',\n AGENT_RUN_FAILED: 'AGENT_RUN_FAILED',\n // Session\n SESSION_NOT_FOUND: 'SESSION_NOT_FOUND',\n SESSION_CORRUPTED: 'SESSION_CORRUPTED',\n SESSION_WRITE_FAILED: 'SESSION_WRITE_FAILED',\n // Container / Registry\n CONTAINER_TOKEN_ALREADY_BOUND: 'CONTAINER_TOKEN_ALREADY_BOUND',\n CONTAINER_TOKEN_NOT_BOUND: 'CONTAINER_TOKEN_NOT_BOUND',\n CONTAINER_CIRCULAR_DEPENDENCY: 'CONTAINER_CIRCULAR_DEPENDENCY',\n REGISTRY_DUPLICATE: 'REGISTRY_DUPLICATE',\n REGISTRY_NOT_FOUND: 'REGISTRY_NOT_FOUND',\n REGISTRY_INVALID: 'REGISTRY_INVALID',\n // File system\n FS_READ_FAILED: 'FS_READ_FAILED',\n FS_WRITE_FAILED: 'FS_WRITE_FAILED',\n FS_MKDIR_FAILED: 'FS_MKDIR_FAILED',\n FS_DELETE_FAILED: 'FS_DELETE_FAILED',\n FS_ATOMIC_WRITE_FAILED: 'FS_ATOMIC_WRITE_FAILED',\n // SDD (Spec-Driven Development)\n SDD_VALIDATION_FAILED: 'SDD_VALIDATION_FAILED',\n SDD_PARSE_FAILED: 'SDD_PARSE_FAILED',\n SDD_INVALID_STATE: 'SDD_INVALID_STATE',\n SDD_NOT_READY: 'SDD_NOT_READY',\n // General\n VALIDATION_ERROR: 'VALIDATION_ERROR',\n PARSE_FAILED: 'PARSE_FAILED',\n UNKNOWN: 'UNKNOWN',\n} as const;\n\n/**\n * Union type derived from `ERROR_CODES`. Using `typeof ERROR_CODES[keyof typeof ERROR_CODES]`\n * instead of a string literal union means TypeScript auto-updates the type whenever\n * a new code is added to `ERROR_CODES` \u2014 no need to keep two lists in sync.\n */\nexport type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];\n\nexport type ErrorSubsystem =\n | 'provider'\n | 'tool'\n | 'config'\n | 'plugin'\n | 'agent'\n | 'session'\n | 'sdd'\n | 'container'\n | 'fs'\n | 'general';\nexport type ErrorSeverity = 'fatal' | 'error' | 'warning';\n\n// \u2500\u2500 Base error class \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class WrongStackError extends Error {\n readonly code: ErrorCode;\n readonly subsystem: ErrorSubsystem;\n readonly severity: ErrorSeverity;\n readonly recoverable: boolean;\n readonly context?: Record<string, unknown> | undefined;\n\n constructor(opts: {\n message: string;\n code: ErrorCode;\n subsystem: ErrorSubsystem;\n severity?: ErrorSeverity | undefined;\n recoverable?: boolean | undefined;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super(opts.message, { cause: opts.cause });\n this.name = 'WrongStackError';\n this.code = opts.code;\n this.subsystem = opts.subsystem;\n this.severity = opts.severity ?? 'error';\n this.recoverable = opts.recoverable ?? false;\n this.context = opts.context;\n }\n\n /**\n * Render a one-line user-facing description.\n * Subclasses should override for domain-specific formatting.\n */\n describe(): string {\n const ctx = this.context ? ` ${formatContext(this.context)}` : '';\n return `${this.code}: ${this.message}${ctx}`;\n }\n}\n\nfunction formatContext(ctx: Record<string, unknown>): string {\n const parts = Object.entries(ctx)\n .filter(([, v]) => v !== undefined)\n .slice(0, 3)\n .map(([k, v]) => `${k}=${String(v)}`);\n return parts.length > 0 ? `[${parts.join(' ')}]` : '';\n}\n\n// \u2500\u2500 Specific error classes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Tool execution errors \u2014 thrown by ToolExecutor and individual tools.\n */\nexport class ToolError extends WrongStackError {\n readonly toolName: string;\n\n constructor(opts: {\n message: string;\n code: Extract<\n ErrorCode,\n | 'TOOL_NOT_FOUND'\n | 'TOOL_PERMISSION_DENIED'\n | 'TOOL_EXECUTION_FAILED'\n | 'TOOL_TIMEOUT'\n | 'TOOL_INPUT_INVALID'\n >;\n toolName: string;\n recoverable?: boolean | undefined;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: opts.code,\n subsystem: 'tool',\n recoverable: opts.recoverable,\n context: { tool: opts.toolName, ...opts.context },\n cause: opts.cause,\n });\n this.name = 'ToolError';\n this.toolName = opts.toolName;\n }\n}\n\n/**\n * Config loading / validation errors.\n */\nexport class ConfigError extends WrongStackError {\n constructor(opts: {\n message: string;\n code: Extract<\n ErrorCode,\n 'CONFIG_INVALID' | 'CONFIG_NOT_FOUND' | 'CONFIG_PARSE_FAILED' | 'CONFIG_MIGRATION_NEEDED'\n >;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: opts.code,\n subsystem: 'config',\n severity: 'fatal',\n recoverable: false,\n context: opts.context,\n cause: opts.cause,\n });\n this.name = 'ConfigError';\n }\n}\n\n/**\n * Plugin loading / lifecycle errors.\n */\nexport class PluginError extends WrongStackError {\n readonly pluginName: string;\n\n constructor(opts: {\n message: string;\n code: Extract<\n ErrorCode,\n 'PLUGIN_LOAD_FAILED' | 'PLUGIN_API_MISMATCH' | 'PLUGIN_MISSING_DEPENDENCY'\n >;\n pluginName: string;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: opts.code,\n subsystem: 'plugin',\n severity: 'error',\n recoverable: opts.code === ERROR_CODES.PLUGIN_MISSING_DEPENDENCY,\n context: { plugin: opts.pluginName, ...opts.context },\n cause: opts.cause,\n });\n this.name = 'PluginError';\n this.pluginName = opts.pluginName;\n }\n}\n\n/**\n * Agent runtime errors \u2014 thrown by Agent.run when a non-WrongStackError\n * escapes the inner loop, so callers always see a structured error.\n */\nexport class AgentError extends WrongStackError {\n constructor(opts: {\n message: string;\n code: Extract<\n ErrorCode,\n 'AGENT_ITERATION_LIMIT' | 'AGENT_CONTEXT_OVERFLOW' | 'AGENT_ABORTED' | 'AGENT_RUN_FAILED'\n >;\n recoverable?: boolean | undefined;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: opts.code,\n subsystem: 'agent',\n severity: opts.code === ERROR_CODES.AGENT_ABORTED ? 'warning' : 'error',\n recoverable: opts.recoverable ?? opts.code === ERROR_CODES.AGENT_ITERATION_LIMIT,\n context: opts.context,\n cause: opts.cause,\n });\n this.name = 'AgentError';\n }\n}\n\n/**\n * Wrap an arbitrary thrown value into a `WrongStackError` so the caller\n * always gets a structured error. Pass-throughs WrongStackError instances\n * unchanged; raw `Error`s and primitives get an `AGENT_RUN_FAILED` wrapper\n * with the original preserved as `cause`.\n */\nexport function toWrongStackError(\n err: unknown,\n code: Extract<ErrorCode, 'AGENT_RUN_FAILED' | 'AGENT_ABORTED' | 'UNKNOWN'> = ERROR_CODES.AGENT_RUN_FAILED,\n): WrongStackError {\n if (err instanceof WrongStackError) return err;\n const message = toErrorMessage(err);\n return new AgentError({\n message,\n code: code === 'UNKNOWN' ? ERROR_CODES.AGENT_RUN_FAILED : code,\n cause: err,\n });\n}\n\n/**\n * Session storage errors.\n */\nexport class SessionError extends WrongStackError {\n readonly sessionId?: string | undefined;\n\n constructor(opts: {\n message: string;\n code: Extract<ErrorCode, 'SESSION_NOT_FOUND' | 'SESSION_CORRUPTED' | 'SESSION_WRITE_FAILED'>;\n sessionId?: string | undefined;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: opts.code,\n subsystem: 'session',\n severity: opts.code === ERROR_CODES.SESSION_WRITE_FAILED ? 'error' : 'warning',\n recoverable: opts.code !== ERROR_CODES.SESSION_CORRUPTED,\n context: { sessionId: opts.sessionId, ...opts.context },\n cause: opts.cause,\n });\n this.name = 'SessionError';\n this.sessionId = opts.sessionId;\n }\n}\n\n/**\n * SDD (Spec-Driven Development) errors \u2014 spec validation, parsing, and\n * state machine violations in the AISpecBuilder, TaskFlow, and TaskTracker.\n */\nexport class SddError extends WrongStackError {\n constructor(opts: {\n message: string;\n code: Extract<\n ErrorCode,\n 'SDD_VALIDATION_FAILED' | 'SDD_PARSE_FAILED' | 'SDD_INVALID_STATE' | 'SDD_NOT_READY'\n >;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: opts.code,\n subsystem: 'sdd',\n severity: opts.code === ERROR_CODES.SDD_PARSE_FAILED ? 'warning' : 'error',\n recoverable: opts.code === ERROR_CODES.SDD_NOT_READY,\n context: opts.context,\n cause: opts.cause,\n });\n this.name = 'SddError';\n }\n}\n\n/**\n * File system operation errors.\n */\nexport class FsError extends WrongStackError {\n readonly path?: string | undefined;\n\n constructor(opts: {\n message: string;\n code: Extract<\n ErrorCode,\n 'FS_READ_FAILED' | 'FS_WRITE_FAILED' | 'FS_MKDIR_FAILED' | 'FS_DELETE_FAILED' | 'FS_ATOMIC_WRITE_FAILED'\n >;\n path?: string | undefined;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: opts.code,\n subsystem: 'fs',\n severity: 'error',\n recoverable: opts.code !== ERROR_CODES.FS_READ_FAILED,\n context: { path: opts.path, ...opts.context },\n cause: opts.cause,\n });\n this.name = 'FsError';\n this.path = opts.path;\n }\n}\n\n/**\n * HTTP fetch error \u2014 thrown when a network request returns a non-OK status.\n * Carries the response status so {@link classifyToolError} can branch on it\n * (429 \u2192 transient, 404 \u2192 not_found, 401 \u2192 permission) without duck-typing\n * the error via `'response' in err`.\n *\n * P3 #18 (before-release.md): the previous `'response' in err` check caught\n * any Error with a `response` property, including custom errors, proxy\n * objects, or mocked errors in tests. `instanceof FetchError` is reliable.\n *\n * Tools and providers that make HTTP requests and need the executor to\n * classify their failures should throw `new FetchError({ status, message })`\n * instead of a bare `Error` with an ad-hoc `response` field.\n */\nexport class FetchError extends WrongStackError {\n readonly status: number;\n\n constructor(opts: {\n message: string;\n status: number;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: ERROR_CODES.VALIDATION_ERROR,\n subsystem: 'general',\n severity: 'error',\n recoverable: opts.status === 429 || opts.status >= 500,\n context: { status: opts.status, ...opts.context },\n cause: opts.cause,\n });\n this.name = 'FetchError';\n this.status = opts.status;\n }\n}\n\n/**\n * Tool input validation error \u2014 thrown when a tool's input fails a validation\n * check that the JSON Schema cannot express (e.g. `old_string === new_string`\n * in edit, or a cross-field invariant). Use this instead of a bare\n * `throw new Error('...validation...')` so {@link classifyToolError} can\n * match on `instanceof` rather than a locale-dependent message substring.\n *\n * P2 #6 (before-release.md): the previous `err.message.includes('validation')`\n * check misclassified any error whose message happened to contain \"validation\"\n * (e.g. a third-party \"input validation timeout\") as a VALIDATION error.\n *\n * Named `ToolValidationError` (not `ValidationError`) to avoid colliding with\n * the existing `ValidationError` interface exported by json-schema-validate.ts\n * (a validation-result shape, not an Error subclass).\n */\nexport class ToolValidationError extends WrongStackError {\n constructor(opts: {\n message: string;\n /** Field path or tool name that failed validation, for diagnostics. */\n field?: string | undefined;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: ERROR_CODES.VALIDATION_ERROR,\n subsystem: 'general',\n severity: 'error',\n recoverable: false,\n context: { field: opts.field, ...opts.context },\n cause: opts.cause,\n });\n this.name = 'ToolValidationError';\n }\n}\n\n/**\n * Response / payload parse error \u2014 thrown when an upstream HTTP response,\n * file, or data structure is well-formed at the transport layer (HTTP 200,\n * valid JSON) but is missing required fields or has an unexpected shape.\n *\n * Distinct from `ConfigError(CONFIG_PARSE_FAILED)` (which is specifically\n * for config-file parsing) and `FetchError` (which covers HTTP non-OK\n * responses). `ParseError` fills the gap: the request succeeded but the\n * response body couldn't be interpreted.\n *\n * Common sites: OAuth token responses missing `access_token`, device-code\n * responses missing `device_code`, registry responses with unexpected\n * schemas.\n */\nexport class ParseError extends WrongStackError {\n readonly source?: string | undefined;\n\n constructor(opts: {\n message: string;\n /**\n * What was being parsed \u2014 e.g. `'oauth-token-response'`,\n * `'device-code-response'`. Lets consumers distinguish parse failures\n * from different upstream APIs without parsing the message.\n */\n source?: string | undefined;\n context?: Record<string, unknown> | undefined;\n cause?: unknown | undefined;\n }) {\n super({\n message: opts.message,\n code: ERROR_CODES.PARSE_FAILED,\n subsystem: 'general',\n severity: 'error',\n recoverable: false,\n context: { source: opts.source, ...opts.context },\n cause: opts.cause,\n });\n this.name = 'ParseError';\n this.source = opts.source;\n }\n}\n\n// \u2500\u2500 Type guards \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function isWrongStackError(err: unknown): err is WrongStackError {\n return err instanceof WrongStackError;\n}\n\nexport function isToolError(err: unknown): err is ToolError {\n return err instanceof ToolError;\n}\n\nexport function isConfigError(err: unknown): err is ConfigError {\n return err instanceof ConfigError;\n}\n\nexport function isPluginError(err: unknown): err is PluginError {\n return err instanceof PluginError;\n}\n\nexport function isSessionError(err: unknown): err is SessionError {\n return err instanceof SessionError;\n}\n\nexport function isAgentError(err: unknown): err is AgentError {\n return err instanceof AgentError;\n}\n\nexport function isFsError(err: unknown): err is FsError {\n return err instanceof FsError;\n}\n\nexport function isToolValidationError(err: unknown): err is ToolValidationError {\n return err instanceof ToolValidationError;\n}\n\nexport function isFetchError(err: unknown): err is FetchError {\n return err instanceof FetchError;\n}\n\nexport function isParseError(err: unknown): err is ParseError {\n return err instanceof ParseError;\n}\n\nexport function isSddError(err: unknown): err is SddError {\n return err instanceof SddError;\n}\n", "import { closeSync, fsyncSync, openSync, writeSync } from 'node:fs';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { EventBus } from '../kernel/events.js';\nimport type {\n FileSnapshot,\n SessionEvent,\n SessionMetadata,\n SessionSummary,\n SessionWriter,\n WorkspaceCheckpointRef,\n} from '../types/session.js';\nimport type { SecretScrubber } from '../types/secret-scrubber.js';\nimport { atomicWrite } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/index.js';\nimport type { SessionCheckpointCas } from './session-checkpoint-cas.js';\nimport { userInputTitle } from './session-helpers.js';\n\n/**\n * Append-mode JSONL session writer with batched writes, write serialization,\n * and enriched summary tracking.\n *\n * Extracted from session-store.ts to keep each module focused: this class\n * owns the per-session write path (append/flush/close/checkpoint/truncate),\n * while `DefaultSessionStore` owns the store-level read/list/index/delete path.\n */\nexport class FileSessionWriter implements SessionWriter {\n private closed = false;\n private closePromise: Promise<void> | null = null;\n private manifestFile: string;\n private summary: SessionSummary;\n private tokenIn = 0;\n private tokenOut = 0;\n private readonly filePath: string;\n get transcriptPath(): string | undefined {\n return this.filePath || undefined;\n }\n /**\n * Lazy session_start/session_resumed init, shared by all appenders.\n * A single promise (not a boolean) so a second append racing the first\n * can't push its event into the buffer BEFORE the first append's event \u2014\n * every appender awaits the same init and resumes in FIFO call order.\n */\n private initPromise: Promise<void> | null = null;\n private ensureInit(): Promise<void> {\n if (!this.initPromise) this.initPromise = this.writeSessionStartLazy();\n return this.initPromise;\n }\n private readonly resumed: boolean;\n private appendFailCount = 0;\n private lastAppendWarnAt = 0;\n private readonly secretScrubber?: SecretScrubber | undefined;\n private readonly checkpointCas?: SessionCheckpointCas | undefined;\n private readonly onCloseCb?: (((summary: SessionSummary) => void | Promise<void>)) | undefined;\n /** Implements SessionWriter.traceId \u2014 propagated from ContextInit.traceId. */\n traceId: string | undefined;\n\n // \u2500\u2500 Write buffer \u2014 batches events to reduce per-event disk I/O \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n //\n // Every append() pushes the scrubbed event into an in-memory buffer instead\n // of calling handle.appendFile() synchronously. The buffer flushes to disk\n // when it reaches FLUSH_SIZE events OR after FLUSH_INTERVAL_MS of inactivity.\n // This cuts the number of disk writes by ~95% without changing the on-disk\n // format \u2014 the JSONL is still one JSON object per line.\n private writeBuffer: SessionEvent[] = [];\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n private static readonly FLUSH_INTERVAL_MS = 500;\n private static readonly FLUSH_SIZE = 50;\n\n // \u2500\u2500 Write serialization \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n //\n // All disk writes are funneled through a FIFO promise chain. Without it,\n // a timer-driven flush racing an explicit flush()/close() issues two\n // concurrent appendFile() calls on the shared O_APPEND handle \u2014 the kernel\n // may complete them out of order (chronology breaks) or, for large\n // batches, interleave partial writes (torn JSONL lines). The write chain\n // serializes handle I/O; flushChain additionally serializes the act of\n // draining/re-queueing the in-memory event buffer so a failed older batch\n // can never be overtaken by a newer successful one.\n private writeChain: Promise<void> = Promise.resolve();\n private flushChain: Promise<void> = Promise.resolve();\n\n /** Enqueue a write on the FIFO chain. Resolves/rejects with that write. */\n private enqueueWrite(data: string): Promise<void> {\n const write = this.writeChain.then(() => this.handle.appendFile(data, 'utf8'));\n this.writeChain = write.then(\n () => undefined,\n () => undefined,\n );\n return write;\n }\n\n // \u2500\u2500 Enriched summary tracking \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n private iterationCount = 0;\n private toolCallCount = 0;\n private toolErrorCount = 0;\n private toolBreakdown: Record<string, number> = {};\n private fileChangeCount = 0;\n private compactionCount = 0;\n private outcome: SessionSummary['outcome'] = undefined;\n\n /**\n * Scrub secrets out of conversation-turn events before they are observed\n * for the summary, written to the JSONL log, or surfaced on resume. Only\n * `user_input`, `llm_response`, and `context_snapshot` carry free-form\n * user/model text; other event types either have no secret-bearing content\n * or are already scrubbed upstream (tool results). Snapshot normalization\n * also strips transient token-cache fields even when no scrubber is set.\n */\n private scrubEvent(event: SessionEvent): SessionEvent {\n const s = this.secretScrubber;\n if (event.type === 'context_snapshot') {\n return {\n ...event,\n messages: event.messages.map((message) => {\n const { _estTokens: _ignored, ...persisted } = message;\n return {\n ...persisted,\n content:\n typeof persisted.content === 'string'\n ? (s?.scrub(persisted.content) ?? persisted.content)\n : (s?.scrubObject(persisted.content) ?? persisted.content),\n };\n }),\n };\n }\n if (!s) return event;\n if (event.type === 'user_input') {\n return {\n ...event,\n content:\n typeof event.content === 'string' ? s.scrub(event.content) : s.scrubObject(event.content),\n };\n }\n if (event.type === 'llm_response') {\n return { ...event, content: s.scrubObject(event.content) };\n }\n return event;\n }\n\n private pendingFileSnapshots: Array<{\n path: string;\n action: 'created' | 'modified' | 'deleted';\n before: string | null;\n after: string | null;\n }> = [];\n /** Prompt whose tool work is currently executing. Set by writeCheckpoint. */\n private activePromptIndex: number | null = null;\n /** Tracks open tool_use IDs during the current run to serialize on close for resume. */\n private openToolUses = new Set<string>();\n\n /**\n * Buffer an event from a synchronous Context callback. ensureInit() starts\n * by pushing the lifecycle preamble synchronously, so session_start always\n * precedes observations even though these callbacks cannot await append().\n */\n private bufferSynchronousEvent(event: SessionEvent): void {\n if (this.closed) return;\n void this.ensureInit();\n this.observeForSummary(event);\n this.writeBuffer.push(event);\n if (this.writeBuffer.length >= FileSessionWriter.FLUSH_SIZE) {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n void this.flushBuffer().catch(() => {\n // Retained at the head of writeBuffer for the boundary retry.\n });\n } else {\n this.scheduleFlush();\n }\n }\n\n recordFileChange(input: {\n path: string;\n action: 'created' | 'modified' | 'deleted';\n before: string | null;\n after: string | null;\n }): void {\n if (this.closed) return;\n if (this.activePromptIndex === null) {\n // Compatibility path for embedders that mutate before their first\n // checkpoint. writeCheckpoint()/close() will attach these changes to the\n // first available prompt index.\n this.pendingFileSnapshots.push(input);\n return;\n }\n\n // This method is intentionally synchronous because file tools call it\n // immediately after their atomic mutation. Put the reconstruct event in\n // the writer buffer before the tool returns; agent-tools flushes the buffer\n // together with the matching tool_result boundary.\n const event: SessionEvent = {\n type: 'file_snapshot',\n ts: new Date().toISOString(),\n promptIndex: this.activePromptIndex,\n files: [input],\n };\n this.bufferSynchronousEvent(event);\n }\n\n recordFileObservation(input: {\n path: string;\n hash: string;\n mtimeMs: number;\n source: 'user' | 'write';\n }): void {\n if (!input.path || !/^[a-f\\d]{64}$/i.test(input.hash) || !Number.isFinite(input.mtimeMs)) return;\n this.bufferSynchronousEvent({\n type: 'file_observation',\n ts: new Date().toISOString(),\n path: input.path,\n hash: input.hash.toLowerCase(),\n mtimeMs: input.mtimeMs,\n source: input.source,\n });\n }\n\n recordSideEffect(input: {\n toolUseId: string;\n toolName: string;\n input: Record<string, unknown>;\n outcome?: string | undefined;\n risk: 'fs.write' | 'shell' | 'package' | 'network' | 'config';\n }): void {\n // Fire-and-forget \u2014 side-effect recording must never block tool execution.\n this.append({\n type: 'side_effect',\n ts: new Date().toISOString(),\n toolUseId: input.toolUseId,\n toolName: input.toolName,\n input: input.input,\n outcome: input.outcome,\n risk: input.risk,\n }).catch(() => { /* best-effort */ });\n }\n\n constructor(\n public readonly id: string,\n private handle: fsp.FileHandle,\n private readonly startedAt: string,\n private readonly meta: Omit<SessionMetadata, 'startedAt'>,\n private readonly events?: EventBus | undefined,\n opts: {\n resumed?: boolean | undefined;\n dir?: string | undefined;\n filePath?: string | undefined;\n secretScrubber?: SecretScrubber | undefined;\n checkpointCas?: SessionCheckpointCas | undefined;\n /** Called on close() with the finalized summary for index/sidecar writes. */\n onClose?: (((summary: SessionSummary) => void | Promise<void>)) | undefined;\n } = {},\n traceId?: string | undefined,\n ) {\n this.resumed = opts.resumed ?? false;\n // id already contains a date-prefix shard (e.g. \"2026-06-06/sess_<ULID>\").\n // opts.dir is the shard directory \u2014 join with basename so the manifest\n // lives next to the JSONL file instead of creating a double-nested path.\n this.manifestFile = opts.dir ? path.join(opts.dir, `${path.basename(id)}.summary.json`) : '';\n this.filePath = opts.filePath ?? '';\n this.secretScrubber = opts.secretScrubber;\n this.checkpointCas = opts.checkpointCas;\n this.onCloseCb = opts.onClose;\n this.summary = {\n id,\n title: '(empty session)',\n startedAt,\n model: meta.model ?? 'unknown',\n provider: meta.provider ?? 'unknown',\n tokenTotal: 0,\n };\n // Propagated from ContextInit.traceId via SessionWriter.traceId so that\n // storage events carry the run-level trace ID without needing a Context\n // handle in every storage operation.\n this.traceId = traceId;\n }\n\n get pendingToolUses(): string[] {\n return Array.from(this.openToolUses);\n }\n\n private async writeSessionStartLazy(): Promise<void> {\n // Keep the lifecycle preamble in the same retryable buffer as every other\n // reconstruct event. Writing it through a separate one-shot append meant\n // an ENOSPC/transient handle failure could permanently lose session_start\n // while later events survived, leaving an unidentifiable transcript.\n this.writeBuffer.push({\n type: this.resumed ? 'session_resumed' : 'session_start',\n ts: this.startedAt,\n id: this.id,\n model: this.meta.model ?? 'unknown',\n provider: this.meta.provider ?? 'unknown',\n });\n }\n\n async append(event: SessionEvent): Promise<void> {\n if (this.closed) return;\n await this.ensureInit();\n // Scrub before observing (the summary title is derived from user_input\n // content) and before buffering, so neither the JSONL nor the sidecar\n // ever holds a cleartext secret.\n const scrubbed = this.scrubEvent(event);\n // observeForSummary MUST run synchronously here \u2014 the summary counters\n // (toolCallCount, tokenIn/Out, outcome) drive the .summary.json sidecar\n // and the session index. Deferring observation to flush time would leave\n // the summary stale if close() fires before the next timer tick.\n this.observeForSummary(scrubbed);\n this.writeBuffer.push(scrubbed);\n\n if (this.writeBuffer.length >= FileSessionWriter.FLUSH_SIZE) {\n // Buffer full \u2014 flush immediately. Cancel any pending timer so we\n // don't double-flush on the next tick.\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n await this.flushBuffer().catch(() => {\n // append() is intentionally best-effort. The failed batch remains at\n // the front of writeBuffer; an explicit boundary flush can surface the\n // error while ordinary audit appends do not abort the agent loop.\n });\n } else {\n this.scheduleFlush();\n }\n }\n\n async appendBatch(events: SessionEvent[]): Promise<void> {\n if (this.closed || events.length === 0) return;\n await this.ensureInit();\n for (const event of events) {\n const scrubbed = this.scrubEvent(event);\n this.observeForSummary(scrubbed);\n this.writeBuffer.push(scrubbed);\n }\n if (this.writeBuffer.length >= FileSessionWriter.FLUSH_SIZE) {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n await this.flushBuffer().catch(() => {\n // Same best-effort append contract as append(); batch is retained.\n });\n } else {\n this.scheduleFlush();\n }\n }\n\n /**\n * Flush buffered events to disk immediately. Critical events\n * (user_input, llm_response) call this so they survive SIGKILL/crash\n * instead of sitting in the in-memory buffer for up to 500ms.\n *\n * Idempotent \u2014 cancels any pending timer, writes whatever has accumulated,\n * then asks the OS to synchronize the file data before resolving. Even an\n * empty-buffer flush synchronizes any earlier timer-driven append.\n */\n async flush(): Promise<void> {\n if (this.closed) return;\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n await this.flushBuffer();\n await this.writeChain;\n await this.handle.datasync();\n }\n\n /**\n * Last-gasp synchronous drain for hard-exit paths (process.exit after\n * rapid Ctrl+C). The async write chain cannot be awaited when the process\n * is about to die, but whatever still sits in the in-memory buffer CAN be\n * saved with a blocking append. Best-effort: an in-flight async write may\n * be cut off by the exit regardless; errors here are swallowed.\n */\n flushSync(): void {\n if (this.writeBuffer.length === 0 || !this.filePath) return;\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n const batch = this.writeBuffer.map((e) => JSON.stringify(e)).join('\\n') + '\\n';\n this.writeBuffer = [];\n let fd: number | undefined;\n try {\n fd = openSync(this.filePath, 'a');\n writeSync(fd, batch, null, 'utf8');\n fsyncSync(fd);\n } catch {\n // best-effort \u2014 the process is exiting either way\n } finally {\n if (fd !== undefined) {\n try {\n closeSync(fd);\n } catch {\n // best-effort \u2014 the process is exiting either way\n }\n }\n }\n }\n\n /** Schedule a deferred flush. No-op if a timer is already pending. */\n private scheduleFlush(): void {\n if (this.flushTimer) return;\n this.flushTimer = setTimeout(() => {\n this.flushTimer = null;\n /* v8 ignore start -- defensive: flushBuffer logs its own errors; this guards the timer callback */\n this.flushBuffer().catch(() => {\n // flushBuffer already logs via the throttled-warning path;\n // this catch prevents an unhandled rejection in the timer callback.\n });\n /* v8 ignore stop */\n }, FileSessionWriter.FLUSH_INTERVAL_MS);\n }\n\n /**\n * Flush all buffered events to disk as a single appendFile call.\n * Concurrent callers join a FIFO flush chain. On failure the drained batch\n * is prepended to the live buffer, preserving chronology and allowing the\n * next timer/boundary flush to retry it. Warnings retain the existing\n * throttled behavior.\n */\n private flushBuffer(): Promise<void> {\n const flush = this.flushChain.then(() => this.flushBufferOnce());\n this.flushChain = flush.then(\n () => undefined,\n () => undefined,\n );\n return flush;\n }\n\n private async flushBufferOnce(): Promise<void> {\n if (this.writeBuffer.length === 0) return;\n const events = this.writeBuffer;\n const eventCount = events.length;\n const batch = events.map((e) => JSON.stringify(e)).join('\\n') + '\\n';\n this.writeBuffer = [];\n const t0 = Date.now();\n let outcome: 'success' | 'failure' = 'success';\n let errorMsg: string | undefined;\n try {\n await this.enqueueWrite(batch);\n } catch (err) {\n outcome = 'failure';\n errorMsg = toErrorMessage(err);\n // No newer batch can be draining concurrently (flushChain serializes\n // this section), so prepending restores the exact original chronology.\n this.writeBuffer = [...events, ...this.writeBuffer];\n this.appendFailCount += eventCount;\n const now = Date.now();\n if (now - this.lastAppendWarnAt > 5000) {\n const suppressed = this.appendFailCount - 1;\n const tail = suppressed > 0 ? ` (+${suppressed} suppressed)` : '';\n console.warn(\n '[session] flush failed:',\n toErrorMessage(err),\n tail,\n );\n this.lastAppendWarnAt = now;\n this.appendFailCount = 0;\n }\n if (!this.closed) this.scheduleFlush();\n throw err;\n } finally {\n this.events?.emit('storage.write', {\n sessionId: this.id,\n store: 'session',\n filePath: this.filePath,\n operation: 'flush',\n outcome,\n durationMs: Date.now() - t0,\n ...(errorMsg !== undefined ? { error: errorMsg } : {}),\n ...(eventCount !== undefined ? { eventCount } : {}),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n }\n }\n\n private observeForSummary(event: SessionEvent): void {\n // Track open tool uses so we can serialize them on close for resume.\n // The authoritative source is the llm_response content (a core event,\n // always written at every audit level); the legacy 'tool_use' event is\n // kept for alternate writers that still emit it.\n if (event.type === 'llm_response') {\n for (const block of event.content) {\n if (block.type === 'tool_use') this.openToolUses.add(block.id);\n }\n }\n if (event.type === 'tool_use') {\n this.openToolUses.add(event.id);\n } else if (event.type === 'tool_call_start') {\n this.toolCallCount++;\n this.toolBreakdown[event.name] = (this.toolBreakdown[event.name] ?? 0) + 1;\n } else if (event.type === 'tool_result') {\n this.openToolUses.delete(event.id);\n if (event.isError) {\n this.toolErrorCount++;\n this.outcome = 'error';\n }\n } else if (event.type === 'file_snapshot') {\n this.fileChangeCount += event.files.length;\n } else if (event.type === 'compaction') {\n this.compactionCount++;\n }\n // Error events (provider errors, execution errors) mark the session as failed.\n if (event.type === 'error' || event.type === 'provider_error') {\n this.outcome = 'error';\n }\n if (event.type === 'user_input' && this.summary.title === '(empty session)') {\n this.summary = { ...this.summary, title: userInputTitle(event.content) };\n } else if (event.type === 'llm_response') {\n this.tokenIn += event.usage.input;\n this.tokenOut += event.usage.output;\n this.summary = { ...this.summary, tokenTotal: this.tokenIn + this.tokenOut };\n } else if (event.type === 'session_end') {\n const total = event.usage.input + event.usage.output;\n if (total > 0) this.summary = { ...this.summary, tokenTotal: total };\n } else if (event.type === 'in_flight_start') {\n this.iterationCount++;\n }\n }\n\n async close(): Promise<void> {\n // Idempotent AND awaitable: concurrent/repeat callers share the same\n // promise, so nobody proceeds (e.g. to tear down the session directory)\n // while the first close is still flushing.\n if (this.closePromise) return this.closePromise;\n this.closePromise = this.doClose().catch((err) => {\n // A failed durable drain must not permanently brick the writer. Keep the\n // handle open and allow the timer or a later close() call to retry.\n this.closed = false;\n this.closePromise = null;\n if (this.writeBuffer.length > 0) this.scheduleFlush();\n throw err;\n });\n return this.closePromise;\n }\n\n private async doClose(): Promise<void> {\n if (this.pendingFileSnapshots.length > 0) {\n await this.writeFileSnapshot(\n this.activePromptIndex ?? 0,\n [...this.pendingFileSnapshots],\n );\n this.pendingFileSnapshots = [];\n }\n this.closed = true;\n // Flush any buffered events before finalizing. The summary counters\n // (toolCallCount, tokenIn/Out, outcome) are already up to date because\n // observeForSummary runs synchronously on every append, but the JSONL\n // must have all events on disk before we write the .summary.json sidecar.\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n await this.flushBuffer();\n // Drain any write enqueued outside flushBuffer (e.g. the lazy\n // session_start record) before the handle is closed.\n await this.writeChain;\n await this.handle.datasync();\n // Finalize the summary before writing.\n this.summary = {\n ...this.summary,\n endedAt: new Date().toISOString(),\n iterationCount: this.iterationCount,\n toolCallCount: this.toolCallCount,\n toolErrorCount: this.toolErrorCount,\n fileChangeCount: this.fileChangeCount,\n compactionCount: this.compactionCount > 0 ? this.compactionCount : undefined,\n toolBreakdown:\n { ...this.toolBreakdown },\n outcome: this.outcome ?? 'completed',\n };\n // Emit storage.write for the manifest sidecar.\n if (this.manifestFile) {\n const t0 = Date.now();\n let outcome: 'success' | 'failure' = 'success';\n let errorMsg: string | undefined;\n try {\n await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 0o600 });\n } catch (err) {\n outcome = 'failure';\n errorMsg = toErrorMessage(err);\n // manifest write is best-effort\n } finally {\n this.events?.emit('storage.write', {\n sessionId: this.id,\n store: 'session',\n filePath: this.manifestFile,\n operation: 'close',\n outcome,\n durationMs: Date.now() - t0,\n ...(errorMsg !== undefined ? { error: errorMsg } : {}),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n }\n }\n // Notify the store so it can update the session index. Await so the\n // index write completes before close() resolves \u2014 otherwise the\n // fire-and-forget _index.jsonl append races callers that tear down the\n // session directory right after close() (e.g. ENOTEMPTY on Windows).\n // Emit storage.write here so it carries this.traceId; the actual I/O\n // is delegated to onCloseCb (appendToIndex) which no longer emits.\n const idxT0 = Date.now();\n let idxOutcome: 'success' | 'failure' = 'success';\n let idxError: string | undefined;\n try {\n await this.onCloseCb?.(this.summary);\n /* v8 ignore start -- best-effort: appendToIndex swallows its own errors */\n } catch (err) {\n idxOutcome = 'failure';\n idxError = toErrorMessage(err);\n // best-effort\n } finally {\n /* v8 ignore stop */\n this.events?.emit('storage.write', {\n sessionId: this.summary.id,\n store: 'session',\n filePath: this.filePath,\n operation: 'index_append',\n outcome: idxOutcome,\n durationMs: Date.now() - idxT0,\n ...(idxError !== undefined ? { error: idxError } : {}),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n }\n try {\n await this.handle.close();\n } catch {\n // ignore\n }\n }\n\n async writeCheckpoint(promptIndex: number, promptPreview: string): Promise<void> {\n const fileCount = this.pendingFileSnapshots.length;\n if (fileCount > 0) {\n await this.writeFileSnapshot(promptIndex, [...this.pendingFileSnapshots]);\n this.pendingFileSnapshots = [];\n }\n let workspaceCheckpoint: WorkspaceCheckpointRef | undefined;\n try {\n workspaceCheckpoint = await this.checkpointCas?.capture(this.id, promptIndex);\n } catch (err) {\n // Conversation checkpoints remain usable even when Git/CAS capture is\n // unavailable. The missing workspaceCheckpoint field makes the reduced\n // guarantee explicit to fork/materialization callers.\n console.warn(\n JSON.stringify({\n level: 'warn',\n event: 'session.workspace_checkpoint_capture_failed',\n sessionId: this.id,\n promptIndex,\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }),\n );\n }\n await this.append({\n type: 'checkpoint',\n ts: new Date().toISOString(),\n promptIndex,\n promptPreview,\n ...(workspaceCheckpoint ? { workspaceCheckpoint } : {}),\n });\n this.activePromptIndex = promptIndex;\n this.events?.emit('checkpoint.written', {\n sessionId: this.id,\n promptIndex,\n promptPreview,\n ts: new Date().toISOString(),\n fileCount,\n });\n }\n\n async writeFileSnapshot(\n promptIndex: number,\n files: FileSnapshot[],\n ): Promise<void> {\n await this.append({\n type: 'file_snapshot',\n ts: new Date().toISOString(),\n promptIndex,\n files,\n });\n }\n\n /**\n * Truncate the session file to the checkpoint with the given promptIndex,\n * removing all events that follow it. Uses a single-pass byte-offset scan\n * so post-checkpoint content is never read or parsed \u2014 O(1) memory instead\n * of O(N) JSON.parse calls over the full file.\n */\n async truncateToCheckpoint(targetPromptIndex: number): Promise<number> {\n /* v8 ignore next -- defensive: filePath is always set for a live writer */\n if (!this.filePath) return 0;\n\n // Flush buffered events to disk before reading \u2014 otherwise the in-memory\n // events that haven't hit the JSONL yet would be invisible to the\n // truncation logic and would be silently dropped by the rewrite.\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n await this.flushBuffer();\n // Drain the write chain so no in-flight write straddles the close/rename/reopen.\n await this.writeChain;\n\n // Single-pass scan: track byte offset of each line start. Stop as soon as\n // the target checkpoint is found \u2014 no I/O or parsing for post-checkpoint data.\n const CHUNK_SIZE = 65_536;\n let fd: fsp.FileHandle | undefined;\n let fileOffset = 0; // cumulative byte position of the start of the current chunk\n let lineStartOffset = 0; // byte offset within the file where the current line begins\n let checkpointByteOffset = -1; // byte offset where we will truncate the file\n let removedCount = 0;\n let targetCheckpointSeen = false; // has the target checkpoint been found yet?\n\n try {\n fd = await fsp.open(this.filePath, 'r', 0o600);\n\n while (true) {\n const buf = Buffer.alloc(CHUNK_SIZE);\n const { bytesRead } = await fd.read(buf, 0, CHUNK_SIZE, fileOffset);\n if (bytesRead === 0) break;\n\n let chunkPos = 0;\n while (chunkPos < bytesRead) {\n const idx = buf.indexOf('\\n', chunkPos);\n if (idx === -1) {\n // No complete line in this chunk \u2014 save partial for next iteration.\n lineStartOffset = fileOffset + chunkPos;\n break;\n }\n\n if (checkpointByteOffset !== -1) {\n // Target already found \u2014 every subsequent line is removed.\n removedCount++;\n } else {\n // Only parse lines that could precede or be the checkpoint.\n const lineBytes = buf.subarray(chunkPos, idx);\n // eslint-disable-next-line no-sync\n const line = new TextDecoder('utf-8', { fatal: false }).decode(lineBytes);\n if (line.trim()) {\n try {\n const event = JSON.parse(line) as { type?: string; promptIndex?: number };\n if (event.type === 'checkpoint') {\n if (event.promptIndex === targetPromptIndex) {\n // Target found \u2014 record its byte offset and stop scanning.\n checkpointByteOffset = lineStartOffset;\n targetCheckpointSeen = true;\n } else if (event.promptIndex !== undefined && event.promptIndex > targetPromptIndex) {\n // A checkpoint with a higher promptIndex means the target is absent.\n // Truncate before this line (exclusive) \u2014 it and all following events\n // will be replaced by the new rewinded history.\n checkpointByteOffset = lineStartOffset;\n }\n } else if (targetCheckpointSeen && event.promptIndex !== undefined && event.promptIndex > targetPromptIndex) {\n // Post-target event with a later promptIndex \u2014 count as removed.\n removedCount++;\n } else if (targetCheckpointSeen && event.promptIndex === undefined) {\n // After the target checkpoint was found: remove events with no\n // promptIndex. (In the original this is the afterTarget &&\n // targetCheckpointLine !== -1 branch.)\n removedCount++;\n } else if (!targetCheckpointSeen && event.promptIndex === undefined) {\n // Past a higher checkpoint but the target checkpoint not yet found.\n // Matches original: remove events with undefined promptIndex\n // (malformed lines, file_snapshots, etc.) that appear after a\n // higher checkpoint but before the target.\n removedCount++;\n } else if (!targetCheckpointSeen && event.promptIndex !== undefined && event.promptIndex > targetPromptIndex) {\n // Past a higher checkpoint but the target not yet found.\n // Matches original: remove events with promptIndex > target that\n // appear before the target checkpoint (e.g. user_inputs belonging\n // to a later prompt).\n removedCount++;\n }\n // Events with promptIndex <= targetPromptIndex (before the target is\n // found) are implicitly kept \u2014 no action needed.\n } catch {\n // Malformed JSON \u2014 matches original: keep it.\n }\n }\n }\n\n // Move to start of next line.\n chunkPos = idx + 1;\n lineStartOffset = fileOffset + chunkPos;\n }\n\n fileOffset += bytesRead;\n if (chunkPos >= bytesRead) {\n // Finished all complete lines; prepare for next chunk.\n lineStartOffset = fileOffset;\n }\n }\n } finally {\n await fd?.close();\n }\n\n if (checkpointByteOffset === -1) return 0;\n\n // Windows EPERM fix: close the append-mode handle before replacing the\n // file. Windows rejects rename() when the destination still has an open\n // handle, even if that handle belongs to this process.\n await this.writeChain;\n await this.handle.close();\n const tmpPath = `${this.filePath}.rewind.tmp`;\n const src = await fsp.open(this.filePath, 'r', 0o600);\n try {\n const statResult = await src.stat();\n const totalSize = statResult.size;\n // checkpointByteOffset points to the start of the checkpoint line.\n // We want to keep everything up to and including that line's '\\n'.\n // Since the file ends with '\\n', keeping bytes [0 .. lineStartAfterCheckpoint]\n // means we include the trailing newline. We find that '\\n' by scanning\n // from checkpointByteOffset forward (at most one chunk's worth).\n const prefixBytes = checkpointByteOffset;\n let newlineAfterCheckpoint = prefixBytes;\n\n if (prefixBytes < totalSize) {\n const probeBuf = Buffer.alloc(Math.min(CHUNK_SIZE, totalSize - prefixBytes));\n const { bytesRead: probeRead } = await src.read(probeBuf, 0, probeBuf.length, prefixBytes);\n if (probeRead > 0) {\n const nl = probeBuf.indexOf('\\n');\n newlineAfterCheckpoint = nl !== -1 ? prefixBytes + nl + 1 : totalSize;\n }\n } else {\n newlineAfterCheckpoint = totalSize;\n }\n\n const writeFd = await fsp.open(tmpPath, 'w', 0o600);\n try {\n let readOffset = 0;\n while (readOffset < newlineAfterCheckpoint) {\n const toCopy = Math.min(CHUNK_SIZE, newlineAfterCheckpoint - readOffset);\n const copyBuf = Buffer.alloc(toCopy);\n const { bytesRead: r } = await src.read(copyBuf, 0, toCopy, readOffset);\n if (r === 0) break;\n await writeFd.write(copyBuf, 0, r);\n readOffset += r;\n }\n\n // Preserve malformed JSONL records even after the rewind target. They\n // are not replayable session events, but keeping them avoids silently\n // deleting diagnostic/corruption evidence during a truncate.\n // Stream the tail from the already-open src handle instead of\n // re-reading the entire file \u2014 the prefix was already streamed above,\n // so reading via readFile() would duplicate all of that I/O.\n let tailOffset = newlineAfterCheckpoint;\n let leftover = '';\n while (tailOffset < totalSize) {\n const toRead = Math.min(CHUNK_SIZE, totalSize - tailOffset);\n const tailBuf = Buffer.alloc(toRead);\n const { bytesRead: tr } = await src.read(tailBuf, 0, toRead, tailOffset);\n if (tr === 0) break;\n const chunk = leftover + tailBuf.subarray(0, tr).toString('utf8');\n const lastNl = chunk.lastIndexOf('\\n');\n if (lastNl === -1) {\n // No complete line in this chunk \u2014 accumulate into leftover.\n leftover = chunk;\n } else {\n for (const line of chunk.slice(0, lastNl + 1).split('\\n')) {\n if (!line.trim()) continue;\n try {\n JSON.parse(line);\n } catch {\n await writeFd.write(`${line}\\n`, undefined, 'utf8');\n }\n }\n leftover = chunk.slice(lastNl + 1);\n }\n tailOffset += tr;\n }\n // Flush trailing partial line (file may not end with \\n).\n if (leftover.trim()) {\n try {\n JSON.parse(leftover);\n } catch {\n await writeFd.write(`${leftover}\\n`, undefined, 'utf8');\n }\n }\n } finally {\n await writeFd.close();\n }\n\n await src.close();\n await fsp.rename(tmpPath, this.filePath);\n // Re-open in append mode for continued use of this file.\n this.handle = await fsp.open(this.filePath, 'a', 0o600);\n /* v8 ignore start -- defensive: close/rename/reopen of a just-written temp file */\n } catch (err) {\n await fsp.unlink(tmpPath).catch(() => undefined);\n this.handle = await fsp.open(this.filePath, 'a', 0o600).catch(() => this.handle);\n throw err;\n }\n /* v8 ignore stop */\n\n await this.append({\n type: 'rewound',\n ts: new Date().toISOString(),\n toPromptIndex: targetPromptIndex,\n revertedFiles: [],\n });\n this.activePromptIndex = targetPromptIndex;\n\n this.events?.emit('session.rewound', {\n sessionId: this.id,\n toPromptIndex: targetPromptIndex,\n revertedFiles: [],\n removedEvents: removedCount,\n });\n\n return removedCount;\n }\n\n async clearSession(): Promise<void> {\n /* v8 ignore next -- defensive: filePath is always set for a live writer */\n if (!this.filePath) return;\n // Discard any buffered events \u2014 the caller is explicitly resetting the\n // session to a clean slate. Cancel the timer so it doesn't fire and\n // append stale events to the freshly-cleared file.\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n // Wait for a currently-draining batch first. If it failed and re-queued\n // itself, the explicit reset below discards it along with the rest of the\n // old conversation; if it succeeded, writeChain has already serialized it.\n await this.flushChain;\n this.writeBuffer = [];\n // Let any in-flight append land first \u2014 otherwise it would re-append\n // stale events AFTER the reset record below.\n await this.writeChain;\n const record = `${JSON.stringify({\n type: 'session_start',\n ts: new Date().toISOString(),\n id: this.id,\n model: this.meta.model ?? 'unknown',\n provider: this.meta.provider ?? 'unknown',\n })}\\n`;\n await fsp.writeFile(this.filePath, record, 'utf8');\n this.activePromptIndex = null;\n this.pendingFileSnapshots = [];\n }\n\n /**\n * Write an in-flight marker. The agent loop should call\n * this at the start of each long-running operation; a matching\n * `clearInFlightMarker` follows on clean exit. A stale marker\n * (no end) is what `SessionRecovery.detectStale` looks for.\n */\n async writeInFlightMarker(context: string): Promise<void> {\n if (!context || context.length > 500) {\n throw new Error('In-flight context must be 1..500 chars');\n }\n await this.append({\n type: 'in_flight_start',\n ts: new Date().toISOString(),\n context,\n });\n this.events?.emit('in_flight.started', { sessionId: this.id, context, ts: new Date().toISOString() });\n }\n\n /**\n * Close the in-flight marker. Idempotent in spirit\n * (you can call it after a successful iteration even if you\n * didn't open one this round) \u2014 but the session log records\n * every call so postmortem tooling can see \"the agent finished\n * cleanly X times, then died without finishing Y\".\n */\n async clearInFlightMarker(reason: 'clean' | 'aborted' | 'recovered'): Promise<void> {\n if (this.pendingFileSnapshots.length > 0) {\n await this.writeFileSnapshot(\n this.activePromptIndex ?? 0,\n [...this.pendingFileSnapshots],\n );\n this.pendingFileSnapshots = [];\n }\n await this.append({\n type: 'in_flight_end',\n ts: new Date().toISOString(),\n reason,\n });\n this.events?.emit('in_flight.ended', { sessionId: this.id, reason, ts: new Date().toISOString() });\n }\n}\n", "import type { ContentBlock } from '../types/blocks.js';\n\n/**\n * Derive a short title from a user_input event's content. Used by both\n * `DefaultSessionStore.summarize()` (offline summary rebuild) and\n * `FileSessionWriter.observeForSummary()` (live tracking), so it lives\n * here to avoid a bidirectional import between those two modules.\n */\nexport function userInputTitle(content: string | ContentBlock[]): string {\n const text =\n typeof content === 'string'\n ? content\n : content\n .filter((b): b is { type: 'text'; text: string } => b.type === 'text')\n .map((b) => b.text)\n .join(' ');\n return (text || '(non-text input)').slice(0, 60);\n}\n", "import { spawn } from 'node:child_process';\nimport { createHash, randomUUID } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type {\n WorkspaceCheckpointRef,\n WorkspaceMaterializationResult,\n} from '../types/session.js';\nimport { atomicWrite } from '../utils/atomic-write.js';\nimport { buildChildEnv } from '../utils/child-env.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport { mapWithConcurrency } from './storage-concurrency.js';\n\nexport interface CheckpointGitResult {\n code: number;\n stdout: string;\n stderr: string;\n /** Set when the runner discarded output after its safety limit. */\n stdoutTruncated?: boolean | undefined;\n stderrTruncated?: boolean | undefined;\n}\n\nexport interface SessionCheckpointCasOptions {\n rootDir: string;\n projectRoot: string;\n runGit?: ((args: string[], cwd: string) => Promise<CheckpointGitResult>) | undefined;\n}\n\ntype WorkspaceCheckpointEntry =\n | { path: string; state: 'file'; blobHash: string; mode: number }\n | { path: string; state: 'absent' }\n | { path: string; state: 'symlink'; linkTarget: string };\n\ninterface WorkspaceCheckpointManifest {\n version: 1;\n baseHead: string;\n coverage: 'git-head-plus-dirty';\n entries: WorkspaceCheckpointEntry[];\n unresolved: Array<{ path: string; reason: string }>;\n}\n\nconst HASH_RE = /^[a-f\\d]{64}$/;\nconst CAPTURE_CONCURRENCY = 8;\nconst MAX_GIT_OUTPUT = 16 * 1024 * 1024;\nconst MAX_BLOB_BYTES = 64 * 1024 * 1024;\nconst MAX_CHECKPOINT_BYTES = 512 * 1024 * 1024;\n\nfunction sha256(content: string | Uint8Array): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nfunction isInside(root: string, target: string): boolean {\n const relative = path.relative(root, target);\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction normalizeRelative(input: string): string | null {\n if (!input || path.isAbsolute(input)) return null;\n const normalized = input.replace(/\\\\/g, '/').replace(/^\\.\\//, '');\n const resolved = path.posix.normalize(normalized);\n if (!resolved || resolved === '.' || resolved === '..' || resolved.startsWith('../')) return null;\n return resolved;\n}\n\nfunction parseNulPaths(output: string): string[] {\n return output\n .split('\\0')\n .map(normalizeRelative)\n .filter((value): value is string => value !== null);\n}\n\nfunction isWrongStackWorktreePath(relative: string): boolean {\n return relative === '.wrongstack/worktrees' || relative.startsWith('.wrongstack/worktrees/');\n}\n\n/**\n * Content-addressed workspace checkpoint store.\n *\n * A manifest references a Git base tree plus CAS blobs for every changed or\n * untracked, non-ignored path. Applying it to a checkout at the same baseHead\n * reproduces the checkpoint without touching the parent working tree.\n */\nexport class SessionCheckpointCas {\n private readonly rootDir: string;\n private readonly projectRoot: string;\n private readonly runGit: (args: string[], cwd: string) => Promise<CheckpointGitResult>;\n\n constructor(opts: SessionCheckpointCasOptions) {\n this.rootDir = path.resolve(opts.rootDir);\n this.projectRoot = path.resolve(opts.projectRoot);\n this.runGit = opts.runGit ?? defaultRunGit;\n }\n\n async capture(_sessionId: string, _promptIndex: number): Promise<WorkspaceCheckpointRef | undefined> {\n const head = await this.runGit(['rev-parse', '--verify', 'HEAD'], this.projectRoot);\n const baseHead = head.stdout.trim();\n if (\n head.code !== 0 ||\n head.stdoutTruncated ||\n !/^[a-f\\d]{40,64}$/i.test(baseHead)\n ) {\n return undefined;\n }\n\n const [tracked, untracked] = await Promise.all([\n this.runGit(['diff', 'HEAD', '--name-only', '-z', '--'], this.projectRoot),\n this.runGit(['ls-files', '--others', '--exclude-standard', '-z'], this.projectRoot),\n ]);\n if (\n tracked.code !== 0 ||\n untracked.code !== 0 ||\n tracked.stdoutTruncated ||\n untracked.stdoutTruncated\n ) {\n return undefined;\n }\n\n const relativePaths = [\n ...new Set([...parseNulPaths(tracked.stdout), ...parseNulPaths(untracked.stdout)]),\n ]\n // Never recursively capture WrongStack's own allocated checkouts, even\n // if a repository accidentally tracks that administrative directory.\n .filter((relative) => !isWrongStackWorktreePath(relative))\n .sort();\n const unresolved: WorkspaceCheckpointManifest['unresolved'] = [];\n let scheduledBytes = 0;\n const captured = await mapWithConcurrency(\n relativePaths,\n CAPTURE_CONCURRENCY,\n async (relative): Promise<WorkspaceCheckpointEntry | null> => {\n const absolute = path.resolve(this.projectRoot, ...relative.split('/'));\n if (!isInside(this.projectRoot, absolute)) {\n unresolved.push({ path: relative, reason: 'path escapes project root' });\n return null;\n }\n try {\n const stat = await fsp.lstat(absolute);\n if (stat.isSymbolicLink()) {\n const linkTarget = await fsp.readlink(absolute);\n const resolvedLink = path.resolve(path.dirname(absolute), linkTarget);\n if (path.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {\n unresolved.push({\n path: relative,\n reason: 'symlink target escapes project root',\n });\n return null;\n }\n return { path: relative, state: 'symlink', linkTarget };\n }\n if (!stat.isFile()) {\n unresolved.push({ path: relative, reason: 'changed path is not a regular file' });\n return null;\n }\n if (stat.size > MAX_BLOB_BYTES) {\n unresolved.push({\n path: relative,\n reason: `file exceeds ${MAX_BLOB_BYTES}-byte checkpoint blob limit`,\n });\n return null;\n }\n if (scheduledBytes + stat.size > MAX_CHECKPOINT_BYTES) {\n unresolved.push({\n path: relative,\n reason: `checkpoint exceeds ${MAX_CHECKPOINT_BYTES}-byte aggregate blob limit`,\n });\n return null;\n }\n scheduledBytes += stat.size;\n const content = await fsp.readFile(absolute);\n const blobHash = sha256(content);\n await this.putBlob(blobHash, content);\n return { path: relative, state: 'file', blobHash, mode: stat.mode & 0o777 };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return { path: relative, state: 'absent' };\n }\n unresolved.push({ path: relative, reason: toErrorMessage(err) });\n return null;\n }\n },\n );\n\n const capturedAt = new Date().toISOString();\n const manifest: WorkspaceCheckpointManifest = {\n version: 1,\n baseHead: baseHead.toLowerCase(),\n coverage: 'git-head-plus-dirty',\n entries: captured.filter((entry): entry is WorkspaceCheckpointEntry => entry !== null),\n unresolved: [...unresolved].sort((a, b) => a.path.localeCompare(b.path)),\n };\n const encoded = JSON.stringify(manifest);\n const manifestHash = sha256(encoded);\n await this.putManifest(manifestHash, encoded);\n return {\n manifestHash,\n baseHead: manifest.baseHead,\n entryCount: manifest.entries.length,\n unresolvedCount: manifest.unresolved.length,\n capturedAt,\n coverage: manifest.coverage,\n };\n }\n\n async materialize(\n checkpoint: WorkspaceCheckpointRef,\n targetRoot: string,\n ): Promise<WorkspaceMaterializationResult> {\n const target = path.resolve(targetRoot);\n if (target === this.projectRoot) {\n throw new Error('Refusing to materialize a workspace checkpoint over the parent project root');\n }\n const targetStat = await fsp.stat(target);\n if (!targetStat.isDirectory()) throw new Error(`Checkpoint target is not a directory: ${target}`);\n\n const manifest = await this.loadManifest(checkpoint.manifestHash);\n if (\n manifest.baseHead !== checkpoint.baseHead.toLowerCase() ||\n manifest.coverage !== checkpoint.coverage ||\n manifest.entries.length !== checkpoint.entryCount ||\n manifest.unresolved.length !== checkpoint.unresolvedCount\n ) {\n throw new Error('Workspace checkpoint reference does not match its CAS manifest');\n }\n if (manifest.unresolved.length > 0) {\n throw new Error(\n `Workspace checkpoint has ${manifest.unresolved.length} unresolved path(s); exact materialization refused`,\n );\n }\n const targetHead = await this.runGit(['rev-parse', '--verify', 'HEAD'], target);\n if (\n targetHead.code !== 0 ||\n targetHead.stdoutTruncated ||\n targetHead.stdout.trim().toLowerCase() !== manifest.baseHead\n ) {\n throw new Error(\n `Checkpoint target HEAD must equal ${manifest.baseHead}; got ${targetHead.stdout.trim() || 'unknown'}`,\n );\n }\n const targetStatus = await this.runGit(\n ['status', '--porcelain=v1', '-z', '--untracked-files=all', '--ignored=matching'],\n target,\n );\n if (targetStatus.code !== 0 || targetStatus.stdoutTruncated) {\n throw new Error('Unable to verify that the checkpoint target is clean');\n }\n if (targetStatus.stdout.length > 0) {\n throw new Error('Checkpoint target must be a clean checkout before materialization');\n }\n\n const realTarget = await fsp.realpath(target);\n const writtenFiles: string[] = [];\n const deletedFiles: string[] = [];\n const errors: string[] = [];\n const prepared: Array<{\n entry: WorkspaceCheckpointEntry;\n output: string;\n content?: Buffer | undefined;\n }> = [];\n for (const entry of manifest.entries) {\n try {\n const output = await this.safeOutputPath(target, realTarget, entry.path);\n if (entry.state === 'symlink') {\n if (path.isAbsolute(entry.linkTarget)) {\n throw new Error('absolute symlink target refused');\n }\n const resolvedLink = path.resolve(path.dirname(output), entry.linkTarget);\n if (!isInside(target, resolvedLink)) throw new Error('symlink target escapes checkpoint root');\n }\n prepared.push({\n entry,\n output,\n ...(entry.state === 'file' ? { content: await this.readBlob(entry.blobHash) } : {}),\n });\n } catch (err) {\n errors.push(`${entry.path}: ${toErrorMessage(err)}`);\n }\n }\n // Integrity and path validation happen before the first mutation. This is\n // not a cross-file filesystem transaction, but corrupt input cannot leave\n // an otherwise clean target half-applied.\n if (errors.length > 0) {\n return { targetRoot: target, writtenFiles, deletedFiles, errors };\n }\n\n for (const { entry, output, content } of prepared) {\n try {\n if (entry.state === 'absent') {\n await fsp.unlink(output).catch((err) => {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;\n });\n deletedFiles.push(entry.path);\n continue;\n }\n if (entry.state === 'symlink') {\n await fsp.unlink(output).catch((err) => {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;\n });\n await fsp.mkdir(path.dirname(output), { recursive: true });\n await fsp.symlink(entry.linkTarget, output);\n writtenFiles.push(entry.path);\n continue;\n }\n if (!content) throw new Error('CAS blob was not prepared');\n await atomicWrite(output, content, { mode: entry.mode });\n await fsp.chmod(output, entry.mode).catch(() => undefined);\n writtenFiles.push(entry.path);\n } catch (err) {\n errors.push(`${entry.path}: ${toErrorMessage(err)}`);\n }\n }\n return { targetRoot: target, writtenFiles, deletedFiles, errors };\n }\n\n private objectPath(hash: string): string {\n if (!HASH_RE.test(hash)) throw new Error(`Invalid CAS object hash: ${hash}`);\n return path.join(this.rootDir, 'objects', hash.slice(0, 2), hash.slice(2));\n }\n\n private manifestPath(hash: string): string {\n if (!HASH_RE.test(hash)) throw new Error(`Invalid checkpoint manifest hash: ${hash}`);\n return path.join(this.rootDir, 'manifests', `${hash}.json`);\n }\n\n private async putBlob(hash: string, content: Buffer): Promise<void> {\n const target = this.objectPath(hash);\n await fsp.mkdir(path.dirname(target), { recursive: true });\n try {\n const existing = await fsp.readFile(target);\n if (sha256(existing) !== hash) throw new Error(`Corrupt CAS object collision: ${hash}`);\n return;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;\n }\n\n // Publish only a fully-written object. Creating the final path first would\n // expose a partial blob to another process (or leave one after a crash).\n // A same-directory hard link is an atomic create-if-absent operation.\n const temp = path.join(\n path.dirname(target),\n `.${path.basename(target)}.${process.pid}.${randomUUID()}.tmp`,\n );\n let handle: fsp.FileHandle | undefined;\n try {\n handle = await fsp.open(temp, 'wx', 0o600);\n await handle.writeFile(content);\n await handle.sync();\n await handle.close();\n handle = undefined;\n try {\n await fsp.link(temp, target);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;\n const existing = await fsp.readFile(target);\n if (sha256(existing) !== hash) throw new Error(`Corrupt CAS object collision: ${hash}`);\n }\n } finally {\n await handle?.close().catch(() => undefined);\n await fsp.unlink(temp).catch(() => undefined);\n }\n }\n\n private async putManifest(hash: string, encoded: string): Promise<void> {\n const target = this.manifestPath(hash);\n const existing = await fsp.readFile(target).catch(() => null);\n if (existing) {\n if (sha256(existing) !== hash) throw new Error(`Corrupt checkpoint manifest collision: ${hash}`);\n return;\n }\n await atomicWrite(target, encoded, { mode: 0o600 });\n }\n\n private async readBlob(hash: string): Promise<Buffer> {\n const content = await fsp.readFile(this.objectPath(hash));\n if (sha256(content) !== hash) throw new Error(`CAS blob integrity check failed: ${hash}`);\n return content;\n }\n\n private async loadManifest(hash: string): Promise<WorkspaceCheckpointManifest> {\n const raw = await fsp.readFile(this.manifestPath(hash));\n if (sha256(raw) !== hash) throw new Error(`Checkpoint manifest integrity check failed: ${hash}`);\n const parsed = JSON.parse(raw.toString('utf8')) as Partial<WorkspaceCheckpointManifest>;\n if (\n parsed.version !== 1 ||\n typeof parsed.baseHead !== 'string' ||\n parsed.coverage !== 'git-head-plus-dirty' ||\n !Array.isArray(parsed.entries) ||\n !Array.isArray(parsed.unresolved)\n ) {\n throw new Error(`Invalid workspace checkpoint manifest: ${hash}`);\n }\n const entriesValid = parsed.entries.every((entry) => {\n if (!entry || typeof entry !== 'object') return false;\n const candidate = entry as Partial<WorkspaceCheckpointEntry>;\n if (typeof candidate.path !== 'string' || !normalizeRelative(candidate.path)) return false;\n if (candidate.state === 'absent') return true;\n if (candidate.state === 'symlink') return typeof candidate.linkTarget === 'string';\n return (\n candidate.state === 'file' &&\n typeof candidate.blobHash === 'string' &&\n HASH_RE.test(candidate.blobHash) &&\n typeof candidate.mode === 'number' &&\n Number.isInteger(candidate.mode) &&\n candidate.mode >= 0 &&\n candidate.mode <= 0o777\n );\n });\n const unresolvedValid = parsed.unresolved.every(\n (entry) =>\n entry !== null &&\n typeof entry === 'object' &&\n typeof entry.path === 'string' &&\n typeof entry.reason === 'string',\n );\n if (!entriesValid || !unresolvedValid) {\n throw new Error(`Invalid workspace checkpoint manifest entries: ${hash}`);\n }\n return parsed as WorkspaceCheckpointManifest;\n }\n\n private async safeOutputPath(target: string, realTarget: string, relative: string): Promise<string> {\n const normalized = normalizeRelative(relative);\n if (!normalized) throw new Error('invalid relative path');\n const output = path.resolve(target, ...normalized.split('/'));\n if (!isInside(target, output)) throw new Error('path escapes checkpoint target');\n\n let probe = output;\n for (;;) {\n try {\n const real = await fsp.realpath(probe);\n if (!isInside(realTarget, real)) throw new Error('path resolves through a symlink outside checkpoint target');\n return output;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;\n const parent = path.dirname(probe);\n if (parent === probe) throw err;\n probe = parent;\n }\n }\n }\n}\n\nfunction defaultRunGit(args: string[], cwd: string): Promise<CheckpointGitResult> {\n return new Promise((resolve) => {\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let stdoutBytes = 0;\n let stderrBytes = 0;\n let stdoutTruncated = false;\n let stderrTruncated = false;\n const child = spawn('git', args, {\n cwd,\n env: buildChildEnv(),\n stdio: ['ignore', 'pipe', 'pipe'],\n signal: AbortSignal.timeout(30_000),\n windowsHide: true,\n });\n child.stdout?.on('data', (chunk: Buffer) => {\n const remaining = MAX_GIT_OUTPUT - stdoutBytes;\n if (remaining <= 0) {\n stdoutTruncated = true;\n return;\n }\n const kept = chunk.subarray(0, remaining);\n stdoutChunks.push(kept);\n stdoutBytes += kept.length;\n if (kept.length < chunk.length) stdoutTruncated = true;\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n const remaining = MAX_GIT_OUTPUT - stderrBytes;\n if (remaining <= 0) {\n stderrTruncated = true;\n return;\n }\n const kept = chunk.subarray(0, remaining);\n stderrChunks.push(kept);\n stderrBytes += kept.length;\n if (kept.length < chunk.length) stderrTruncated = true;\n });\n const result = (code: number, extraStderr?: string): CheckpointGitResult => ({\n code,\n stdout: Buffer.concat(stdoutChunks).toString('utf8'),\n stderr: `${Buffer.concat(stderrChunks).toString('utf8')}${extraStderr ?? ''}`,\n stdoutTruncated,\n stderrTruncated,\n });\n child.on('error', (err) => resolve(result(1, err.message)));\n child.on('close', (code) => resolve(result(code ?? 1)));\n });\n}\n", "export async function mapWithConcurrency<T, R>(\n items: readonly T[],\n concurrency: number,\n mapper: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n if (items.length === 0) return [];\n const limit = Math.max(1, Math.floor(concurrency));\n const results = new Array<R>(items.length);\n let next = 0;\n\n async function worker(): Promise<void> {\n while (true) {\n const index = next;\n next++;\n if (index >= items.length) return;\n results[index] = await mapper(items[index] as T, index);\n }\n }\n\n const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());\n await Promise.all(workers);\n return results;\n}\n", "import { createHash } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type {\n ResumeFileValidationEntry,\n ResumeValidation,\n SessionEvent,\n} from '../types/session.js';\nimport { toErrorMessage } from '../utils/index.js';\nimport { mapWithConcurrency } from './storage-concurrency.js';\n\nconst MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;\nconst VALIDATION_CONCURRENCY = 8;\nconst NOTICE_PATH_LIMIT = 20;\n\ninterface FileObservation {\n path: string;\n hash: string;\n ts: string;\n}\n\nfunction isInside(root: string, target: string): boolean {\n const relative = path.relative(root, target);\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction errno(err: unknown): string | undefined {\n return err && typeof err === 'object' && 'code' in err\n ? String((err as NodeJS.ErrnoException).code)\n : undefined;\n}\n\nfunction latestObservations(events: readonly SessionEvent[], projectRoot: string): FileObservation[] {\n const latest = new Map<string, FileObservation>();\n for (const event of events) {\n if (\n event.type !== 'file_observation' ||\n typeof event.path !== 'string' ||\n event.path.length === 0 ||\n typeof event.hash !== 'string' ||\n !/^[a-f\\d]{64}$/i.test(event.hash)\n ) {\n continue;\n }\n const normalized = path.resolve(projectRoot, event.path);\n latest.set(normalized, {\n path: normalized,\n hash: event.hash.toLowerCase(),\n ts: event.ts,\n });\n }\n return [...latest.values()];\n}\n\nasync function validateOne(\n observation: FileObservation,\n lexicalRoot: string,\n realRoot: string,\n): Promise<ResumeFileValidationEntry | null> {\n const base = {\n path: observation.path,\n observedAt: observation.ts,\n expectedHash: observation.hash,\n };\n\n if (!isInside(lexicalRoot, observation.path)) {\n return {\n ...base,\n status: 'outside_project',\n detail: 'Recorded path is outside the active project root.',\n };\n }\n\n let realFile: string;\n try {\n realFile = await fsp.realpath(observation.path);\n } catch (err) {\n if (errno(err) === 'ENOENT') return { ...base, status: 'deleted' };\n return { ...base, status: 'unreadable', detail: toErrorMessage(err) };\n }\n\n if (!isInside(realRoot, realFile)) {\n return {\n ...base,\n status: 'outside_project',\n detail: 'Recorded path resolves through a symlink outside the active project root.',\n };\n }\n\n try {\n const stat = await fsp.stat(realFile);\n if (!stat.isFile()) {\n return { ...base, status: 'unreadable', detail: 'Path is no longer a regular file.' };\n }\n if (stat.size > MAX_REVALIDATE_BYTES) {\n return {\n ...base,\n status: 'unreadable',\n detail: `File now exceeds the ${MAX_REVALIDATE_BYTES}-byte validation limit.`,\n };\n }\n // File tools persist sha256(UTF-8 text), so validation deliberately uses\n // the same decoding/hash semantics rather than hashing raw bytes.\n const content = await fsp.readFile(realFile, 'utf8');\n const actualHash = createHash('sha256').update(content, 'utf8').digest('hex');\n if (actualHash === observation.hash) return null;\n return { ...base, status: 'modified', actualHash };\n } catch (err) {\n if (errno(err) === 'ENOENT') return { ...base, status: 'deleted' };\n return { ...base, status: 'unreadable', detail: toErrorMessage(err) };\n }\n}\n\n/** Revalidate the latest persisted hash for every distinct observed path. */\nexport async function validateResumeFileObservations(\n events: readonly SessionEvent[],\n projectRoot: string,\n): Promise<ResumeValidation> {\n const lexicalRoot = path.resolve(projectRoot);\n const realRoot = await fsp.realpath(lexicalRoot).catch(() => lexicalRoot);\n const observations = latestObservations(events, lexicalRoot);\n const results = await mapWithConcurrency(\n observations,\n VALIDATION_CONCURRENCY,\n (observation) => validateOne(observation, lexicalRoot, realRoot),\n );\n return {\n checkedAt: new Date().toISOString(),\n checkedFileCount: observations.length,\n staleFiles: results.filter((entry): entry is ResumeFileValidationEntry => entry !== null),\n };\n}\n\n/** Build the ephemeral system message injected into the first resumed turn. */\nexport function formatResumeValidationNotice(\n validation: ResumeValidation,\n projectRoot: string,\n): string | null {\n if (validation.staleFiles.length === 0) return null;\n const root = path.resolve(projectRoot);\n const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {\n const relative = path.relative(root, entry.path);\n const display = isInside(root, entry.path) ? relative || '.' : entry.path;\n return `- ${JSON.stringify(display)} [${entry.status}]`;\n });\n const omitted = validation.staleFiles.length - shown.length;\n if (omitted > 0) shown.push(`- ... and ${omitted} more stale path(s)`);\n\n return [\n '[SESSION RESUME FILE VALIDATION]',\n `Journal hash validation found ${validation.staleFiles.length} previously observed file(s) whose current state cannot be trusted.`,\n 'Prior tool results and reasoning based on these paths are stale. Re-read each relevant file before relying on it or editing it.',\n ...shown,\n ].join('\\n');\n}\n", "/**\n * Session ID generation \u2014 extracted from session-store.ts.\n *\n * Pure functions: no I/O, no class state, no side effects.\n * Safe to unit-test in isolation.\n */\nimport { ulid } from '../utils/ulid.js';\n\n/**\n * @deprecated Legacy helper kept for callers that still need filename-safe\n * labels. New session ids are opaque and do not include model/provider names.\n */\nexport function sanitizeModel(model: string): string {\n return model\n .replace(/[^a-zA-Z0-9_-]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n .slice(0, 40);\n}\n\n/**\n * Generate a session ID in the format:\n * `YYYY-MM-DD/sess_<ULID>`\n *\n * Examples:\n * `2026-06-06/sess_01JX2S9V7T5M6N7P8Q9R0STXVW`\n *\n * The date prefix becomes a subdirectory so sessions group naturally by day.\n * The leaf is an opaque sortable id; provider/model names belong in metadata,\n * not file paths. Older IDs that contain model/provider text remain readable.\n */\nexport function generateSessionId(startedAt: string, _model?: string): string {\n const date = startedAt.slice(0, 10); // \"2026-06-06\"\n const seedTime = Number.isNaN(Date.parse(startedAt)) ? Date.now() : Date.parse(startedAt);\n return `${date}/sess_${ulid(seedTime)}`;\n}\n", "import type { SessionSummary } from '../types/session.js';\n\nexport interface SessionFilterCriteria {\n since?: string | undefined;\n until?: string | undefined;\n provider?: string | undefined;\n model?: string | undefined;\n minTokens?: number | undefined;\n titleContains?: string | undefined;\n}\n\nexport function compareSessionSummaries(a: SessionSummary, b: SessionSummary): number {\n if (a.startedAt < b.startedAt) return 1;\n if (a.startedAt > b.startedAt) return -1;\n return a.id.localeCompare(b.id);\n}\n\nexport function matchesSessionFilter(summary: SessionSummary, criteria: SessionFilterCriteria): boolean {\n if (criteria.since && summary.startedAt < criteria.since) return false;\n if (criteria.until && summary.startedAt > criteria.until) return false;\n if (criteria.provider && summary.provider !== criteria.provider) return false;\n if (criteria.model && summary.model !== criteria.model) return false;\n if (criteria.minTokens !== undefined && summary.tokenTotal < criteria.minTokens) return false;\n if (criteria.titleContains) {\n const needle = criteria.titleContains.toLocaleLowerCase();\n if (!summary.title.toLocaleLowerCase().includes(needle)) return false;\n }\n return true;\n}\n", "import type { SessionData, SessionEvent } from '../types/session.js';\n\ntype ToolCallEnd = SessionData['toolCallEnds'][number];\n\nexport function extractToolCallEnds(events: readonly SessionEvent[]): ToolCallEnd[] {\n const out: ToolCallEnd[] = [];\n for (const event of events) {\n if (event.type !== 'tool_call_end') continue;\n out.push({\n name: event.name,\n id: event.id,\n durationMs: event.durationMs,\n ok: event.ok ?? true,\n outputBytes: event.outputBytes ?? event.outputSize,\n outputTokens: event.outputTokens,\n outputLines: event.outputLines,\n });\n }\n return out;\n}\n", "import * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { EventBus } from '../kernel/events.js';\nimport type { ContentBlock } from '../types/blocks.js';\nimport type { Logger } from '../types/logger.js';\nimport { atomicWrite } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/error.js';\n\n/**\n * The persisted form of a single queued user message. The TUI's\n * in-memory QueueItem has a render id; that's pure UI bookkeeping, so\n * we drop it when serializing \u2014 fresh ids are assigned on rehydrate.\n */\nexport interface PersistedQueueItem {\n displayText: string;\n blocks: ContentBlock[];\n}\n\n/**\n * Side-file storage for a session's pending input queue. Lives at\n * `<sessionDir>/queue.json` next to the attachment spool. Reads are\n * tolerant (missing/malformed file \u2192 empty array); writes are atomic\n * (tmp + rename) so a crash mid-write can never leave a partial file\n * the next launch would choke on.\n *\n * The contract is \"snapshot replacement\": every mutation hands the\n * full queue and we rewrite the file. The queue is small (rarely more\n * than a handful of messages), so this is cheaper than delta logging\n * and avoids the replay complexity.\n */\nexport class QueueStore {\n private readonly file: string;\n // Use `| undefined` (not `?`) so exactOptionalPropertyTypes doesn't\n // reject assigning an optional constructor parameter to these fields.\n private readonly events: EventBus | undefined;\n private readonly traceId: string | undefined;\n private readonly logger: Logger | undefined;\n\n constructor(opts: { dir: string; events?: EventBus; traceId?: string; logger?: Logger | undefined }) {\n this.file = path.join(opts.dir, 'queue.json');\n this.events = opts.events;\n this.traceId = opts.traceId;\n this.logger = opts.logger;\n }\n\n private logWarn(msg: string, ctx?: Record<string, unknown>): void {\n if (this.logger) {\n this.logger.warn(msg, ctx);\n } else {\n console.warn(JSON.stringify({ ...ctx, message: msg, timestamp: new Date().toISOString() }));\n }\n }\n\n async write(items: PersistedQueueItem[]): Promise<void> {\n const t0 = Date.now();\n if (items.length === 0) {\n // Empty queue \u2192 remove the file rather than write `[]`. Keeps\n // a clean idle state on disk and makes `read()` cheaper.\n await this.clear();\n return;\n }\n try {\n await atomicWrite(this.file, JSON.stringify(items), { mode: 0o600 });\n this.events?.emit('storage.write', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'write',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n } catch (err) {\n this.events?.emit('storage.error', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'write',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: false,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n this.logWarn('Queue store write failed', { event: 'queue_store.write_failed', path: this.file, message: toErrorMessage(err) });\n }\n }\n\n async read(): Promise<PersistedQueueItem[]> {\n const t0 = Date.now();\n let raw: string;\n try {\n raw = await fsp.readFile(this.file, 'utf8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') {\n this.events?.emit('storage.read', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'read',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n return [];\n }\n this.events?.emit('storage.error', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'read',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: true,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n this.logWarn('Queue store read failed', { event: 'queue_store.read_failed', path: this.file, message: toErrorMessage(err) });\n return [];\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n this.events?.emit('storage.read', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'read',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'parse_failed',\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n return [];\n }\n if (!Array.isArray(parsed)) {\n this.events?.emit('storage.read', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'read',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'invalid_schema',\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n return [];\n }\n this.events?.emit('storage.read', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'read',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n const out: PersistedQueueItem[] = [];\n for (const v of parsed) {\n if (isPersistedQueueItem(v)) out.push(v);\n }\n return out;\n }\n\n async clear(): Promise<void> {\n const t0 = Date.now();\n try {\n await fsp.unlink(this.file);\n this.events?.emit('storage.write', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'clear',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') return;\n this.events?.emit('storage.error', {\n sessionId: this.traceId ?? '~boot~',\n store: 'queue',\n filePath: this.file,\n operation: 'clear',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: true,\n ...(this.traceId !== undefined && { traceId: this.traceId }),\n });\n // Best-effort: a permission/lock error during clear is rare and\n // the queue slash command is non-critical. Warn so it's observable\n // but don't throw so the slash command doesn't crash.\n this.logWarn('Queue store clear failed', { event: 'queue_store.clear_failed', path: this.file, message: (err as Error).message });\n }\n }\n}\n\nfunction isPersistedQueueItem(v: unknown): v is PersistedQueueItem {\n if (typeof v !== 'object' || v === null) return false;\n const o = v as Record<string, unknown>;\n return typeof o['displayText'] === 'string' && Array.isArray(o['blocks']);\n}\n", "import { randomBytes } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { atomicWrite } from '../utils/atomic-write.js';\nimport type {\n AddAttachmentInput,\n Attachment,\n AttachmentKind,\n AttachmentRef,\n AttachmentStore,\n} from '../types/attachment.js';\nimport type { ContentBlock } from '../types/blocks.js';\n\nexport interface AttachmentStoreOptions {\n /**\n * Directory for spooling payloads larger than `spoolThresholdBytes`.\n * When omitted, all payloads stay in memory.\n */\n spoolDir?: string | undefined;\n spoolThresholdBytes?: number | undefined;\n}\n\nconst DEFAULT_SPOOL_THRESHOLD = 256 * 1024; // 256 KB\n// Two placeholder shapes:\n// - seq-keyed `[<kind> #<seq>\u2026]` \u2014 kind is `pasted` / `image` / `file`. A\n// cosmetic suffix after the seq (e.g. `, 123 lines`) is tolerated so the\n// TUI can render `[pasted #1, 123 lines]` while still resolving by seq.\n// - path-keyed `[file:<path>]` \u2014 resolves to the most recent file ref whose\n// stored path matches, so the TUI can show a human-readable file chip.\nconst PLACEHOLDER_RE = /\\[(pasted|image|file) #(\\d+)[^\\]]*\\]|\\[file:([^\\]]+)\\]/g;\n\n/**\n * In-memory attachment store with optional disk spool. Placeholder syntax\n * is `[<kind> #<seq>]` (seq-keyed) or `[file:<path>]` (path-keyed) where kind\n * is `pasted` / `image` / `file`. Unknown placeholders are passed through\n * as-is so users can write that literal text without losing it.\n */\nexport class DefaultAttachmentStore implements AttachmentStore {\n private readonly items = new Map<string, Attachment>();\n private readonly refs: AttachmentRef[] = [];\n private nextSeq: Record<AttachmentKind, number> = { text: 0, image: 0, file: 0 };\n private readonly spoolDir: string | undefined;\n private readonly spoolThreshold: number;\n\n constructor(opts: AttachmentStoreOptions = {}) {\n this.spoolDir = opts.spoolDir;\n this.spoolThreshold = opts.spoolThresholdBytes ?? DEFAULT_SPOOL_THRESHOLD;\n }\n\n async add(input: AddAttachmentInput): Promise<AttachmentRef> {\n const seq = ++this.nextSeq[input.kind];\n const id = `${kindPrefix(input.kind)}-${seq}-${randomBytes(3).toString('hex')}`;\n const bytes = Buffer.byteLength(input.data, input.kind === 'image' ? 'base64' : 'utf8');\n let spooledPath: string | undefined;\n let data: string | undefined = input.data;\n if (this.spoolDir && bytes >= this.spoolThreshold) {\n await fsp.mkdir(this.spoolDir, { recursive: true });\n spooledPath = path.join(this.spoolDir, `${id}.bin`);\n // atomicWrite: torn spool would silently corrupt the attachment;\n // the user would see garbled output the next time it's expanded.\n await atomicWrite(spooledPath, input.data, {\n encoding: input.kind === 'image' ? 'base64' : 'utf8',\n });\n data = undefined;\n }\n const att: Attachment = {\n id,\n kind: input.kind,\n meta: input.meta ?? {},\n data,\n path: spooledPath,\n bytes,\n createdAt: new Date().toISOString(),\n };\n this.items.set(id, att);\n const ref: AttachmentRef = { id, kind: input.kind, seq, meta: att.meta };\n this.refs.push(ref);\n return ref;\n }\n\n async get(id: string): Promise<Attachment | undefined> {\n return this.items.get(id);\n }\n\n list(): AttachmentRef[] {\n return [...this.refs];\n }\n\n async expand(text: string): Promise<ContentBlock[]> {\n const matches = [...text.matchAll(PLACEHOLDER_RE)];\n if (matches.length === 0) return text ? [{ type: 'text', text }] : [];\n const blocks: ContentBlock[] = [];\n let lastIndex = 0;\n for (const m of matches) {\n const idx = m.index ?? 0;\n const before = text.slice(lastIndex, idx);\n if (before) blocks.push({ type: 'text', text: before });\n let ref: AttachmentRef | undefined;\n if (m[3] !== undefined) {\n // Path-keyed `[file:<path>]` \u2014 most recent matching file ref wins.\n const wantPath = m[3];\n ref = findLast(this.refs, (r) => r.kind === 'file' && refPath(r) === wantPath);\n } else {\n const kind = prefixToKind(m[1] as string);\n const seq = Number(m[2]);\n ref = this.refs.find((r) => r.kind === kind && r.seq === seq);\n }\n const att = ref ? this.items.get(ref.id) : undefined;\n if (!att) {\n blocks.push({ type: 'text', text: m[0] });\n } else {\n blocks.push(await this.toBlock(att));\n }\n lastIndex = idx + m[0].length;\n }\n const tail = text.slice(lastIndex);\n if (tail) blocks.push({ type: 'text', text: tail });\n return mergeAdjacentText(blocks);\n }\n\n async clear(): Promise<void> {\n // Unlink any spooled files so we don't leak disk space.\n if (this.spoolDir) {\n const toDelete: string[] = [];\n for (const att of this.items.values()) {\n if (att.path) toDelete.push(att.path);\n }\n /* v8 ignore next -- best-effort: unlink of a just-spooled file does not reject */\n await Promise.all(toDelete.map((p) => fsp.unlink(p).catch(() => undefined)));\n }\n this.items.clear();\n this.refs.length = 0;\n this.nextSeq = { text: 0, image: 0, file: 0 };\n }\n\n private async toBlock(att: Attachment): Promise<ContentBlock> {\n if (att.kind === 'image') {\n const data =\n att.data ?? (att.path ? await fsp.readFile(att.path, { encoding: 'base64' }) : '');\n return {\n type: 'image',\n source: {\n type: 'base64',\n media_type: att.meta.mediaType ?? 'image/png',\n data,\n },\n };\n }\n const raw = att.data ?? (att.path ? await fsp.readFile(att.path, 'utf8') : '');\n const label = att.meta.filename ? `<file path=\"${att.meta.filename}\">` : '<pasted>';\n const close = att.meta.filename ? '</file>' : '</pasted>';\n return { type: 'text', text: `${label}\\n${raw}\\n${close}` };\n }\n}\n\nfunction kindPrefix(kind: AttachmentKind): string {\n return kind === 'text' ? 'pasted' : kind;\n}\n\nfunction prefixToKind(prefix: string): AttachmentKind {\n if (prefix === 'pasted') return 'text';\n if (prefix === 'image') return 'image';\n return 'file';\n}\n\n/** Path a file ref was registered under, for `[file:<path>]` lookup. */\nfunction refPath(ref: AttachmentRef): string | undefined {\n return ref.meta.filename ?? ref.meta.label;\n}\n\n/** Last element matching the predicate (Node < 20 lacks Array.findLast). */\nfunction findLast<T>(arr: readonly T[], pred: (v: T) => boolean): T | undefined {\n for (let i = arr.length - 1; i >= 0; i--) {\n if (pred(arr[i] as T)) return arr[i];\n }\n return undefined;\n}\n\nfunction mergeAdjacentText(blocks: ContentBlock[]): ContentBlock[] {\n const out: ContentBlock[] = [];\n for (const b of blocks) {\n const prev = out[out.length - 1];\n if (b.type === 'text' && prev && prev.type === 'text') {\n prev.text += b.text;\n } else {\n out.push(b);\n }\n }\n return out;\n}\n", "import { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { MemoryEntry, MemoryScope } from '../types/memory.js';\nimport { MEMORY_TYPE_LABELS, type MemoryPriority, type MemoryType } from '../types/memory.js';\nimport { atomicWrite, ensureDir, withFileLock } from '../utils/atomic-write.js';\nimport type { WstackPaths } from '../utils/wstack-paths.js';\n\n// \u2500\u2500 Backend interface \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface MemoryBackend {\n readonly kind: string;\n remember(scope: MemoryScope, entry: MemoryEntry, filePath: string): Promise<void>;\n forget(scope: MemoryScope, query: string, filePath: string): Promise<number>;\n readAll(scope: MemoryScope, filePath: string): Promise<string>;\n list(scope: MemoryScope, filePath: string, limit?: number | undefined): Promise<MemoryEntry[]>;\n search(scope: MemoryScope, query: string, filePath: string, limit?: number | undefined): Promise<MemoryEntry[]>;\n /** Find memories related to the given text via graph traversal. Optional \u2014 falls back to search. */\n findRelated?(scope: MemoryScope, filePath: string, text: string, limit: number): Promise<MemoryEntry[]>;\n clear(scope: MemoryScope, filePath: string): Promise<void>;\n consolidate(scope: MemoryScope, filePath: string): Promise<number>;\n}\n\n// \u2500\u2500 Entry serialization format \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Full format:\n// - [ISO] [TYPE|PRIORITY] mem_<id> text content #tag1 #tag2\n//\n// Examples:\n// - [2026-06-07T...] mem_1234_abcd Project uses pnpm #pnpm #build\n// - [2026-06-07T...] [convention|high] mem_5678_ef01 Use conventional commits #git #commit\n//\n// Old format (backward compatible):\n// - [ISO] mem_<id> text content\n\nconst TYPE_PRIORITY_RE = /^\\[(\\w+)\\|(\\w+)\\]\\s+/;\nconst TAG_RE = /#([\\w-]+)/g;\nconst MAX_MEMORY_CONSOLIDATE_BACKUPS = 5;\n\nfunction formatMetadata(entry: MemoryEntry): string {\n const parts: string[] = [];\n if (entry.type && entry.priority) {\n parts.push(`[${entry.type}|${entry.priority}]`);\n } else if (entry.type) {\n parts.push(`[${entry.type}]`);\n } else if (entry.priority) {\n parts.push(`[${entry.priority}]`);\n }\n if (entry.tags && entry.tags.length > 0) {\n parts.push(entry.tags.map((t) => `#${t}`).join(' '));\n }\n return parts.length > 0 ? ` ${parts.join(' ')}` : '';\n}\n\nfunction lineToEntry(line: string, scope: MemoryScope): MemoryEntry | null {\n const trimmed = line.trim();\n if (!trimmed.startsWith('- [')) return null;\n\n // Parse timestamp: `- [ISO] ...`\n const tsMatch = trimmed.match(/^-\\s*\\[([^\\]]+)\\]/);\n if (!tsMatch) return null;\n const ts = tsMatch[1] ?? '';\n\n let rest = trimmed.slice(tsMatch[0].length).trim();\n\n // Parse optional type|priority: `[convention|high]` or `[fact]`\n let type: MemoryType | undefined;\n let priority: MemoryPriority | undefined;\n const tpMatch = rest.match(TYPE_PRIORITY_RE);\n if (tpMatch) {\n const a = tpMatch[1] ?? '';\n const b = tpMatch[2] ?? '';\n if (isMemoryType(a)) {\n type = a;\n priority = isPriority(b) ? b : undefined;\n } else if (isPriority(a)) {\n priority = a;\n }\n rest = rest.slice(tpMatch[0].length).trim();\n }\n\n // Parse optional entry ID: `mem_<ts>_<rand>`\n const idMatch = rest.match(/^mem_\\d+_\\w+\\s+/);\n let text: string;\n if (idMatch) {\n text = rest.slice(idMatch[0].length).trim();\n } else {\n text = rest.trim();\n }\n\n // Extract #tags from text\n const tags: string[] = [];\n let tagMatch: RegExpExecArray | null;\n TAG_RE.lastIndex = 0;\n // biome-ignore lint/suspicious/noAssignInExpressions: standard regex loop\n while ((tagMatch = TAG_RE.exec(text)) !== null) {\n tags.push(tagMatch[1] ?? '');\n }\n // Remove tags from display text\n const cleanText = text.replace(TAG_RE, '').replace(/\\s{2,}/g, ' ').trim();\n\n if (!cleanText) return null;\n\n return {\n scope,\n text: cleanText,\n ts,\n type,\n priority,\n tags: tags.length > 0 ? tags : undefined,\n };\n}\n\nfunction isMemoryType(s: string): s is MemoryType {\n return s in MEMORY_TYPE_LABELS;\n}\n\nfunction isPriority(s: string): s is MemoryPriority {\n return s === 'critical' || s === 'high' || s === 'medium' || s === 'low';\n}\n\n// \u2500\u2500 Inverted index for fast search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface IndexedEntry {\n entry: MemoryEntry;\n /** Lower-cased words extracted from text. */\n words: string[];\n /** Lower-cased tags. */\n tags: string[];\n}\n\ninterface InvertedIndex {\n /** word -> entry indices */\n wordMap: Map<string, number[]>;\n /** tag -> entry indices */\n tagMap: Map<string, number[]>;\n entries: IndexedEntry[];\n /** Last known file mtime for cache invalidation. */\n mtimeMs: number;\n}\n\n// Exported for the perf microbenchmark (tests/perf/memory-search.bench.ts),\n// which exercises the O(1) exact-lookup fast path against a large vocabulary.\n// Not part of the public API surface \u2014 internal to memory search.\nexport function buildInvertedIndex(entries: MemoryEntry[]): InvertedIndex {\n const wordMap = new Map<string, number[]>();\n const tagMap = new Map<string, number[]>();\n const indexed: IndexedEntry[] = new Array(entries.length);\n\n for (let i = 0; i < entries.length; i++) {\n const e = entries[i]!;\n const words = e.text.toLowerCase().split(/\\s+/).filter((w) => w.length > 0);\n const tags = (e.tags ?? []).map((t) => t.toLowerCase());\n indexed[i] = { entry: e, words, tags };\n\n for (const w of words) {\n const arr = wordMap.get(w);\n if (arr) arr.push(i);\n else wordMap.set(w, [i]);\n }\n for (const t of tags) {\n const arr = tagMap.get(t);\n if (arr) arr.push(i);\n else tagMap.set(t, [i]);\n }\n }\n\n return { wordMap, tagMap, entries: indexed, mtimeMs: 0 };\n}\n\n/**\n * Needles shorter than this skip the O(vocabulary) substring fallback. A\n * 1\u20132 char needle (e.g. \"a\", \"go\") otherwise forces a full-vocabulary walk\n * AND matches almost everything via `n.includes(word)`, so it's both the\n * slowest and least selective case. Exact-match lookups still run for any\n * length, so short whole-word queries (\"go\", \"ci\") keep working.\n */\nconst MIN_SUBSTRING_NEEDLE_LEN = 3;\n\n// Exported alongside buildInvertedIndex for the perf microbenchmark.\nexport function searchIndex(\n index: InvertedIndex,\n query: string,\n limit?: number,\n): MemoryEntry[] {\n const needles = query.toLowerCase().split(/\\s+/).filter((n) => n.length > 0);\n if (needles.length === 0) return [];\n\n const scores = new Map<number, number>();\n\n for (const n of needles) {\n // Fast path: exact word/tag hit via O(1) Map.get. This is the dominant\n // case (whole-word queries) and avoids walking the whole vocabulary.\n let matched = false;\n\n const wordExact = index.wordMap.get(n);\n if (wordExact) {\n for (const idx of wordExact) scores.set(idx, (scores.get(idx) ?? 0) + 1);\n matched = true;\n }\n const tagExact = index.tagMap.get(n);\n if (tagExact) {\n for (const idx of tagExact) scores.set(idx, (scores.get(idx) ?? 0) + 2);\n matched = true;\n }\n\n // Bounded substring fallback: only when the needle had no exact hit and is\n // long enough to be selective. Preserves partial-match recall (\"perf\" \u2192\n // \"performance\", \"rebuilding\" \u2192 stored word \"build\") without paying the\n // full-vocabulary scan on every whole-word query.\n if (matched || n.length < MIN_SUBSTRING_NEEDLE_LEN) continue;\n\n for (const [word, indices] of index.wordMap) {\n if (word.includes(n) || n.includes(word)) {\n for (const idx of indices) {\n scores.set(idx, (scores.get(idx) ?? 0) + 1);\n }\n }\n }\n for (const [tag, indices] of index.tagMap) {\n if (tag.includes(n) || n.includes(tag)) {\n for (const idx of indices) {\n scores.set(idx, (scores.get(idx) ?? 0) + 2);\n }\n }\n }\n }\n\n if (scores.size === 0) return [];\n\n const scored = Array.from(scores.entries());\n scored.sort((a, b) => b[1] - a[1]);\n\n const result: MemoryEntry[] = [];\n const max = limit ? Math.min(limit, scored.length) : scored.length;\n for (let i = 0; i < max; i++) {\n result.push(index.entries[scored[i]![0]]!.entry);\n }\n return result;\n}\n\n// \u2500\u2500 File-based backend \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface FileMemoryBackendOptions {\n paths: WstackPaths;\n}\n\nexport class FileMemoryBackend implements MemoryBackend {\n readonly kind = 'file';\n private readonly files: Record<MemoryScope, string>;\n /** Cache of parsed entries per file path. */\n private readonly entryCache = new Map<string, MemoryEntry[]>();\n /** Inverted index per file path. */\n private readonly indexCache = new Map<string, InvertedIndex>();\n /** File mtime cache for invalidation. */\n private readonly mtimeCache = new Map<string, number>();\n\n constructor(opts: FileMemoryBackendOptions) {\n this.files = {\n 'project-agents': opts.paths.inProjectAgentsFile,\n 'project-memory': opts.paths.projectMemory,\n 'user-memory': opts.paths.globalMemory,\n };\n }\n\n private resolveFile(filePath: string, scope: MemoryScope): string {\n return filePath || this.files[scope];\n }\n\n private async getMtime(file: string): Promise<number> {\n try {\n const stat = await fs.stat(file);\n return stat.mtimeMs;\n } catch {\n return 0;\n }\n }\n\n private invalidateCache(file: string): void {\n this.entryCache.delete(file);\n this.indexCache.delete(file);\n this.mtimeCache.delete(file);\n }\n\n /**\n * Load (and cache) the parsed entries for a file. Callers that have already\n * stat'd the file this tick (e.g. `getIndex`) can pass the known `mtime` to\n * avoid a redundant `fs.stat` \u2014 otherwise it's fetched here.\n */\n private async loadEntries(\n file: string,\n scope: MemoryScope,\n mtime?: number,\n ): Promise<MemoryEntry[]> {\n const resolvedMtime = mtime ?? (await this.getMtime(file));\n const cachedMtime = this.mtimeCache.get(file);\n if (cachedMtime === resolvedMtime && this.entryCache.has(file)) {\n return this.entryCache.get(file)!;\n }\n\n const raw = await this.readAll(scope, file);\n if (!raw.trim()) {\n this.entryCache.set(file, []);\n this.mtimeCache.set(file, resolvedMtime);\n return [];\n }\n\n const entries = parseEntries(raw, scope);\n this.entryCache.set(file, entries);\n this.mtimeCache.set(file, resolvedMtime);\n return entries;\n }\n\n private async getIndex(file: string, scope: MemoryScope): Promise<InvertedIndex> {\n const mtime = await this.getMtime(file);\n const cached = this.indexCache.get(file);\n if (cached && cached.mtimeMs === mtime) {\n return cached;\n }\n\n // Reuse the mtime we just fetched \u2014 loadEntries would otherwise stat again.\n const entries = await this.loadEntries(file, scope, mtime);\n const index = buildInvertedIndex(entries);\n index.mtimeMs = mtime;\n this.indexCache.set(file, index);\n return index;\n }\n\n async remember(scope: MemoryScope, entry: MemoryEntry, filePath: string): Promise<void> {\n const file = this.resolveFile(filePath, scope);\n await ensureDir(path.dirname(file));\n let existing = '';\n try { existing = await fs.readFile(file, 'utf8'); } catch { /* new file */ }\n\n const id = `mem_${Date.now()}_${randomUUID().slice(0, 8)}`;\n const meta = formatMetadata(entry);\n const line = `\\n- [${entry.ts}] ${id}${meta} ${entry.text.replace(/\\n/g, ' ')}\\n`;\n const next = existing.trim()\n ? existing.replace(/\\n+$/, '') + line\n : `# Agent Memory\\n${line}`;\n await atomicWrite(file, next);\n this.invalidateCache(file);\n }\n\n async forget(scope: MemoryScope, query: string, filePath: string): Promise<number> {\n const file = this.resolveFile(filePath, scope);\n return withFileLock(file, async () => {\n let existing: string;\n try { existing = await fs.readFile(file, 'utf8'); } catch { return 0; /* best-effort */ }\n\n const needle = query.toLowerCase();\n const idMatcher = /mem_\\d+_\\w+/;\n let removed = 0;\n const lines = existing.split('\\n').filter((line) => {\n const trimmed = line.trim();\n if (!trimmed.startsWith('- ')) return true;\n if (idMatcher.test(query)) {\n const entryIdMatch = /mem_\\d+_\\w+/.exec(trimmed);\n if (entryIdMatch && entryIdMatch[0] === query) { removed++; return false; }\n }\n if (trimmed.toLowerCase().includes(needle)) { removed++; return false; }\n return true;\n });\n if (removed > 0) {\n if (lines.length === 0 || (lines.length === 1 && !lines[0]?.trim())) {\n await atomicWrite(file, '');\n } else {\n await atomicWrite(file, lines.join('\\n'));\n }\n }\n this.invalidateCache(file);\n return removed;\n });\n }\n\n async readAll(scope: MemoryScope, filePath: string): Promise<string> {\n const file = this.resolveFile(filePath, scope);\n try { return await fs.readFile(file, 'utf8'); } catch { return ''; /* best-effort */ }\n }\n\n async list(scope: MemoryScope, filePath: string, limit?: number): Promise<MemoryEntry[]> {\n const file = this.resolveFile(filePath, scope);\n const entries = await this.loadEntries(file, scope);\n return limit ? entries.slice(0, limit) : entries;\n }\n\n async search(scope: MemoryScope, query: string, filePath: string, limit?: number): Promise<MemoryEntry[]> {\n const file = this.resolveFile(filePath, scope);\n const index = await this.getIndex(file, scope);\n return searchIndex(index, query, limit);\n }\n\n async clear(scope: MemoryScope, filePath: string): Promise<void> {\n const file = this.resolveFile(filePath, scope);\n await atomicWrite(file, '');\n this.invalidateCache(file);\n }\n\n async consolidate(scope: MemoryScope, filePath: string): Promise<number> {\n const file = this.resolveFile(filePath, scope);\n let existing: string;\n try { existing = await fs.readFile(file, 'utf8'); } catch { return 0; /* best-effort */ }\n\n const seen = new Set<string>();\n let removed = 0;\n const lines = existing.split('\\n').filter((line) => {\n const trimmed = line.trim();\n if (!trimmed.startsWith('- ')) return true;\n // Normalize: strip timestamp, ID, type|priority, tags\n const norm = trimmed\n .replace(/\\[[^\\]]+\\]/g, '')\n .replace(/\\bmem_\\d+_\\w+\\s*/, '')\n .replace(/#[\\w-]+/g, '')\n .replace(/\\s+/g, ' ')\n .trim()\n .toLowerCase();\n if (seen.has(norm)) { removed++; return false; }\n seen.add(norm);\n return true;\n });\n\n const next = lines.join('\\n');\n const backup = `${file}.bak.${Date.now()}`;\n try {\n await fs.copyFile(file, backup);\n await pruneConsolidateBackups(file);\n } catch { /* best-effort */ }\n /* v8 ignore next -- best-effort: atomicWrite failure during consolidate is non-fatal */\n try { await atomicWrite(file, next); } catch { return 0; /* best-effort */ }\n this.invalidateCache(file);\n return removed;\n }\n}\n\nasync function pruneConsolidateBackups(file: string): Promise<void> {\n const dir = path.dirname(file);\n const base = path.basename(file);\n const prefix = `${base}.bak.`;\n const backups = (await fs.readdir(dir))\n .filter((name) => name.startsWith(prefix))\n .sort()\n .reverse();\n\n await Promise.all(\n backups.slice(MAX_MEMORY_CONSOLIDATE_BACKUPS).map(async (name) => {\n try {\n await fs.unlink(path.join(dir, name));\n } catch {\n // best-effort\n }\n }),\n );\n}\n\n// \u2500\u2500 Entry parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function parseEntries(raw: string, scope: MemoryScope = 'project-memory'): MemoryEntry[] {\n const entries: MemoryEntry[] = [];\n for (const line of raw.split('\\n')) {\n const entry = lineToEntry(line, scope);\n if (entry) entries.push(entry);\n }\n return entries.reverse(); // newest first\n}\n", "export type MemoryScope = 'project-agents' | 'project-memory' | 'user-memory';\n\n// \u2500\u2500 Memory categories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type MemoryType = 'fact' | 'decision' | 'convention' | 'preference' | 'reference' | 'anti_pattern';\n\nexport const MEMORY_TYPE_LABELS: Record<MemoryType, string> = {\n fact: 'Fact',\n decision: 'Decision',\n convention: 'Convention',\n preference: 'Preference',\n reference: 'Reference',\n anti_pattern: 'Anti-pattern',\n};\n\nexport type MemoryPriority = 'critical' | 'high' | 'medium' | 'low';\n\nexport interface MemoryEntry {\n scope: MemoryScope;\n text: string;\n ts: string;\n /** Category \u2014 helps the agent decide whether to inject or ignore. */\n type?: MemoryType | undefined;\n /** Free-form tags for grouping (e.g. [\"build\", \"pnpm\", \"typescript\"]). */\n tags?: string[] | undefined;\n /** Priority \u2014 critical entries are always injected; low may be skipped. */\n priority?: MemoryPriority | undefined;\n /** Session or agent that created this entry. */\n source?: string | undefined;\n /** 0.0\u20131.0 confidence. Low-confidence entries are injected less often. */\n confidence?: number | undefined;\n /** ISO timestamp of last access (read or injection into context). */\n lastAccessed?: string | undefined;\n}\n\n// \u2500\u2500 Memory events \u2014 emitted by SuperMemoryStore so plugins can react \u2500\u2500\n\nexport interface MemoryRememberedPayload {\n scope: MemoryScope;\n text: string;\n ts: string;\n type?: MemoryType | undefined;\n tags?: string[] | undefined;\n priority?: MemoryPriority | undefined;\n}\n\nexport interface MemoryForgottenPayload {\n scope: MemoryScope;\n query: string;\n removed: number;\n}\n\nexport interface MemoryClearedPayload {\n /** Scope that was cleared, or undefined when all scopes were cleared. */\n scope?: MemoryScope | undefined;\n}\n\nexport interface MemoryConsolidatedPayload {\n scope: MemoryScope;\n /** Entries removed by deduplication. */\n removed: number;\n}\n\n// \u2500\u2500 Relevance scoring \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Context used to score memory relevance for context injection.\n * Passed by the system prompt builder.\n */\nexport interface MemoryRelevanceContext {\n /** Current user message or task description. */\n currentTask: string;\n /** Active skills in this session (e.g. [\"typescript-strict\", \"git-flow\"]). */\n activeSkills?: string[] | undefined;\n /** Active mode (e.g. \"Teach\", \"Brief\", \"Code Reviewer\"). */\n activeMode?: string | undefined;\n /** Available tools \u2014 memories referencing relevant tools score higher. */\n toolNames?: string[] | undefined;\n}\n\nexport interface ScoredEntry extends MemoryEntry {\n score: number;\n matchReason: string;\n}\n\n// \u2500\u2500 Store interface \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface MemoryStore {\n readAll(): Promise<string>;\n read(scope: MemoryScope): Promise<string>;\n remember(text: string, scope?: MemoryScope, metadata?: Omit<Partial<MemoryEntry>, 'scope' | 'text' | 'ts'>): Promise<void>;\n forget(query: string, scope?: MemoryScope): Promise<number>;\n consolidate(scope: MemoryScope): Promise<void>;\n clear(scope?: MemoryScope): Promise<void>;\n /** List entries, newest first. */\n list(scope?: MemoryScope, limit?: number): Promise<MemoryEntry[]>;\n /** Search by content (substring or semantic). */\n search(query: string, scope?: MemoryScope, limit?: number): Promise<MemoryEntry[]>;\n /** Access the backend for advanced queries. */\n getBackend?(): unknown;\n /** Graph-based related memory traversal. */\n findRelated?(text: string, scope?: MemoryScope, limit?: number): Promise<MemoryEntry[]>;\n /**\n * Score and rank memories by relevance to the current context.\n * Returns only entries that meet a relevance threshold.\n */\n scoreRelevant?(ctx: MemoryRelevanceContext, scope?: MemoryScope, limit?: number): Promise<ScoredEntry[]>;\n /**\n * Attach a trace ID to this store so that all subsequent `storage.*`\n * events include it for observability correlation. Mutates the store\n * in place and returns the same instance (convenience chaining).\n */\n withTraceId(traceId: string): MemoryStore;\n}\n", "import * as fs from 'node:fs/promises';\nimport type { MemoryEntry, MemoryScope } from '../types/memory.js';\nimport { type FileMemoryBackendOptions, FileMemoryBackend } from './memory-backend.js';\nimport type { MemoryBackend } from './memory-backend.js';\n\n// \u2500\u2500 Graph node and edge types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface GraphNode {\n id: string;\n entry: MemoryEntry;\n firstSeen: string;\n count: number;\n /** Extracted metadata for fast lookup. */\n type?: MemoryEntry['type'] | undefined;\n tags?: string[] | undefined;\n priority?: MemoryEntry['priority'] | undefined;\n}\n\ninterface GraphEdge {\n from: string;\n to: string;\n /** Why these nodes are related. */\n relation: 'co_occurring' | 'similar' | 'same_turn' | 'explicit';\n weight: number;\n ts: string;\n}\n\n// \u2500\u2500 Backend \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface GraphMemoryBackendOptions extends FileMemoryBackendOptions {\n /**\n * Path to the graph metadata file (edges + node metadata).\n * Defaults to `<projectDir>/memory-graph.json`.\n */\n graphPath?: string | undefined;\n}\n\n/**\n * Graph-based memory backend that tracks relationships between entries.\n * Builds on top of FileMemoryBackend \u2014 entries are still persisted as\n * markdown bullets, but the graph layer adds:\n *\n * - Co-occurrence edges (entries from the same remember() batch)\n * - Content similarity edges (simple Jaccard on word overlap)\n * - Turn-based edges (entries created in the same LLM turn)\n * - Graph traversal queries (find related memories)\n *\n * The graph metadata persists to `<projectDir>/memory-graph.json`.\n */\nexport class GraphMemoryBackend implements MemoryBackend {\n readonly kind = 'graph';\n\n private readonly file: FileMemoryBackend;\n private readonly graphFile: string;\n\n // In-memory graph state \u2014 lazily loaded, saved on mutation.\n private nodes = new Map<string, GraphNode>();\n private edges: GraphEdge[] = [];\n private loadedScope: MemoryScope | null = null;\n private loaded = false;\n\n /**\n * Inverted index for O(1) term \u2192 node lookup during search.\n * Built incrementally on remember/forget, rebuilt on loadGraph if absent.\n * Maps lowercase term (min 3 chars) \u2192 Set of node IDs containing that term.\n */\n private invertedIndex = new Map<string, Set<string>>();\n\n /** Minimum term length to index \u2014 avoids noise from 1-2 char fragments. */\n private static readonly MIN_TERM_LEN = 3;\n\n /**\n * Promise that resolves when the current in-flight _saveGraph completes.\n * Tests call flush() to await this before deleting the backend or its temp dir.\n * Each save operation chains onto the previous one so concurrent saves are serialised.\n */\n private _saveDone: Promise<void> = Promise.resolve();\n\n constructor(opts: GraphMemoryBackendOptions) {\n this.file = new FileMemoryBackend({ paths: opts.paths });\n this.graphFile = opts.graphPath ?? `${opts.paths.projectDir}/memory-graph.json`;\n }\n\n // \u2500\u2500 Backend interface \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async remember(scope: MemoryScope, entry: MemoryEntry, filePath: string): Promise<void> {\n await this.file.remember(scope, entry, filePath);\n await this.loadGraph(scope);\n\n const nodeId = this.nodeId(entry);\n const existing = this.nodes.get(nodeId);\n if (existing) {\n existing.count++;\n existing.entry = entry;\n existing.type = entry.type;\n existing.tags = entry.tags;\n existing.priority = entry.priority;\n // Update inverted index for the modified node's text and tags\n this.updateNodeIndex(nodeId, entry);\n } else {\n this.nodes.set(nodeId, {\n id: nodeId,\n entry,\n firstSeen: entry.ts,\n count: 1,\n type: entry.type,\n tags: entry.tags,\n priority: entry.priority,\n });\n\n // Index the new node in the inverted index\n this.indexNode(nodeId, entry);\n\n // Create similarity edges \u2014 but only against the last SIMILARITY_WINDOW most\n // recent nodes instead of the full in-memory set. This converts O(N\u00B2) on\n // every remember() into O(K) where K is a small constant (100), making\n // remember() O(1) amortized regardless of how many entries are stored.\n const SIMILARITY_WINDOW = 100;\n const recentNodes = [...this.nodes.values()].slice(-SIMILARITY_WINDOW);\n for (const other of recentNodes) {\n if (other.id === nodeId) continue;\n const sim = wordOverlap(entry.text, other.entry.text);\n const tagSim = sharedTags(entry.tags ?? [], other.tags ?? []);\n const weight = Math.max(sim, tagSim * 0.5);\n if (weight > 0.15) {\n this.edges.push({\n from: nodeId,\n to: other.id,\n relation: sim >= tagSim ? 'similar' : 'same_turn',\n weight,\n ts: entry.ts,\n });\n }\n }\n }\n\n // Await the graph save to ensure flush() sees the file on disk before\n // cleanup. Unlike memory-consolidator (LLM call, 15s), this is a fast fs operation\n // and the caller's event loop is already freed by not awaiting file.remember().\n this._saveDone = this._saveGraph(scope);\n await this._saveDone;\n }\n\n async forget(scope: MemoryScope, query: string, filePath: string): Promise<number> {\n const removed = await this.file.forget(scope, query, filePath);\n if (removed > 0) {\n await this.loadGraph(scope);\n // Remove nodes whose entry text matches the query\n const n = query.toLowerCase();\n const toRemove: string[] = [];\n for (const [id, node] of this.nodes) {\n if (node.entry.text.toLowerCase().includes(n)) {\n toRemove.push(id);\n }\n }\n for (const id of toRemove) {\n // Remove from inverted index before deleting the node\n this.removeNodeFromIndex(id, this.nodes.get(id)?.entry);\n this.nodes.delete(id);\n }\n this.edges = this.edges.filter((e) => !toRemove.includes(e.from) && !toRemove.includes(e.to));\n this._saveDone = this._saveGraph(scope);\n await this._saveDone;\n }\n return removed;\n }\n\n async readAll(scope: MemoryScope, filePath: string): Promise<string> {\n return this.file.readAll(scope, filePath);\n }\n\n async list(scope: MemoryScope, filePath: string, limit?: number): Promise<MemoryEntry[]> {\n await this.loadGraph(scope);\n // Merge: file entries are canonical, graph adds metadata and dedup\n const fileEntries = await this.file.list(scope, filePath);\n const nodeMap = new Map(this.nodes.entries());\n\n // Enrich file entries with graph metadata\n const enriched = fileEntries.map((fe) => {\n const nodeId = this.nodeId(fe);\n const node = nodeMap.get(nodeId);\n if (node) {\n return {\n ...fe,\n type: node.type ?? fe.type,\n tags: node.tags ?? fe.tags,\n priority: node.priority ?? fe.priority,\n };\n }\n return fe;\n });\n\n // Add graph-only nodes not in file (shouldn't happen normally, but safety)\n const fileIds = new Set(fileEntries.map((e) => this.nodeId(e)));\n for (const [id, node] of this.nodes) {\n if (!fileIds.has(id)) {\n enriched.push(node.entry);\n }\n }\n\n enriched.sort((a, b) => b.ts.localeCompare(a.ts));\n return limit ? enriched.slice(0, limit) : enriched;\n }\n\n async search(scope: MemoryScope, query: string, _filePath: string, limit?: number): Promise<MemoryEntry[]> {\n await this.loadGraph(scope);\n const needle = query.toLowerCase().split(/\\s+/).filter((t) => t.length >= GraphMemoryBackend.MIN_TERM_LEN);\n\n // Use inverted index to find candidate nodes \u2014 O(K * avg_results) instead of O(N)\n const candidates = new Map<string, number>();\n for (const term of needle) {\n const nodeIds = this.invertedIndex.get(term);\n if (!nodeIds) continue;\n for (const nodeId of nodeIds) {\n const node = this.nodes.get(nodeId);\n if (!node || node.entry.scope !== scope) continue;\n // Count how many terms matched this node\n candidates.set(nodeId, (candidates.get(nodeId) ?? 0) + 1);\n }\n }\n\n // Also include high-priority entries even without lexical matches (priority boost > 0)\n // This preserves the original behavior where priority/count could surface entries\n // that don't match any query terms.\n for (const [nodeId, node] of this.nodes) {\n if (node.entry.scope !== scope) continue;\n if (candidates.has(nodeId)) continue; // Already in candidates\n // Include if priority boost > 0 (critical=3, high=2)\n if (node.priority === 'critical' || node.priority === 'high') {\n candidates.set(nodeId, 0); // 0 lexical matches but priority boost applies\n }\n }\n\n // Score candidates: text match (1pt/term), tag match (2pts/term), priority, count\n const scored: { entry: MemoryEntry; score: number }[] = [];\n for (const [nodeId] of candidates) {\n const node = this.nodes.get(nodeId)!;\n let score = 0;\n // Text match score: 1pt per matching term\n score += (candidates.get(nodeId) ?? 0) * 1;\n // Tag match score: 2pts per term that appears in any tag\n for (const term of needle) {\n if (node.entry.tags?.some((t) => t.toLowerCase().includes(term))) score += 2;\n }\n if (node.priority === 'critical') score += 3;\n else if (node.priority === 'high') score += 2;\n score += node.count * 0.5;\n scored.push({ entry: node.entry, score });\n }\n\n scored.sort((a, b) => b.score - a.score);\n const matched = scored.map((s) => s.entry);\n return limit ? matched.slice(0, limit) : matched;\n }\n\n async clear(scope: MemoryScope, filePath: string): Promise<void> {\n await this.file.clear(scope, filePath);\n this.nodes.clear();\n this.edges = [];\n this.invertedIndex = new Map();\n this.loadedScope = scope;\n this.loaded = true;\n // Write empty graph\n try { await fs.unlink(this.graphFile); } catch { /* ok */ }\n }\n\n async consolidate(scope: MemoryScope, filePath: string): Promise<number> {\n return this.file.consolidate(scope, filePath);\n }\n\n // \u2500\u2500 Graph-specific queries \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Find memories related to the given entry, ordered by edge weight.\n */\n async findRelated(scope: MemoryScope, _filePath: string, entryText: string, limit = 5): Promise<MemoryEntry[]> {\n await this.loadGraph(scope);\n const targetId = this.nodeId({ scope, text: entryText, ts: '' });\n const related = this.edges\n .filter((e) => e.from === targetId || e.to === targetId)\n .sort((a, b) => b.weight - a.weight)\n .slice(0, limit);\n\n const result: MemoryEntry[] = [];\n for (const edge of related) {\n const otherId = edge.from === targetId ? edge.to : edge.from;\n const node = this.nodes.get(otherId);\n if (node) result.push(node.entry);\n }\n return result;\n }\n\n /**\n * Get all edges for visualization or traversal.\n */\n getGraph(): { nodes: GraphNode[]; edges: GraphEdge[] } {\n return {\n nodes: [...this.nodes.values()],\n edges: [...this.edges],\n };\n }\n\n // \u2500\u2500 Persistence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private nodeId(entry: MemoryEntry): string {\n // Stable ID from scope + normalized text\n const norm = entry.text.toLowerCase().trim().replace(/\\s+/g, ' ');\n return `${entry.scope ?? 'mem'}::${simpleHash(norm)}`;\n }\n\n private async loadGraph(scope: MemoryScope): Promise<void> {\n if (this.loaded && this.loadedScope === scope) return;\n try {\n const raw = await fs.readFile(this.graphFile, 'utf8');\n const data: { nodes: Array<[string, GraphNode]>; edges: GraphEdge[] } = JSON.parse(raw);\n this.nodes = new Map(data.nodes);\n this.edges = data.edges;\n } catch {\n this.nodes = new Map();\n this.edges = [];\n }\n // Build inverted index from loaded nodes\n this.buildInvertedIndex();\n this.loadedScope = scope;\n this.loaded = true;\n }\n\n /** Fire-and-forget graph persistence. Named _saveGraph to signal it must not be awaited. */\n private async _saveGraph(scope: MemoryScope): Promise<void> {\n this.loadedScope = scope;\n this.loaded = true;\n try {\n const data = {\n nodes: [...this.nodes.entries()],\n edges: this.edges,\n };\n const dir = this.graphFile.substring(0, this.graphFile.lastIndexOf('/'));\n await fs.mkdir(dir, { recursive: true });\n // Atomic write via temp file\n const tmp = `${this.graphFile}.tmp`;\n await fs.writeFile(tmp, JSON.stringify(data));\n await fs.rename(tmp, this.graphFile);\n } catch {\n // best-effort \u2014 graph is an enhancement, not critical\n }\n }\n\n /**\n * Wait for all in-flight _saveGraph operations to complete.\n * Call this before deleting the backend or its temp directory.\n */\n async flush(): Promise<void> {\n await this._saveDone;\n }\n\n // \u2500\u2500 Inverted Index Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Build the inverted index from all currently loaded nodes.\n * Called on loadGraph() to reconstruct the index after loading from disk.\n */\n private buildInvertedIndex(): void {\n this.invertedIndex = new Map();\n for (const [nodeId, node] of this.nodes) {\n for (const term of this.extractTerms(node.entry)) {\n let nodeIds = this.invertedIndex.get(term);\n if (!nodeIds) {\n nodeIds = new Set();\n this.invertedIndex.set(term, nodeIds);\n }\n nodeIds.add(nodeId);\n }\n }\n }\n\n /**\n * Index a node in the inverted index. Adds the node's ID to all term entries.\n */\n private indexNode(nodeId: string, entry: MemoryEntry): void {\n for (const term of this.extractTerms(entry)) {\n let nodeIds = this.invertedIndex.get(term);\n if (!nodeIds) {\n nodeIds = new Set();\n this.invertedIndex.set(term, nodeIds);\n }\n nodeIds.add(nodeId);\n }\n }\n\n /**\n * Remove a node's terms from the inverted index before deleting it.\n * Used in forget() and when updating existing nodes.\n */\n private removeNodeFromIndex(nodeId: string, entry?: MemoryEntry): void {\n if (!entry) return;\n for (const term of this.extractTerms(entry)) {\n const nodeIds = this.invertedIndex.get(term);\n if (nodeIds) {\n nodeIds.delete(nodeId);\n if (nodeIds.size === 0) {\n this.invertedIndex.delete(term);\n }\n }\n }\n }\n\n /**\n * Update an existing node's entries in the inverted index.\n * Removes old terms and adds new ones.\n */\n private updateNodeIndex(nodeId: string, entry: MemoryEntry): void {\n // Remove old entries for this node\n const existing = this.nodes.get(nodeId);\n if (existing) {\n this.removeNodeFromIndex(nodeId, existing.entry);\n }\n // Add new entries\n this.indexNode(nodeId, entry);\n }\n\n /**\n * Extract searchable terms from a memory entry.\n * Returns lowercase words from text (min length) and full tag strings.\n */\n private extractTerms(entry: MemoryEntry): string[] {\n const terms: string[] = [];\n // Extract words from text (only terms >= MIN_TERM_LEN to avoid noise)\n const words = entry.text.toLowerCase().split(/\\s+/);\n for (const word of words) {\n if (word.length >= GraphMemoryBackend.MIN_TERM_LEN) {\n terms.push(word);\n }\n }\n // Index full tags as complete strings (not individual words)\n if (entry.tags) {\n for (const tag of entry.tags) {\n const lower = tag.toLowerCase();\n if (lower.length >= GraphMemoryBackend.MIN_TERM_LEN && !terms.includes(lower)) {\n terms.push(lower);\n }\n }\n }\n return terms;\n }\n}\n\n// \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Jaccard-style word overlap between two strings. 0.0 = no overlap, 1.0 = identical. */\nfunction wordOverlap(a: string, b: string): number {\n const wordsA = new Set(a.toLowerCase().split(/\\s+/).filter((w) => w.length > 2));\n const wordsB = new Set(b.toLowerCase().split(/\\s+/).filter((w) => w.length > 2));\n if (wordsA.size === 0 || wordsB.size === 0) return 0;\n let intersection = 0;\n for (const w of wordsA) {\n if (wordsB.has(w)) intersection++;\n }\n return intersection / Math.max(wordsA.size, wordsB.size);\n}\n\n/** Tag overlap ratio \u2014 how many tags two nodes share. */\nfunction sharedTags(a: string[], b: string[]): number {\n if (a.length === 0 || b.length === 0) return 0;\n const setB = new Set(b);\n let shared = 0;\n for (const t of a) if (setB.has(t)) shared++;\n return shared / Math.max(a.length, b.length);\n}\n\n/** Fast non-crypto hash for stable node IDs. */\nfunction simpleHash(s: string): string {\n let h = 0;\n for (let i = 0; i < s.length; i++) {\n h = ((h << 5) - h + s.charCodeAt(i)) | 0;\n }\n return Math.abs(h).toString(36);\n}\n", "import { readFileSync, statSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Cache of resolved instruction text, keyed by the relative path. Bundled\n * instruction files are immutable for the process lifetime, so re-reading them\n * on every call (some callers do so per LLM request) is wasted disk I/O.\n */\nconst textCache = new Map<string, string>();\n\n/** Resolved once \u2014 the candidate roots only depend on this module's location. */\nlet rootCandidates: string[] | undefined;\n\nexport function readBundledInstructionText(relativePath: string): string {\n const cached = textCache.get(relativePath);\n if (cached !== undefined) return cached;\n\n let resolved = '';\n for (const root of instructionRootCandidates()) {\n try {\n resolved = readFileSync(path.join(root, relativePath), 'utf8').trimEnd();\n break;\n } catch {\n // try next candidate\n }\n }\n textCache.set(relativePath, resolved);\n return resolved;\n}\n\nexport function renderInstructionTemplate(\n template: string,\n values: Record<string, string>,\n): string {\n return template.replace(/\\{\\{\\s*([a-zA-Z0-9_.-]+)\\s*\\}\\}/g, (match, key: string) =>\n Object.hasOwn(values, key) ? (values[key] ?? '') : match,\n );\n}\n\nfunction instructionRootCandidates(): string[] {\n if (rootCandidates !== undefined) return rootCandidates;\n const here = path.dirname(fileURLToPath(import.meta.url));\n const candidates = [\n path.resolve(here, '../../instructions'),\n path.resolve(here, '../instructions'),\n path.resolve(here, 'instructions'),\n ];\n rootCandidates = candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));\n return rootCandidates;\n}\n\nfunction isDirectory(candidate: string): boolean {\n try {\n return statSync(candidate).isDirectory();\n } catch {\n return false;\n }\n}\n", "import type { RunResult } from '../core/agent-types.js';\nimport type { Context } from '../core/context.js';\nimport type { AfterRunHook, AgentExtension } from '../extension/extension-points.js';\nimport type { MemoryEntry, MemoryStore } from '../types/memory.js';\nimport type { Provider } from '../types/provider.js';\nimport type { OneShotOrchestrator } from '../execution/one-shot-llm.js';\nimport {\n readBundledInstructionText,\n renderInstructionTemplate,\n} from '../utils/instruction-file.js';\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ConsolidationOp {\n action: 'add' | 'edit' | 'delete';\n /** For add: the fact to remember. For edit: the new text replacing the old. */\n text?: string | undefined;\n /** For edit/delete: the query to match existing entries. */\n query?: string | undefined;\n /** Memory type for categorization. */\n type?: string | undefined;\n /** Tags for grouping. */\n tags?: string[] | undefined;\n /** Priority level. */\n priority?: string | undefined;\n}\n\ninterface ConsolidationResponse {\n operations: ConsolidationOp[];\n summary?: string | undefined;\n}\n\nexport interface MemoryConsolidatorOptions {\n memoryStore: MemoryStore;\n /**\n * Provider used for the consolidation LLM call. Uses the session's\n * provider by default.\n */\n provider?: Provider | undefined;\n /**\n * Model override for the consolidation call. Uses the session's model\n * by default. A smaller/faster model is recommended (e.g. deepseek-chat, haiku, flash).\n */\n model?: string | undefined;\n /**\n * Minimum session iterations before consolidation fires.\n * Sessions shorter than this are skipped (default 2).\n */\n minIterations?: number | undefined;\n /**\n * Maximum memory entries to include in the prompt as context.\n */\n maxExistingEntries?: number | undefined;\n /**\n * OneShotOrchestrator for the consolidation LLM call. When set, uses\n * it instead of the direct provider.complete() path, gaining fallback\n * chain support and cheap-model defaulting.\n */\n oneShotOrchestrator?: OneShotOrchestrator | undefined;\n}\n\n// \u2500\u2500 Prompt \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction buildConsolidationPrompt(\n finalText: string,\n iterations: number,\n existingEntries: MemoryEntry[],\n): string {\n const existingBlock =\n existingEntries.length > 0\n ? `\\n\\nExisting memory entries:\\n${existingEntries\n .map((e) => `- [${e.ts.slice(0, 10)}] ${e.text}`)\n .join('\\n')}`\n : '';\n\n return renderInstructionTemplate(readBundledInstructionText('llm/memory-consolidator.md'), {\n iterations: String(iterations),\n summary: finalText.slice(0, 3000),\n existingEntries: existingBlock,\n });\n}\n\n// \u2500\u2500 Consolidator \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class SessionMemoryConsolidator implements AgentExtension {\n name = 'session-memory-consolidator';\n owner = 'core';\n\n private readonly memoryStore: MemoryStore;\n private readonly provider?: Provider | undefined;\n private readonly model?: string | undefined;\n private readonly minIterations: number;\n private readonly maxExistingEntries: number;\n private readonly oneShotOrchestrator?: OneShotOrchestrator | undefined;\n\n constructor(opts: MemoryConsolidatorOptions) {\n this.memoryStore = opts.memoryStore;\n this.provider = opts.provider;\n this.model = opts.model;\n this.minIterations = opts.minIterations ?? 2;\n this.maxExistingEntries = opts.maxExistingEntries ?? 15;\n this.oneShotOrchestrator = opts.oneShotOrchestrator;\n }\n\n afterRun: AfterRunHook = (ctx: Context, result: RunResult) => {\n // Only consolidate successful sessions with meaningful output\n if (result.status !== 'done') return;\n if (!result.finalText || result.finalText.trim().length < 20) return;\n if (result.iterations < this.minIterations) return;\n\n const provider = this.provider ?? ctx.provider;\n if (!provider?.complete) return;\n\n // Capture narrowed values for the fire-and-forget closure.\n const _finalText: string = result.finalText;\n const _iterations: number = result.iterations;\n const _model: string | undefined = this.model ?? ctx.model;\n\n // Fire-and-forget: consolidation is best-effort and should never block\n // session teardown (the LLM call can take up to 15s). The catch block\n // below prevents unhandled rejections.\n void (async () => {\n try {\n // Load existing memory for dedup context\n const existingEntries = await this.memoryStore.list('project-memory', this.maxExistingEntries);\n const prompt = buildConsolidationPrompt(\n _finalText,\n _iterations,\n existingEntries,\n );\n\n // Call the LLM with a focused, one-shot prompt\n let text: string;\n if (this.oneShotOrchestrator) {\n const oneShotResult = await this.oneShotOrchestrator.call({\n system: prompt,\n userPrompt: 'Review the session and return memory operations as JSON.',\n model: _model ?? 'deepseek-chat',\n maxTokens: 500,\n timeoutMs: 15_000,\n fallbackProfile: 'memory',\n });\n text = oneShotResult.text;\n } else {\n // Legacy path: direct provider.complete()\n const signal = AbortSignal.timeout(15_000);\n const response = await provider.complete(\n {\n model: _model,\n system: [{ type: 'text', text: prompt }],\n messages: [\n { role: 'user', content: 'Review the session and return memory operations as JSON.' },\n ],\n maxTokens: 500,\n },\n { signal },\n );\n text = response.content\n .filter((b): b is { type: 'text'; text: string } => b.type === 'text')\n .map((b) => b.text)\n .join('')\n .trim();\n }\n if (!text) return;\n\n // Extract JSON from possible markdown wrapper\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n if (!jsonMatch) return;\n\n const parsed: ConsolidationResponse = JSON.parse(jsonMatch[0]);\n if (!Array.isArray(parsed.operations) || parsed.operations.length === 0) return;\n\n // Apply operations\n let added = 0;\n let edited = 0;\n let deleted = 0;\n\n for (const op of parsed.operations) {\n switch (op.action) {\n case 'add': {\n if (op.text?.trim()) {\n await this.memoryStore.remember(op.text.trim(), undefined, {\n type: op.type as MemoryEntry['type'],\n tags: op.tags,\n priority: op.priority as MemoryEntry['priority'],\n });\n added++;\n }\n break;\n }\n case 'edit': {\n if (op.query && op.text?.trim()) {\n await this.memoryStore.forget(op.query);\n await this.memoryStore.remember(op.text.trim(), undefined, {\n type: op.type as MemoryEntry['type'],\n tags: op.tags,\n priority: op.priority as MemoryEntry['priority'],\n });\n edited++;\n }\n break;\n }\n case 'delete': {\n if (op.query) {\n const n = await this.memoryStore.forget(op.query);\n deleted += n;\n }\n break;\n }\n }\n }\n\n if (added > 0 || edited > 0 || deleted > 0) {\n const parts: string[] = [];\n if (added) parts.push(`${added} added`);\n if (edited) parts.push(`${edited} edited`);\n if (deleted) parts.push(`${deleted} deleted`);\n // Log to stderr so it surfaces in the terminal\n process.stderr.write(`[memory] Session consolidation: ${parts.join(', ')}\\n`);\n }\n } catch {\n // Silent \u2014 memory consolidation is best-effort, never blocks session cleanup\n }\n })();\n };\n}\n", "import type { Config, ConfigStore } from '../types/config.js';\nimport { ConfigError, ERROR_CODES } from '../types/errors.js';\nimport { toErrorMessage } from '../utils/error.js';\n\n\n/**\n * Strip fields that originated from environment variables so they are never\n * persisted back to disk. This prevents an env-sourced secret (e.g.\n * WRONGSTACK_API_KEY) from being accidentally written to ~/.wrongstack/config.json.\n */\nfunction stripEphemeralFields(cfg: Partial<Config>): Partial<Config> {\n const env = (cfg as Partial<Config & { _envSource?: Set<string> | undefined}>)._envSource;\n if (!env?.size) return cfg;\n const out: Partial<Config> = { ...cfg };\n for (const field of env) {\n delete (out as Record<string, unknown>)[field];\n }\n delete (out as Record<string, unknown>)._envSource;\n return out;\n}\n\n/**\n * Reference implementation of `ConfigStore`. Stores a single frozen Config\n * and notifies watchers synchronously on every update. Updates use a deep\n * clone so callers can mutate their `partial` argument freely without\n * tainting state.\n *\n * For the CLI: instantiate once at boot, pass the store (not the Config)\n * to subsystems that care about runtime changes (provider switching,\n * extension reload).\n */\nexport class DefaultConfigStore implements ConfigStore {\n private current: Readonly<Config>;\n private watchers = new Set<(next: Readonly<Config>, prev: Readonly<Config>) => void>();\n\n constructor(initial: Config) {\n this.current = deepFreeze(structuredClone(initial));\n }\n\n get(): Readonly<Config> {\n return this.current;\n }\n\n getSection<K extends keyof Config>(key: K): Readonly<Config[K]> {\n return this.current[key] as Readonly<Config[K]>;\n }\n\n getExtension(pluginName: string): Readonly<Record<string, unknown>> {\n const ext = this.current.extensions?.[pluginName];\n return ext ? (ext as Readonly<Record<string, unknown>>) : FROZEN_EMPTY;\n }\n\n update(partial: Partial<Config>): Readonly<Config> {\n // Strip env-sourced fields before persisting to prevent secrets leaking\n // from in-memory env-derived config values into the on-disk config file.\n const scrubbed = stripEphemeralFields(partial);\n // Shallow merge \u2014 top-level fields replace, nested objects do too unless\n // the caller passes a fully-formed sub-object. That matches the JSON\n // config user mental model (replace `tools.maxIterations` by passing\n // the whole `tools` block, or by patching `extensions.<name>`).\n const next = deepFreeze(structuredClone({ ...this.current, ...scrubbed })) as Readonly<Config>;\n\n if (next.version !== 1) {\n throw new ConfigError({\n message: `ConfigStore.update: version must remain 1, got ${String(next.version)}`,\n code: ERROR_CODES.CONFIG_INVALID,\n context: { field: 'version', actual: next.version },\n });\n }\n\n const prev = this.current;\n this.current = next;\n // Notify watchers AFTER mutating `current` so re-entrant watcher reads\n // see the new state. Watcher exceptions are caught individually so one\n // misbehaving subscriber can't block the others.\n for (const w of this.watchers) {\n try {\n w(next, prev);\n } catch (err) {\n // A plugin watcher that crashes on /model switch or similar would\n // otherwise leave the system in a quietly-inconsistent state. We\n // still don't propagate (one bad subscriber must not break the\n // others), but we surface the error so it's discoverable.\n console.error(JSON.stringify({\n level: 'error',\n event: 'config_store.watcher_threw',\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }));\n }\n }\n return next;\n }\n\n watch(cb: (next: Readonly<Config>, prev: Readonly<Config>) => void): () => void {\n this.watchers.add(cb);\n return () => this.watchers.delete(cb);\n }\n}\n\nconst FROZEN_EMPTY: Readonly<Record<string, unknown>> = Object.freeze({});\n\nfunction deepFreeze<T>(obj: T): T {\n /* v8 ignore start -- defensive: callers (and the recursion guard below) only pass unfrozen objects */\n if (obj === null || typeof obj !== 'object') return obj;\n if (Object.isFrozen(obj)) return obj;\n /* v8 ignore stop */\n for (const key of Object.keys(obj as object)) {\n const v = (obj as Record<string, unknown>)[key];\n if (v !== null && typeof v === 'object' && !Object.isFrozen(v)) {\n deepFreeze(v);\n }\n }\n return Object.freeze(obj);\n}\n", "/**\n * provider-config-watcher \u2014 live hot-reload of provider credentials.\n *\n * Provider API keys are read from `config.json` once at boot and baked into\n * the constructed `Provider`. When a user adds/removes/replaces a key in\n * another process (a second terminal running `wstack auth`, or either WebUI\n * provider panel), a *running* session would otherwise keep using the\n * boot-time key until restart.\n *\n * This watcher closes that gap: it tails the global `config.json`, re-reads\n * and decrypts its `providers` map on change, and hands a fresh snapshot to\n * the caller, who updates in-memory config and rebuilds the active provider.\n *\n * It mirrors {@link module:hq/auth-store.watchHqAuthFile}:\n * - Watches the *directory* (not the file) so atomic tmp+rename writes\n * (see {@link module:utils/atomic-write.atomicWrite}) surface reliably.\n * - Debounces events (most writers do a tmp+rename dance emitting several).\n * - Reads leniently \u2014 a missing/corrupt/torn file yields a warning, never a\n * throw, and the watcher stays live for the next valid write.\n *\n * A `lastSerialized` guard suppresses the callback when the on-disk providers\n * map is byte-for-byte identical to the last one we surfaced. This makes the\n * watcher a no-op for self-induced writes (e.g. the same process's `/model`\n * switch or a WebUI key save), so there is no rebuild storm.\n *\n * `fs.watch` is best-effort across platforms; on some network filesystems\n * events may not fire and the operator must restart to pick up changes.\n *\n * @module storage/provider-config-watcher\n */\nimport * as syncFs from 'node:fs';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\n\nimport { decryptConfigSecrets } from '../security/config-secrets.js';\nimport type { ProviderConfig } from '../types/config.js';\nimport type { SecretVault } from '../types/secret-vault.js';\n\n/**\n * A decrypted snapshot of the credential-bearing slice of `config.json`.\n * `providers` is always present (empty object when the file has none);\n * `apiKey`/`baseUrl` are the top-level fallbacks a provider inherits when its\n * saved entry omits them.\n */\nexport interface ProviderConfigSnapshot {\n providers: Record<string, ProviderConfig>;\n apiKey?: string;\n baseUrl?: string;\n /** Display language (Config.uiLocale), surfaced so a cross-process change\n * (e.g. the desktop shell) propagates to running webui instances. */\n uiLocale?: string;\n}\n\nexport interface WatchProviderConfigOptions {\n /** Surface non-fatal read/parse/decrypt issues. */\n warn?: (msg: string) => void;\n /** Coalesce bursts of fs events (default 200ms). */\n debounceMs?: number;\n}\n\n/**\n * Read and decrypt the credential slice of a config file. Returns `undefined`\n * when the file is missing or unparseable (the `warn` callback carries the\n * detail); never throws for routine I/O.\n */\nasync function readProviderSnapshot(\n configPath: string,\n vault: SecretVault,\n warn?: (msg: string) => void,\n): Promise<ProviderConfigSnapshot | undefined> {\n let raw: string;\n try {\n raw = await fs.readFile(configPath, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n warn?.(`Could not read ${configPath}: ${(err as Error).message}`);\n }\n return undefined;\n }\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch (err) {\n // A torn read mid-rename can surface as invalid JSON; treat as transient.\n warn?.(`Config at ${configPath} is not valid JSON: ${(err as Error).message}`);\n return undefined;\n }\n const decrypted = decryptConfigSecrets(parsed, vault, warn ? { warn } : {}) as {\n providers?: Record<string, ProviderConfig>;\n apiKey?: string;\n baseUrl?: string;\n uiLocale?: string;\n };\n const snapshot: ProviderConfigSnapshot = {\n providers: decrypted.providers ?? {},\n };\n if (typeof decrypted.apiKey === 'string') snapshot.apiKey = decrypted.apiKey;\n if (typeof decrypted.baseUrl === 'string') snapshot.baseUrl = decrypted.baseUrl;\n if (typeof decrypted.uiLocale === 'string' && decrypted.uiLocale)\n snapshot.uiLocale = decrypted.uiLocale;\n return snapshot;\n}\n\n/** Stable serialization for the no-op guard (key order independent). */\nfunction serializeSnapshot(s: ProviderConfigSnapshot): string {\n return JSON.stringify({\n providers: s.providers,\n apiKey: s.apiKey ?? null,\n baseUrl: s.baseUrl ?? null,\n uiLocale: s.uiLocale ?? null,\n });\n}\n\n/**\n * Watch `configPath` for credential changes and invoke `onChange` with a\n * freshly-decrypted snapshot whenever the `providers`/`apiKey`/`baseUrl` slice\n * actually changes. Returns a `close()` that stops watching.\n *\n * The initial on-disk state is read once to seed the no-op guard but does NOT\n * fire `onChange` \u2014 only subsequent changes do.\n */\nexport function watchProviderConfig(\n configPath: string,\n vault: SecretVault,\n onChange: (snapshot: ProviderConfigSnapshot) => void,\n opts: WatchProviderConfigOptions = {},\n): { close: () => void } {\n const debounceMs = opts.debounceMs ?? 200;\n const warn = opts.warn;\n const base = path.basename(configPath);\n let timer: ReturnType<typeof setTimeout> | undefined;\n let closed = false;\n let lastSerialized: string | undefined;\n\n // Seed the guard with current on-disk content so the first *change* fires\n // rather than re-applying what we already have.\n void readProviderSnapshot(configPath, vault, warn).then((seed) => {\n if (!closed && seed && lastSerialized === undefined) {\n lastSerialized = serializeSnapshot(seed);\n }\n });\n\n let watcher: syncFs.FSWatcher;\n try {\n // Watch the directory (not the file) so atomic-rename events surface\n // reliably across platforms.\n watcher = syncFs.watch(path.dirname(configPath), { recursive: false });\n } catch (err) {\n warn?.(`Provider config watcher could not start: ${(err as Error).message}`);\n return { close: () => {} };\n }\n\n const trigger = (): void => {\n if (closed) return;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = undefined;\n void readProviderSnapshot(configPath, vault, warn).then(\n (next) => {\n if (closed || !next) return;\n const serialized = serializeSnapshot(next);\n if (serialized === lastSerialized) return; // no-op / self-write\n lastSerialized = serialized;\n onChange(next);\n },\n () => {\n // readProviderSnapshot never rejects for routine I/O \u2014 guard only.\n },\n );\n }, debounceMs);\n };\n\n watcher.on('change', (eventType: string, filename: string | Buffer | null) => {\n const name = typeof filename === 'string' ? filename : '';\n if (eventType === 'rename' || eventType === 'change') {\n if (!name || name === base) trigger();\n }\n });\n\n watcher.on('error', (err: Error) => {\n warn?.(`Provider config watcher error: ${err.message}`);\n });\n\n return {\n close: () => {\n closed = true;\n if (timer) clearTimeout(timer);\n watcher.close();\n },\n };\n}\n", "import type { SecretVault } from '../types/secret-vault.js';\n\n/**\n * Config-walking secret helpers, split out of secret-vault.ts so that\n * consumers which only need the walk logic (config loader, provider-config\n * watcher) don't pull the AES-GCM vault implementation \u2014 and its node:crypto\n * imports \u2014 into their bundle chunk. Everything here operates purely through\n * the `SecretVault` interface.\n */\n\n/**\n * Walk a Config-shaped object and decrypt any apiKey-like fields in place,\n * returning a new object. Used by the config loader so the rest of the\n * system never has to know about the wire format.\n *\n * @param warn \u2014 callback for decryption warnings. Defaults to `console.warn`\n * for backward compatibility; pass `logger.warn` when a structured logger\n * is available (preferred in long-running/server contexts).\n */\nexport function decryptConfigSecrets<T>(\n cfg: T,\n vault: SecretVault,\n opts?: { warn?: (msg: string) => void },\n): T {\n const warn = opts?.warn ?? ((msg: string) => console.warn(msg));\n // A single corrupted/malformed encrypted field should not kill the entire\n // config load. Swallow per-field decrypt errors (zero the field so callers\n // see \"missing key\" instead of holding ciphertext) and surface a warning.\n return walk(cfg, vault, (v, key) => {\n try {\n return vault.decrypt(v);\n } catch (err) {\n warn(\n `[secret-vault] Failed to decrypt \"${key}\": ${err instanceof Error ? err.message : err}`,\n );\n return '';\n }\n });\n}\n\nexport function encryptConfigSecrets<T>(\n cfg: T,\n vault: SecretVault,\n _opts?: { warn?: (msg: string) => void },\n): T {\n return walk(cfg, vault, (v) => vault.encrypt(v));\n}\n\nfunction walk<T>(node: T, vault: SecretVault, transform: (s: string, key: string) => string): T {\n if (node === null || node === undefined) return node;\n if (typeof node !== 'object') return node;\n if (Array.isArray(node)) {\n return node.map((item) => walk(item, vault, transform)) as never as T;\n }\n const out: Record<string, unknown> = Object.create(null);\n for (const [k, v] of Object.entries(node as Record<string, unknown>)) {\n if (typeof v === 'string' && isSecretField(k)) {\n out[k] = transform(v, k);\n } else if (typeof v === 'object' && v !== null) {\n out[k] = walk(v, vault, transform);\n } else {\n out[k] = v;\n }\n }\n return out as T;\n}\n\n/**\n * A key is treated as secret-bearing if its name (case-insensitive) contains\n * one of these tokens. Captures common variants like `apiKey`, `authToken`,\n * `refreshToken`, `sessionKey`, `password`, `client_secret`, `bearer`, etc.\n * Use a named field with `isSecret: false` annotation if you must opt out \u2014\n * see `NON_SECRET_OVERRIDES` below.\n */\nconst SECRET_KEY_PATTERN =\n /(?:apikey|api_key|authtoken|auth_token|bearer|secret|password|passwd|pwd|refreshtoken|refresh_token|sessionkey|session_key|access[_-]?token|private[_-]?key|token\\b)/i;\n\n// Field names that contain the literal substring \"key\" but are not secrets.\n// Keep this list short; the substring rule itself is intentionally narrow.\nconst NON_SECRET_OVERRIDES = new Set(['publickey', 'public_key']);\n\nexport function isSecretField(name: string): boolean {\n const lc = name.toLowerCase();\n if (NON_SECRET_OVERRIDES.has(lc)) return false;\n return SECRET_KEY_PATTERN.test(lc);\n}\n", "import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { EventBus } from '../kernel/events.js';\nimport { decryptConfigSecrets } from '../security/config-secrets.js';\nimport {\n type Config,\n type ConfigLoader,\n DEFAULT_TUI_THINKING_WORD,\n type SyncConfig,\n} from '../types/config.js';\nimport {\n DEFAULT_CONTEXT_WINDOW_MODE_ID,\n isContextWindowModeId,\n listContextWindowModes,\n} from '../types/context-window.js';\nimport {\n DEFAULT_AUTONOMY_CONFIG,\n DEFAULT_CIRCUIT_BREAKER_CONFIG,\n DEFAULT_CONTEXT_CONFIG,\n DEFAULT_SESSION_LOGGING_CONFIG,\n DEFAULT_TOOLS_CONFIG,\n} from '../types/default-config.js';\nimport { ConfigError, ERROR_CODES } from '../types/errors.js';\nimport type { Logger } from '../types/logger.js';\nimport type { SecretVault } from '../types/secret-vault.js';\nimport { atomicWrite, withFileLock } from '../utils/atomic-write.js';\nimport { type DeepMergeOptions, deepMerge as deepMergeCore } from '../utils/deep-merge.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport { safeParse } from '../utils/safe-json.js';\nimport type { WstackPaths } from '../utils/wstack-paths.js';\n\n/**\n * Surface the OS error code (EACCES, ENOSPC, \u2026) alongside the message in\n * storage.* event payloads. Codes are stable and locale-independent, so\n * they are what dashboards and alerts key on; the message is supplementary.\n */\nfunction storageErrorString(err: unknown): string {\n if (err instanceof Error) {\n const code = (err as NodeJS.ErrnoException).code;\n return code ? `${code}: ${err.message}` : err.message;\n }\n /* v8 ignore next -- defensive: callers only pass fs Error instances */\n return String(err);\n}\n\n/**\n * Defaults express *behavior*, not identity. Provider and model are NOT\n * hardcoded \u2014 they must be resolved at runtime from config + env + the\n * ModelsRegistry. A bare Config returned by this loader will throw when\n * the agent tries to construct a provider, with a message that points\n * users at `wstack init`.\n */\nconst BEHAVIOR_DEFAULTS: Omit<Config, 'provider' | 'model'> = {\n version: 1,\n context: {\n mode: DEFAULT_CONTEXT_WINDOW_MODE_ID,\n warnThreshold: 0.6,\n softThreshold: 0.75,\n hardThreshold: 0.9,\n autoCompact: true,\n preserveK: DEFAULT_CONTEXT_CONFIG.preserveK,\n eliseThreshold: DEFAULT_CONTEXT_CONFIG.eliseThreshold,\n strategy: 'hybrid',\n },\n tools: {\n defaultExecutionStrategy: DEFAULT_TOOLS_CONFIG.defaultExecutionStrategy,\n maxIterations: DEFAULT_TOOLS_CONFIG.maxIterations,\n iterationTimeoutMs: DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,\n maxToolTimeoutMs: DEFAULT_TOOLS_CONFIG.maxToolTimeoutMs,\n sessionTimeoutMs: DEFAULT_TOOLS_CONFIG.sessionTimeoutMs,\n perIterationOutputCapBytes: DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,\n descriptionMode: DEFAULT_TOOLS_CONFIG.descriptionMode,\n disabledTools: DEFAULT_TOOLS_CONFIG.disabledTools as string[],\n autoExtendLimit: DEFAULT_TOOLS_CONFIG.autoExtendLimit,\n restrictToProjectRoot: DEFAULT_TOOLS_CONFIG.restrictToProjectRoot,\n loopDetection: DEFAULT_TOOLS_CONFIG.loopDetection,\n },\n log: { level: 'info' },\n features: {\n mcp: true,\n plugins: true,\n memory: true,\n modelsRegistry: true,\n skills: true,\n prompts: true,\n tokenSavingMode: 'off',\n allowOutsideProjectRoot: true,\n },\n superMemory: {\n enabled: true,\n storage: {\n projectLocal: true,\n directory: '.wrongstack/memories',\n },\n inject: {\n turnContext: true,\n toolResults: true,\n maxHintsPerTool: 4,\n maxCharsPerTool: 1200,\n maxTurnMemories: 8,\n maxCharsPerTurn: 2400,\n minScore: 0.65,\n repeatCooldownMs: 30 * 60_000,\n },\n hygiene: {\n autoAfterSession: true,\n autoOnFileChange: true,\n retentionDays: 90,\n archiveLowConfidenceAfterDays: 30,\n },\n embeddings: {\n enabled: false,\n },\n },\n skills: { readClaudeSkills: true },\n mcpServers: {},\n fallbackAuto: true,\n maxConcurrent: 4,\n // YOLO opt-in: users who want auto-approve behaviour set `yolo: true`.\n // Off by default so prompt injection in repository content, fetched\n // content, or mailbox messages cannot induce arbitrary shell calls or\n // write operations without human confirmation.\n yolo: false,\n nextPrediction: false,\n hints: true,\n debugStream: false,\n configScope: 'global',\n indexing: {\n onSessionStart: true,\n onEdit: true,\n watchExternal: true,\n debounceMs: 400,\n },\n session: { ...DEFAULT_SESSION_LOGGING_CONFIG },\n autonomy: {\n // 'auto' by default: the agent self-drives (picks the top next-step after\n // each turn). This is a startup default, not a runtime flip \u2014 the\n // autoProceedMaxIterations cap + autoProceedDelayMs cooldown + Ctrl+C /\n // [GOAL_COMPLETE] stops remain in force. Explicit 'off'\n // in a config file is preserved (opt-out respected).\n defaultMode: 'auto',\n autoProceedDelayMs: DEFAULT_AUTONOMY_CONFIG.autoProceedDelayMs,\n autoProceedMaxIterations: 50,\n autonomyNextPrompt: 'auto {{suggestion}}',\n terminalTitleAnimation: true,\n // Mirrored from the top-level yolo default so the autonomy subsystem\n // (which reads autonomy.yolo) stays consistent with config.yolo.\n yolo: false,\n streamFleet: true,\n chime: false,\n confirmExit: true,\n mouseMode: false,\n enhance: true,\n enhanceDelayMs: 60_000,\n enhanceLanguage: 'original',\n statuslineMode: 'detailed',\n thinkingWord: DEFAULT_TUI_THINKING_WORD,\n },\n circuitBreaker: { ...DEFAULT_CIRCUIT_BREAKER_CONFIG },\n modelRuntime: {\n // `effort` is intentionally undefined by default. Leaving it unset lets\n // each model use its provider-recommended reasoning effort (or none at\n // all) instead of forcing an opinionated value that may be unsupported,\n // silently omitted, and surfaced as a per-request warning. Users who\n // want a specific effort can opt in via `/settings` or the WebUI panel.\n reasoning: { mode: 'auto' },\n cache: {},\n },\n};\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction cloneJsonValue<T>(value: T): T {\n return structuredClone(value);\n}\n\nfunction fillMissingDefaults(\n target: Record<string, unknown>,\n defaults: Record<string, unknown>,\n): { value: Record<string, unknown>; changed: boolean } {\n const value = cloneJsonValue(target);\n const changed = fillMissingDefaultsInPlace(value, defaults);\n return { value, changed };\n}\n\nfunction fillMissingDefaultsInPlace(\n target: Record<string, unknown>,\n defaults: Record<string, unknown>,\n): boolean {\n let changed = false;\n for (const [key, defaultValue] of Object.entries(defaults)) {\n if (!Object.hasOwn(target, key)) {\n target[key] = cloneJsonValue(defaultValue);\n changed = true;\n continue;\n }\n const current = target[key];\n if (isPlainRecord(current) && isPlainRecord(defaultValue)) {\n changed = fillMissingDefaultsInPlace(current, defaultValue) || changed;\n }\n }\n return changed;\n}\n\n/** Parse a boolean-ish env var: \"0\"/\"false\"/\"no\"/\"off\" \u2192 false, anything else \u2192 true. */\nfunction envBool(v: string): boolean {\n return !/^(0|false|no|off)$/i.test(v.trim());\n}\n\nfunction envBoolOptional(v: string | undefined): boolean {\n return v !== undefined && envBool(v);\n}\n\nconst LOG_LEVELS = new Set<Config['log']['level']>(['error', 'warn', 'info', 'debug', 'trace']);\n\nfunction envLogLevel(v: string): Config['log']['level'] {\n return LOG_LEVELS.has(v as Config['log']['level']) ? (v as Config['log']['level']) : 'info';\n}\n\nconst ENV_MAP: Record<string, (cfg: PartialConfig, val: string) => void> = {\n WRONGSTACK_PROVIDER: (c, v) => {\n c.provider = v;\n if (c._envSource === undefined) c._envSource = new Set();\n c._envSource.add('provider');\n },\n WRONGSTACK_MODEL: (c, v) => {\n c.model = v;\n if (c._envSource === undefined) c._envSource = new Set();\n c._envSource.add('model');\n },\n WRONGSTACK_API_KEY: (c, v) => {\n c.apiKey = v;\n if (c._envSource === undefined) c._envSource = new Set();\n c._envSource.add('apiKey');\n },\n WRONGSTACK_BASE_URL: (c, v) => {\n c.baseUrl = v;\n if (c._envSource === undefined) c._envSource = new Set();\n c._envSource.add('baseUrl');\n },\n WRONGSTACK_LOG_LEVEL: (c, v) => {\n /* v8 ignore next -- defensive: config defaults always seed c.log before env handlers run */\n if (!c.log) c.log = { level: 'info' };\n c.log.level = envLogLevel(v);\n },\n WRONGSTACK_INDEX_ON_START: (c, v) => {\n c.indexing = { ...defaultIndexing, ...c.indexing, onSessionStart: envBool(v) };\n },\n WRONGSTACK_INDEX_ON_EDIT: (c, v) => {\n c.indexing = { ...defaultIndexing, ...c.indexing, onEdit: envBool(v) };\n },\n WRONGSTACK_INDEX_WATCH: (c, v) => {\n c.indexing = { ...defaultIndexing, ...c.indexing, watchExternal: envBool(v) };\n },\n};\n\nconst defaultIndexing = {\n onSessionStart: true,\n onEdit: true,\n watchExternal: true,\n debounceMs: 400,\n} as const;\n\ntype PartialConfig = Partial<Config> & {\n providers?: Record<\n string,\n { apiKey?: string | undefined; baseUrl?: string | undefined; type?: string | undefined }\n >;\n /** Fields that came from environment variables \u2014 must not be persisted. */\n _envSource?: Set<string> | undefined;\n};\n\n/**\n * Top-level config keys a REPO-COMMITTED `<project>/.wrongstack/config.json`\n * (the `inProjectConfig` layer) IS permitted to set. The in-project config\n * is attacker-controllable (it ships inside a cloned/pulled repository), so\n * every other field is denied by default. Anything not in this list is\n * stripped by `stripUnsafeInProjectFields()` before the merge.\n *\n * Why an allow-list and not a deny-list? A deny-list of N known-bad keys is\n * structurally incomplete: any new field added to `Config` without a matching\n * edit to the deny list silently becomes attacker-controllable, and the next\n * field that carries an executable string or a credential immediately turns\n * `<project>/.wrongstack/config.json` into an RCE / secret-exfiltration\n * vector the moment someone clones a malicious repo. An allow-list inverts\n * that \u2014 new fields are denied by default and must be explicitly added, so a\n * forgotten update is a safe default instead of an unsafe one.\n *\n * Each entry below is a benign user-preference that a project author may\n * legitimately want to pin for everyone who works in the repo:\n *\n * - `version` \u2014 schema marker required for any config merge.\n * - `model` \u2014 model id (also settable via env / CLI).\n * - `cwd` \u2014 working-directory hint (UX, not a permission).\n * - `context` \u2014 compaction thresholds, mode, preserveK.\n * - `tools` \u2014 iteration / timeouts / restrictToProjectRoot.\n * - `features` \u2014 feature toggles (display-only side effects).\n * - `autonomy` \u2014 autoProceedDelayMs, thinkingWord.\n * - `indexing` \u2014 onSessionStart / onEdit / debounceMs.\n * - `session` \u2014 audit level + sampling.\n * - `log` \u2014 log level.\n * - `launch` \u2014 saved launch prefs.\n * - `nextPrediction` \u2014 toggle `/next` after-turn suggestions.\n * - `hints` \u2014 toggle startup hints.\n * - `debugStream` \u2014 verbose SSE dump (noisy, not security-sensitive).\n * - `configScope` \u2014 where settings persist.\n * - `maxConcurrent` \u2014 fleet concurrency limit.\n * - `fallbackModels` \u2014 model references tried on 429/5xx.\n * - `fallbackProfiles` \u2014 named fallback chains.\n * - `favoriteModels` \u2014 model references preferred for display/routing.\n * - `favoriteModelsOnly` \u2014 restrict auto-derived chains to favorite models.\n * - `fallbackAuto` \u2014 derived-fallback toggle.\n * - `models` \u2014 custom model definitions (data, not code).\n * - `modelMatrix` \u2014 per-task model matrix.\n * - `circuitBreaker` \u2014 process circuit-breaker config (process gating).\n * - `adaptiveConcurrency` \u2014 adaptive concurrency controller.\n * - `modelRuntime` \u2014 runtime reasoning/cache/parameters.\n * - `superMemory` \u2014 benign local memory storage/injection/hygiene knobs.\n *\n * Fields deliberately NOT in the allow-list (and therefore always stripped\n * from `<project>/.wrongstack/config.json`) \u2014 see `KNOWN_DENIED_IN_PROJECT`\n * below for the reason each is unsafe.\n */\nconst IN_PROJECT_ALLOWED_KEYS: ReadonlySet<string> = new Set([\n 'version',\n 'model',\n 'cwd',\n 'context',\n 'tools',\n 'features',\n 'superMemory',\n 'skills',\n 'autonomy',\n 'indexing',\n 'session',\n 'log',\n 'launch',\n 'nextPrediction',\n 'hints',\n 'debugStream',\n 'configScope',\n 'maxConcurrent',\n 'uiLocale',\n 'fallbackModels',\n 'fallbackProfiles',\n 'favoriteModels',\n 'favoriteModelsOnly',\n 'fallbackAuto',\n 'models',\n 'modelMatrix',\n 'circuitBreaker',\n 'adaptiveConcurrency',\n 'modelRuntime',\n]);\n\n/**\n * Top-level config keys that exist on `Config` but MUST NEVER be settable\n * from a repo-committed `<project>/.wrongstack/config.json`. Each entry pairs\n * the field name with the specific way a malicious repo would abuse it. This\n * list is documentation + exhaustiveness checking; the runtime enforcement\n * is the *allow-list* above (anything not in the allow-list is stripped).\n *\n * - `provider` \u2014 set provider id to a custom / evil implementation \u2192\n * intercepts every prompt and response.\n * - `apiKey` \u2014 overrides the user's API key with attacker-controlled\n * value, exfiltrating prompts to the attacker.\n * - `baseUrl` \u2014 redirects the provider endpoint so the user's real\n * decrypted API key is sent to the attacker's server.\n * - `providers` \u2014 per-provider `apiKey`/`baseUrl`/`oauthConfig` map,\n * same endpoint-redirect + secret-exfiltration vector.\n * - `mcpServers` \u2014 arbitrary `command` + `args` + `env` spawned at boot.\n * - `hooks` \u2014 shell command arrays attached to lifecycle events.\n * - `plugins` \u2014 npm package names dynamically loaded into the agent\n * process at boot.\n * - `sync` \u2014 carries `githubToken` (credential) and the repo\n * the user's sync push targets.\n * - `yolo` \u2014 flips off every permission confirmation prompt so a\n * malicious agent turn can run `bash` / `write` /\n * `install` without user approval.\n * - `extensions` \u2014 per-plugin namespaced config; the LSP plugin's\n * `servers[].command` is spawned on autoStart, and\n * arbitrary plugin configs can carry their own\n * credential / command fields \u2192 RCE / secret exposure.\n * - `hq` \u2014 carries `token` (HQ client credential) and `url`\n * (HQ endpoint, similar to `baseUrl`).\n * - `git` \u2014 carries `identity` (commit author/committer env\n * injection); a malicious repo could spoof who appears\n * to have authored the user's commits.\n */\nconst KNOWN_DENIED_IN_PROJECT: ReadonlyArray<{ key: string; reason: string }> = [\n { key: 'provider', reason: 'Provider id override; can intercept prompts/responses.' },\n { key: 'apiKey', reason: 'Overrides user API key; exfiltrates prompts.' },\n { key: 'baseUrl', reason: 'Redirects provider endpoint; leaks real API key.' },\n { key: 'providers', reason: 'Per-provider apiKey/baseUrl/oauthConfig; same redirect/exfil.' },\n { key: 'mcpServers', reason: 'Arbitrary command/args/env spawned at boot (RCE).' },\n { key: 'hooks', reason: 'Shell command arrays on lifecycle events (RCE).' },\n { key: 'plugins', reason: 'Dynamic npm package load at boot (RCE).' },\n { key: 'sync', reason: 'Carries githubToken credential and target repo.' },\n { key: 'yolo', reason: 'Disables all permission confirmation prompts.' },\n { key: 'extensions', reason: 'Per-plugin config can carry command/credential fields.' },\n { key: 'hq', reason: 'Carries HQ client token credential and endpoint URL.' },\n { key: 'acp', reason: 'Per-agent ACP command/args/env override \u2192 arbitrary command exec (RCE).' },\n {\n key: 'fleet',\n reason:\n 'Fleet supervision knobs: a repo-committed config could enable autonomous subagent spawning/steering/termination and mailbox traffic on the victim machine.',\n },\n {\n key: 'brain',\n reason:\n 'Brain decision-layer knobs: a repo-committed config could raise the autonomy risk ceiling, switch the Brain to headless (removing the human tier), or reroute Brain decisions to an attacker-chosen provider/model.',\n },\n {\n key: 'git',\n reason:\n 'Carries git.identity (GIT_AUTHOR_*/GIT_COMMITTER_* injection): a repo-committed config could spoof the author identity written into the victim\\'s commit history (impersonation).',\n },\n];\n\n/**\n * Every top-level key that exists on the `Config` interface. This is the\n * *ground truth* used by `assertInProjectAllowListComplete()` to detect when\n * a new field has been added to `Config` without a corresponding decision\n * about whether it is safe for an attacker-controllable source to set it.\n *\n * Each entry must appear in EXACTLY ONE of:\n * - `IN_PROJECT_ALLOWED_KEYS` \u2014 explicitly safe for in-project config\n * - `KNOWN_DENIED_IN_PROJECT` \u2014 explicitly documented as unsafe\n *\n * The drift-check function below throws at runtime / test time when this\n * invariant is violated, so a forgotten update fails loudly instead of\n * silently widening the attack surface.\n */\nconst KNOWN_CONFIG_TOP_LEVEL_KEYS: ReadonlySet<string> = new Set([\n 'version',\n 'provider',\n 'model',\n 'apiKey',\n 'baseUrl',\n 'maxConcurrent',\n 'uiLocale',\n 'providers',\n 'models',\n 'modelMatrix',\n 'context',\n 'tools',\n 'mcpServers',\n 'fallbackModels',\n 'fallbackProfiles',\n 'favoriteModels',\n 'favoriteModelsOnly',\n 'fallbackAuto',\n 'hooks',\n 'plugins',\n 'log',\n 'features',\n 'superMemory',\n 'skills',\n 'yolo',\n 'nextPrediction',\n 'cwd',\n 'autonomy',\n 'hints',\n 'debugStream',\n 'configScope',\n 'indexing',\n 'circuitBreaker',\n 'adaptiveConcurrency',\n 'launch',\n 'session',\n 'modelRuntime',\n 'hq',\n 'sync',\n 'extensions',\n 'acp',\n 'fleet',\n 'brain',\n 'git',\n]);\n\n/**\n * Assert that the allow-list and deny-list together cover every top-level\n * field of `Config`. Throws on drift so the failure is loud at test time and\n * at first boot, not a silent widening of the attack surface. Exported so\n * tests (and any consumer building tooling on top of this) can call it\n * explicitly; `stripUnsafeInProjectFields()` also calls it lazily on its\n * first invocation so the guarantee is structural, not test-only.\n *\n * The check is two-sided:\n * 1. Every key in `KNOWN_CONFIG_TOP_LEVEL_KEYS` is either allowed or\n * explicitly documented as denied (catches: \"added a new field but\n * forgot to decide\").\n * 2. Every entry in `KNOWN_DENIED_IN_PROJECT` actually exists on Config\n * (catches: \"left a stale denied-field entry behind after a rename\").\n * 3. The two lists are disjoint (catches: \"put the same field in both\n * lists; allow-list silently wins and the deny docs lie\").\n */\nexport function assertInProjectAllowListComplete(): void {\n const missingFromBoth: string[] = [];\n for (const key of KNOWN_CONFIG_TOP_LEVEL_KEYS) {\n if (IN_PROJECT_ALLOWED_KEYS.has(key)) continue;\n const denied = KNOWN_DENIED_IN_PROJECT.find((d) => d.key === key);\n if (!denied) missingFromBoth.push(key);\n }\n const staleDenials = KNOWN_DENIED_IN_PROJECT.filter(\n (d) => !KNOWN_CONFIG_TOP_LEVEL_KEYS.has(d.key),\n ).map((d) => d.key);\n const duplicate = KNOWN_DENIED_IN_PROJECT.filter((d) => IN_PROJECT_ALLOWED_KEYS.has(d.key)).map(\n (d) => d.key,\n );\n\n const problems: string[] = [];\n if (missingFromBoth.length > 0) {\n problems.push(\n `new Config field(s) not classified as allowed or denied for in-project config: ` +\n missingFromBoth.join(', ') +\n '. Add each to IN_PROJECT_ALLOWED_KEYS (if safe) or KNOWN_DENIED_IN_PROJECT (with a reason).',\n );\n }\n if (staleDenials.length > 0) {\n problems.push(\n `KNOWN_DENIED_IN_PROJECT references keys that no longer exist on Config: ` +\n staleDenials.join(', ') +\n '. Remove them or restore the field on Config.',\n );\n }\n if (duplicate.length > 0) {\n problems.push(\n `field(s) appear in BOTH IN_PROJECT_ALLOWED_KEYS and KNOWN_DENIED_IN_PROJECT: ` +\n duplicate.join(', ') +\n '. The allow-list wins at runtime; remove from one of the two.',\n );\n }\n if (problems.length > 0) {\n throw new Error(\n `stripUnsafeInProjectFields drift check failed:\\n - ${problems.join('\\n - ')}`,\n );\n }\n}\n\nlet driftChecked = false;\n\n/**\n * Remove forbidden top-level keys from a repo-committed in-project config\n * before it is merged. Returns a new object; the original is not mutated.\n * Emits a warning (and a `config.read` failure-style event) naming the\n * stripped keys so the behavior is observable rather than silent.\n *\n * On first invocation, runs `assertInProjectAllowListComplete()` to verify\n * the allow-list + deny-list together still cover every top-level field of\n * `Config`. The check is idempotent and the result is memoized so the cost\n * is paid at most once per process. The assertion throws on drift, which\n * surfaces the issue at boot in production and at first test invocation in\n * CI \u2014 both observable, never silent.\n */\nexport function stripUnsafeInProjectFields(\n inProject: PartialConfig,\n sourcePath: string,\n warn: (msg: string) => void = (msg) => console.warn(msg),\n): PartialConfig {\n if (!driftChecked) {\n assertInProjectAllowListComplete();\n driftChecked = true;\n }\n const stripped: string[] = [];\n const out: PartialConfig = {};\n for (const [k, v] of Object.entries(inProject)) {\n if (IN_PROJECT_ALLOWED_KEYS.has(k)) {\n (out as Record<string, unknown>)[k] = v;\n continue;\n }\n stripped.push(k);\n }\n\n // Nested strip: `tools` is allow-listed (it carries benign limits), but\n // `tools.exec.allow` EXPANDS what the agent may execute \u2014 never honor that\n // from an attacker-controllable repo config. Remove it while preserving\n // `tools.exec.deny` (removing commands only narrows, so it is always safe).\n //\n // `tools.exec.danger.bypass` is the same threat model: a bypass list\n // weakens the heuristic danger gate on a per-rule basis. A malicious\n // repo that auto-loaded its own bypass rules could silently disarm\n // safety checks for anyone who clones it. Same strip semantics.\n //\n // Clone the affected objects so the caller's input is not mutated.\n const outTools = (out as Record<string, unknown>)['tools'];\n if (outTools && typeof outTools === 'object') {\n const execCfg = (outTools as Record<string, unknown>)['exec'];\n if (execCfg && typeof execCfg === 'object') {\n const hasAllow = 'allow' in (execCfg as Record<string, unknown>);\n const hasDanger = 'danger' in (execCfg as Record<string, unknown>);\n if (hasAllow || hasDanger) {\n const clonedExec: Record<string, unknown> = { ...(execCfg as Record<string, unknown>) };\n if (hasAllow) {\n delete clonedExec['allow'];\n stripped.push('tools.exec.allow');\n }\n if (hasDanger) {\n // Strip the whole `danger` object \u2014 `ExecDangerConfig` only\n // has `bypass` today, and bypass is the unsafe field, so\n // stripping the parent is equivalent and forward-compat with\n // any future fields added under `danger` that we don't yet\n // know about. Better to be conservative: anything new under\n // `danger` from a repo config is rejected until it gets an\n // explicit allow decision.\n delete clonedExec['danger'];\n stripped.push('tools.exec.danger');\n }\n (out as Record<string, unknown>)['tools'] = {\n ...(outTools as Record<string, unknown>),\n exec: clonedExec,\n };\n }\n }\n }\n\n // Nested strip: `skills` is allow-listed (benign prefs), but `skills.extraDirs`\n // points the loader at ARBITRARY directories to scan and inject into the\n // prompt \u2014 never honor that from an attacker-controllable repo config\n // (prompt-injection / read-into-prompt vector). `readClaudeSkills` and `mode`\n // are safe prefs and survive. `skills.registryUrl` is also stripped: it points\n // the skill-search HTTP client at an arbitrary URL whose parsed response flows\n // into the prompt (SSRF + prompt-injection vector). Safe prefs\n // (`readClaudeSkills`, `mode`, `eagerMaxChars`) survive.\n const outSkills = (out as Record<string, unknown>)['skills'];\n if (outSkills && typeof outSkills === 'object') {\n const skillsRec = outSkills as Record<string, unknown>;\n const needsClone = 'extraDirs' in skillsRec || 'registryUrl' in skillsRec;\n if (needsClone) {\n const clonedSkills = { ...skillsRec };\n if ('extraDirs' in clonedSkills) {\n delete clonedSkills['extraDirs'];\n stripped.push('skills.extraDirs');\n }\n if ('registryUrl' in clonedSkills) {\n delete clonedSkills['registryUrl'];\n stripped.push('skills.registryUrl');\n }\n (out as Record<string, unknown>)['skills'] = clonedSkills;\n }\n }\n\n // `superMemory` is benign except for `storage.directory`: that value is a\n // filesystem write destination and accepts absolute paths in trusted user\n // config. A repo-committed config must not redirect memory logs outside the\n // project, so only the destination override is stripped.\n const outSuperMemory = (out as Record<string, unknown>)['superMemory'];\n if (outSuperMemory && typeof outSuperMemory === 'object') {\n const storage = (outSuperMemory as Record<string, unknown>)['storage'];\n if (storage && typeof storage === 'object' && 'directory' in (storage as Record<string, unknown>)) {\n const clonedStorage = { ...(storage as Record<string, unknown>) };\n delete clonedStorage['directory'];\n (out as Record<string, unknown>)['superMemory'] = {\n ...(outSuperMemory as Record<string, unknown>),\n storage: clonedStorage,\n };\n stripped.push('superMemory.storage.directory');\n }\n }\n\n if (stripped.length > 0) {\n warn(\n JSON.stringify({\n level: 'warn',\n event: 'config.in_project_unsafe_fields_ignored',\n path: sourcePath,\n ignoredKeys: stripped,\n message:\n `Ignored ${stripped.length} field(s) from the repo-committed config ` +\n `\"${sourcePath}\": ${stripped.join(', ')}. ` +\n `Only a small allow-list of benign preferences (model, context, tools limits, ` +\n `features, \u2026) may be set by <project>/.wrongstack/config.json. ` +\n `Everything else must live in your personal ~/.wrongstack/config.json.`,\n timestamp: new Date().toISOString(),\n }),\n );\n }\n return out;\n}\n\n/**\n * Compare two absolute filesystem paths for identity. Normalizes separators and\n * `.`/`..` segments via `path.resolve`, and folds case on win32 (and darwin,\n * whose default APFS/HFS+ volumes are case-insensitive) so the same file\n * reached through differently-cased drive/dir spellings still compares equal \u2014\n * the same Windows path-casing hazard that bit the core token symbols.\n */\nfunction samePath(a: string, b: string): boolean {\n let ra = path.resolve(a);\n let rb = path.resolve(b);\n if (process.platform === 'win32' || process.platform === 'darwin') {\n ra = ra.toLowerCase();\n rb = rb.toLowerCase();\n }\n return ra === rb;\n}\n\n/**\n * Config-layer deep merge \u2014 delegates to the shared utility with\n * `arrayMode: 'concat-primitives'` and optional debug logging for\n * non-primitive array replacements.\n */\nfunction deepMerge<T, TPatch extends object>(base: T, patch: TPatch): T {\n const opts: DeepMergeOptions = { arrayMode: 'concat-primitives' };\n if (envBoolOptional(process.env.WRONGSTACK_DEBUG_CONFIG)) {\n opts.onNonPrimitiveArrayReplace = (key, existingLen, patchLen) => {\n console.warn(\n `[config] Non-primitive array for \"${key}\" replaced (global + local config merge). ` +\n `Global entries: ${existingLen}, local entries: ${patchLen}.`,\n );\n };\n }\n return deepMergeCore(\n base as Record<string, unknown>,\n patch as Record<string, unknown>,\n opts,\n ) as T;\n}\n\n/**\n * A single config source. Higher priority wins in merges.\n * Sources are applied in priority order (lowest first), so a source\n * with priority=10 overrides one with priority=1.\n */\nexport interface ConfigSource {\n /** Unique name for debugging and error messages. */\n name: string;\n /** Lower numbers merge first, higher numbers override lower. Default: 50. */\n priority?: number | undefined;\n /**\n * Read the raw config patch. Return an empty object if unavailable.\n * Errors are surfaced but do not abort loading \u2014 the source is skipped.\n */\n read(): Promise<Partial<Config>>;\n}\n\nexport interface ConfigLoaderOptions {\n paths: WstackPaths;\n strict?: boolean | undefined;\n vault?: SecretVault | undefined;\n /** Extra sources merged after the built-in layers. */\n sources?: ConfigSource[] | undefined;\n events?: EventBus;\n traceId?: string;\n /** Logger for structured warning/error events. When omitted, falls back to console.warn. */\n logger?: Logger | undefined;\n}\n\ninterface MemoizedConfigSource {\n mtimeMs: number | null;\n value: PartialConfig;\n}\n\nexport class DefaultConfigLoader implements ConfigLoader {\n private readonly paths: WstackPaths;\n private readonly strict: boolean;\n private readonly vault: SecretVault | undefined;\n private readonly extraSources: ConfigSource[];\n private readonly events: EventBus | undefined;\n private readonly traceId: string | undefined;\n private readonly logger: Logger | undefined;\n private readonly jsonCache = new Map<string, MemoizedConfigSource>();\n\n constructor(opts: ConfigLoaderOptions) {\n this.paths = opts.paths;\n this.strict = opts.strict ?? false;\n this.vault = opts.vault;\n this.extraSources = opts.sources ?? [];\n this.events = opts.events;\n this.traceId = opts.traceId;\n this.logger = opts.logger;\n }\n\n /**\n * Emit a structured warning. Uses the configured Logger when available;\n * falls back to console.warn(JSON) so warnings are never silently dropped\n * during early boot (before a Logger is constructed).\n */\n private logWarn(msg: string, ctx?: Record<string, unknown>): void {\n if (this.logger) {\n this.logger.warn(msg, ctx);\n } else {\n console.warn(JSON.stringify({ ...ctx, message: msg, timestamp: new Date().toISOString() }));\n }\n }\n\n async load(\n opts: { cliFlags?: Partial<Config> | undefined; cwd?: string | undefined; skipIdentityValidation?: boolean } = {},\n ): Promise<Config> {\n let cfg: PartialConfig = { ...BEHAVIOR_DEFAULTS } as PartialConfig;\n\n // Materialize behavior/settings defaults into the trusted global config\n // before env vars or CLI flags are applied, so first boot creates a real\n // ~/.wrongstack/config.json without persisting ephemeral overrides.\n await this.ensureGlobalDefaults();\n\n // Layer 2, 3 & 3b: global + project-local + in-project config \u2014 read in parallel.\n // inProjectConfig (<project>/.wrongstack/config.json) merges AFTER\n // projectLocalConfig so it takes priority (user-intended > auto-cached).\n //\n // When the project root *is* the user's home (e.g. launching from `~`),\n // `<projectRoot>/.wrongstack/config.json` resolves to the very same file as\n // the trusted global config. Reading it again as the untrusted in-project\n // layer would strip `provider`/`apiKey`/\u2026 from the user's *own* file and\n // emit a spurious `config.in_project_unsafe_fields_ignored` warning \u2014 even\n // though the trusted global layer already merged those fields. Skip the\n // in-project read entirely on collision so trust isn't applied to a file\n // the user fully controls.\n const inProjectCollides =\n samePath(this.paths.inProjectConfig, this.paths.globalConfig) ||\n samePath(this.paths.inProjectConfig, this.paths.projectLocalConfig);\n const [global, local, inProject] = await Promise.all([\n this.readJson(this.paths.globalConfig),\n this.readJson(this.paths.projectLocalConfig),\n inProjectCollides\n ? Promise.resolve({} as PartialConfig)\n : this.readJson(this.paths.inProjectConfig),\n ]);\n cfg = deepMerge(cfg, global);\n cfg = deepMerge(cfg, local);\n // The in-project config is repo-committed and therefore attacker-\n // controllable. Strip credential/endpoint/code-execution fields before\n // merging so a malicious repo cannot redirect the provider endpoint\n // (API-key exfiltration) or auto-run an MCP server / hook (RCE) on launch.\n cfg = deepMerge(\n cfg,\n stripUnsafeInProjectFields(\n inProject,\n this.paths.inProjectConfig,\n this.logger ? (msg: string) => this.logger!.warn(msg) : undefined,\n ),\n );\n\n // Layer 4: env vars\n for (const [key, fn] of Object.entries(ENV_MAP)) {\n const v = process.env[key];\n if (v) fn(cfg, v);\n }\n\n // Layer 5: extra sources \u2014 sorted by priority (lowest first).\n // When priorities tie, sort by name for deterministic order.\n const sorted = [...this.extraSources].sort((a, b) => {\n const pd = (a.priority ?? 50) - (b.priority ?? 50);\n if (pd !== 0) return pd;\n return a.name.localeCompare(b.name);\n });\n for (const src of sorted) {\n try {\n const patch = await src.read();\n if (patch && Object.keys(patch).length > 0) {\n cfg = deepMerge(cfg, patch);\n }\n } catch (err) {\n // Best-effort: skip failing sources so one bad source doesn't block boot.\n this.logWarn(\n 'Config source load failed',\n { event: 'config.source_load_failed', source: src.name, message: toErrorMessage(err) },\n );\n }\n }\n\n // Layer 6: CLI flags\n if (opts.cliFlags) {\n cfg = deepMerge(cfg, opts.cliFlags);\n }\n\n // Decrypt apiKey-like fields if a vault is configured.\n if (this.vault) {\n cfg = decryptConfigSecrets(cfg, this.vault);\n }\n\n // Multi-key resolution: when a provider has `apiKeys[]` configured,\n // mirror the active entry into `apiKey` so downstream construction\n // code (provider registry, wire adapters) needs no changes. Honors\n // `activeKey` (by label), else falls back to the first entry. A\n // pre-existing `apiKey` set by env/CLI flags wins so an explicit\n // override still beats the saved list.\n if (cfg.providers) {\n for (const pcfg of Object.values(cfg.providers)) {\n if (!pcfg || typeof pcfg !== 'object') continue;\n const rawKeys = (pcfg as { apiKeys?: unknown | undefined }).apiKeys;\n if (!Array.isArray(rawKeys) || rawKeys.length === 0) continue;\n // Each apiKeys entry came from arbitrary JSON. Filter to entries\n // that actually have a string apiKey + label so a malformed array\n // (null entry, missing field) doesn't crash the .find / chosen.apiKey\n // path below.\n const keys = rawKeys.filter(\n (k): k is { label: string; apiKey: string } =>\n !!k &&\n typeof k === 'object' &&\n typeof (k as { label?: unknown | undefined }).label === 'string' &&\n typeof (k as { apiKey?: unknown | undefined }).apiKey === 'string',\n );\n if (keys.length === 0) continue;\n const existing = (pcfg as { apiKey?: string | undefined }).apiKey;\n if (existing && existing.length > 0) continue;\n const activeLabel = (pcfg as { activeKey?: string | undefined }).activeKey;\n const chosen = activeLabel\n ? (keys.find((k) => k.label === activeLabel) ?? keys[0])\n : keys[0];\n if (chosen?.apiKey) {\n (pcfg as { apiKey?: string | undefined }).apiKey = chosen.apiKey;\n }\n }\n }\n\n this.validateBehavior(cfg);\n if (this.strict && !opts.skipIdentityValidation) {\n this.validateIdentity(cfg);\n }\n // In strict mode, validateIdentity has confirmed provider/model are set;\n // it's safe to assert the full Config contract. In non-strict mode the\n // caller (e.g. early-boot wizard) accepts a Partial and constructs the\n // provider later, so we deliberately return without the cast.\n return Object.freeze(cfg) as Config;\n }\n\n private async ensureGlobalDefaults(): Promise<void> {\n const fp = this.paths.globalConfig;\n const t0 = Date.now();\n try {\n await withFileLock(fp, async () => {\n let parsed: Record<string, unknown>;\n try {\n const raw = await fs.readFile(fp, 'utf8');\n const result = safeParse<unknown>(raw);\n if (!result.ok || !isPlainRecord(result.value)) {\n // readJson() below owns the user-visible parse warning. Do not\n // overwrite a malformed config while trying to seed defaults.\n return;\n }\n parsed = result.value;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n this.events?.emit('storage.error', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'ensure_defaults',\n outcome: 'failure',\n error: storageErrorString(err),\n recoverable: false,\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n this.logWarn(\n 'Config defaults read failed',\n { event: 'config.defaults_read_failed', path: fp, message: toErrorMessage(err) },\n );\n return;\n }\n parsed = {};\n }\n\n const { value, changed } = fillMissingDefaults(\n parsed,\n BEHAVIOR_DEFAULTS as Record<string, unknown>,\n );\n if (!changed) return;\n\n await atomicWrite(fp, JSON.stringify(value, null, 2), { mode: 0o600 });\n this.events?.emit('storage.write', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'ensure_defaults',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n });\n } catch (err) {\n this.events?.emit('storage.error', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'ensure_defaults',\n outcome: 'failure',\n error: storageErrorString(err),\n recoverable: false,\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n this.logWarn(\n 'Config defaults write failed',\n { event: 'config.defaults_write_failed', path: fp, message: toErrorMessage(err) },\n );\n }\n }\n\n /**\n * Persist a sync config to ~/.wrongstack/sync.json, with the token encrypted\n * by the vault (if provided). The file is isolated from the main config\n * hierarchy to prevent accidental commits.\n */\n async persistSyncConfig(cfg: SyncConfig): Promise<void> {\n let toWrite = { ...cfg };\n if (this.vault && toWrite.githubToken && !toWrite.githubToken.startsWith('enc:')) {\n // Re-encrypt if plaintext (e.g. came from in-memory configStore update\n // rather than direct /sync enable call). Idempotent for already-encrypted.\n toWrite = { ...toWrite, githubToken: this.vault.encrypt(toWrite.githubToken) };\n }\n const fp = this.paths.syncConfig;\n const t0 = Date.now();\n try {\n await atomicWrite(fp, JSON.stringify(toWrite, null, 2), { mode: 0o600 });\n this.events?.emit('storage.write', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'persist_sync',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n } catch (err) {\n this.events?.emit('storage.error', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'persist_sync',\n outcome: 'failure',\n error: storageErrorString(err),\n recoverable: false,\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n throw err;\n }\n }\n\n /**\n * Read ~/.wrongstack/sync.json (encrypted GitHub token storage) and decrypt\n * the token if a vault is available. Returns null if the file doesn't exist.\n * This is separate from main config loading because sync.json is intentionally\n * isolated \u2014 it should never be part of project-local or env-driven config.\n */\n async loadSyncConfig(): Promise<SyncConfig | null> {\n const fp = this.paths.syncConfig;\n const t0 = Date.now();\n try {\n const raw = await fs.readFile(fp, 'utf8');\n const parsed = safeParse<SyncConfig>(raw);\n if (!parsed.ok || !parsed.value) {\n this.events?.emit('storage.read', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'load_sync',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'parse error or empty file',\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return null;\n }\n\n // Decrypt the token if vault is available (field name matches secret pattern)\n if (this.vault) {\n const decrypted = decryptConfigSecrets({ sync: parsed.value } as PartialConfig, this.vault);\n const result = (decrypted as { sync: SyncConfig }).sync ?? null;\n this.events?.emit('storage.read', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'load_sync',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return result;\n }\n this.events?.emit('storage.read', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'load_sync',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return parsed.value;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n // Non-ENOENT failures (EACCES, ENOSPC, etc.) \u2014 emit storage.read failure, then return null\n this.events?.emit('storage.read', {\n sessionId: '~config~',\n store: 'config',\n filePath: fp,\n operation: 'load_sync',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: storageErrorString(err),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n this.logWarn(\n 'Config sync load failed',\n { event: 'config.sync_load_failed', message: toErrorMessage(err) },\n );\n return null;\n }\n }\n\n private async readJson(file: string): Promise<PartialConfig> {\n const t0 = Date.now();\n let mtimeMs: number | null = null;\n try {\n const stat = await fs.stat(file);\n mtimeMs = stat.mtimeMs;\n const cached = this.jsonCache.get(file);\n if (cached && cached.mtimeMs === mtimeMs) {\n return structuredClone(cached.value);\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n this.jsonCache.set(file, { mtimeMs: null, value: {} });\n return {};\n }\n this.events?.emit('storage.read', {\n sessionId: '~config~',\n store: 'config',\n filePath: file,\n operation: 'read_json',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: storageErrorString(err),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n this.logWarn(\n 'Config read failed (stat)',\n { event: 'config.read_failed', path: file, message: toErrorMessage(err) },\n );\n return {};\n }\n\n let raw: string;\n try {\n raw = await fs.readFile(file, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n this.events?.emit('storage.read', {\n sessionId: '~config~',\n store: 'config',\n filePath: file,\n operation: 'read_json',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: storageErrorString(err),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n this.logWarn(\n 'Config read failed (read)',\n { event: 'config.read_failed', path: file, message: toErrorMessage(err) },\n );\n }\n this.jsonCache.set(file, { mtimeMs: null, value: {} });\n return {};\n }\n const parsed = safeParse<PartialConfig>(raw);\n if (!parsed.ok || !parsed.value) {\n this.events?.emit('storage.read', {\n sessionId: '~config~',\n store: 'config',\n filePath: file,\n operation: 'read_json',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'parse error or empty file',\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n this.logWarn(\n 'Config parse failed \u2014 falling back to defaults',\n { event: 'config.parse_failed', path: file },\n );\n return {};\n }\n this.jsonCache.set(file, { mtimeMs, value: structuredClone(parsed.value) });\n return parsed.value;\n }\n\n private validateBehavior(cfg: PartialConfig): void {\n /* v8 ignore start -- defensive: config defaults always seed version:1 before validation */\n if (cfg.version === undefined)\n throw new ConfigError({\n message: 'Config: missing version field',\n code: ERROR_CODES.CONFIG_INVALID,\n context: { field: 'version' },\n });\n /* v8 ignore stop */\n if (cfg.version !== 1)\n throw new ConfigError({\n message: `Config: unsupported version ${cfg.version}`,\n code: ERROR_CODES.CONFIG_INVALID,\n context: { field: 'version', actual: cfg.version },\n });\n const c = cfg.context;\n if (!c)\n throw new ConfigError({\n message: 'Config: missing context section',\n code: ERROR_CODES.CONFIG_INVALID,\n context: { field: 'context' },\n });\n // A user-edited config.json can land strings here (\"0.6\") and slip past\n // truthiness checks; the `>=` comparison then coerces silently and the\n // threshold ordering check passes for nonsense values. Validate types\n // explicitly so misconfigs surface here, not as confusing failures deep\n // in the auto-compaction logic.\n const fields: Array<keyof typeof c> = ['warnThreshold', 'softThreshold', 'hardThreshold'];\n for (const f of fields) {\n const v = c[f];\n if (typeof v !== 'number' || !Number.isFinite(v)) {\n throw new ConfigError({\n message: `Config: context.${String(f)} must be a finite number (got ${typeof v})`,\n code: ERROR_CODES.CONFIG_INVALID,\n context: { field: `context.${String(f)}`, actualType: typeof v },\n });\n }\n }\n if (c.warnThreshold >= c.softThreshold || c.softThreshold >= c.hardThreshold) {\n throw new ConfigError({\n message: 'Config: context thresholds must satisfy warn < soft < hard',\n code: ERROR_CODES.CONFIG_INVALID,\n context: { warn: c.warnThreshold, soft: c.softThreshold, hard: c.hardThreshold },\n });\n }\n if (c.mode !== undefined && !isContextWindowModeId(c.mode)) {\n // An unknown mode (typo or value from an older/renamed scheme) should not\n // brick the CLI \u2014 unlike the numeric thresholds above there is a safe\n // default. Warn and fall back rather than throwing.\n const known = listContextWindowModes()\n .map((m) => m.id)\n .join(', ');\n this.logWarn(\n `Ignoring unknown context.mode \"${c.mode}\" \u2014 falling back to \"${DEFAULT_CONTEXT_WINDOW_MODE_ID}\"`,\n { event: 'config.unknown_context_mode', mode: c.mode, known },\n );\n c.mode = DEFAULT_CONTEXT_WINDOW_MODE_ID;\n }\n }\n\n private validateIdentity(cfg: PartialConfig): void {\n if (!cfg.provider) {\n throw new ConfigError({\n message: 'Config: no provider configured. Run `wstack init` or set WRONGSTACK_PROVIDER.',\n code: ERROR_CODES.CONFIG_INVALID,\n context: { field: 'provider' },\n });\n }\n if (!cfg.model) {\n throw new ConfigError({\n message: 'Config: no model configured. Run `wstack init` or set WRONGSTACK_MODEL.',\n code: ERROR_CODES.CONFIG_INVALID,\n context: { field: 'model' },\n });\n }\n }\n}\n", "import type { ContextWindowModeId } from './context-window.js';\nimport type { ConfiguredHook, HookEvent } from './hooks.js';\nimport type { WireFamily } from './models-registry.js';\nimport type { CacheTtl, Capabilities, ReasoningEffort } from './provider.js';\nimport type { Permission } from './tool.js';\n\n/**\n * Runtime reasoning controls the user can set per-session/project. Mapped into\n * the provider `Request.reasoning` field by the model-runtime request\n * middleware, gated by the active model's `reasoningConfig` capabilities so\n * unsupported values are omitted (and warned) instead of triggering provider\n * 400s. See `resolveReasoningForRequest()` in packages/core.\n */\nexport interface ModelRuntimeReasoningConfig {\n /**\n * Whether to send explicit reasoning enable/disable.\n * - 'auto' \u2192 do not send explicit fields; provider/model default wins\n * - 'on' \u2192 send `reasoning.enabled = true`\n * - 'off' \u2192 send `reasoning.enabled = false` only when the model supports disable\n */\n mode?: 'auto' | 'on' | 'off' | undefined;\n /** Reasoning effort. Only sent when the model advertises `effortSupported`. */\n effort?: ReasoningEffort | undefined;\n /** Preserve thinking across turns. Only sent when `preserveThinking !== 'unsupported'`. */\n preserve?: boolean | undefined;\n}\n\n/**\n * Runtime prompt-cache controls mapped into `Request.cache`. Currently only the\n * Anthropic TTL toggle (5m vs 1h) is exposed; other providers ignore it.\n */\nexport interface ModelRuntimeCacheConfig {\n ttl?: CacheTtl | undefined;\n}\n\n/**\n * Shared runtime controls applied to every provider request, regardless of host\n * (REPL / TUI / WebUI). The CLI installs a single request-pipeline middleware\n * that reads these and mutates the outgoing `Request`.\n */\nexport interface ModelRuntimeConfig {\n reasoning?: ModelRuntimeReasoningConfig | undefined;\n cache?: ModelRuntimeCacheConfig | undefined;\n /**\n * Generic generation parameters mapped directly onto `Request` fields.\n * Only sent when the active model's `Capabilities` advertise support.\n */\n parameters?: ModelRuntimeParametersConfig | undefined;\n}\n\n/**\n * Generic generation parameters the user can set per-session / per-project.\n * Each field maps to a `Request` field of the same name and is gated by the\n * corresponding `Capabilities` flag so unsupported models don't receive\n * parameters they'd reject.\n */\nexport interface ModelRuntimeParametersConfig {\n /** Top-K sampling (Anthropic, Gemini). Gated by `capabilities.topK`. */\n topK?: number | undefined;\n /** Frequency penalty (OpenAI, Gemini). Gated by `capabilities.frequencyPenalty`. */\n frequencyPenalty?: number | undefined;\n /** Presence penalty (OpenAI, Gemini). Gated by `capabilities.presencePenalty`. */\n presencePenalty?: number | undefined;\n /** Random seed (OpenAI, Gemini). Gated by `capabilities.seed`. */\n seed?: number | undefined;\n /** End-user identifier for abuse monitoring. */\n user?: string | undefined;\n /** Log probabilities (OpenAI, Gemini). Gated by `capabilities.logprobs`. */\n logprobs?: boolean | undefined;\n /** Number of top logprobs to return (OpenAI). Only when `logprobs` is true. */\n topLogprobs?: number | undefined;\n}\n\n/**\n * HQ client connection settings. Same-machine clients can auto-discover the\n * local HQ auth file; remote clients use this config-backed URL/token pair.\n */\nexport interface HqClientConfig {\n /** Enable HQ publishing. Env WRONGSTACK_HQ_ENABLED still overrides at runtime. */\n enabled?: boolean | undefined;\n /** HQ HTTP base URL, e.g. http://host:3499. */\n url?: string | undefined;\n /** Client token for /ws/client. Stored encrypted by SecretVault when persisted. */\n token?: string | undefined;\n /** Optional HQ data dir for same-machine auth.json discovery. */\n dataDir?: string | undefined;\n /** Send raw content previews to HQ instead of redacted previews. */\n rawContent?: boolean | undefined;\n /** Override project display name in HQ. */\n projectAlias?: string | undefined;\n}\n\n/**\n * Token-saving mode tier levels. Controls how aggressively the system prompt\n * is compacted to reduce per-request token consumption.\n *\n * - 'off' \u2014 Full prompt, all tools, complete guidance (no reduction)\n * - 'minimal' \u2014 TIER1 tools only (~10), stripped guidance (~3-4k tokens saved)\n * - 'light' \u2014 Core + memory tools (~14), common patterns, minimal guidance\n * - 'medium' \u2014 Most development tools (~24), some guidance (default when `true`)\n * - 'aggressive' \u2014 Maximum savings before tools become unusable (~4-5k tokens saved)\n */\nexport type TokenSavingTier = 'off' | 'minimal' | 'light' | 'medium' | 'aggressive';\n\n/**\n * Normalize a TokenSavingTier value, handling backward-compatible boolean inputs.\n * - `true` \u2192 'medium' (existing behavior)\n * - `false` \u2192 'off'\n * - string values are returned as-is after validation\n * - `undefined` \u2192 'off'\n */\nexport function normalizeTokenSavingTier(val?: TokenSavingTier | boolean): TokenSavingTier {\n if (val === undefined) return 'off';\n if (typeof val === 'boolean') return val ? 'medium' : 'off';\n const validTiers = new Set<TokenSavingTier>(['off', 'minimal', 'light', 'medium', 'aggressive']);\n return validTiers.has(val) ? val : 'off';\n}\n\n/**\n * Verbosity of fleet/subagent activity streamed into the main TUI chat.\n * See {@link AutonomyConfig.fleetChatVerbosity}.\n */\nexport type FleetChatVerbosity = 'off' | 'compact' | 'full';\n\nexport const FLEET_CHAT_VERBOSITY_VALUES: readonly FleetChatVerbosity[] = ['off', 'compact', 'full'];\n\n/**\n * Resolve the effective fleet-chat verbosity from autonomy config.\n * An explicit `fleetChatVerbosity` wins; otherwise the legacy `streamFleet`\n * boolean is honored (`false` \u2192 'off'); absence of both means 'compact'.\n * `fleetChatVerbosity` must never be given a merge-time default \u2014 the\n * absence of the field is what lets legacy `streamFleet: false` configs\n * keep their intent.\n */\nexport function resolveFleetChatVerbosity(\n autonomy?: Pick<AutonomyConfig, 'fleetChatVerbosity' | 'streamFleet'>,\n): FleetChatVerbosity {\n const explicit = autonomy?.fleetChatVerbosity;\n if (explicit && (FLEET_CHAT_VERBOSITY_VALUES as readonly string[]).includes(explicit)) {\n return explicit;\n }\n if (autonomy?.streamFleet === false) return 'off';\n return 'compact';\n}\n\nexport const DEFAULT_TUI_THINKING_WORD = 'thinking';\nexport const MAX_TUI_THINKING_WORD_LENGTH = 16;\n\n/**\n * Normalize the configurable statusline word shown while the TUI is working.\n * The value must be a single short word; invalid values fall back to the default.\n */\nexport function normalizeTuiThinkingWord(value: unknown): string {\n if (typeof value !== 'string') return DEFAULT_TUI_THINKING_WORD;\n const word = value.trim();\n if (word.length === 0 || word.length > MAX_TUI_THINKING_WORD_LENGTH) {\n return DEFAULT_TUI_THINKING_WORD;\n }\n if (!/^[\\p{L}\\p{N}_-]+$/u.test(word)) return DEFAULT_TUI_THINKING_WORD;\n return word;\n}\n\nexport interface ContextConfig {\n /** Context-window policy mode. Controls compaction thresholds and preservation depth. */\n mode?: ContextWindowModeId | undefined;\n warnThreshold: number;\n softThreshold: number;\n hardThreshold: number;\n /** Enable automatic compaction when thresholds are crossed (default: true). */\n autoCompact?: boolean | undefined;\n /**\n * Model used for LLM-assisted summarization in IntelligentCompactor.\n * Falls back to the main model when omitted.\n */\n summarizerModel?: string | undefined;\n /**\n * Override the effective context window size (in tokens). Use this when\n * you want the compactor to trigger earlier than the provider's actual\n * maxContext. Defaults to the provider's reported maxContext.\n */\n effectiveMaxContext?: number | undefined;\n maxSessionTokens?: number | undefined;\n maxDailyTokens?: number | undefined;\n preserveK: number;\n eliseThreshold: number;\n /** Compactor strategy: 'hybrid' (default, fast rules), 'intelligent' (LLM summarization), 'selective' (LLM-driven selection). */\n strategy?: 'hybrid' | 'intelligent' | 'selective' | undefined;\n /** Enable LLM-driven selective compaction (default: false for backward compat). */\n llmSelector?: boolean | undefined;\n}\n\n/**\n * Runtime configuration for the process circuit breaker (the one owned by the\n * ProcessRegistry that gates `bash`/`exec`). Toggle via `/settings breaker`.\n *\n * The breaker itself is a low-level primitive (`packages/tools/.../circuit-breaker.ts`)\n * that is on by default; this section controls whether the registry actually\n * participates in it and how it auto-recovers.\n */\nexport interface CircuitBreakerRuntimeConfig {\n /**\n * Enable circuit-breaker protection. When false (the default), the breaker\n * is bypassed \u2014 `bash`/`exec` calls always proceed regardless of failure\n * history. When true, the breaker trips on repeated failures / slow calls /\n * bursts and blocks further calls until it recovers.\n */\n enabled?: boolean | undefined;\n /**\n * When the breaker trips, automatically kill all tracked processes AND\n * reset the breaker to closed after this delay (ms). 0 = disabled (manual\n * recovery only via `/kill reset`). Only effective when `enabled` is true.\n * While armed, the statusline shows a live countdown to the kill/reset.\n */\n autoKillResetMs?: number | undefined;\n}\n\n/**\n * Adaptive concurrency controller configuration. When enabled, the controller\n * automatically adjusts `maxConcurrent` based on rate-limit (429) errors:\n * - On 429: halves `maxConcurrent` (floor at 1)\n * - On sustained success (no 429 for `recoveryIntervalMs`): increases `maxConcurrent` by 1\n */\nexport interface AdaptiveConcurrencyConfig {\n /** Enable adaptive concurrency. Default: false (disabled). */\n enabled?: boolean | undefined;\n /**\n * Minimum concurrency floor. The controller never drops below this.\n * Default: 1.\n */\n minConcurrent?: number | undefined;\n /**\n * Maximum concurrency ceiling. The controller never exceeds this.\n * Default: 16 (matches MultiAgentCoordinator default).\n */\n maxConcurrent?: number | undefined;\n /**\n * Multiplicative decrease factor when a 429 is hit.\n * `newConcurrency = floor(currentConcurrency * decreaseFactor)`.\n * Default: 0.5 (halves concurrency).\n */\n decreaseFactor?: number | undefined;\n /**\n * Number of consecutive successful requests before increasing concurrency by 1.\n * Default: 10.\n */\n successThreshold?: number | undefined;\n /**\n * How often (ms) to check for recovery and bump concurrency.\n * Default: 30_000 (30 seconds).\n */\n recoveryIntervalMs?: number | undefined;\n}\n\nexport interface ToolsConfig {\n defaultExecutionStrategy: 'parallel' | 'sequential' | 'smart';\n maxIterations: number;\n iterationTimeoutMs: number;\n /** Hard upper bound for a single tool call timeout. Defaults to 5 minutes. */\n maxToolTimeoutMs?: number | undefined;\n sessionTimeoutMs: number;\n perIterationOutputCapBytes: number;\n /**\n * Per-tool prose budget for the tool's top-level description and usage hint.\n * Missing entries default to \"extend\".\n */\n descriptionMode?: ToolDescriptionModeConfig | undefined;\n /**\n * Per-tool on-screen result rendering mode (terminal / WebUI / TUI).\n * Missing entries default to \"extend\". Independent of `descriptionMode`:\n * `/tool <name> result simple` toggles this without touching the\n * LLM-side description length.\n */\n resultRenderMode?: ToolResultRenderModeConfig | undefined;\n /**\n * Tool names to disable. Disabled tools are excluded from the tool registry\n * (`ToolRegistry.list()` / `get()`), so they do NOT appear in the system\n * prompt's \"## Tool usage\" block \u2014 reducing per-request token consumption.\n * Override per-session with `/tool enable <name>` or re-enable all via\n * `/tool enable-all`.\n */\n disabledTools?: string[] | undefined;\n /**\n * When true (default), the agent automatically extends its iteration\n * limit by 100 when hit. Set to false to require user confirmation.\n */\n autoExtendLimit?: boolean | undefined;\n /**\n * When true, file tools (read/write/edit/grep/glob/install) are confined to\n * the project root and `set_working_dir` may not leave it. Default: false \u2014\n * tools may access paths outside the project root, still subject to each\n * tool's permission tier (writes/edits prompt for confirmation). Toggle via\n * `/settings` (\"Filesystem access\").\n */\n restrictToProjectRoot?: boolean | undefined;\n /**\n * Per-command policy for the `exec` tool's allowlist. The tool ships a\n * curated default allowlist of dev/build commands; this extends or trims it.\n *\n * SECURITY: `allow` EXPANDS what the agent may execute, so it is honored only\n * from TRUSTED config (`~/.wrongstack/config.json`) \u2014 the config loader\n * strips `tools.exec.allow` from the untrusted, repo-committed\n * `<project>/.wrongstack/config.json`. `deny` only ever REMOVES commands, so\n * it is honored from any source.\n */\n exec?: ExecToolConfig | undefined;\n /**\n * Agent-loop repetition detector tuning. The detector watches two signals:\n * consecutive effectively-identical iterations (same tool-name set + inputs\n * + text) and per-call repeats (the same tool invoked with identical\n * arguments N times within a sliding window, even when interleaved with\n * other calls). In the default `steer-then-cut` mode the first detection\n * folds a corrective note into the conversation and lets the run continue;\n * only persistent repetition cuts the turn. Omitted fields use built-in\n * defaults (see DEFAULT_TOOLS_CONFIG.loopDetection).\n */\n loopDetection?: LoopDetectionConfig | undefined;\n}\n\n/** Tuning for the agent-loop repetition detector (`tools.loopDetection`). */\nexport interface LoopDetectionConfig {\n /**\n * `steer-then-cut` (default): inject a corrective note at the steer\n * threshold, cut the turn only if repetition persists to the cut threshold.\n * `cut`: legacy behavior \u2014 hard-stop at the steer threshold, per-call\n * detector disabled. `off`: disable loop detection entirely.\n */\n mode?: 'steer-then-cut' | 'cut' | 'off' | undefined;\n /** Consecutive identical iterations before the detector acts (default 3, min 2). */\n steerThreshold?: number | undefined;\n /**\n * Consecutive identical iterations at which the turn is cut in\n * `steer-then-cut` mode (default steerThreshold + 2, min steerThreshold + 1).\n */\n cutThreshold?: number | undefined;\n /** Sliding window of recent tool calls for per-call repeat detection (default 12, min 4). */\n windowSize?: number | undefined;\n /**\n * Identical (name + canonicalized args) calls within the window that\n * trigger a steer note (default 4, min 2).\n */\n callRepeatThreshold?: number | undefined;\n}\n\n/** Allow/deny extension of the `exec` tool's built-in command allowlist. */\nexport interface ExecToolConfig {\n /**\n * Extra command names to add to the allowlist (e.g. `[\"make\", \"dotnet\"]`).\n * Trusted sources only \u2014 stripped from in-project repo config.\n */\n allow?: string[] | undefined;\n /**\n * Command names to remove from the allowlist. Honored from any source \u2014\n * removing a command can only narrow what runs, so it is always safe.\n */\n deny?: string[] | undefined;\n /**\n * Per-rule bypass for the heuristic danger detector. Each entry is a\n * stable `matchedRule` id (e.g. `rm-recursive`, `git-push-force`); a\n * matched rule whose id is in this list is suppressed.\n *\n * Use case: a project that legitimately runs `rm -rf ./build` on every\n * CI run can add `\"rm-recursive\"` to bypass so the detector stops\n * emitting banners for that one rule \u2014 without disabling it for every\n * other `rm -rf` invocation.\n *\n * **Trusted sources only.** Bypassing a danger rule means the user\n * agreed to a specific destructive pattern; in-project repo config\n * could otherwise be used to silently opt everyone in. The boot path\n * strips this field from `<project>/.wrongstack/config.json` the\n * same way it strips `allow`.\n */\n danger?: ExecDangerConfig | undefined;\n}\n\nexport interface ExecDangerConfig {\n /**\n * List of danger rule ids to skip. Each id corresponds to a rule in\n * `@wrongstack/tools/src/_danger-detect.ts` (e.g. `rm-recursive`,\n * `git-push-force`, `inline-eval`, `sudo`). Unknown ids are ignored\n * (forward-compat: a rule added in a future version can be referenced\n * before the user upgrades).\n */\n bypass?: string[] | undefined;\n}\n\nexport type ToolDescriptionMode = 'extend' | 'simple';\nexport type ToolDescriptionModeConfig = Record<string, ToolDescriptionMode | undefined>;\n\n/**\n * Per-tool on-screen result rendering mode. Independent of\n * {@link ToolDescriptionMode}: `descriptionMode` controls the prose the\n * model sees in the system prompt, `resultRenderMode` controls how the\n * tool's RESULT is printed to the user (terminal / WebUI / TUI).\n *\n * - `simple` \u2014 meta only (filename, line count, exit code). Body is hidden\n * by default; the user can still expand on demand where the renderer\n * supports it.\n * - `extend` \u2014 full preview, up to 10 lines for read-like tools.\n *\n * The two modes are toggled independently via `/tool <name> desc simple`\n * and `/tool <name> result simple`. The legacy `/tool <name> simple`\n * command sets BOTH at once for backward compatibility.\n */\nexport type ToolResultRenderMode = 'extend' | 'simple';\nexport type ToolResultRenderModeConfig = Record<string, ToolResultRenderMode | undefined>;\n\nexport interface ProviderApiKey {\n /** Short human-readable label (e.g. \"personal\", \"work\", \"rate-limit-backup\"). */\n label: string;\n /**\n * The key itself. The field name contains `apiKey` so the secret-vault\n * walker will encrypt it on write and decrypt it on read.\n */\n apiKey: string;\n /** ISO-8601 timestamp the key was added. */\n createdAt: string;\n /**\n * How this credential was obtained.\n * - `api_key` \u2014 manually pasted API key (default)\n * - `oauth` \u2014 OAuth 2.0 device-code / authorization-code flow\n * - `session_token` \u2014 extracted from browser session (ChatGPT web, etc.)\n */\n authMethod?: 'api_key' | 'oauth' | 'session_token' | undefined;\n /** ISO-8601 expiry. When set, the token manager will refresh before this time. */\n expiresAt?: string | undefined;\n /**\n * OAuth refresh token. Stored encrypted by the secret-vault walker because\n * the field name contains `Token` (case-insensitive match by vault).\n */\n refreshToken?: string | undefined;\n /** Token type as returned by the OAuth endpoint (e.g. \"bearer\"). */\n tokenType?: string | undefined;\n /** OAuth scope string (e.g. \"openai.models.read openai.models.use\"). */\n scope?: string | undefined;\n /**\n * ChatGPT account id, extracted from the OAuth access-token JWT\n * (`https://api.openai.com/auth`.chatgpt_account_id). Sent as the\n * `chatgpt-account-id` header by the `openai-codex` wire family. Cached\n * here for display/diagnostics; the provider re-derives it from the live\n * token at request time so it can never go stale after a refresh.\n */\n accountId?: string | undefined;\n}\n\nexport interface ProviderConfig {\n type: string;\n /**\n * Legacy single-key field. Still honored as a read fallback when `apiKeys`\n * is empty (for configs not yet migrated to multi-key format). After key\n * management operations (`writeKeysBack`), this field is **cleared** to\n * prevent accidental serialization of the plaintext key. Consumers that\n * need the active API key should use `resolveActiveApiKey()` (cli) or\n * resolve from `apiKeys[]` directly \u2014 never read `cfg.apiKey` in new code.\n */\n apiKey?: string | undefined;\n /** Multiple keys for the same provider \u2014 pick one with `activeKey`. */\n apiKeys?: ProviderApiKey[] | undefined;\n /** Label of the entry in `apiKeys` to use. Defaults to the first one. */\n activeKey?: string | undefined;\n baseUrl?: string | undefined;\n headers?: Record<string, string>;\n model?: string | undefined;\n quirks?: Record<string, unknown>;\n capabilities?: Record<string, unknown>;\n /**\n * Optional wire-family override. When present, the provider can be\n * constructed without consulting the models.dev catalog \u2014 useful for\n * self-hosted endpoints, internal proxies, or for working offline.\n */\n family?: WireFamily | undefined;\n /** Custom env var names to probe when `apiKey` is missing. */\n envVars?: string[] | undefined;\n /** Optional list of models the user wants visible for this provider. */\n models?: string[] | undefined;\n /**\n * Fetch this provider's model list + per-model capabilities from its\n * `{baseUrl}/models` endpoint at startup and inject them into the catalog.\n * For openai-compatible gateways/proxies (omniroute, LiteLLM, vLLM, \u2026) that\n * expose rich metadata there. Defaults on for presets that set it (omniroute).\n * Discovery is best-effort: a down server or missing key is a no-op.\n */\n autoDiscoverModels?: boolean | undefined;\n /**\n * Provider-relative custom model definitions (maps modelId \u2192 definition).\n * Each entry adds/overrides a model for this provider with optional\n * capability overrides. The model id is the key, not a fully qualified id.\n */\n customModels?: Record<string, CustomModelDefinition>;\n /**\n * Per-provider OAuth configuration. When present, `wstack auth login <id>`\n * uses this instead of prompting for a raw API key. Set by the catalog or\n * by the user via `/settings`.\n */\n oauthConfig?:\n | {\n /** OAuth client id registered with the provider. */\n clientId?: string | undefined;\n /** Device authorization endpoint (RFC 8628). */\n deviceCodeEndpoint?: string | undefined;\n /** Token endpoint for code exchange and refresh. */\n tokenEndpoint?: string | undefined;\n /** Authorization server URL shown to the user for opening in browser. */\n authorizationEndpoint?: string | undefined;\n /** Default OAuth scopes to request. */\n scopes?: string[] | undefined;\n }\n | undefined;\n}\n\n/**\n * One entry in the per-task model matrix. Pins a catalog role, a phase, or\n * the `*` default to a specific model (and, optionally, a specific provider).\n * Resolved at subagent-spawn time so e.g. `security-scanner` can run a\n * different model than `documentation` while the leader stays on its own.\n */\nexport interface ModelMatrixEntry {\n /** Provider registry id (e.g. \"anthropic\", \"minimax\", \"zai\"). When omitted,\n * the leader's provider is used with this entry's model. */\n provider?: string | undefined;\n /** Model id to run for the matched role/phase/default. */\n model?: string | undefined;\n /**\n * Runtime request overrides for subagents matched by this entry. This is\n * intentionally scoped to subagents: leader requests keep using top-level\n * `Config.modelRuntime`, while a role/phase can opt into its own reasoning\n * effort, cache TTL, or gated generation parameters.\n */\n modelRuntime?: ModelRuntimeConfig | undefined;\n /**\n * Named fallback profile to use for the matched role/phase/default. When\n * `model` is omitted, the first model in the profile becomes the primary and\n * the remaining entries become that subagent's fallback chain.\n */\n fallbackProfile?: string | undefined;\n}\n\nexport interface MCPServerConfig {\n /** Human-readable description shown in `wstack mcp list`. */\n description?: string | undefined;\n name: string;\n transport: 'stdio' | 'sse' | 'streamable-http';\n command?: string | undefined;\n args?: string[] | undefined;\n env?: Record<string, string>;\n url?: string | undefined;\n headers?: Record<string, string>;\n enabled?: boolean | undefined;\n allowedTools?: string[] | undefined;\n permission?: Permission | undefined;\n startupTimeoutMs?: number | undefined;\n requestTimeoutMs?: number | undefined;\n /**\n * Lazy connect: when true, the server process is NOT spawned at boot. Its\n * tools are registered from a cached manifest (discovered on the first ever\n * connect) and the server only spawns when one of its tools is actually\n * called, then auto-sleeps after an idle period. Default (false/undefined) =\n * eager connect at boot.\n */\n lazy?: boolean | undefined;\n /**\n * Allowlist of environment variable names to forward from the parent process\n * to this MCP server's child process. The values are resolved from\n * `process.env` at spawn time, NOT stored in the config file.\n *\n * Why this exists: WrongStack's `buildChildEnv()` security filter scrubs\n * env vars whose names look like secrets (TOKEN, SECRET, AUTH, KEY, ...)\n * from all child processes \u2014 this prevents a compromised MCP server from\n * exfiltrating provider API keys. But most MCP servers (GitHub, Slack,\n * Brave Search, ...) need their own API tokens from the environment.\n * `passthroughEnv` is the explicit bypass: only vars listed here survive\n * the filter, and they go through the `extra` path (unfiltered merge).\n *\n * Built-in presets declare their required env vars here so they work\n * out of the box when the user has the corresponding env vars exported\n * in their shell. Users can also add entries for custom servers.\n *\n * Example: passthroughEnv: ['GITHUB_PERSONAL_ACCESS_TOKEN', 'GITHUB_TOKEN']\n */\n passthroughEnv?: string[] | undefined;\n /**\n * Operational-health settings for this MCP server. Thresholds are optional;\n * when omitted the server is considered healthy as long as its connection\n * lifecycle succeeds. Latency thresholds compare against the rolling p95 of\n * the bounded sample buffer; the in-flight threshold compares against the\n * observed peak in-flight call count.\n */\n health?: MCPHealthConfig | undefined;\n}\n\n/** Per-server operational-health knobs. */\nexport interface MCPHealthConfig {\n thresholds?: MCPHealthThresholds | undefined;\n}\n\n/**\n * Configurable thresholds that can push an otherwise-healthy MCP server into\n * the `degraded` health state. All thresholds are optional and disabled when\n * omitted so existing behaviour is preserved.\n */\nexport interface MCPHealthThresholds {\n /** Connection latency p95 above this value marks the server degraded. */\n connectionLatencyP95Ms?: number | undefined;\n /** Discovery (capability listing) latency p95 above this marks degraded. */\n discoveryLatencyP95Ms?: number | undefined;\n /** Tool-call latency p95 above this marks degraded. */\n callLatencyP95Ms?: number | undefined;\n /** Peak in-flight calls above this marks the server saturated/degraded. */\n inFlightCalls?: number | undefined;\n}\n\nexport interface LogConfig {\n level: 'error' | 'warn' | 'info' | 'debug' | 'trace';\n file?: string | undefined;\n}\n\nexport interface PluginConfig {\n name: string;\n enabled?: boolean | undefined;\n options?: Record<string, unknown>;\n}\n\n/**\n * Optional subsystems that the CLI can boot without. The core flow\n * (provider + agent loop + bundled tools + session) always works; these\n * just add capabilities. `--no-features` flips all of these off, which\n * is the minimum viable WrongStack: a single provider, a fixed config,\n * no network calls at startup.\n */\nexport interface FeaturesConfig {\n /** Load MCP servers declared in `mcpServers`. */\n mcp: boolean;\n /** Load + initialise npm plugins declared in `plugins`. */\n plugins: boolean;\n /** Register `remember` / `forget` tools backed by memory store. */\n memory: boolean;\n /**\n * Automatically consolidate session learnings into long-term memory\n * after each completed run. The agent extracts key facts, conventions,\n * and decisions via a lightweight LLM call and persists them.\n * Enabled by default when `memory` is on; set to false to opt out.\n */\n memoryConsolidation?: boolean | undefined;\n /** Fetch the models.dev catalog at startup. When false, the provider\n * must declare its `family` explicitly in `providers[<id>]`. */\n modelsRegistry: boolean;\n /** Discover + load skills from disk. */\n skills: boolean;\n /**\n * Enable the prompt library (`/prompt`, `/prompts`, `/prompt-gen`, the WebUI\n * modal and the bundled 168-prompt dataset). Defaults to on; set to false to\n * disable the subsystem entirely (the loader is withheld so every surface\n * reports it unavailable).\n */\n prompts?: boolean | undefined;\n /**\n * Token-saving mode tier. Controls how aggressively the system prompt\n * is compacted to reduce per-request token consumption.\n *\n * - 'off' \u2014 Full prompt, all tools, complete guidance\n * - 'minimal' \u2014 TIER1 tools only, stripped guidance (~3-4k tokens saved)\n * - 'light' \u2014 Core + memory tools, common patterns, minimal guidance\n * - 'medium' \u2014 Most development tools, some guidance\n * - 'aggressive' \u2014 Maximum savings before tools become unusable (~4-5k tokens)\n *\n * Boolean values are accepted for backward compatibility:\n * - `true` \u2192 'medium'\n * - `false` \u2192 'off'\n *\n * Enable via CLI: `--token-saving-tier <level>` or `--token-saving-mode` (maps to 'medium').\n * Configure via: `features.tokenSavingMode: \"minimal\"` in config.\n */\n tokenSavingMode?: TokenSavingTier | boolean | undefined;\n /**\n * Enable the autonomous-coordination toolkit (AutonomousCoordinator +\n * KnowledgeGraph + ConsensusProtocol + TaskAuctioneer + ChangeManager +\n * TaskDAG). When true (the default), the TUI boot wires the coordinator\n * lazily on the first Director spawn. When false, the coordinator is\n * never constructed and the `/coordinator` slash command reports it\n * unavailable \u2014 reducing the coordination domain's runtime surface for\n * users who only use the simpler Director/Fleet path.\n */\n autonomousCoordination?: boolean | undefined;\n /**\n * Allow tools to read/write paths outside the project root directory.\n * When true (default), tools can access any path on the filesystem.\n * When false, tools are restricted to the project root directory.\n */\n allowOutsideProjectRoot?: boolean | undefined;\n /**\n * Auto-bootstrap the mailbox HTTP bridge from any WrongStack surface\n * (REPL/TUI/WebUI/eternal). When 'auto' (the default), the first\n * surface to come up for a given project joins or spawns the bridge\n * so external agents can connect without the user running\n * `wstack mailbox serve` themselves. 'off' disables this \u2014 operators\n * must start the bridge explicitly (e.g. via the `/mailbox-serve`\n * slash command or the standalone `wstack mailbox serve` subcommand).\n * The per-project lock + token-persistence model means a second\n * surface on the same project joins the first's bridge rather than\n * spawning a duplicate.\n */\n mailboxBridge?: 'auto' | 'off' | undefined;\n}\n\nexport interface SuperMemoryConfig {\n /**\n * Default: true. Super Memory is the ONLY memory backend \u2014 this flag no longer\n * swaps the store. When `false`, the backend is still Super Memory (explicit\n * `/memory`, agent memory tools, and WebUI all keep working); only automatic\n * context injection and session-end hygiene are turned off.\n */\n enabled?: boolean | undefined;\n storage?: {\n /** Store memory inside the project under a gitignored directory. Default: true. */\n projectLocal?: boolean | undefined;\n /** Project-relative directory. Default: \".wrongstack/memories\". */\n directory?: string | undefined;\n } | undefined;\n inject?: {\n /** Add relevant memory to ordinary turn-level context. Default: true. */\n turnContext?: boolean | undefined;\n /** Add relevant memory to read/tree/grep/bash/edit tool results. Default: true. */\n toolResults?: boolean | undefined;\n /** Maximum hints appended to a single tool result. Default: 4. */\n maxHintsPerTool?: number | undefined;\n /** Maximum characters appended to a single tool result. Default: 1200. */\n maxCharsPerTool?: number | undefined;\n /** Maximum memories appended to ordinary turn context. Default: 8. */\n maxTurnMemories?: number | undefined;\n /** Maximum characters appended to ordinary turn context. Default: 2400. */\n maxCharsPerTurn?: number | undefined;\n /** Minimum retrieval score for ordinary hints. Default: 0.65. */\n minScore?: number | undefined;\n /** Cooldown before the same memory can be injected again. Default: 30 minutes. */\n repeatCooldownMs?: number | undefined;\n triggers?: Partial<Record<\n 'read' | 'tree' | 'grep' | 'glob' | 'codebase_search' | 'bash' | 'write' | 'edit' | 'patch',\n boolean\n >> | undefined;\n } | undefined;\n hygiene?: {\n /** Run hygiene after successful sessions. Default: true. */\n autoAfterSession?: boolean | undefined;\n /** Re-check anchored memories when files are edited. Default: true. */\n autoOnFileChange?: boolean | undefined;\n /** Archive stale/low-value memories after this many days. Default: 90. */\n retentionDays?: number | undefined;\n /** Archive low-confidence memories after this many days. Default: 30. */\n archiveLowConfidenceAfterDays?: number | undefined;\n } | undefined;\n embeddings?: {\n /** Optional future semantic layer. Disabled by default and never required. */\n enabled?: boolean | undefined;\n } | undefined;\n}\n\nexport interface AutonomyConfig {\n /** Default autonomy mode at startup. Default: \"auto\". */\n defaultMode?: 'off' | 'suggest' | 'auto' | undefined;\n /** ms to wait before auto-proceeding in 'auto' mode. Default: 45000. */\n autoProceedDelayMs?: number | undefined;\n /** Maximum consecutive auto-proceed turns before pausing. 0 = unlimited. Default: 50. */\n autoProceedMaxIterations?: number | undefined;\n /** Template used for YOLO+auto suggestions. Must include {{suggestion}}. */\n autonomyNextPrompt?: string | undefined;\n /** Animate the terminal/window title while the agent is active. Default: true. */\n terminalTitleAnimation?: boolean | undefined;\n /** Persisted YOLO preference mirrored into top-level config.yolo at runtime. Default: false. */\n yolo?: boolean | undefined;\n /**\n * @deprecated Mirror of `fleetChatVerbosity !== 'off'`, kept for readers that\n * still expect a boolean (webui prefs). Writers must keep it in sync.\n */\n streamFleet?: boolean | undefined;\n /**\n * How much fleet/subagent activity is streamed into the main TUI chat.\n * - 'off': no subagent lines (failures/errors still surface); F2/F3 stay live.\n * - 'compact': spawn, one tool/message summary per agent turn, completion.\n * - 'full': every subagent tool call and interim message (legacy behavior).\n * Resolved via {@link resolveFleetChatVerbosity}. Default: 'compact'.\n */\n fleetChatVerbosity?: FleetChatVerbosity | undefined;\n /** Ring terminal bell when an agent run completes. Default: false. */\n chime?: boolean | undefined;\n /** Ask for confirmation before interrupt/exit. Default: true. */\n confirmExit?: boolean | undefined;\n /** Terminal mouse tracking preference. Default: false. */\n mouseMode?: boolean | undefined;\n /** Enable prompt refinement before sending. Default: true. */\n enhance?: boolean | undefined;\n /**\n * Provider id to use for goal refinement (`/goal set`). When set,\n * the refiner uses this provider's model (see `refinerModel`)\n * instead of the session's main provider/model. Falls back to the\n * main session provider when unset or when the provider is unavailable.\n * Default: unset (uses the main session provider).\n */\n refinerProvider?: string | undefined;\n /**\n * Model id to use for goal refinement. When `refinerProvider` is\n * also set, the refiner uses this specific model on that provider.\n * When only `refinerModel` is set (without a provider), the model\n * is used on the session's main provider. When both are unset, the\n * session's main model is used. Falls back to heuristic on failure.\n * Default: unset (uses the main session model).\n */\n refinerModel?: string | undefined;\n /**\n * Named fallback profile to use for goal refinement. When set, the\n * refiner uses the first valid entry from the named chain (stored in\n * top-level `fallbackProfiles`) instead of `refinerProvider`+`refinerModel`.\n * Falls back to the session model when the profile is empty or missing.\n * Default: unset (uses refinerProvider+refinerModel, or session defaults).\n */\n refinerFallbackProfile?: string | undefined;\n /** Prompt-refinement preview countdown in ms. Default: 60000. */\n enhanceDelayMs?: number | undefined;\n /** Prompt-refinement language mode. Default: \"original\". */\n enhanceLanguage?: 'original' | 'english' | undefined;\n /**\n * `provider/model` ref used for the one-key \"retry with another model\" action\n * offered when a refinement fails. When unset, the recovery UI falls back to\n * the first entry of the effective fallback chain (see\n * `resolveEnhanceFallbackRef`). Default: unset.\n */\n enhanceFallbackModel?: string | undefined;\n /**\n * Timeout (ms) used when RETRYING a refinement after the first attempt timed\n * out \u2014 the \"extra time\" retry. When unset, the retry uses\n * `max(baseTimeout * 2, 180000)`. Default: unset.\n */\n enhanceRetryTimeoutMs?: number | undefined;\n /** TUI statusline density. Default: \"detailed\". */\n statuslineMode?: 'minimum' | 'detailed' | 'no-color' | undefined;\n /** Single short word shown in the TUI rainbow working-state chip. Default: \"thinking\". */\n thinkingWord?: string | undefined;\n /**\n * Persist the TUI prompt input history to disk per project so Up/Down\n * navigation recalls prompts across sessions. Secrets are scrubbed before\n * they reach disk. Default: enabled, 100 entries.\n */\n inputHistory?: InputHistoryConfig | undefined;\n}\n\n/**\n * Per-project TUI input history persistence options. Lives under\n * `config.autonomy.inputHistory` because the TUI-specific knobs on Config\n * are grouped there.\n */\nexport interface InputHistoryConfig {\n /** Persist history to ~/.wrongstack/projects/<slug>/input-history.json. Default: true. */\n enabled?: boolean | undefined;\n /** Max entries kept on disk (and in memory). Default: 100. */\n maxEntries?: number | undefined;\n}\n\n/**\n * Automatic codebase symbol-index maintenance. Keeps the `codebase-search`\n * index (SQLite, `~/.wrongstack/projects/<hash>/codebase-index/index.db`) fresh\n * without the user having to call `codebase-index` by hand.\n */\nexport interface IndexingConfig {\n /** Run a blocking incremental index at session start (with a visible summary). Default: true. */\n onSessionStart: boolean;\n /** Reindex files the agent writes/edits via tools, in the background. Default: true. */\n onEdit: boolean;\n /** Watch the project root for external editor changes and reindex them. Default: true. */\n watchExternal: boolean;\n /** Debounce window (ms) coalescing rapid edits to the same file. Default: 400. */\n debounceMs: number;\n /**\n * Watchdog timeout (ms) for a full index run. A run exceeding this is\n * aborted (so it can never wedge the indexing mutex or freeze the terminal)\n * and counts toward the indexing circuit breaker. Default: 240000.\n */\n indexTimeoutMs?: number | undefined;\n}\n\n/**\n * Saved launch preferences \u2014 restored on next boot so the pre-launch prompt\n * can offer a one-line \"Continue with last settings? [Y/n]\" instead of\n * re-asking every question from scratch.\n */\nexport interface LaunchConfig {\n /** Interactive mode: 'tui' (Ink TUI) or 'repl' (readline REPL). */\n mode?: 'tui' | 'repl' | undefined;\n // (removed: director \u2014 Director Mode is permanently on)\n /**\n * Launch-time autonomy mode (binary choice from pre-launch prompt).\n * 'off' = stops after each turn; 'auto' = self-driving.\n * Distinct from `AutonomyConfig.defaultMode` which also supports 'suggest'.\n */\n autonomy?: 'off' | 'auto' | undefined;\n}\n\n/**\n * Controls how much detail is persisted to the per-session JSONL log\n * (`~/.wrongstack/projects/<hash>/sessions/<date>/sess_<ULID>.jsonl`).\n */\nexport interface SessionLoggingConfig {\n /**\n * How much detail to write to the persistent session log.\n *\n * - \"minimal\" \u2192 Only events required for resume/rewind/recovery\n * - \"standard\" \u2192 (default) + high-value lightweight audit events\n * (compaction, tool timing, retries, errors, etc.)\n * - \"full\" \u2192 Also persist full request payloads (very large).\n * Consider enabling a separate replay log instead.\n */\n auditLevel?: 'minimal' | 'standard' | 'full' | undefined;\n\n /**\n * Sampling configuration for high-volume events (especially relevant at\n * `auditLevel: \"full\"`).\n */\n sampling?: {\n /** Controls sampling of `tool_progress` events. */\n toolProgress?: {\n /**\n * Sample rate for noisy progress events (`log`, `partial_output`).\n * - 1 = no sampling (every message is logged)\n * - 8 = default (first message + every 8th)\n */\n sampleRate?: number | undefined;\n };\n };\n}\n\nexport type SyncCategory = 'settings' | 'skills' | 'prompts' | 'memory' | 'history';\n\nexport interface SyncConfig {\n enabled: boolean;\n repo: string;\n /** GitHub token (fine-grained PAT). Encrypted at rest via SecretVault. */\n githubToken: string;\n categories: SyncCategory[];\n lastSyncedAt?: string | undefined;\n}\n\n/**\n * Per-model capability overrides the user can define in their config.\n * Used to add models not in the models.dev catalog, or override catalog\n * facts when the real backend differs (e.g. local Ollama models, proxies).\n */\nexport interface CustomModelDefinition {\n /** Provider this model belongs to. Defaults to the owning ProviderConfig. */\n provider?: string | undefined;\n /** Optional display name. */\n name?: string | undefined;\n /** Capability overrides \u2014 only specified fields are overlaid. */\n capabilities?: Partial<Capabilities> | undefined;\n /**\n * Max output tokens. If not specified, the provider family default\n * or catalog entry is used.\n */\n maxOutput?: number | undefined;\n}\n\n/**\n * Skill subsystem configuration. All fields optional; the subsystem itself is\n * gated by `features.skills`. Honored from the user's `~/.wrongstack/config.json`;\n * in the repo-committed in-project config the `extraDirs` field is stripped\n * (arbitrary directories are a prompt-injection vector) \u2014 only `readClaudeSkills`\n * and `mode` survive there.\n */\nexport interface SkillsConfig {\n /**\n * Read skills from foreign coding-agent directories (`<project>/.claude/skills`\n * and `~/.claude/skills`). Default `true`. Lets Claude Code / Codex / Gemini /\n * `asm` / `gh skill` skills be used without copying them.\n */\n readClaudeSkills?: boolean | undefined;\n /**\n * Scan OTHER coding agents' skill directories (`~/.codex/skills`,\n * `~/.cursor/skills`, `~/.agents/skills`, `~/.qwen/skills`,\n * `~/.trae/skills`, \u2026 + their `<project>/.<tool>/\u2026` equivalents). Default\n * `true` (all known tools); pass a tool-id list to restrict, or `false` to\n * disable. Non-existent dirs are skipped. Unknown ids in the list (likely\n * typos) are dropped and surfaced via a config warning.\n */\n foreignSources?: boolean | string[] | undefined;\n /**\n * How skill bodies reach the system prompt.\n * - `'eager'` (default): inject every discovered skill body into the prompt.\n * - `'progressive'`: inject only the metadata manifest; the agent loads a\n * skill body on demand via the `skill` tool (the agentskills.io model).\n */\n mode?: 'eager' | 'progressive' | undefined;\n /**\n * Extra skill directories to scan (lowest priority, after the `.claude`\n * layers). Honored only from the user config; stripped from in-project config.\n */\n extraDirs?: string[] | undefined;\n /**\n * In eager mode, the maximum total chars of skill bodies injected into the\n * prompt (highest-priority skills first; the rest are listed as a manifest the\n * agent loads via the `skill` tool). Bounds prompt cost when many skills are\n * discovered. Default 24000 (~6k tokens). Set very high to disable. Ignored in\n * progressive mode (which injects only the manifest anyway).\n */\n eagerMaxChars?: number | undefined;\n /**\n * Base URL of the skill registry used by `/skill-search` and\n * `/skill-install <registry>:<id>`. Default `https://skills.sh` (the open\n * marketplace backed by mastra-ai/skills-api). Honored only from the user\n * config; stripped from in-project config (a repo-committed override would be\n * an SSRF / prompt-injection vector \u2014 the registry response is parsed into the\n * prompt). Set to a self-hosted skills-api instance to use a private catalog.\n */\n registryUrl?: string | undefined;\n}\n\n/**\n * Fleet peer-awareness + supervision settings. All sub-features are\n * enabled-by-default with conservative throttles; each has its own kill\n * switch. See `FleetSupervisor` (coordination/fleet-supervisor.ts) for the\n * supervisor semantics.\n */\nexport interface FleetConfig {\n /** Subagent process/registry lifecycle after it is no longer doing work. */\n lifecycle?:\n | {\n /**\n * Remove a spawned or between-task subagent after this much idle time.\n * This is separate from the in-task activity watchdog. Default 30000.\n */\n idleTimeoutMs?: number | undefined;\n /**\n * Retire a subagent as soon as its final task result is delivered and\n * no queued task reused it in the same dispatch cycle. Default true.\n */\n retireOnTaskComplete?: boolean | undefined;\n }\n | undefined;\n /** Fleet-wide hard ceilings. In-flight work may finish; new spawns are refused at the cap. */\n budget?:\n | {\n /** Maximum subagents spawned during one Director lifetime. Default 64 in CLI. */\n maxSpawns?: number | undefined;\n /** Maximum cumulative input+output tokens across all fleet subagents. */\n maxTokens?: number | undefined;\n /** Maximum cumulative estimated USD cost across all fleet subagents. */\n maxCostUsd?: number | undefined;\n }\n | undefined;\n /** Periodic \"[FLEET PULSE]\" peer-status digest folded into each agent's context. */\n pulse?:\n | {\n /** Default true. */\n enabled?: boolean | undefined;\n /** Inject at most every N agent iterations. Default 5. */\n everyNIterations?: number | undefined;\n /** Hard cap on digest characters. Default 900. */\n maxChars?: number | undefined;\n /** Max peers listed per digest. Default 15. */\n maxAgents?: number | undefined;\n }\n | undefined;\n /** Broadcast `type:'status'` mails on meaningful subagent transitions. */\n statusBroadcasts?:\n | {\n /** Default true. */\n enabled?: boolean | undefined;\n /** Min interval between broadcasts about the same subagent. Default 15000. */\n minIntervalMsPerAgent?: number | undefined;\n /** Global cap on broadcasts per minute (excess dropped + counted). Default 20. */\n globalPerMinuteCap?: number | undefined;\n }\n | undefined;\n /**\n * Per-subagent git-worktree isolation for Director fleets. The default is\n * `auto`: mutating/build-capable subagents run in isolated checkouts and are\n * squash-merged back on success; read-only review agents usually stay on the\n * shared checkout. Set `enabled:false` or `mode:'off'` when a workflow cannot\n * use worktrees.\n */\n worktrees?:\n | {\n /** Kill switch. Default true. */\n enabled?: boolean | undefined;\n /**\n * `auto` (default): isolate only side-effectful subagents.\n * `required`: side-effectful subagents must get a worktree or fail.\n * `off`: never allocate worktrees.\n */\n mode?: 'auto' | 'required' | 'off' | undefined;\n /**\n * Merge successful task branches back into the base checkout. Default\n * true. When false, successful worktrees are committed and kept for\n * manual `/worktree merge`.\n */\n autoMerge?: boolean | undefined;\n /** Keep failed/timeout worktrees when they contain changes. Default true. */\n keepFailed?: boolean | undefined;\n }\n | undefined;\n /** Brain-gated fleet supervisor (rebalance/steer/spawn-helper). */\n supervisor?: FleetSupervisorConfig | undefined;\n}\n\n/** Config surface for the brain-gated FleetSupervisor. */\nexport interface FleetSupervisorConfig {\n /** Kill switch. Default true (active whenever a Director is running). */\n enabled?: boolean | undefined;\n /** Evaluation tick. Default 20000. */\n intervalMs?: number | undefined;\n /** Per-(signal,subject) re-engagement cooldown. Default 120000. */\n cooldownMs?: number | undefined;\n /** Hard cap on interventions touching one subagent per run. Default 3. */\n maxInterventionsPerSubagent?: number | undefined;\n /** Pending task pinned to a busy worker longer than this \u2192 starvation signal. Default 60000. */\n pinnedWaitMs?: number | undefined;\n /** \u2265 this many pending tasks pinned to one worker (with an idle sibling) \u2192 overload signal. Default 2. */\n overloadPinnedThreshold?: number | undefined;\n /** pending > backlogFactor \u00D7 live workers (sustained) \u2192 spawn-helper signal. Default 2. */\n backlogFactor?: number | undefined;\n /** Running subagent with no observable fleet activity for this long \u2192 stuck signal. Default 180000. */\n stuckMs?: number | undefined;\n /** Consecutive failed/timeout results from one subagent \u2192 failure-streak signal. Default 2. */\n failureStreak?: number | undefined;\n /** Allow the supervisor to spawn helper subagents. Default true. */\n allowSpawn?: boolean | undefined;\n /** Allow the supervisor to terminate subagents (highest risk). Default false. */\n allowTerminate?: boolean | undefined;\n}\n\n/**\n * One member of the Brain's LLM pool or council. String entries elsewhere\n * (`Config.brain.models`, council voters) parse with the same `parseModelRef`\n * grammar as `fallbackModels`: bare `model`, `provider/model`, or\n * `provider model`.\n */\nexport interface BrainModelEntry {\n /** Provider id (a key of `Config.providers` or a catalog id). Defaults to the session provider. */\n provider?: string | undefined;\n /** Model id, required. */\n model: string;\n}\n\n/** One voting seat on the Brain council. */\nexport interface BrainCouncilVoterConfig extends BrainModelEntry {\n /**\n * Decision lens for this seat. Built-ins: 'executor' (progress-biased),\n * 'skeptic' (risk-hunting), 'auditor' (cost/waste-focused). Any other\n * string is injected verbatim as the persona description.\n */\n persona?: string | undefined;\n /** Vote weight in the tally. Default 1. */\n weight?: number | undefined;\n /** When true, this seat's explicit refusal denies the request outright. */\n veto?: boolean | undefined;\n}\n\n/** Multi-LLM council configuration for high-stakes Brain decisions. */\nexport interface BrainCouncilConfig {\n /** Kill switch. Default: enabled when `voters` is non-empty or \u22652 pool models exist. */\n enabled?: boolean | undefined;\n /**\n * Minimum request risk that convenes the council instead of the single-LLM\n * tier. Default 'high'. 'critical' = council only for critical questions;\n * 'medium' = council for most non-trivial questions (slow + expensive).\n */\n minRisk?: 'medium' | 'high' | 'critical' | undefined;\n /**\n * Voting seats. String entries use the `parseModelRef` grammar and get\n * default personas (executor, skeptic w/ veto, auditor) assigned in order.\n * When omitted, seats are derived from `brain.models` (up to 3).\n */\n voters?: Array<string | BrainCouncilVoterConfig> | undefined;\n /** Fraction of seats that must return a valid vote. Default 0.5. */\n quorum?: number | undefined;\n /** Fraction of cast vote weight the winning option must exceed. Default 0.5. */\n approval?: number | undefined;\n /**\n * Tie-breaker / synthesizer model (`parseModelRef` grammar or entry).\n * Sees every vote's rationale and issues the final structured decision.\n * Default: the first pool/voter model.\n */\n judge?: string | BrainModelEntry | undefined;\n}\n\n/**\n * Brain decision-layer configuration. SECURITY: in the in-project config\n * DENY list \u2014 a repo-committed config must not be able to raise the\n * autonomy ceiling, remove the human tier, or point Brain decisions at an\n * attacker-chosen provider. Only honoured from `~/.wrongstack/config.json`.\n */\nexport interface BrainConfig {\n /**\n * 'headless' \u2014 the Brain NEVER blocks on a human. Escalations resolve\n * via the terminal policy (recommended option for low/medium\n * risk, request fallback semantics, otherwise deny).\n * 'interactive' \u2014 escalations prompt the human in the TUI/WebUI.\n * Default (resolved at boot by `resolveBrainConfigDefaults`): 'headless' \u2014\n * minimum-human out of the box. Switch live with `/brain mode <m>`.\n */\n mode?: 'headless' | 'interactive' | undefined;\n /**\n * Initial autonomy ceiling for the LLM tier. Default (resolved at boot):\n * adaptive \u2014 'all' when a council can convene (\u22652 voters/pool models),\n * otherwise 'high'. Live-set via `/brain risk`.\n */\n maxAutoRisk?: 'off' | 'low' | 'medium' | 'high' | 'all' | undefined;\n /**\n * Ordered LLM pool for Brain decisions (`parseModelRef` grammar or\n * entries). With `strategy: 'fallback'` the first entry is primary and the\n * rest are tried in order when it fails; with 'round-robin' calls rotate\n * across the pool. Default (resolved at boot): the user's `fallbackModels`\n * chain; with none configured, the session provider/model is used.\n */\n models?: Array<string | BrainModelEntry> | undefined;\n /** Pool selection strategy. Default 'fallback'. */\n strategy?: 'fallback' | 'round-robin' | undefined;\n /** Per-LLM-call decision timeout (ms). Default 15000. */\n decisionTimeoutMs?: number | undefined;\n /**\n * Interactive mode only: how long an ask-human prompt may stay unanswered\n * before it resolves through the terminal policy instead of blocking\n * forever. Default (resolved at boot): 120000. Set 0 to wait indefinitely\n * (legacy behavior).\n */\n humanTimeoutMs?: number | undefined;\n /** Multi-LLM council for high-stakes decisions. */\n council?: BrainCouncilConfig | undefined;\n /**\n * Persistent decision ledger (`<project>/.wrongstack/brain-ledger.jsonl`):\n * every decision + observed outcome is appended, and outcome stats for\n * similar past decisions are fed back into the LLM/council prompts.\n * Default: enabled.\n */\n ledger?:\n | {\n enabled?: boolean | undefined;\n /**\n * Deterministic guard: once this many consecutive approvals of a\n * decision group ended in observed failures, deny outright without\n * consulting any LLM (a later success lifts the guard). Default 3.\n * 0 disables.\n */\n autoDenyAfterFailures?: number | undefined;\n }\n | undefined;\n /**\n * BrainMonitor distress-signal thresholds (self-activation). All optional;\n * defaults match `BrainMonitorOptions`.\n */\n monitor?:\n | {\n /** Consecutive failures of the same tool before engaging. Default 3. */\n toolFailureStreak?: number | undefined;\n /** Errors within 60s before engaging. Default 4. */\n errorStormCount?: number | undefined;\n /** Active run with no progress for this long \u2192 stall signal (ms). Default 300000. 0 disables. */\n stallMs?: number | undefined;\n /** Edits to the same file within the churn window before engaging. Default 5. */\n fileChurnThreshold?: number | undefined;\n /** Sliding window for the file-churn signal (ms). Default 600000. */\n fileChurnWindowMs?: number | undefined;\n /** Per-signal re-engagement cooldown (ms). Default 120000. */\n cooldownMs?: number | undefined;\n }\n | undefined;\n}\n\n/** Git behavior overrides for agent-run git commands. See `Config.git`. */\nexport interface GitBehaviorConfig {\n /**\n * Commit identity injected as `GIT_AUTHOR_NAME/EMAIL` +\n * `GIT_COMMITTER_NAME/EMAIL` into every child process. Either field may be\n * set alone; the missing one falls back to git's own config.\n */\n identity?: {\n name?: string | undefined;\n email?: string | undefined;\n } | undefined;\n}\n\nexport interface Config {\n version: 1;\n provider: string;\n model: string;\n apiKey?: string | undefined;\n baseUrl?: string | undefined;\n /**\n * Maximum number of subagent tasks the fleet coordinator dispatches\n * simultaneously. Extra tasks queue until a slot frees. Default: 4.\n * Overridden by WRONGSTACK_MAX_CONCURRENT env var and --max-concurrent\n * CLI flag. Change at runtime with /fleet concurrency <n>.\n */\n maxConcurrent?: number | undefined;\n /**\n * Display language for the UI chrome (WebUI + desktop shell). A BCP-47-ish\n * code from SUPPORTED_LOCALES (en/tr/de/fr/it/es/pt-BR). Persisted here so a\n * change in one surface propagates to all others via the shared machine\n * config; each surface may keep a local cache for instant reactivity. When\n * unset, surfaces fall back to their own browser/system detection.\n */\n uiLocale?: string | undefined;\n providers?: Record<string, ProviderConfig>;\n /**\n * Top-level custom models (maps modelId \u2192 definition). Merged with\n * per-provider `customModels` at resolution time. The key is the\n * model id \u2014 not a fully qualified name. When the same model id\n * appears in both places, the top-level one wins.\n */\n models?: Record<string, CustomModelDefinition>;\n /**\n * Per-task model matrix. Keys are catalog roles (e.g. \"security-scanner\"),\n * phase names (e.g. \"review\"), or the `*` default. Resolution precedence at\n * subagent spawn: exact role \u2192 the role's phase \u2192 `*` \u2192 leader model. Set via\n * the `/setmodel` slash command; persisted to ~/.wrongstack/config.json.\n */\n modelMatrix?: Record<string, ModelMatrixEntry>;\n /**\n * User-curated model references shown/prioritized by model commands and used\n * by smart fallback derivation. Entries are `model`, `provider/model`, or\n * `provider model`.\n */\n favoriteModels?: string[] | undefined;\n /**\n * When true, auto-derived fallback chains are restricted to `favoriteModels`.\n * Explicit fallback profiles/chains are always honored as written.\n */\n favoriteModelsOnly?: boolean | undefined;\n context: ContextConfig;\n tools: ToolsConfig;\n mcpServers?: Record<string, MCPServerConfig>;\n /**\n * Per-agent ACP invocation overrides, keyed by catalog agent id\n * (`claude-code`, `codex-cli`, `gemini-cli`, \u2026). Lets a user correct an\n * agent's ACP entry command \u2014 e.g. point `claude-code` at the right\n * adapter \u2014 without a code change. Consumed by `/acp`, `/ensemble`, and\n * `wstack acp`. SECURITY: this is an arbitrary-command exec surface, so it\n * is in the in-project config DENY list \u2014 only honoured from the user's\n * `~/.wrongstack/config.json`, never from a repo-committed config.\n */\n acp?: {\n agents?: Record<string, { command: string; args?: string[]; env?: Record<string, string> }>;\n };\n /**\n * Ordered list of fallback model references tried, in order, when the\n * primary model is overloaded (HTTP 429/529/5xx) and its own retries are\n * exhausted. Each entry is a model reference: a bare model id (same\n * provider), `provider/model`, or `provider model`. After a fallback hop,\n * the primary is retried only after its cooldown expires. See\n * `createFallbackModelExtension`.\n */\n fallbackModels?: string[] | undefined;\n /**\n * Named fallback chains. A profile's first entry can be used as a primary\n * model by `/setmodel`, while the whole ordered list is used for failover.\n */\n fallbackProfiles?: Record<string, string[]> | undefined;\n /**\n * When `true` (the default) and `fallbackModels` is empty, a fallback chain\n * is derived automatically from the other keyed providers/models so 429s\n * recover out of the box. Set `false` to disable the smart default and only\n * use an explicit `fallbackModels` list. Toggle via `/fallback auto on|off`.\n */\n fallbackAuto?: boolean | undefined;\n /**\n * Lifecycle command/HTTP hooks, keyed by event. Commands receive HookInput\n * JSON on stdin; HTTP hooks receive the same object as a POST body. A typed\n * outcome can allow, deny, or mutate. `policy: true` enforcement hooks remain\n * active under `--no-hooks`; ordinary automation is disabled.\n */\n hooks?: Partial<Record<HookEvent, ConfiguredHook[]>>;\n plugins?: (string | PluginConfig)[] | undefined;\n log: LogConfig;\n features: FeaturesConfig;\n /** Project-local structured memory, graph-ready anchors, retrieval, and hygiene. */\n superMemory?: SuperMemoryConfig | undefined;\n /** Skill subsystem options (readClaudeSkills / mode / extraDirs). */\n skills?: SkillsConfig | undefined;\n yolo?: boolean | undefined;\n /** When true, show lightweight LLM-predicted next steps after each turn (/next). */\n nextPrediction?: boolean | undefined;\n cwd?: string | undefined;\n /** Autonomy mode configuration (auto-proceed delay, etc.). */\n autonomy?: AutonomyConfig | undefined;\n /** Show rotating launch hints on startup. Default: true. Set to false to suppress. */\n hints?: boolean | undefined;\n /** Raw SSE stream debugging \u2014 hex-dump every byte received from providers to stderr. */\n debugStream?: boolean | undefined;\n /**\n * Where settings are persisted. 'global' \u2192 ~/.wrongstack/config.json\n * (default). 'project' \u2192 <project>/.wrongstack/config.json.\n * When 'project', safe settings are saved per-project.\n */\n configScope?: 'global' | 'project' | undefined;\n /** Automatic codebase symbol-index maintenance (session-start + live updates). */\n indexing?: IndexingConfig | undefined;\n /**\n * Process circuit-breaker protection (gates `bash`/`exec` on repeated\n * failures). Default off \u2014 toggle with `/settings breaker on|off`.\n */\n circuitBreaker?: CircuitBreakerRuntimeConfig | undefined;\n /**\n * Adaptive concurrency controller \u2014 automatically adjusts `maxConcurrent` based on\n * rate-limit (429) errors. On 429: decreases concurrency. On sustained success:\n * gradually increases concurrency back up. Default off.\n */\n adaptiveConcurrency?: AdaptiveConcurrencyConfig | undefined;\n /** Saved launch preferences \u2014 restored on next boot for one-line confirmation. */\n launch?: LaunchConfig | undefined;\n\n /**\n * Session logging & audit configuration.\n * Controls what gets written to the persistent JSONL transcript.\n */\n session?: SessionLoggingConfig | undefined;\n /**\n * Runtime reasoning / cache controls applied to every provider request\n * (REPL/TUI/WebUI). Mapped into `Request.reasoning` and `Request.cache` by a\n * single request-pipeline middleware, gated by the active model's\n * capabilities. See `ModelRuntimeConfig`.\n */\n modelRuntime?: ModelRuntimeConfig | undefined;\n /** HQ client publishing settings, used by CLI/REPL/TUI/WebUI consistently. */\n hq?: HqClientConfig | undefined;\n /**\n * Fleet awareness + supervision settings (peer-status pulse digests,\n * status-broadcast mails, and the brain-gated FleetSupervisor). SECURITY:\n * in the in-project config DENY list \u2014 a repo-committed config must not be\n * able to enable autonomous spawning/steering or mailbox traffic. Only\n * honoured from the user's `~/.wrongstack/config.json`.\n */\n fleet?: FleetConfig | undefined;\n /**\n * Brain decision-layer settings: escalation mode (headless = never block\n * on a human), LLM pool with fallback/round-robin, autonomy ceiling, and\n * the multi-LLM council. SECURITY: in the in-project config DENY list \u2014\n * a repo-committed config must not be able to raise the autonomy ceiling\n * or reroute Brain decisions. Only honoured from `~/.wrongstack/config.json`.\n */\n brain?: BrainConfig | undefined;\n /**\n * Cloud sync configuration. Stored separately in sync.json to avoid\n * accidentally committing the GitHub token to project configs.\n */\n sync?: SyncConfig | undefined;\n /**\n * Git behavior overrides for agent-run git commands.\n *\n * `identity` sets the commit author/committer used by every git process\n * WrongStack spawns (git tool, bash/exec shells, worktree manager,\n * plugins) via the `GIT_AUTHOR_*` / `GIT_COMMITTER_*` env vars. It never\n * touches the repo's or the user's `git config`, so commits made outside\n * WrongStack keep their normal identity. Unset \u2192 git's own config applies\n * (today's behavior). Manage at runtime with `/gitid`.\n *\n * SECURITY: in the in-project config DENY list \u2014 a repo-committed config\n * must not be able to spoof the identity written into the user's commit\n * history. Only honoured from the user's `~/.wrongstack/config.json`.\n */\n git?: GitBehaviorConfig | undefined;\n /**\n * Per-plugin namespaced config sections. Each plugin reads its own\n * subtree via `ConfigStore.getExtension(pluginName)`. Plugins should\n * declare a `configSchema` so the loader validates this section\n * automatically before `setup()` runs.\n *\n * Example:\n * extensions: {\n * 'wstack-auth': { tokenUrl: 'https://...', refreshBefore: 300 },\n * 'wstack-metrics': { sink: 'prometheus', port: 9090 },\n * }\n */\n extensions?: Record<string, Record<string, unknown>>;\n}\n\nexport interface ConfigLoader {\n load(opts?: {\n cliFlags?: Partial<Config> | undefined;\n cwd?: string | undefined;\n }): Promise<Config>;\n /** Load and decrypt the sync config from ~/.wrongstack/sync.json. */\n loadSyncConfig(): Promise<SyncConfig | null>;\n /** Persist sync config to ~/.wrongstack/sync.json with encrypted token. */\n persistSyncConfig(cfg: SyncConfig): Promise<void>;\n}\n\n/**\n * Subscribable view over Config. Plugins and CLI subsystems use this instead\n * of holding a frozen Config reference, so they can react to runtime updates\n * (e.g. `/model` switching the active provider, secrets rotation, dynamic\n * extension reload).\n *\n * The store enforces immutability \u2014 `get()` always returns a frozen object.\n * Updates happen through `update(partial)`, which produces a new Config\n * (structurally cloned, then frozen) and notifies watchers.\n */\nexport interface ConfigStore {\n get(): Readonly<Config>;\n /**\n * Get a typed top-level section. Convenience for consumers that only\n * care about one slice (e.g. `tools` or `context`).\n */\n getSection<K extends keyof Config>(key: K): Readonly<Config[K]>;\n /**\n * Return the extension namespace for `pluginName`, or an empty record\n * when none is configured. The returned object is frozen.\n */\n getExtension(pluginName: string): Readonly<Record<string, unknown>>;\n /**\n * Apply a partial update. Returns the new Config. Watchers are notified\n * synchronously after the update completes. Throws if the result fails\n * any registered invariants (currently: version must stay 1).\n */\n update(partial: Partial<Config>): Readonly<Config>;\n /** Subscribe to changes. Returns an unsubscribe function. */\n watch(cb: (next: Readonly<Config>, prev: Readonly<Config>) => void): () => void;\n}\n", "import { expectDefined } from '../utils/expect-defined.js';\nexport type ContextWindowModeId = 'balanced' | 'frugal' | 'deep' | 'archival';\n\nexport type ContextWindowAggressiveOn = 'hard' | 'soft' | 'warn';\n\nexport interface ContextWindowThresholds {\n warn: number;\n soft: number;\n hard: number;\n}\n\nexport interface ContextWindowMode {\n id: ContextWindowModeId;\n name: string;\n description: string;\n thresholds: ContextWindowThresholds;\n aggressiveOn: ContextWindowAggressiveOn;\n preserveK: number;\n eliseThreshold: number;\n targetLoad: number;\n}\n\nexport interface ContextWindowPolicy extends ContextWindowMode {}\n\nexport interface ContextWindowConfigLike {\n mode?: ContextWindowModeId | string | undefined;\n warnThreshold?: number | undefined;\n softThreshold?: number | undefined;\n hardThreshold?: number | undefined;\n preserveK?: number | undefined;\n eliseThreshold?: number | undefined;\n}\n\nexport const DEFAULT_CONTEXT_WINDOW_MODE_ID: ContextWindowModeId = 'frugal';\n\nexport const CONTEXT_WINDOW_MODES: readonly ContextWindowMode[] = Object.freeze([\n {\n id: 'balanced',\n name: 'Balanced',\n description: 'Default rolling compaction: recent work stays verbatim, old tool output is trimmed.',\n thresholds: { warn: 0.5, soft: 0.65, hard: 0.8 },\n aggressiveOn: 'soft',\n preserveK: 8,\n eliseThreshold: 1000,\n targetLoad: 0.55,\n },\n {\n id: 'frugal',\n name: 'Frugal',\n description: 'Token-saver mode: compacts early and keeps a tighter verbatim tail.',\n thresholds: { warn: 0.45, soft: 0.6, hard: 0.75 },\n aggressiveOn: 'warn',\n preserveK: 6,\n eliseThreshold: 700,\n targetLoad: 0.5,\n },\n {\n id: 'deep',\n name: 'Deep',\n description: 'Long-reasoning mode: delays compaction and keeps more recent turns intact.',\n thresholds: { warn: 0.72, soft: 0.86, hard: 0.96 },\n aggressiveOn: 'hard',\n preserveK: 18,\n eliseThreshold: 5000,\n targetLoad: 0.78,\n },\n {\n id: 'archival',\n name: 'Archival',\n description: 'Decision-preserving mode: compacts steadily while keeping summaries prominent.',\n thresholds: { warn: 0.55, soft: 0.7, hard: 0.84 },\n aggressiveOn: 'soft',\n preserveK: 8,\n eliseThreshold: 1200,\n targetLoad: 0.58,\n },\n]);\n\nexport function listContextWindowModes(): ContextWindowMode[] {\n return CONTEXT_WINDOW_MODES.map((m) => ({ ...m, thresholds: { ...m.thresholds } }));\n}\n\nexport function getContextWindowMode(id: string | null | undefined): ContextWindowMode | null {\n if (!id) return null;\n const mode = CONTEXT_WINDOW_MODES.find((m) => m.id === id);\n return mode ? { ...mode, thresholds: { ...mode.thresholds } } : null;\n}\n\nexport function isContextWindowModeId(id: string): id is ContextWindowModeId {\n return CONTEXT_WINDOW_MODES.some((m) => m.id === id);\n}\n\nexport function resolveContextWindowPolicy(\n config: ContextWindowConfigLike = {},\n overrideMode?: string | null | undefined,\n): ContextWindowPolicy {\n const requested = overrideMode ?? config.mode ?? DEFAULT_CONTEXT_WINDOW_MODE_ID;\n const mode = getContextWindowMode(requested) ?? expectDefined(getContextWindowMode(DEFAULT_CONTEXT_WINDOW_MODE_ID));\n\n return {\n ...mode,\n thresholds: {\n warn: config.warnThreshold ?? mode.thresholds.warn,\n soft: config.softThreshold ?? mode.thresholds.soft,\n hard: config.hardThreshold ?? mode.thresholds.hard,\n },\n preserveK: config.preserveK ?? mode.preserveK,\n eliseThreshold: config.eliseThreshold ?? mode.eliseThreshold,\n };\n}\n\nexport function formatContextWindowModeList(activeId?: string | null): string {\n return CONTEXT_WINDOW_MODES.map((m) => {\n const marker = m.id === activeId ? '*' : ' ';\n return `${marker} ${m.id.padEnd(9)} ${m.name} - ${m.description}`;\n }).join('\\n');\n}\n", "/**\n * Shared configuration constants used across execution, storage, CLI, and WebUI.\n * Centralized here to avoid cross-domain import cycles.\n */\n\n/** Default tools config \u2014 mirrors values baked into BEHAVIOR_DEFAULTS. */\nexport const DEFAULT_TOOLS_CONFIG = Object.freeze({\n defaultExecutionStrategy: 'smart',\n maxIterations: 100,\n iterationTimeoutMs: 300_000,\n maxToolTimeoutMs: 300_000,\n sessionTimeoutMs: 1_800_000,\n perIterationOutputCapBytes: 100_000,\n descriptionMode: Object.freeze({}) as Record<string, 'extend' | 'simple' | undefined>,\n disabledTools: Object.freeze([]) as readonly string[],\n autoExtendLimit: true,\n restrictToProjectRoot: true,\n loopDetection: Object.freeze({\n mode: 'steer-then-cut',\n steerThreshold: 3,\n cutThreshold: 5,\n windowSize: 12,\n callRepeatThreshold: 4,\n }) as Readonly<{\n mode: 'steer-then-cut' | 'cut' | 'off';\n steerThreshold: number;\n cutThreshold: number;\n windowSize: number;\n callRepeatThreshold: number;\n }>,\n});\n\n/** Default context config \u2014 mirrors BEHAVIOR_DEFAULTS.context. */\nexport const DEFAULT_CONTEXT_CONFIG = Object.freeze({\n preserveK: 8,\n eliseThreshold: 1000,\n});\n\n/** Default autonomy config \u2014 auto-proceed delay etc. */\nexport const DEFAULT_AUTONOMY_CONFIG = Object.freeze({\n autoProceedDelayMs: 45_000,\n});\n\n/**\n * Default process circuit-breaker config. Protection is OFF by default \u2014 the\n * breaker only gates `bash`/`exec` once the user opts in via `/settings breaker on`.\n * The auto kill/reset delay is only consulted when protection is enabled.\n */\nexport const DEFAULT_CIRCUIT_BREAKER_CONFIG = Object.freeze({\n enabled: false,\n autoKillResetMs: 60_000,\n});\n\n/** Default session logging / audit configuration. */\nexport const DEFAULT_SESSION_LOGGING_CONFIG = Object.freeze({\n auditLevel: 'standard' as const,\n sampling: {\n toolProgress: {\n sampleRate: 8,\n },\n },\n});\n\n/** Default retention window for local session pruning. */\nexport const DEFAULT_SESSION_PRUNE_DAYS = 30;\n", "/**\n * L2-D: Config version migration framework. Pure functions, decoupled\n * from disk/CLI \u2014 caller passes a parsed JSON object and gets back the\n * up-to-date `Config` shape (or a structured error explaining why\n * migration failed).\n *\n * Migrations are registered as `{ from, to, migrate }` triples and run\n * sequentially. Each migration is independently testable. Adding a new\n * version means appending one migration; existing user configs are\n * upgraded in place at load time.\n */\n\nexport interface MigrationContext {\n /**\n * Original on-disk version of the input. Migrations may use this to\n * decide between in-place patches and rewrites.\n */\n fromVersion: number;\n /**\n * Set when the migration writes back to disk. Callers persist the\n * migrated config when this is true so the user doesn't see the same\n * migration banner on every boot.\n */\n shouldPersist: boolean;\n}\n\nexport interface ConfigMigration {\n /** Version of the input this migration accepts. */\n from: number;\n /** Version of the output it produces. */\n to: number;\n /** Pure transform \u2014 no I/O. */\n migrate(input: Record<string, unknown>, ctx: MigrationContext): Record<string, unknown>;\n /** Optional human-readable description for migration logs / banners. */\n describe?: string | undefined;\n}\n\nexport interface MigrationResult {\n /** Final config (still typed as `unknown`-keyed \u2014 caller validates). */\n config: Record<string, unknown>;\n /** Ordered list of `from\u2192to` versions that ran. */\n applied: string[];\n /** True when at least one migration produced changes worth persisting. */\n shouldPersist: boolean;\n}\n\nexport class ConfigMigrationError extends Error {\n readonly fromVersion: number;\n readonly targetVersion: number;\n readonly missingStep: number | null;\n\n constructor(opts: {\n message: string;\n fromVersion: number;\n targetVersion: number;\n missingStep: number | null;\n }) {\n super(opts.message);\n this.name = 'ConfigMigrationError';\n this.fromVersion = opts.fromVersion;\n this.targetVersion = opts.targetVersion;\n this.missingStep = opts.missingStep;\n }\n}\n\n/**\n * Run registered migrations until the input reaches `targetVersion`.\n *\n * Resolution rules:\n * 1. If `input.version === targetVersion`, no migrations run; `shouldPersist`\n * is false.\n * 2. Otherwise walk the migration chain from `input.version` upward,\n * picking the migration whose `from` matches the current version.\n * 3. Stop when `current.version === targetVersion`.\n * 4. If no migration matches at some point, throw `ConfigMigrationError`\n * with the missing step recorded for diagnostics.\n *\n * Migrations may be downward (e.g. for staged rollouts), but `targetVersion`\n * must be reachable strictly via the registered chain \u2014 there's no implicit\n * \"skip\" or transitive resolution.\n */\nexport function runConfigMigrations(\n input: Record<string, unknown>,\n targetVersion: number,\n migrations: readonly ConfigMigration[],\n): MigrationResult {\n const initial = typeof input['version'] === 'number' ? (input['version'] as number) : 1;\n let current: Record<string, unknown> = { ...input };\n let currentVersion = initial;\n const applied: string[] = [];\n let shouldPersist = false;\n\n let guard = 0;\n while (currentVersion !== targetVersion) {\n if (++guard > 100) {\n throw new ConfigMigrationError({\n message: `Config migration looped past 100 steps (from v${initial} toward v${targetVersion})`,\n fromVersion: initial,\n targetVersion,\n missingStep: currentVersion,\n });\n }\n const step = migrations.find((m) => m.from === currentVersion);\n if (!step) {\n throw new ConfigMigrationError({\n message: `No migration registered from config v${currentVersion} (target v${targetVersion}). Update the framework or revert the config file.`,\n fromVersion: initial,\n targetVersion,\n missingStep: currentVersion,\n });\n }\n const ctx: MigrationContext = { fromVersion: currentVersion, shouldPersist: false };\n const next = step.migrate(current, ctx);\n // Ensure the migration set the new version. Be tolerant: if it didn't,\n // patch it in so the chain doesn't infinite-loop on author oversight.\n if (typeof next['version'] !== 'number' || next['version'] !== step.to) {\n next['version'] = step.to;\n }\n current = next;\n currentVersion = step.to;\n applied.push(`v${step.from}\u2192v${step.to}`);\n shouldPersist = shouldPersist || ctx.shouldPersist || step.from < step.to;\n }\n return { config: current, applied, shouldPersist };\n}\n\n/**\n * Default empty migration registry. Real migrations are appended as new\n * Config versions are introduced. Example (when v2 lands):\n *\n * export const CONFIG_MIGRATIONS: readonly ConfigMigration[] = [\n * {\n * from: 1, to: 2, describe: 'rename `apiKey` \u2192 `auth.apiKey`',\n * migrate(cfg) {\n * const apiKey = cfg.apiKey;\n * delete cfg.apiKey;\n * return { ...cfg, auth: { ...(cfg.auth ?? {}), apiKey } };\n * },\n * },\n * ];\n */\nexport const DEFAULT_CONFIG_MIGRATIONS: readonly ConfigMigration[] = [];\n", "import * as fsp from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport type { SessionStore } from '../types/session.js';\nimport { ensureDir } from '../utils/atomic-write.js';\n\n/**\n * Per-project lockfile used for crash detection. The CLI writes one of\n * these alongside the session JSONLs (`<projectSessions>/active.json`)\n * when an interactive run starts, and deletes it on clean exit. If we\n * find one on the next launch whose owning PID is dead (or whose host\n * doesn't match), we know the previous run was killed mid-flight and\n * the session it was writing to is a recovery candidate.\n *\n * The lockfile is intentionally per-project (already isolated by\n * `wpaths.projectSessions`), so two TUIs in two different repos do not\n * fight each other.\n */\nexport interface RecoveryLockOptions {\n /** Directory the lockfile lives in. Usually `wpaths.projectSessions`. */\n dir: string;\n /** This process's PID. Default: `process.pid`. */\n pid?: number | undefined;\n /** Hostname recorded for the lock. Default: `os.hostname()`. */\n hostname?: string | undefined;\n /** Locks older than this are considered orphaned (disk wiped, etc.). Default 24h. */\n maxAgeMs?: number | undefined;\n /** Used to check whether the abandoned session was actually closed cleanly. */\n sessionStore?: SessionStore | undefined;\n /**\n * Override the PID-liveness probe. Default: `process.kill(pid, 0)` \u2014\n * succeeds (or throws EPERM) when the PID is alive, throws ESRCH when\n * it is gone. Tests inject a deterministic stub.\n */\n isPidAlive?: (((pid: number) => boolean)) | undefined;\n}\n\nexport interface AbandonedSession {\n sessionId: string;\n pid: number;\n startedAt: string;\n /** Lockfile age in ms at the time of the check. */\n ageMs: number;\n /** Number of messages already on disk for this session. */\n messageCount: number;\n}\n\ninterface LockFile {\n v: 1;\n sessionId: string;\n pid: number;\n hostname: string;\n startedAt: string;\n}\n\nconst LOCK_FILE = 'active.json';\nconst DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1000;\n\nexport class RecoveryLock {\n private readonly file: string;\n private readonly pid: number;\n private readonly hostname: string;\n private readonly maxAgeMs: number;\n private readonly sessionStore?: SessionStore | undefined;\n private readonly probe: (pid: number) => boolean;\n\n constructor(opts: RecoveryLockOptions) {\n this.file = path.join(opts.dir, LOCK_FILE);\n this.pid = opts.pid ?? process.pid;\n this.hostname = opts.hostname ?? os.hostname();\n this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;\n this.sessionStore = opts.sessionStore;\n this.probe = opts.isPidAlive ?? defaultIsPidAlive;\n }\n\n /**\n * Examine the lockfile and decide whether it represents an abandoned\n * session. Returns `null` if the file is missing, points to a live\n * instance, references a clean-closed session, is too old, or is\n * malformed. Otherwise returns enough detail to prompt the user.\n *\n * Important: this is a read-only check. We never delete an active\n * lock from here \u2014 if another wstack instance is alive, the caller\n * should bail or run with a fresh session instead.\n */\n async checkAbandoned(): Promise<AbandonedSession | null> {\n const lock = await this.readLock();\n if (!lock) return null;\n\n const ageMs = Date.now() - new Date(lock.startedAt).getTime();\n if (Number.isNaN(ageMs) || ageMs < 0) {\n // Clock skew or corrupted timestamp \u2014 treat as orphan.\n return null;\n }\n if (ageMs > this.maxAgeMs) return null;\n\n // PID liveness only meaningful on the same host. Different host\n // means we can't probe \u2014 assume abandoned (the other machine's\n // wstack can't be holding *our* sessions dir unless it was\n // shared via network mount, in which case the user is on their\n // own).\n if (lock.hostname === this.hostname && this.probe(lock.pid)) {\n // Another wstack on this box is actively writing here.\n return null;\n }\n\n let messageCount = 0;\n if (this.sessionStore) {\n try {\n const data = await this.sessionStore.load(lock.sessionId);\n // Closed means the LAST session_end is not followed by further\n // conversation activity. Legacy /save wrote mid-stream session_end\n // markers \u2014 `some()` would treat a session that crashed AFTER such a\n // marker as cleanly closed and silently skip recovery.\n const lastEnd = data.events.findLastIndex((e) => e.type === 'session_end');\n const closed =\n lastEnd >= 0 &&\n !data.events\n .slice(lastEnd + 1)\n .some(\n (e) =>\n e.type === 'user_input' ||\n e.type === 'llm_response' ||\n e.type === 'in_flight_start',\n );\n if (closed) return null;\n messageCount = data.messages.length;\n } catch {\n // Lock points to a session that doesn't exist on disk (deleted\n // out from under us). Nothing to recover.\n return null;\n }\n }\n\n return {\n sessionId: lock.sessionId,\n pid: lock.pid,\n startedAt: lock.startedAt,\n ageMs,\n messageCount,\n };\n }\n\n /**\n * Claim the lock for the given session. Uses exclusive-create (`O_EXCL`)\n * to detect whether another process acquired the lock between our\n * `checkAbandoned()` call and now. If the file already exists, it means\n * another process won the race and we throw instead of silently\n * overwriting their recovery record.\n *\n * The caller MUST have already called `checkAbandoned()` and handled its\n * null return before calling this.\n */\n async write(sessionId: string): Promise<void> {\n await ensureDir(path.dirname(this.file));\n const lock: LockFile = {\n v: 1,\n sessionId,\n pid: this.pid,\n hostname: this.hostname,\n startedAt: new Date().toISOString(),\n };\n // O_EXCL: atomic create \u2014 fails with EEXIST if another process wrote\n // the file between our checkAbandoned() and this write. This prevents\n // two processes that scanned the same stale lock from both believing\n // they hold it. The atomicWrite approach (temp+rename) would silently\n // replace on POSIX, hiding the race.\n try {\n await fsp.writeFile(this.file, JSON.stringify(lock), { flag: 'wx', mode: 0o600 });\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'EEXIST') {\n throw new Error(`Recovery lock already held by another process`);\n }\n /* v8 ignore next -- defensive: an unexpected (non-EEXIST) write failure is rethrown */\n throw err;\n }\n }\n\n /**\n * Release the lock. Idempotent \u2014 silently succeeds if the file is\n * already gone (e.g. someone else cleared it, or the directory was\n * wiped).\n */\n async clear(): Promise<void> {\n try {\n await fsp.unlink(this.file);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') return;\n /* v8 ignore next -- defensive: an unexpected (non-ENOENT) unlink failure is rethrown */\n throw err;\n }\n }\n\n private async readLock(): Promise<LockFile | null> {\n let raw: string;\n try {\n raw = await fsp.readFile(this.file, 'utf8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') return null;\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (!isLockFile(parsed)) return null;\n return parsed;\n } catch {\n return null;\n }\n }\n}\n\nfunction isLockFile(v: unknown): v is LockFile {\n if (typeof v !== 'object' || v === null) return false;\n const o = v as Record<string, unknown>;\n return (\n o['v'] === 1 &&\n typeof o['sessionId'] === 'string' &&\n typeof o['pid'] === 'number' &&\n typeof o['hostname'] === 'string' &&\n typeof o['startedAt'] === 'string'\n );\n}\n\n/**\n * Probe whether a process is alive without sending it a real signal.\n *\n * Unix: `process.kill(pid, 0)` succeeds for our own processes, throws\n * EPERM for others (still alive, just not ours), and throws ESRCH\n * when the PID is gone.\n * Windows (Node 22+): same call returns true if the process exists,\n * throws otherwise.\n */\nfunction defaultIsPidAlive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n /* v8 ignore next -- platform/permission-specific: EPERM means alive but owned by another user */\n if (code === 'EPERM') return true; // alive, but owned by someone else\n return false;\n }\n}\n", "import * as fs from 'node:fs/promises';\nimport { expectDefined } from '../utils/expect-defined.js';\nimport type { ContentBlock } from '../types/blocks.js';\nimport type {\n DefaultSessionReaderOptions,\n SessionExportOptions,\n SessionQuery,\n SessionReader,\n SessionSearchHit,\n SessionSearchQuery,\n SessionSummaryLite,\n} from '../types/session-reader.js';\nimport { compileUserRegex } from '../utils/regex-guard.js';\nimport { sessionScopedPath } from '../utils/session-scoped-path.js';\nimport type { SessionData, SessionEvent, SessionMetadata, SessionStore } from '../types/session.js';\n\n/**\n * L2-A: read-only view over a `SessionStore` with query, replay, search,\n * and export helpers. Implemented on top of the public `SessionStore`\n * surface so any concrete store can be inspected without re-implementation.\n */\nexport class DefaultSessionReader implements SessionReader {\n private readonly store: SessionStore;\n private readonly eventCache = new Map<string, SessionData>();\n private readonly eventCacheMtimes = new Map<string, number>();\n private static readonly EVENT_CACHE_MAX_ENTRIES = 32;\n\n constructor(opts: DefaultSessionReaderOptions) {\n this.store = opts.store;\n }\n\n private async loadCachedSessionData(sessionId: string): Promise<SessionData> {\n const storeWithPath = this.store as SessionStore & {\n dir?: string | undefined;\n clearLoadCache?: ((sessionId?: string | undefined) => void) | undefined;\n };\n const rootDir = storeWithPath.dir;\n if (!rootDir) {\n return await this.store.load(sessionId);\n }\n const sessionPath = sessionScopedPath(rootDir, sessionId, '.jsonl');\n let mtimeMs: number | null = null;\n try {\n const stat = await fs.stat(sessionPath);\n mtimeMs = stat.mtimeMs;\n } catch {\n this.eventCache.delete(sessionId);\n this.eventCacheMtimes.delete(sessionId);\n return await this.store.load(sessionId);\n }\n\n const cachedMtime = this.eventCacheMtimes.get(sessionId);\n const cachedData = this.eventCache.get(sessionId);\n if (cachedData && cachedMtime === mtimeMs) {\n this.eventCache.delete(sessionId);\n this.eventCacheMtimes.delete(sessionId);\n this.eventCache.set(sessionId, cachedData);\n this.eventCacheMtimes.set(sessionId, mtimeMs);\n return cachedData;\n }\n\n const data = await this.store.load(sessionId);\n this.eventCache.delete(sessionId);\n this.eventCacheMtimes.delete(sessionId);\n this.eventCache.set(sessionId, data);\n this.eventCacheMtimes.set(sessionId, mtimeMs);\n while (this.eventCache.size > DefaultSessionReader.EVENT_CACHE_MAX_ENTRIES) {\n const oldest = this.eventCache.keys().next().value;\n if (oldest === undefined) break;\n this.eventCache.delete(oldest);\n this.eventCacheMtimes.delete(oldest);\n }\n\n if (data.metadata.endedAt) {\n storeWithPath.clearLoadCache?.(sessionId);\n }\n\n return data;\n }\n\n async query(q: SessionQuery = {}): Promise<SessionSummaryLite[]> {\n // Prefer the store's filtered list when available \u2014 it pushes the\n // filter into the cached index instead of fetching 1000 + linear scan.\n const storeWithFilter = this.store as SessionStore & {\n listFiltered?: ((criteria: {\n since?: string | undefined;\n until?: string | undefined;\n provider?: string | undefined;\n model?: string | undefined;\n minTokens?: number | undefined;\n titleContains?: string | undefined;\n limit?: number | undefined;\n }) => Promise<import('../types/session.js').SessionSummary[]>) | undefined;\n };\n let raw: import('../types/session.js').SessionSummary[];\n if (typeof storeWithFilter.listFiltered === 'function') {\n raw = await storeWithFilter.listFiltered({\n since: q.since,\n until: q.until,\n provider: q.provider,\n model: q.model,\n minTokens: q.minTokens,\n titleContains: q.titleContains,\n limit: q.limit,\n });\n } else {\n const fetched = await this.store.list(q.limit ? Math.max(q.limit, 100) : 1000);\n const titleNeedle = q.titleContains?.toLowerCase();\n raw = fetched.filter((s) => {\n if (q.since && s.startedAt < q.since) return false;\n if (q.until && s.startedAt > q.until) return false;\n if (q.provider && s.provider !== q.provider) return false;\n if (q.model && s.model !== q.model) return false;\n if (q.minTokens !== undefined && s.tokenTotal < q.minTokens) return false;\n if (titleNeedle && !s.title.toLowerCase().includes(titleNeedle)) return false;\n return true;\n });\n }\n const out: SessionSummaryLite[] = raw.map((s) => ({\n id: s.id,\n title: s.title,\n startedAt: s.startedAt,\n provider: s.provider,\n model: s.model,\n tokenTotal: s.tokenTotal,\n }));\n return q.limit ? out.slice(0, q.limit) : out;\n }\n\n async *replay(sessionId: string): AsyncIterable<SessionEvent> {\n const data = await this.loadCachedSessionData(sessionId);\n for (const e of data.events) yield e;\n }\n\n async search(q: SessionSearchQuery, sessionId?: string | undefined, sessionQuery?: SessionQuery): Promise<SessionSearchHit[]> {\n const limit = q.limit ?? 100;\n const matcher = buildMatcher(q);\n const allowedTypes = q.types ? new Set(q.types) : null;\n\n // Filter sessions BEFORE scanning events \u2014 avoids touching the JSONL\n // for sessions that don't match the time/provider/model criteria.\n let ids: string[];\n if (sessionId) {\n ids = [sessionId];\n } else {\n // Prefer the store's filtered list when available \u2014 avoids fetching\n // 1000 sessions and linear-filtering in-process.\n const storeWithFilter = this.store as SessionStore & {\n listFiltered?: ((criteria: {\n since?: string | undefined;\n until?: string | undefined;\n provider?: string | undefined;\n model?: string | undefined;\n minTokens?: number | undefined;\n titleContains?: string | undefined;\n limit?: number | undefined;\n }) => Promise<import('../types/session.js').SessionSummary[]>) | undefined;\n };\n let sessions: import('../types/session.js').SessionSummary[];\n if (typeof storeWithFilter.listFiltered === 'function') {\n sessions = await storeWithFilter.listFiltered({\n since: sessionQuery?.since,\n until: sessionQuery?.until,\n provider: sessionQuery?.provider,\n model: sessionQuery?.model,\n minTokens: sessionQuery?.minTokens,\n titleContains: sessionQuery?.titleContains,\n limit: 1000,\n });\n } else {\n sessions = await this.store.list(1000);\n const titleNeedle = sessionQuery?.titleContains?.toLowerCase();\n sessions = sessions.filter((s) => {\n if (sessionQuery?.since && s.startedAt < sessionQuery.since) return false;\n if (sessionQuery?.until && s.startedAt > sessionQuery.until) return false;\n if (sessionQuery?.provider && s.provider !== sessionQuery.provider) return false;\n if (sessionQuery?.model && s.model !== sessionQuery.model) return false;\n if (sessionQuery?.minTokens !== undefined && s.tokenTotal < sessionQuery.minTokens) return false;\n if (titleNeedle && !s.title.toLowerCase().includes(titleNeedle)) return false;\n return true;\n });\n }\n ids = sessions.map((s) => s.id);\n }\n\n const hits: SessionSearchHit[] = [];\n\n // Fast path: when the underlying store supports streaming search,\n // walk each session's JSONL line-by-line and bail out the moment we\n // hit `limit`. This avoids reading + parsing the entire file (which\n // `load()` does) and never reuses `_loadCache`, so concurrent\n // analytics queries don't churn the writer-side cache.\n const streaming = this.store.searchEvents?.bind(this.store);\n if (streaming) {\n for (const id of ids) {\n const matched = await streaming(\n id,\n (ev) => {\n if (allowedTypes && !allowedTypes.has(ev.type)) return false;\n const text = eventText(ev);\n if (text === null) return false;\n return matcher(text) !== null;\n },\n { limit: limit - hits.length },\n );\n for (const m of matched) {\n const text = expectDefined(eventText(m.event));\n const hit = expectDefined(matcher(text));\n hits.push({\n sessionId: id,\n eventIndex: m.eventIndex,\n ts: m.ts,\n type: m.event.type,\n snippet: snippetOf(text, hit.start, hit.end),\n });\n if (hits.length >= limit) return hits;\n }\n }\n return hits;\n }\n\n // Fallback: stores that don't implement streaming. Loads the full\n // event stream per session \u2014 necessary for in-memory or non-file\n // stores that don't expose a streaming surface.\n for (const id of ids) {\n let data;\n try {\n data = await this.loadCachedSessionData(id);\n } catch {\n continue;\n }\n for (let i = 0; i < data.events.length; i++) {\n const ev = expectDefined(data.events[i]);\n if (allowedTypes && !allowedTypes.has(ev.type)) continue;\n const text = eventText(ev);\n if (text === null) continue;\n const hit = matcher(text);\n if (!hit) continue;\n hits.push({\n sessionId: id,\n eventIndex: i,\n ts: ev.ts,\n type: ev.type,\n snippet: snippetOf(text, hit.start, hit.end),\n });\n if (hits.length >= limit) return hits;\n }\n }\n return hits;\n }\n\n async export(sessionId: string, opts: SessionExportOptions): Promise<string> {\n const data = await this.loadCachedSessionData(sessionId);\n const includeTools = opts.includeTools ?? true;\n const includeDiagnostics = opts.includeDiagnostics ?? true;\n\n const filtered = data.events.filter((e) => {\n if (\n !includeTools &&\n (e.type === 'tool_use' ||\n e.type === 'tool_result' ||\n e.type === 'tool_call_start' ||\n e.type === 'tool_call_end')\n ) {\n return false;\n }\n if (\n !includeDiagnostics &&\n (e.type === 'error' || e.type === 'compaction' || e.type === 'message_truncated')\n ) {\n return false;\n }\n return true;\n });\n\n if (opts.format === 'json') {\n return JSON.stringify({ metadata: data.metadata, events: filtered }, null, 2);\n }\n if (opts.format === 'text') {\n return renderPlainText(data.metadata, filtered);\n }\n return renderMarkdown(data.metadata, filtered);\n }\n\n async metadata(sessionId: string): Promise<SessionMetadata> {\n const data = await this.loadCachedSessionData(sessionId);\n return data.metadata;\n }\n}\n\nfunction buildMatcher(\n q: SessionSearchQuery,\n): (text: string) => { start: number; end: number } | null {\n const ci = q.caseInsensitive ?? true;\n if (q.regex) {\n const flags = ci ? 'i' : '';\n const compiled = compileUserRegex(q.query, flags);\n if (!compiled.ok) {\n throw new Error(`Invalid search regex \"${q.query}\": ${compiled.reason}`);\n }\n const re = compiled.regex;\n return (text) => {\n const m = re.exec(text);\n return m ? { start: m.index, end: m.index + m[0].length } : null;\n };\n }\n const needle = ci ? q.query.toLowerCase() : q.query;\n return (text) => {\n const hay = ci ? text.toLowerCase() : text;\n const idx = hay.indexOf(needle);\n return idx === -1 ? null : { start: idx, end: idx + needle.length };\n };\n}\n\nfunction eventText(e: SessionEvent): string | null {\n switch (e.type) {\n case 'user_input':\n return contentToString(e.content);\n case 'llm_response':\n return contentToString(e.content);\n case 'tool_use':\n return `${e.name} ${JSON.stringify(e.input)}`;\n case 'tool_result':\n return typeof e.content === 'string' ? e.content : JSON.stringify(e.content);\n case 'error':\n return `${e.phase}: ${e.message}`;\n case 'session_start':\n case 'session_resumed':\n return `${e.model}/${e.provider}`;\n case 'task_created':\n case 'task_completed':\n return e.title;\n case 'task_failed':\n return `${e.title}: ${e.error}`;\n case 'skill_activated':\n case 'skill_deactivated':\n return e.skillName;\n default:\n return null;\n }\n}\n\nfunction contentToString(content: string | ContentBlock[]): string {\n if (typeof content === 'string') return content;\n return content\n .map((b) => {\n switch (b.type) {\n case 'text':\n return b.text;\n case 'tool_use':\n return `[tool_use:${b.name} ${JSON.stringify(b.input)}]`;\n case 'tool_result':\n return typeof b.content === 'string' ? b.content : JSON.stringify(b.content);\n default:\n return '';\n }\n })\n .join('\\n');\n}\n\nconst SNIPPET_RADIUS = 60;\n\nfunction snippetOf(text: string, start: number, end: number): string {\n const from = Math.max(0, start - SNIPPET_RADIUS);\n const to = Math.min(text.length, end + SNIPPET_RADIUS);\n const prefix = from > 0 ? '\u2026' : '';\n const suffix = to < text.length ? '\u2026' : '';\n return prefix + text.slice(from, to).replace(/\\s+/g, ' ').trim() + suffix;\n}\n\nfunction renderMarkdown(meta: SessionMetadata, events: SessionEvent[]): string {\n const lines: string[] = [];\n lines.push(`# Session ${meta.id}`);\n lines.push('');\n if (meta.model || meta.provider) {\n lines.push(`- **Model:** ${meta.provider ?? '?'}/${meta.model ?? '?'}`);\n }\n lines.push(`- **Started:** ${meta.startedAt}`);\n if (meta.endedAt) lines.push(`- **Ended:** ${meta.endedAt}`);\n lines.push('');\n lines.push('---');\n lines.push('');\n for (const e of events) {\n switch (e.type) {\n case 'user_input': {\n lines.push(`## User \u2014 ${e.ts}`);\n lines.push('');\n lines.push(contentToString(e.content));\n lines.push('');\n break;\n }\n case 'llm_response': {\n lines.push(`## Assistant \u2014 ${e.ts}`);\n lines.push('');\n lines.push(contentToString(e.content));\n if (e.stopReason && e.stopReason !== 'end_turn') {\n lines.push('');\n lines.push(`*stop: ${e.stopReason}*`);\n }\n lines.push('');\n break;\n }\n case 'tool_use': {\n lines.push(`### Tool call: \\`${e.name}\\``);\n lines.push('');\n lines.push('```json');\n lines.push(JSON.stringify(e.input, null, 2));\n lines.push('```');\n lines.push('');\n break;\n }\n case 'tool_result': {\n const body = typeof e.content === 'string' ? e.content : JSON.stringify(e.content, null, 2);\n lines.push(`### Tool result${e.isError ? ' (error)' : ''}`);\n lines.push('');\n lines.push('```');\n lines.push(body);\n lines.push('```');\n lines.push('');\n break;\n }\n case 'error': {\n lines.push(`> **Error** (${e.phase}): ${e.message}`);\n lines.push('');\n break;\n }\n case 'compaction': {\n lines.push(`> **Compaction**: ${e.before} \u2192 ${e.after} tokens`);\n lines.push('');\n break;\n }\n default:\n break;\n }\n }\n return lines.join('\\n');\n}\n\nfunction renderPlainText(meta: SessionMetadata, events: SessionEvent[]): string {\n const lines: string[] = [];\n lines.push(\n `Session ${meta.id} \u2014 ${meta.provider ?? '?'}/${meta.model ?? '?'} \u2014 started ${meta.startedAt}`,\n );\n lines.push(''.padEnd(72, '-'));\n for (const e of events) {\n switch (e.type) {\n case 'user_input':\n lines.push(`[${e.ts}] USER`);\n lines.push(contentToString(e.content));\n lines.push('');\n break;\n case 'llm_response':\n lines.push(`[${e.ts}] ASSISTANT`);\n lines.push(contentToString(e.content));\n lines.push('');\n break;\n case 'tool_use':\n lines.push(`[${e.ts}] TOOL_USE ${e.name} ${JSON.stringify(e.input)}`);\n break;\n case 'tool_result':\n lines.push(\n `[${e.ts}] TOOL_RESULT${e.isError ? ' (error)' : ''} ${\n typeof e.content === 'string' ? e.content : JSON.stringify(e.content)\n }`,\n );\n break;\n case 'error':\n lines.push(`[${e.ts}] ERROR (${e.phase}): ${e.message}`);\n break;\n default:\n break;\n }\n }\n return lines.join('\\n');\n}\n", "import { expectDefined } from '../utils/expect-defined.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs/promises';\nimport { sessionScopedPath } from '../utils/session-scoped-path.js';\nimport { atomicWrite, withFileLock } from '../utils/atomic-write.js';\nimport { WrongStackError, ERROR_CODES } from '../types/errors.js';\nimport type { EventBus } from '../kernel/events.js';\n/**\n * L2-B: AnnotationsStore \u2014 sidecar storage for collaboration annotations\n * (Phase 2 of idea #13 from IDEAS.md).\n *\n * Why a sidecar file, not the session JSONL?\n *\n * The session log is an event-sourced append-only journal\n * (`packages/core/src/types/session.ts` invariant: events are\n * append-only, with `truncateToCheckpoint` only as an explicit\n * rewind). Mixing in human-typed annotations would break that\n * invariant \u2014 the user's note about \"this rm looks dangerous\"\n * is not part of the agent's event history; it is meta-commentary\n * on the history.\n *\n * So we keep annotations in a sibling file: one JSON document per\n * session, resolved with `sessionScopedPath(sessionDir, sessionId,\n * '.annotations.json')`. The shape is a simple versioned array,\n * written atomically.\n *\n * Concurrency model:\n *\n * The store uses a per-session Promise chain to serialize writes.\n * Multiple annotators adding notes at the same time will queue,\n * not race. The atomic write itself is the second line of\n * defense (in case the chain is bypassed \u2014 e.g. two processes\n * pointing at the same dir).\n */\n\n/** Wire/storage shape for one annotation. */\nexport interface Annotation {\n /** Stable id (UUIDv4-ish). Referenced by resolve/delete. */\n id: string;\n /** Session this annotation belongs to. */\n sessionId: string;\n /** Index into the session event log the annotation refers to. */\n atEventIndex: number;\n /** Participant id of the annotator (matches WSCollabParticipantJoined.participantId). */\n authorId: string;\n /** Human-readable role label snapshot for display (e.g. \"annotator\"). */\n authorRole: 'annotator';\n /** The note itself. Trimmed, capped at `MAX_TEXT_LENGTH` on add. */\n text: string;\n /** ISO timestamp of creation. */\n createdAt: string;\n /** Resolved state. Annotations start unresolved. */\n resolved: boolean;\n /** ISO timestamp when resolved (if resolved). */\n resolvedAt?: string | undefined;\n /** Participant id of the resolver (if resolved). */\n resolvedBy?: string | undefined;\n}\n\ninterface AnnotationsFile {\n /** Bumped when the on-disk shape changes. v1 = initial release. */\n version: 1;\n annotations: Annotation[];\n}\n\n/** Bumped when the on-disk shape changes. Bump + migration on change. */\nconst FILE_VERSION = 1;\n/** Hard cap to keep a runaway annotator from writing megabytes. */\nconst MAX_TEXT_LENGTH = 2000;\n/** Hard cap on total annotations per session (oldest are evicted beyond this). */\nconst MAX_ANNOTATIONS = 1000;\n\nexport interface AnnotationsStoreOptions {\n /** Root sessions directory used with `sessionScopedPath(..., '.annotations.json')`. */\n dir: string;\n events?: EventBus;\n traceId?: string;\n /**\n * Override the max annotations per session (default 1000).\n * Exposed for tests to avoid 1001 sequential disk writes on every eviction test.\n */\n maxAnnotations?: number;\n}\n\nexport class AnnotationsStore {\n private readonly dir: string;\n private readonly events: EventBus | undefined;\n private readonly traceId: string | undefined;\n private readonly maxAnnotations: number;\n /** Per-session write queue. Created lazily on first add. */\n private readonly writeChains = new Map<string, Promise<void>>();\n\n constructor(opts: AnnotationsStoreOptions) {\n this.dir = opts.dir;\n this.events = opts.events;\n this.traceId = opts.traceId;\n this.maxAnnotations = opts.maxAnnotations ?? MAX_ANNOTATIONS;\n }\n\n // \u2500\u2500 Reads \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Return all annotations for `sessionId` in insertion order\n * (oldest first). Returns an empty array when no file exists\n * yet (the normal case for a fresh session) and also degrades\n * gracefully to `[]` on a read error (permissions, corruption) \u2014\n * the failure is still surfaced via a `storage.read` event so it\n * never silently hides I/O problems from observers.\n */\n async list(sessionId: string): Promise<Annotation[]> {\n const t0 = Date.now();\n const fp = this.filePath(sessionId);\n try {\n const file = await this.readFile(sessionId);\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.read', {\n sessionId,\n store: 'annotations',\n filePath: fp,\n operation: 'list',\n outcome: 'success',\n durationMs,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return file ? file.annotations : [];\n } catch (err) {\n this.events?.emit('storage.read', {\n sessionId,\n store: 'annotations',\n filePath: fp,\n operation: 'list',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: toErrorMessage(err),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return [];\n }\n }\n\n /**\n * Convenience: only unresolved annotations, newest first \u2014 the\n * common UI rendering for \"what still needs attention?\".\n */\n async listOpen(sessionId: string): Promise<Annotation[]> {\n const all = await this.list(sessionId);\n return all.filter((a) => !a.resolved).reverse();\n }\n\n // \u2500\u2500 Writes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Add a new annotation. Returns the persisted record (with id\n * and timestamps filled in). Throws when `text` is empty or\n * exceeds `MAX_TEXT_LENGTH`.\n */\n async add(input: {\n sessionId: string;\n atEventIndex: number;\n authorId: string;\n text: string;\n }): Promise<Annotation> {\n const text = input.text.trim();\n if (text.length === 0) {\n throw new WrongStackError({\n message: 'Annotation text must be non-empty',\n code: ERROR_CODES.VALIDATION_ERROR,\n subsystem: 'general',\n context: { field: 'text', sessionId: input.sessionId },\n });\n }\n if (text.length > MAX_TEXT_LENGTH) {\n throw new WrongStackError({\n message: `Annotation text exceeds ${MAX_TEXT_LENGTH} chars (got ${text.length})`,\n code: ERROR_CODES.VALIDATION_ERROR,\n subsystem: 'general',\n context: { field: 'text', maxLength: MAX_TEXT_LENGTH, actualLength: text.length },\n });\n }\n if (!Number.isInteger(input.atEventIndex) || input.atEventIndex < 0) {\n throw new WrongStackError({\n message: 'atEventIndex must be a non-negative integer',\n code: ERROR_CODES.VALIDATION_ERROR,\n subsystem: 'general',\n context: { field: 'atEventIndex', value: input.atEventIndex },\n });\n }\n const annotation: Annotation = {\n id: randomUUID(),\n sessionId: input.sessionId,\n atEventIndex: input.atEventIndex,\n authorId: input.authorId,\n authorRole: 'annotator',\n text,\n createdAt: new Date().toISOString(),\n resolved: false,\n };\n const fp = this.filePath(input.sessionId);\n const t0 = Date.now();\n try {\n await this.enqueue(input.sessionId, async () => {\n await withFileLock(fp, async () => {\n const all = await this.list(input.sessionId);\n all.push(annotation);\n // Evict oldest if we crossed the cap. Resolved first, then oldest.\n if (all.length > this.maxAnnotations) {\n const sorted = all\n .map((a, i) => ({ a, i }))\n .sort((x, y) => {\n // resolved=false wins (keep unresolved); among same resolved state, oldest first.\n /* v8 ignore next -- scale+tiebreak: needs >1000 annotations with mixed resolved states */\n if (x.a.resolved !== y.a.resolved) return x.a.resolved ? 1 : -1;\n return x.a.createdAt.localeCompare(y.a.createdAt);\n });\n const evictCount = all.length - this.maxAnnotations;\n const toEvict = new Set(sorted.slice(0, evictCount).map((s) => s.a.id));\n const kept = all.filter((a) => !toEvict.has(a.id));\n await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: kept });\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: input.sessionId,\n store: 'annotations',\n filePath: fp,\n operation: 'evict',\n outcome: 'success',\n durationMs,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n } else {\n await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: input.sessionId,\n store: 'annotations',\n filePath: fp,\n operation: 'add',\n outcome: 'success',\n durationMs,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n }\n });\n });\n return annotation;\n } catch (err) {\n this.events?.emit('storage.error', {\n sessionId: input.sessionId,\n store: 'annotations',\n filePath: fp,\n operation: 'add',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: false,\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n throw err;\n }\n }\n\n /**\n * Mark an annotation as resolved. Returns the updated record, or\n * `null` if no annotation with that id exists in this session.\n * Idempotent: resolving an already-resolved annotation refreshes\n * `resolvedAt` / `resolvedBy` to the latest call.\n */\n async resolve(input: {\n sessionId: string;\n annotationId: string;\n resolvedBy: string;\n }): Promise<Annotation | null> {\n let updated: Annotation | null = null;\n const fp = this.filePath(input.sessionId);\n const t0 = Date.now();\n try {\n await this.enqueue(input.sessionId, async () => {\n await withFileLock(fp, async () => {\n const all = await this.list(input.sessionId);\n const idx = all.findIndex((a) => a.id === input.annotationId);\n if (idx === -1) {\n updated = null;\n return;\n }\n const next: Annotation = {\n ...expectDefined(all[idx]),\n resolved: true,\n resolvedAt: new Date().toISOString(),\n resolvedBy: input.resolvedBy,\n };\n all[idx] = next;\n await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });\n updated = next;\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: input.sessionId,\n store: 'annotations',\n filePath: fp,\n operation: 'resolve',\n outcome: 'success',\n durationMs,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n });\n });\n return updated;\n } catch (err) {\n this.events?.emit('storage.error', {\n sessionId: input.sessionId,\n store: 'annotations',\n filePath: fp,\n operation: 'resolve',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: false,\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n throw err;\n }\n }\n\n // \u2500\u2500 Internals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private filePath(sessionId: string): string {\n // Containment-checked: date-sharded ids (\"2026-06-11/<base>\") are\n // legitimate; traversal is rejected. A plain slash ban here used to\n // throw for every modern session id, breaking annotations entirely.\n return sessionScopedPath(this.dir, sessionId, '.annotations.json');\n }\n\n private async readFile(sessionId: string): Promise<AnnotationsFile | null> {\n const fp = this.filePath(sessionId);\n let raw: string;\n try {\n raw = await fs.readFile(fp, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n // Non-ENOENT I/O errors (EACCES, ENOSPC): re-throw so callers emit\n // storage.error.\n throw err;\n }\n try {\n const parsed = JSON.parse(raw) as AnnotationsFile;\n if (parsed.version !== FILE_VERSION) {\n return { version: FILE_VERSION, annotations: [] };\n }\n return parsed;\n } catch {\n // JSON parse error (SyntaxError): treat as empty store \u2014 not an I/O failure.\n return null;\n }\n }\n\n private async writeFile(sessionId: string, file: AnnotationsFile): Promise<void> {\n const fp = this.filePath(sessionId);\n await atomicWrite(fp, JSON.stringify(file, null, 2));\n }\n\n /**\n * Serialize writes per-sessionId. We chain promises instead of\n * using a Mutex class so the contract is obvious from the\n * call-site: `enqueue(sid, fn)` runs `fn` after every prior\n * enqueue for `sid` has settled.\n */\n private enqueue(sessionId: string, fn: () => Promise<void>): Promise<void> {\n const prev = this.writeChains.get(sessionId) ?? Promise.resolve();\n const next = prev.then(fn, fn);\n // Keep the chain intact even when `fn` throws; failures\n // shouldn't break subsequent writes.\n this.writeChains.set(\n sessionId,\n next.catch(() => undefined),\n );\n return next;\n }\n}\n", "import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { atomicWrite, withFileLock } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport { sessionScopedPath } from '../utils/session-scoped-path.js';\nimport { hashRequest } from '../replay/hash.js';\nimport type { Request, Response } from '../types/provider.js';\nimport { safeParse } from '../utils/safe-json.js';\nimport type { EventBus } from '../kernel/events.js';\n\n/**\n * Surface the OS error code (EACCES, ENOSPC, \u2026) alongside the message in\n * storage.* event payloads. Codes are stable and locale-independent, so\n * they are what dashboards and alerts key on; the message is supplementary.\n */\nfunction storageErrorString(err: unknown): string {\n if (err instanceof Error) {\n const code = (err as NodeJS.ErrnoException).code;\n return code ? `${code}: ${err.message}` : err.message;\n }\n /* v8 ignore next -- defensive: fs/lock failures are always Error instances */\n return String(err);\n}\n\n/**\n * ReplayLogStore \u2014 sidecar store for deterministic-replay support\n * (idea #2 from IDEAS.md). One JSONL file per session, recording\n * every provider request/response pair so the same agent loop can\n * be re-run later with frozen API responses.\n *\n * Why a sidecar (not the session JSONL)?\n *\n * Same reason as `AnnotationsStore` \u2014 the session log is\n * event-sourced and append-only; a provider request payload can be\n * tens of kilobytes (especially with long conversation history),\n * and we want replay to be opt-in (recorded only when the user\n * runs with `--replay` or a future equivalent). Mixing it into\n * the event log would inflate every read for replay-irrelevant\n * paths.\n *\n * File layout: `sessionScopedPath(dir, sessionId, '.replay.jsonl')`,\n * one entry per line.\n * Each entry: `{ hash, ts, request, response }`. The `hash` is\n * computed via `hashRequest` so lookups are O(1) by hash.\n *\n * Concurrency: per-session write queue (same pattern as\n * `AnnotationsStore`). Reads are lock-free; the write chain makes\n * the append + rehash sequence atomic.\n */\nexport interface ReplayEntry {\n hash: string;\n ts: string;\n request: Request;\n response: Response;\n}\n\nconst FILE_VERSION = 1;\n\n/** Default cap on the number of entries per session. */\nconst DEFAULT_MAX_ENTRIES = 1000;\n\nexport interface ReplayLogStoreOptions {\n /** Root sessions directory used with `sessionScopedPath(..., '.replay.jsonl')`. */\n dir: string;\n /**\n * Cap on the number of entries per session. When a `record` would\n * push the file beyond this, the oldest entries are evicted (LRU\n * by insertion order). Set to `Infinity` to disable rotation.\n * Defaults to 1000 \u2014 a single LLM call averages ~5KB serialized\n * (messages + tools + response), so 1000 entries is ~5MB per\n * session which is a reasonable upper bound.\n */\n maxEntries?: number | undefined;\n events?: EventBus;\n traceId?: string;\n}\n\ninterface ReplayEntryLocation {\n entry?: ReplayEntry;\n offset: number;\n length: number;\n}\n\nexport class ReplayLogStore {\n private readonly dir: string;\n private readonly events: EventBus | undefined;\n private readonly traceId: string | undefined;\n private readonly writeChains = new Map<string, Promise<void>>();\n /** Per-session hash \u2192 on-disk location, with lazy entry hydration. */\n private readonly cache = new Map<string, Map<string, ReplayEntryLocation>>();\n /** Per-session entry count on disk, to detect when compaction is needed. */\n private readonly diskCount = new Map<string, number>();\n private readonly maxEntries: number;\n\n constructor(opts: ReplayLogStoreOptions) {\n this.dir = opts.dir;\n this.events = opts.events;\n this.traceId = opts.traceId;\n this.maxEntries = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;\n }\n\n // \u2500\u2500 Writes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Record a request/response pair. Idempotent on hash: a second\n * `record` for the same hash is a no-op (the existing entry wins).\n * Returns the hash.\n */\n async record(input: {\n sessionId: string;\n request: Request;\n response: Response;\n }): Promise<string> {\n const hash = hashRequest(input.request);\n const fp = this.filePath(input.sessionId);\n const t0 = Date.now();\n try {\n await this.enqueue(input.sessionId, async () => {\n await withFileLock(fp, async () => {\n // Dedup via the in-memory hash map \u2014 O(1) instead of re-reading\n // and re-parsing the whole JSONL just to run `entries.some(...)`.\n // `ensureCache` populates both the cache and `diskCount` from a\n // single read on first contact; subsequent records reuse it.\n const cache = await this.ensureCache(input.sessionId);\n if (cache.has(hash)) return; // already recorded\n\n const entry: ReplayEntry = {\n hash,\n ts: new Date().toISOString(),\n request: input.request,\n response: input.response,\n };\n\n const currentCount = this.diskCount.get(input.sessionId) ?? 0;\n const willEvict = currentCount + 1 > this.maxEntries;\n\n if (!willEvict) {\n // Common path (the first `maxEntries` writes per session, plus\n // any session that never hits the cap): a single O(1) append.\n // The previous implementation did a full readAll + full rewrite\n // (atomicWrite of the entire file) on every single record, which\n // was quadratic in session length \u2014 a 1000-call session rewrote\n // a multi-MB file 1000 times.\n const line = JSON.stringify(entry) + '\\n';\n let offset = 0;\n try {\n const stat = await fs.stat(fp);\n offset = stat.size;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;\n }\n await fs.appendFile(fp, line, 'utf8');\n cache.set(hash, { entry, offset, length: Buffer.byteLength(line, 'utf8') });\n this.diskCount.set(input.sessionId, currentCount + 1);\n this.events?.emit('storage.write', {\n sessionId: input.sessionId,\n store: 'replay',\n filePath: fp,\n operation: 'record',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return;\n }\n\n // Eviction path: we're at capacity, drop the oldest entry to make\n // room. This does require a full read + rewrite, but it fires at\n // most once per `maxEntries` writes (default 1000), not per write.\n const all = await this.readAll(input.sessionId);\n all.push(entry);\n const keep = all.slice(-this.maxEntries);\n const refreshed = new Map<string, ReplayEntryLocation>();\n let offset = 0;\n for (const e of keep) {\n const line = JSON.stringify(e) + '\\n';\n refreshed.set(e.hash, { entry: e, offset, length: Buffer.byteLength(line, 'utf8') });\n offset += Buffer.byteLength(line, 'utf8');\n }\n this.cache.set(input.sessionId, refreshed);\n this.diskCount.set(input.sessionId, keep.length);\n await this.writeAll(input.sessionId, keep, 'compact');\n });\n });\n return hash;\n } catch (err) {\n this.events?.emit('storage.error', {\n sessionId: input.sessionId,\n store: 'replay',\n filePath: fp,\n operation: 'record',\n outcome: 'failure',\n error: storageErrorString(err),\n recoverable: false,\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n throw err;\n }\n }\n\n // \u2500\u2500 Reads \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Look up an entry by hash. Returns `null` when the request has\n * not been recorded for this session. O(1) after the first call\n * per session (in-memory cache).\n */\n async lookup(sessionId: string, hash: string): Promise<ReplayEntry | null> {\n const fp = this.filePath(sessionId);\n const t0 = Date.now();\n try {\n const cache = await this.ensureCache(sessionId);\n const location = cache.get(hash);\n const entry = location ? await this.hydrateEntry(sessionId, hash, location) : null;\n this.events?.emit('storage.read', {\n sessionId,\n store: 'replay',\n filePath: fp,\n operation: 'lookup',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return entry;\n } catch (err) {\n this.events?.emit('storage.read', {\n sessionId,\n store: 'replay',\n filePath: fp,\n operation: 'lookup',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: storageErrorString(err),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n throw err;\n }\n }\n\n /** All recorded entries for a session, in insertion order. */\n async load(sessionId: string): Promise<ReplayEntry[]> {\n const fp = this.filePath(sessionId);\n const t0 = Date.now();\n try {\n const cache = await this.ensureCache(sessionId);\n const entries: ReplayEntry[] = [];\n for (const [hash, location] of cache) {\n entries.push(await this.hydrateEntry(sessionId, hash, location));\n }\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.read', {\n sessionId,\n store: 'replay',\n filePath: fp,\n operation: 'load',\n outcome: 'success',\n durationMs,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return entries;\n } catch (err) {\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.read', {\n sessionId,\n store: 'replay',\n filePath: fp,\n operation: 'load',\n outcome: 'failure',\n durationMs,\n error: storageErrorString(err),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n throw err;\n }\n }\n\n /**\n * List every session id that has a replay log in the store dir.\n * Returns an array of `{ sessionId, entryCount, path }` sorted\n * by sessionId for stable output. Used by `wstack replay --list`.\n */\n async list(): Promise<Array<{ sessionId: string; entryCount: number; path: string }>> {\n const out: Array<{ sessionId: string; entryCount: number; path: string }> = [];\n // Replay logs sit next to their session JSONL \u2014 flat at the root for\n // legacy/`record-<ts>` ids, inside a date-shard dir for modern ids.\n // Scan both levels; a root-only scan misses every sharded session.\n const scan = async (dir: string, prefix: string, depth: number): Promise<void> => {\n let entries: import('node:fs').Dirent[];\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch (err) {\n if (depth === 0 && (err as NodeJS.ErrnoException).code !== 'ENOENT') {\n // EACCES, ENOTDIR, etc. \u2014 log the real error so the operator can\n // diagnose a misconfiguration, but still return empty list so the\n // caller (slash command display) doesn't crash.\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'replay_log_store.list_readdir_failed',\n dir,\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }));\n }\n return;\n }\n for (const entry of entries) {\n if (entry.name.startsWith('.')) continue;\n if (entry.isDirectory()) {\n if (depth === 0) await scan(path.join(dir, entry.name), entry.name, depth + 1);\n continue;\n }\n if (!entry.isFile() || !entry.name.endsWith('.replay.jsonl')) continue;\n const base = entry.name.slice(0, -'.replay.jsonl'.length);\n const sessionId = prefix ? `${prefix}/${base}` : base;\n const fp = path.join(dir, entry.name);\n out.push({\n sessionId,\n entryCount: await this.countEntries(fp),\n path: fp,\n });\n }\n };\n await scan(this.dir, '', 0);\n return out.sort((a, b) => a.sessionId.localeCompare(b.sessionId));\n }\n\n // \u2500\u2500 Internals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private filePath(sessionId: string): string {\n // Containment-checked: date-sharded ids (\"2026-06-11/<base>\") are\n // legitimate; traversal is rejected. A plain slash ban would throw\n // the moment a real (sharded) session id is used for --replay.\n return sessionScopedPath(this.dir, sessionId, '.replay.jsonl');\n }\n\n private async countEntries(filePath: string): Promise<number> {\n // list() only needs a count, not provider request/response payloads. Count\n // non-empty JSONL rows in fixed-size chunks so large replay logs are not\n // parsed or materialized just to render `wstack replay --list`.\n const handle = await fs.open(filePath, 'r');\n const buffer = Buffer.allocUnsafe(64 * 1024);\n let count = 0;\n let hasNonWhitespace = false;\n try {\n while (true) {\n const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);\n if (bytesRead === 0) break;\n for (let i = 0; i < bytesRead; i++) {\n const ch = buffer[i]!;\n if (ch === 10) {\n if (hasNonWhitespace) count++;\n hasNonWhitespace = false;\n continue;\n }\n if (ch !== 13 && ch !== 32 && ch !== 9) {\n hasNonWhitespace = true;\n }\n }\n }\n } finally {\n await handle.close();\n }\n if (hasNonWhitespace) count++;\n return count;\n }\n\n private parseReplayLine(line: string): ReplayEntry | null {\n try {\n const parsed = safeParse<\n { version?: number | undefined; entry?: ReplayEntry | undefined } & ReplayEntry\n >(line);\n if (!parsed.ok || !parsed.value) return null;\n if ('entry' in parsed.value && parsed.value.entry) {\n return parsed.value.entry;\n }\n return parsed.value;\n } catch {\n return null;\n }\n }\n\n private async readAll(sessionId: string): Promise<ReplayEntry[]> {\n const fp = this.filePath(sessionId);\n try {\n const raw = await fs.readFile(fp, 'utf8');\n const out: ReplayEntry[] = [];\n for (const line of raw.split('\\n')) {\n if (!line.trim()) continue;\n const parsed = this.parseReplayLine(line);\n if (parsed) out.push(parsed);\n }\n return out;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw err;\n }\n }\n\n private async writeAll(\n sessionId: string,\n entries: ReplayEntry[],\n operation: 'record' | 'compact' = 'record',\n ): Promise<void> {\n const fp = this.filePath(sessionId);\n const t0 = Date.now();\n const body = entries.map((e) => JSON.stringify(e)).join('\\n') + (entries.length ? '\\n' : '');\n await atomicWrite(fp, body);\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId,\n store: 'replay',\n filePath: fp,\n operation,\n outcome: 'success',\n durationMs,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n // Drop the version-stamp comment at the top \u2014 v1 has no envelope,\n // but we keep a one-line marker for human readers / future tooling.\n // (The atomicWrite just wrote pure JSONL; that's correct for v1.)\n void FILE_VERSION;\n }\n\n private async ensureCache(sessionId: string): Promise<Map<string, ReplayEntryLocation>> {\n let cache = this.cache.get(sessionId);\n if (cache) return cache;\n\n const fp = this.filePath(sessionId);\n cache = new Map();\n try {\n const handle = await fs.open(fp, 'r');\n const CHUNK = 64 * 1024;\n const buffer = Buffer.alloc(CHUNK);\n let leftover = '';\n let leftoverOffset = 0;\n let fileOffset = 0;\n try {\n while (true) {\n const { bytesRead } = await handle.read(buffer, 0, CHUNK, fileOffset);\n if (bytesRead === 0) break;\n const chunk = buffer.subarray(0, bytesRead).toString('utf8');\n const text = leftover + chunk;\n let lineStart = leftoverOffset;\n let searchFrom = 0;\n while (true) {\n const newlineIndex = text.indexOf('\\n', searchFrom);\n if (newlineIndex === -1) break;\n const line = text.slice(searchFrom, newlineIndex);\n const lineLength = Buffer.byteLength(text.slice(searchFrom, newlineIndex + 1), 'utf8');\n if (line.trim()) {\n const entry = this.parseReplayLine(line);\n if (entry) {\n cache.set(entry.hash, { offset: lineStart, length: lineLength });\n }\n }\n lineStart += lineLength;\n searchFrom = newlineIndex + 1;\n }\n leftover = text.slice(searchFrom);\n leftoverOffset = lineStart;\n fileOffset += bytesRead;\n }\n if (leftover.trim()) {\n const entry = this.parseReplayLine(leftover);\n if (entry) {\n cache.set(entry.hash, { offset: leftoverOffset, length: Buffer.byteLength(leftover, 'utf8') });\n }\n }\n } finally {\n await handle.close();\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;\n }\n\n this.cache.set(sessionId, cache);\n this.diskCount.set(sessionId, cache.size);\n return cache;\n }\n\n private async hydrateEntry(\n sessionId: string,\n hash: string,\n location: ReplayEntryLocation,\n ): Promise<ReplayEntry> {\n if (location.entry) return location.entry;\n\n const fp = this.filePath(sessionId);\n const handle = await fs.open(fp, 'r');\n try {\n const buffer = Buffer.alloc(location.length);\n const { bytesRead } = await handle.read(buffer, 0, location.length, location.offset);\n const line = buffer.subarray(0, bytesRead).toString('utf8').trimEnd();\n const entry = this.parseReplayLine(line);\n if (!entry) {\n throw new Error(`Replay entry ${hash} is unreadable`);\n }\n location.entry = entry;\n return entry;\n } finally {\n await handle.close();\n }\n }\n\n private enqueue(sessionId: string, fn: () => Promise<void>): Promise<void> {\n const prev = this.writeChains.get(sessionId) ?? Promise.resolve();\n const next = prev.then(fn, fn);\n this.writeChains.set(\n sessionId,\n next.catch(() => undefined),\n );\n return next;\n }\n}\n", "import { createHash } from 'node:crypto';\nimport type { Request } from '../types/provider.js';\n\n/**\n * Idea #2 from IDEAS.md \u2014 Deterministic Replay.\n *\n * The hash function is the foundation of replay: given a `Request`,\n * produce a stable identifier so a recorded `Response` can be looked\n * up later when we want to \"re-run\" the same agent loop without\n * burning API credits.\n *\n * Stability rules:\n *\n * - All object keys are sorted recursively before stringification.\n * Without this, two semantically identical requests that differ\n * only in key insertion order would produce different hashes.\n * - We hash ONLY the fields that affect the response: `model`,\n * `system`, `messages`, `tools`, `maxTokens`, and the four\n * sampling knobs (`temperature`, `topP`, `stopSequences`,\n * `toolChoice`). Anything else on the `Request` (metadata,\n * future extensions) is ignored so replay stays forward-compat.\n * - We serialize to JSON. The `ContentBlock` and `Message` shapes\n * are pure data; this works as long as no `undefined` values\n * sneak in (those get dropped by `JSON.stringify`, which is\n * fine \u2014 the structural diff is what matters).\n *\n * The SHA-256 output is hex-encoded and prefixed with the algorithm\n * tag so a future migration to a different hash (e.g. blake3) is\n * trivial to detect.\n */\nexport function stableStringify(value: unknown): string {\n return JSON.stringify(sortKeys(value));\n}\n\nfunction sortKeys(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortKeys);\n if (value && typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(obj).sort()) {\n sorted[key] = sortKeys(obj[key]);\n }\n return sorted;\n }\n return value;\n}\n\nexport function hashRequest(request: Request): string {\n // Pick only the fields that affect the response. See stability rules.\n const payload = {\n model: request.model,\n system: request.system,\n messages: request.messages,\n tools: request.tools,\n maxTokens: request.maxTokens,\n temperature: request.temperature,\n topP: request.topP,\n stopSequences: request.stopSequences,\n toolChoice: request.toolChoice,\n };\n const json = stableStringify(payload);\n const digest = createHash('sha256').update(json, 'utf8').digest('hex');\n return `sha256:${digest}`;\n}\n", "import { expectDefined } from '../utils/expect-defined.js';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { SessionEvent } from '../types/session.js';\nimport { sessionScopedPath } from '../utils/session-scoped-path.js';\n/**\n * Idea #1 from IDEAS.md \u2014 Stateful Session Recovery.\n *\n * `SessionRecovery` is the read-side companion to the in-flight\n * marker mechanism. When the agent loop is running, it writes an\n * `in_flight_start` event at the current point in the log. On\n * clean shutdown, a matching `in_flight_end` follows. Provider and\n * tool events may legitimately appear after the start marker, so\n * recovery finds the latest lifecycle boundary rather than assuming\n * the marker is literally the last JSONL record.\n *\n * Detection is diagnostic only. Normal session resume reconstructs the\n * conversation by replaying persisted JSONL events; it must not blindly\n * re-execute tool calls after a crash. `recover()` exposes the tail after the\n * last checkpoint so callers can explain which persisted work was in flight.\n *\n * Concurrency: pure read; no writes. Safe to call from multiple\n * processes simultaneously.\n */\nexport interface StaleSession {\n sessionId: string;\n /** Path to the JSONL log. */\n path: string;\n /** Last event ts (the in_flight_start timestamp). */\n lastEventTs: string;\n /** Context the agent was working on when it died. */\n context: string;\n /** Total events in the log. */\n eventCount: number;\n}\n\nexport interface RecoveryPlan {\n sessionId: string;\n /** True if the session is stale (has a dangling in_flight_start). */\n stale: boolean;\n /** The last `checkpoint` event before the un-replayed work, or null. */\n lastCheckpoint: SessionEvent | null;\n /** All persisted events after the last checkpoint (diagnostic in-flight tail). */\n pendingEvents: SessionEvent[];\n /** The dangling in_flight_start event, if any. */\n inFlightStart: SessionEvent | null;\n /** Free-form context the agent was working on, if any. */\n context: string | null;\n}\n\n/**\n * Result of `SessionRecovery.recover(sessionId)`. Distinct from\n * `StaleSession`: a session is \"stale\" if its latest lifecycle\n * boundary is an open marker, but a \"recovery plan\" can also be generated for\n * clean sessions whose last checkpoint is older than the\n * conversation history (e.g. a user-initiated \"rewind to last\n * good state\" flow). This is a diagnostic plan, not authorization to replay\n * external side effects.\n */\nexport class SessionRecovery {\n /**\n * Scan a session log and return a `StaleSession` if and only if the newest\n * lifecycle boundary is an `in_flight_start` without a later\n * `in_flight_end`/`session_end`. Ordinary provider/tool events after the\n * marker do not make the session clean. Returns `null` when:\n * - the log does not exist;\n * - the log is empty;\n * - the latest lifecycle boundary is `in_flight_end` or `session_end`;\n * - there is no lifecycle boundary (legacy/pre-marker log).\n *\n * The reverse scanner is chunked and line-aware. It can cross arbitrarily\n * large JSONL records (for example a large tool result) without imposing a\n * fixed tail-size correctness limit. Clean logs normally return after the\n * first chunk; stale logs continue counting lines so `eventCount` remains\n * the documented total rather than a tail-only approximation.\n */\n async detectStale(sessionId: string): Promise<StaleSession | null> {\n const fp = this.filePath(sessionId);\n let stat;\n try {\n stat = await fs.stat(fp);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n /* v8 ignore next -- defensive: any other stat failure is also non-recoverable */\n return null;\n }\n if (stat.size === 0) return null;\n\n try {\n const scan = await scanLatestLifecycleBoundary(fp, stat.size);\n if (scan?.boundary.type !== 'in_flight_start') return null;\n return {\n sessionId,\n path: fp,\n lastEventTs: scan.boundary.ts,\n context: scan.boundary.context,\n eventCount: scan.eventCount,\n };\n /* v8 ignore start -- defensive: reverse scan failure after a successful stat is rare */\n } catch {\n return null;\n }\n /* v8 ignore stop */\n }\n\n /**\n * Generate a recovery plan for a session. The plan describes\n * the persisted tail after the last checkpoint, plus the dangling in-flight\n * marker if present. SessionStore.resume() independently reconstructs state\n * from the journal and does not re-run these events as commands.\n *\n * Returns a non-null plan for ANY session that has at least\n * one event after a checkpoint (or, for legacy sessions, at\n * least one event). Pure read; no mutation.\n */\n async recover(sessionId: string): Promise<RecoveryPlan | null> {\n const fp = this.filePath(sessionId);\n let raw: string;\n try {\n raw = await fs.readFile(fp, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n /* v8 ignore next -- defensive: any other read failure is also non-recoverable */\n return null;\n }\n const events: SessionEvent[] = [];\n for (const line of raw.split('\\n')) {\n if (!line.trim()) continue;\n try {\n events.push(JSON.parse(line) as SessionEvent);\n } catch {\n // skip corrupt lines\n }\n }\n if (events.length === 0) return null;\n // Find the last checkpoint.\n let lastCheckpoint: SessionEvent | null = null;\n let lastCheckpointIdx = -1;\n for (let i = 0; i < events.length; i++) {\n if (events[i]?.type === 'checkpoint') {\n lastCheckpoint = expectDefined(events[i]);\n lastCheckpointIdx = i;\n }\n }\n // Events after the last checkpoint = the diagnostic in-flight tail.\n const pendingEvents =\n lastCheckpointIdx >= 0 ? events.slice(lastCheckpointIdx + 1) : events;\n // The dangling in_flight_start, if it is the newest lifecycle boundary.\n // Provider/tool events may follow the marker and are still pending work.\n const latestBoundary = events.findLast(isLifecycleBoundary);\n const inFlightStart = latestBoundary?.type === 'in_flight_start' ? latestBoundary : null;\n const context = inFlightStart && inFlightStart.type === 'in_flight_start'\n ? inFlightStart.context\n : null;\n return {\n sessionId,\n stale: inFlightStart !== null,\n lastCheckpoint,\n pendingEvents,\n inFlightStart,\n context,\n };\n }\n\n /**\n * List every stale session in a directory. Returns an array\n * (possibly empty) sorted by `lastEventTs` descending \u2014 most\n * recent crash first.\n */\n async listResumable(): Promise<StaleSession[]> {\n const out: StaleSession[] = [];\n // Modern sessions live inside date-shard subdirectories\n // (\"2026-06-11/<base>.jsonl\"); legacy/flat sessions sit at the root.\n // Scan both \u2014 a root-only scan silently misses every modern crash.\n const collect = async (dir: string, prefix: string, depth: number): Promise<void> => {\n let entries: import('node:fs').Dirent[];\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n /* v8 ignore start -- defensive: the sessions dir (and its shards) are readable during a scan */\n } catch {\n return;\n }\n /* v8 ignore stop */\n for (const entry of entries) {\n if (entry.name.startsWith('.')) continue;\n if (\n entry.name === 'shared' ||\n entry.name === 'subagents' ||\n entry.name === 'attachments'\n )\n continue;\n if (entry.isDirectory()) {\n if (depth === 0) {\n await collect(path.join(dir, entry.name), entry.name, depth + 1);\n }\n continue;\n }\n if (!entry.isFile() || !entry.name.endsWith('.jsonl')) continue;\n if (entry.name === '_index.jsonl' || entry.name === '_mailbox.jsonl') continue;\n const base = entry.name.slice(0, -'.jsonl'.length);\n if (base.includes('.replay') || base.includes('.annotations') || base.includes('.audit'))\n continue;\n const sessionId = prefix ? `${prefix}/${base}` : base;\n const stale = await this.detectStale(sessionId);\n if (stale) out.push(stale);\n }\n };\n await collect(this.dir, '', 0);\n return out.sort((a, b) => b.lastEventTs.localeCompare(a.lastEventTs));\n }\n\n // \u2500\u2500 Internals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private filePath(sessionId: string): string {\n // Containment-checked: date-sharded ids (\"2026-06-11/<base>\") are\n // legitimate; traversal is rejected. Shared with the other per-session\n // sidecar stores so the contract can't drift.\n return sessionScopedPath(this.dir, sessionId, '.jsonl');\n }\n\n constructor(private readonly dir: string) {}\n}\n\ntype LifecycleBoundary = Extract<\n SessionEvent,\n { type: 'in_flight_start' | 'in_flight_end' | 'session_end' }\n>;\n\nfunction isLifecycleBoundary(event: SessionEvent): event is LifecycleBoundary {\n return (\n event.type === 'in_flight_start' ||\n event.type === 'in_flight_end' ||\n event.type === 'session_end'\n );\n}\n\nfunction parseLifecycleBoundary(line: Buffer): LifecycleBoundary | null {\n try {\n const parsed = JSON.parse(line.toString('utf8')) as SessionEvent;\n return isLifecycleBoundary(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nfunction hasNonWhitespace(line: Buffer): boolean {\n for (const byte of line) {\n if (byte !== 0x09 && byte !== 0x0a && byte !== 0x0d && byte !== 0x20) return true;\n }\n return false;\n}\n\n/**\n * Walk JSONL records newest-first without decoding partial UTF-8 chunks. A\n * record split across chunks is retained as bytes and decoded only after its\n * opening newline is found, so large/non-ASCII tool results cannot hide the\n * lifecycle marker immediately before them.\n */\nasync function scanLatestLifecycleBoundary(\n filePath: string,\n size: number,\n): Promise<{ boundary: LifecycleBoundary; eventCount: number } | null> {\n const CHUNK_SIZE = 64 * 1024;\n const handle = await fs.open(filePath, 'r');\n let position = size;\n let laterLineFragment = Buffer.alloc(0);\n let latestBoundary: LifecycleBoundary | null = null;\n let eventCount = 0;\n\n const observeLine = (line: Buffer): LifecycleBoundary | null => {\n if (!hasNonWhitespace(line)) return null;\n eventCount++;\n return parseLifecycleBoundary(line);\n };\n\n try {\n while (position > 0) {\n const length = Math.min(CHUNK_SIZE, position);\n position -= length;\n const chunk = Buffer.allocUnsafe(length);\n const { bytesRead } = await handle.read(chunk, 0, length, position);\n const data = Buffer.concat([chunk.subarray(0, bytesRead), laterLineFragment]);\n\n let lineEnd = data.length;\n for (let i = data.length - 1; i >= 0; i--) {\n if (data[i] !== 0x0a) continue;\n const boundary = observeLine(data.subarray(i + 1, lineEnd));\n if (!latestBoundary && boundary) latestBoundary = boundary;\n lineEnd = i;\n }\n laterLineFragment = data.subarray(0, lineEnd);\n\n // A clean boundary needs no exact event count because no StaleSession is\n // returned. The common clean path therefore remains a one-chunk read.\n if (latestBoundary && latestBoundary.type !== 'in_flight_start') {\n return { boundary: latestBoundary, eventCount };\n }\n }\n\n const boundary = observeLine(laterLineFragment);\n if (!latestBoundary && boundary) latestBoundary = boundary;\n return latestBoundary ? { boundary: latestBoundary, eventCount } : null;\n } finally {\n await handle.close();\n }\n}\n", "import { expectDefined } from '../utils/expect-defined.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport { createHash, randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs/promises';\nimport { withFileLock } from '../utils/atomic-write.js';\nimport { safeParse } from '../utils/safe-json.js';\nimport { sessionScopedPath } from '../utils/session-scoped-path.js';\nimport type { EventBus } from '../kernel/events.js';\n/**\n * ToolAuditLog \u2014 idea #9 from IDEAS.md.\n *\n * Tamper-evident audit trail for tool calls. Every tool_use /\n * tool_result pair is appended to a sidecar JSONL with a chained\n * SHA-256 \u2014 each entry's `prevHash` is the prior entry's `hash`,\n * so any post-hoc modification of a single line breaks the chain\n * from that point forward.\n *\n * Why a sidecar (not the session JSONL)?\n * Same reason as `AnnotationsStore` and `ReplayLogStore`: the\n * session log is an event-sourced journal. Mixing in a hash\n * chain would inflate every read and tightly couple the\n * integrity check to the event format. Sidecar keeps both\n * concerns orthogonal.\n *\n * What \"tamper-evident\" means here:\n * - The hash covers the full serialized entry: tool name, id,\n * input, output, timestamp, author. Changing any byte\n * changes the hash.\n * - The chain is sequential \u2014 a verifier walks the file in\n * order, recomputing each hash, and checks `prevHash`\n * matches the previous entry's `hash`.\n * - Any insertion, deletion, or modification of a single\n * entry surfaces as a \"chain broken at entry N\" verdict.\n *\n * What it does NOT defend against:\n * - An attacker who rewrites the whole file consistently.\n * For that you'd need an external anchor (signing key,\n * transparency log, etc.) \u2014 out of scope for Phase 1.\n * - The agent itself misbehaving; this is post-hoc audit, not\n * real-time enforcement. Use `PermissionPolicy` for that.\n *\n * File layout: `sessionScopedPath(dir, sessionId, '.audit.jsonl')`,\n * one entry per line. The chain starts with a `genesis` entry whose\n * `prevHash` is all zeros.\n */\nexport interface AuditEntry {\n /** Monotonic index (0-based). */\n index: number;\n /** UUID for cross-referencing with logs. */\n id: string;\n /** ISO timestamp. */\n ts: string;\n /** Hash of the previous entry (or all-zeros for the genesis entry). */\n prevHash: string;\n /** Hash of this entry's content (sha256 over the canonical JSON). */\n hash: string;\n toolName: string;\n toolUseId: string;\n input: unknown;\n output: unknown;\n isError: boolean;\n}\n\nconst GENESIS_PREV = '0'.repeat(64);\n\nexport type VerifyResult =\n | { ok: true; entries: number }\n | { ok: false; brokenAt: number; reason: string };\n\nexport interface ToolAuditLogOptions {\n /** Root sessions directory used with `sessionScopedPath(..., '.audit.jsonl')`. */\n dir: string;\n /**\n * Flush the file system cache to disk every N writes per session.\n * Default 100. Lower values = better crash durability, more I/O overhead.\n * Set to `Infinity` to disable periodic fsync (fastest, but highest data-loss risk).\n */\n fsyncEvery?: number | undefined;\n events?: EventBus;\n traceId?: string;\n}\n\n/** Default number of writes between fsync calls. */\nconst DEFAULT_FSYNC_EVERY = 100;\n\nexport class ToolAuditLog {\n private readonly dir: string;\n private readonly events: EventBus | undefined;\n private readonly traceId: string | undefined;\n /** In-memory cache of the last entry's hash (per session), to compute chains efficiently. */\n private readonly tailHash = new Map<string, string>();\n /** In-memory counter for entry indices \u2014 avoids re-reading the file on every write. */\n private readonly tailIndex = new Map<string, number>();\n /**\n * File mtime+size recorded after our last write, per session. Used to\n * detect cross-process writes (session handoff, recovery) that would\n * invalidate the in-memory tail cache: if the stat no longer matches\n * we re-read the file to re-establish the chain tip before appending.\n */\n private readonly tailStat = new Map<string, { mtimeMs: number; size: number }>();\n /** Tracks writes since last fsync, per session. */\n private readonly unSyncedWrites = new Map<string, number>();\n private readonly writeChains = new Map<string, Promise<void>>();\n private readonly fsyncEvery: number;\n\n constructor(opts: ToolAuditLogOptions) {\n this.dir = opts.dir;\n this.events = opts.events;\n this.traceId = opts.traceId;\n this.fsyncEvery = opts.fsyncEvery ?? DEFAULT_FSYNC_EVERY;\n }\n\n /**\n * Append a tool call/result pair to the chain. Returns the\n * resulting entry. Idempotency is not guaranteed \u2014 if you\n * record the same tool_use twice you get two entries. That's\n * intentional: the audit log is a record, not a cache.\n */\n async record(input: {\n sessionId: string;\n toolName: string;\n toolUseId: string;\n input: unknown;\n output: unknown;\n isError: boolean;\n }): Promise<AuditEntry> {\n let entry!: AuditEntry; // assigned inside the enqueue callback\n const fp = this.filePath(input.sessionId);\n const t0 = Date.now();\n try {\n await this.enqueue(input.sessionId, async () => {\n await withFileLock(fp, async () => {\n // Resolve the chain tip from the in-memory cache when possible,\n // re-reading the file only on first contact or when an external\n // writer (cross-process session handoff) changed it under us.\n // The previous implementation did `readAll` on every single\n // record just to find `entries.at(-1)` \u2014 a full parse of the\n // audit file per tool call.\n const tip = await this._resolveChainTip(input.sessionId, fp);\n const prevHash = tip.prevHash;\n const index = tip.nextIndex;\n\n const id = randomUUID();\n const ts = new Date().toISOString();\n const content = {\n id,\n ts,\n prevHash,\n toolName: input.toolName,\n toolUseId: input.toolUseId,\n input: input.input,\n output: input.output,\n isError: input.isError,\n index,\n };\n const hash = createHash('sha256').update(stableStringify(content), 'utf8').digest('hex');\n entry = {\n id,\n ts,\n prevHash,\n hash,\n toolName: input.toolName,\n toolUseId: input.toolUseId,\n input: input.input,\n output: input.output,\n isError: input.isError,\n index,\n };\n\n // True O(1) append \u2014 one line, no full-file rewrite. The previous\n // `writeAll(entries)` path re-serialized every entry on every\n // record; a 1000-call session rewrote a multi-MB audit file 1000\n // times (quadratic in session length). `withFileLock` above\n // guarantees no concurrent writer interleaves with our append.\n await fs.appendFile(fp, JSON.stringify(entry) + '\\n', 'utf8');\n\n // Refresh caches + fsync bookkeeping. We stat post-write so the\n // mtime+size tracker reflects the just-appended line \u2014 the next\n // record can trust the cache without re-reading.\n try {\n const st = await fs.stat(fp);\n this.tailStat.set(input.sessionId, { mtimeMs: st.mtimeMs, size: st.size });\n } catch {\n /* best-effort; next record will just re-read */\n }\n this.tailHash.set(input.sessionId, hash);\n this.tailIndex.set(input.sessionId, index + 1);\n await this._trackUnsynced(input.sessionId, fp);\n\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.write', {\n sessionId: input.sessionId,\n store: 'audit',\n filePath: fp,\n operation: 'record',\n outcome: 'success',\n durationMs,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n });\n });\n return entry;\n } catch (err) {\n this.events?.emit('storage.error', {\n sessionId: input.sessionId,\n store: 'audit',\n filePath: fp,\n operation: 'record',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: false,\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n throw err;\n }\n }\n\n /**\n * Resolve the chain tip (previous hash + next index) for an append.\n * Uses the in-memory `tailHash`/`tailIndex` cache when the file's\n * stat matches our last known write; falls back to a full read on\n * cache miss or when an external writer has extended the file.\n */\n private async _resolveChainTip(\n sessionId: string,\n fp: string,\n ): Promise<{ prevHash: string; nextIndex: number }> {\n const cachedHash = this.tailHash.get(sessionId);\n const cachedIndex = this.tailIndex.get(sessionId);\n const cachedStat = this.tailStat.get(sessionId);\n\n if (cachedHash !== undefined && cachedIndex !== undefined && cachedStat) {\n // Verify no other process appended since our last write. Stat is a\n // single inode lookup; the common case (same-process sequential\n // writes) returns immediately and we skip the full read.\n try {\n const st = await fs.stat(fp);\n if (st.mtimeMs === cachedStat.mtimeMs && st.size === cachedStat.size) {\n return { prevHash: cachedHash, nextIndex: cachedIndex };\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n // File was removed out from under us \u2014 reset and start a new chain.\n this.tailHash.delete(sessionId);\n this.tailIndex.delete(sessionId);\n this.tailStat.delete(sessionId);\n return { prevHash: GENESIS_PREV, nextIndex: 0 };\n }\n /* v8 ignore next -- defensive: a non-ENOENT stat failure during cache-hit is rare */\n throw err;\n }\n }\n\n // Cache miss or external write detected \u2014 read the true tail.\n const entries = await this.readAll(sessionId);\n const prev = entries.at(-1);\n const prevHash = prev?.hash ?? GENESIS_PREV;\n const nextIndex = prev ? prev.index + 1 : 0;\n this.tailHash.set(sessionId, prevHash);\n this.tailIndex.set(sessionId, nextIndex);\n try {\n const st = await fs.stat(fp);\n this.tailStat.set(sessionId, { mtimeMs: st.mtimeMs, size: st.size });\n } catch {\n /* leave cache empty; next record re-reads */\n }\n return { prevHash, nextIndex };\n }\n\n /**\n * Walk the chain and verify every entry's hash and prevHash.\n * Returns a structured verdict \u2014 never throws.\n */\n async verify(sessionId: string): Promise<VerifyResult> {\n const fp = this.filePath(sessionId);\n const t0 = Date.now();\n let entries: AuditEntry[];\n try {\n entries = await this.readAll(sessionId);\n } catch (err) {\n // The file exists but can't be read (permissions, corruption). We\n // can't verify it, so emit a read failure for observability and\n // degrade gracefully \u2014 this method's contract is \"never throws\".\n this.events?.emit('storage.read', {\n sessionId,\n store: 'audit',\n filePath: fp,\n operation: 'verify',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: toErrorMessage(err),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return { ok: true, entries: 0 };\n }\n\n // Walk the chain into a verdict, then emit a single storage.read whose\n // outcome reflects the verification result (a broken chain is a failure).\n const verdict = ((): VerifyResult => {\n if (entries.length === 0) return { ok: true, entries: 0 };\n // The first entry's prevHash must be the all-zeros genesis marker.\n if (entries[0]?.prevHash !== GENESIS_PREV) {\n return {\n ok: false,\n brokenAt: 0,\n reason: 'first entry is not the genesis (prevHash != 0\u20260)',\n };\n }\n let prevHash = GENESIS_PREV;\n for (let i = 0; i < entries.length; i++) {\n const e = expectDefined(entries[i]);\n if (e.prevHash !== prevHash) {\n return {\n ok: false,\n brokenAt: i,\n reason: `prevHash mismatch at entry ${i} (expected ${prevHash.slice(0, 8)}\u2026, got ${e.prevHash.slice(0, 8)}\u2026)`,\n };\n }\n // Recompute the hash from the entry's content (without the\n // `hash` field itself, which is what we are verifying).\n const content = {\n id: e.id,\n ts: e.ts,\n prevHash: e.prevHash,\n toolName: e.toolName,\n toolUseId: e.toolUseId,\n input: e.input,\n output: e.output,\n isError: e.isError,\n index: e.index,\n };\n const expectedHash = createHash('sha256')\n .update(stableStringify(content), 'utf8')\n .digest('hex');\n if (expectedHash !== e.hash) {\n return {\n ok: false,\n brokenAt: i,\n reason: `hash mismatch at entry ${i} (entry content was modified)`,\n };\n }\n prevHash = e.hash;\n }\n return { ok: true, entries: entries.length };\n })();\n\n this.events?.emit('storage.read', {\n sessionId,\n store: 'audit',\n filePath: fp,\n operation: 'verify',\n outcome: verdict.ok ? 'success' : 'failure',\n durationMs: Date.now() - t0,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return verdict;\n }\n\n /** All entries for a session, in insertion order. */\n async load(sessionId: string): Promise<AuditEntry[]> {\n const fp = this.filePath(sessionId);\n const t0 = Date.now();\n try {\n const entries = await this.readAll(sessionId);\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.read', {\n sessionId,\n store: 'audit',\n filePath: fp,\n operation: 'load',\n outcome: 'success',\n durationMs,\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n return entries;\n } catch (err) {\n const durationMs = Date.now() - t0;\n this.events?.emit('storage.read', {\n sessionId,\n store: 'audit',\n filePath: fp,\n operation: 'load',\n outcome: 'failure',\n durationMs,\n error: toErrorMessage(err),\n ...(this.traceId !== undefined ? { traceId: this.traceId } : {}),\n });\n throw err;\n }\n }\n\n // \u2500\u2500 Internals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private filePath(sessionId: string): string {\n // Containment-checked: date-sharded ids (\"2026-06-11/<base>\") are\n // legitimate; traversal is rejected. A plain slash ban would throw\n // for every modern session id the moment record() gets wired in.\n return sessionScopedPath(this.dir, sessionId, '.audit.jsonl');\n }\n\n private async readAll(sessionId: string): Promise<AuditEntry[]> {\n const fp = this.filePath(sessionId);\n try {\n const raw = await fs.readFile(fp, 'utf8');\n const out: AuditEntry[] = [];\n for (const line of raw.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const parsed = safeParse<AuditEntry>(line);\n if (parsed.ok && parsed.value) out.push(parsed.value);\n } catch {\n // Skip corrupt lines \u2014 audit data is meta, not fatal.\n }\n }\n return out;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return [];\n // Non-ENOENT errors (EACCES, ENOSPC, etc.) are real I/O failures \u2014\n // re-throw so callers (verify, load) can emit storage.error.\n throw err;\n }\n }\n\n /**\n * Tracks writes since last fsync and triggers periodic fsync.\n * Called after each O(1) append to maintain the same durability\n * guarantees as the old writeAll approach.\n */\n private async _trackUnsynced(sessionId: string, fp: string): Promise<void> {\n const count = (this.unSyncedWrites.get(sessionId) ?? 0) + 1;\n this.unSyncedWrites.set(sessionId, count);\n if (this.fsyncEvery !== Number.POSITIVE_INFINITY && count % this.fsyncEvery === 0) {\n await this.sync(sessionId, fp);\n }\n }\n\n /**\n * Explicitly sync the file to disk. Called automatically every\n * `fsyncEvery` writes, and available for callers who want to\n * force a sync before closing or during graceful shutdown.\n */\n async flush(sessionId: string): Promise<void> {\n await this.sync(sessionId, this.filePath(sessionId));\n }\n\n private async sync(sessionId: string, fp: string): Promise<void> {\n try {\n const fh = await fs.open(fp, 'r+');\n try {\n await fh.sync();\n } finally {\n await fh.close();\n }\n } catch {\n // fsync is best-effort; a failure here does not corrupt the chain.\n } finally {\n this.unSyncedWrites.set(sessionId, 0);\n }\n }\n\n private enqueue(sessionId: string, fn: () => Promise<void>): Promise<void> {\n const prev = this.writeChains.get(sessionId) ?? Promise.resolve();\n const next = prev.then(fn, fn);\n this.writeChains.set(\n sessionId,\n next.catch(() => undefined),\n );\n return next;\n }\n}\n\nfunction stableStringify(value: unknown): string {\n return JSON.stringify(sortKeys(value));\n}\n\nfunction sortKeys(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortKeys);\n if (value && typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(obj).sort()) {\n sorted[key] = sortKeys(obj[key]);\n }\n return sorted;\n }\n return value;\n}\n", "import type { SessionEvent } from '../types/session.js';\n\nexport interface QueryFilter {\n eventTypes?: string[] | undefined;\n toolNames?: string[] | undefined;\n timeRange?: { start: string; end: string } | undefined;\n}\n\nexport interface ToolInvocation {\n ts: string;\n name: string;\n input: unknown;\n output?: unknown | undefined;\n error?: string | undefined;\n durationMs: number;\n}\n\nexport interface SessionError {\n ts: string;\n phase: string;\n message: string;\n}\n\nexport interface ModeChange {\n ts: string;\n from: string;\n to: string;\n}\n\nexport interface TaskSummary {\n taskId: string;\n title: string;\n status: string;\n createdAt: string;\n completedAt?: string | undefined;\n}\n\nexport interface SessionAnalysis {\n sessionId: string;\n totalDuration: number;\n toolUsageCount: Record<string, number>;\n errorCount: number;\n modeChanges: ModeChange[];\n tasks: TaskSummary[];\n}\n\nexport class SessionAnalyzer {\n analyze(events: SessionEvent[]): SessionAnalysis {\n const toolUsageCount: Record<string, number> = {};\n const errors: SessionError[] = [];\n const modeChanges: ModeChange[] = [];\n const tasksById = new Map<string, TaskSummary>();\n let sessionId = '';\n\n for (const event of events) {\n // sessionId comes from session_start / session_resumed.\n if (event.type === 'session_start' || event.type === 'session_resumed') {\n if (!sessionId) sessionId = event.id;\n }\n if (event.type === 'tool_use') {\n toolUsageCount[event.name] = (toolUsageCount[event.name] ?? 0) + 1;\n }\n if (event.type === 'error') {\n errors.push({ ts: event.ts, phase: event.phase, message: event.message });\n }\n if (event.type === 'mode_changed') {\n modeChanges.push({ ts: event.ts, from: event.from, to: event.to });\n }\n if (event.type === 'task_created') {\n tasksById.set(event.taskId, {\n taskId: event.taskId,\n title: event.title,\n status: 'created',\n createdAt: event.ts,\n });\n }\n if (event.type === 'task_updated') {\n const t = tasksById.get(event.taskId);\n if (t) t.status = event.status;\n }\n if (event.type === 'task_completed') {\n const t = tasksById.get(event.taskId);\n if (t) {\n t.status = 'completed';\n t.completedAt = event.ts;\n } else {\n tasksById.set(event.taskId, {\n taskId: event.taskId,\n title: event.title,\n status: 'completed',\n createdAt: event.ts,\n completedAt: event.ts,\n });\n }\n }\n if (event.type === 'task_failed') {\n const t = tasksById.get(event.taskId);\n if (t) {\n t.status = 'failed';\n t.completedAt = event.ts;\n } else {\n tasksById.set(event.taskId, {\n taskId: event.taskId,\n title: event.title,\n status: 'failed',\n createdAt: event.ts,\n completedAt: event.ts,\n });\n }\n }\n }\n\n return {\n sessionId,\n totalDuration: this.calcDuration(events),\n toolUsageCount,\n errorCount: errors.length,\n modeChanges,\n tasks: Array.from(tasksById.values()),\n };\n }\n\n query(events: SessionEvent[], filter: QueryFilter): SessionEvent[] {\n return events.filter((e) => {\n if (filter.eventTypes?.length && !filter.eventTypes.includes(e.type)) return false;\n if (filter.toolNames?.length && e.type === 'tool_use') {\n const toolEvent = e as { type: 'tool_use'; name: string };\n if (!filter.toolNames.includes(toolEvent.name)) return false;\n }\n if (filter.timeRange) {\n const ts = new Date(e.ts).getTime();\n const start = new Date(filter.timeRange.start).getTime();\n const end = new Date(filter.timeRange.end).getTime();\n if (ts < start || ts > end) return false;\n }\n return true;\n });\n }\n\n private calcDuration(events: SessionEvent[]): number {\n if (events.length < 2) return 0;\n const firstEvent = events[0];\n const lastEvent = events[events.length - 1];\n /* v8 ignore next -- defensive: length>=2 guard above guarantees both ends exist */\n if (!firstEvent || !lastEvent) return 0;\n const first = new Date(firstEvent.ts).getTime();\n const last = new Date(lastEvent.ts).getTime();\n return last - first;\n }\n}\n", "/**\n * SessionRegistry \u2014 cross-process session and agent tracker.\n *\n * Each WrongStack process registers its session on start and updates its\n * status periodically. The registry is a single JSON file at\n * `~/.wrongstack/session-registry.json`. Entries are keyed by session ID.\n *\n * Because multiple processes may write concurrently, every write is an\n * atomic read-modify-write protected by a per-file advisory lock (flock on\n * Unix, exclusive open on Windows). Stale entries (process no longer alive)\n * are pruned on every read.\n *\n * @module session-registry\n */\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { randomUUID } from 'node:crypto';\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Live status of a single agent within a session. */\nexport type AgentLiveStatus =\n | 'idle'\n | 'running'\n | 'streaming'\n | 'waiting_user' // brain.ask_human, confirm prompt\n | 'error';\n\nexport interface AgentEntry {\n /** Unique agent id (ULID or UUID). */\n id: string;\n /** Human-readable label (e.g. \"leader\", \"bug-hunter #1\"). */\n name: string;\n /** UTC ISO timestamp when this agent/run started, when known. */\n startedAt?: string | undefined;\n status: AgentLiveStatus;\n /** Current tool name if running, undefined otherwise. */\n currentTool?: string | undefined;\n /** Iteration count so far. */\n iterations: number;\n /** Tool calls so far. */\n toolCalls: number;\n /** Cumulative cost in USD for this agent, when known. */\n costUsd?: number | undefined;\n /** Cumulative input tokens, when known. */\n tokensIn?: number | undefined;\n /** Cumulative output tokens, when known. */\n tokensOut?: number | undefined;\n /** Context window fill 0\u2013100 (may exceed 100 when over limit), when known. */\n ctxPct?: number | undefined;\n /** Model id this agent is running on, when known. */\n model?: string | undefined;\n /**\n * Tail of the assistant text currently being streamed (capped, throttled).\n * Lets a cross-process watcher see the response form in near-real-time\n * instead of waiting for the completed turn to land in the session log.\n */\n partialText?: string | undefined;\n /** UTC ISO timestamp of last activity. */\n lastActivityAt: string;\n}\n\nexport type SessionLiveStatus =\n | 'active' // process running, agents may be idle or busy\n | 'idle' // process running, no agent activity\n | 'closing' // session_end written, process shutting down\n | 'stale' // process no longer alive (pruned on next read)\n | 'lost'; // heartbeat timeout \u2014 process may still be alive but unreachable\n\nexport interface SessionRegistryEntry {\n sessionId: string;\n projectSlug: string;\n projectRoot: string;\n projectName: string;\n workingDir: string;\n /**\n * Which surface owns this session \u2014 `'tui'` / `'webui'` / `'cli'` (one-shot or\n * REPL). Lets cross-process consumers (e.g. the WebUI Fleet HQ office map) label\n * each live session by client kind. Optional for back-compat with older entries.\n */\n clientType?: 'tui' | 'webui' | 'cli' | 'repl' | string | undefined;\n /** Current git branch, if the project is a git repo. Detected at registration. */\n gitBranch?: string | undefined;\n status: SessionLiveStatus;\n pid: number;\n /** UTC ISO */\n startedAt: string;\n /** UTC ISO \u2014 updated on every heartbeat */\n lastHeartbeatAt: string;\n /** Count of tracked agents */\n agentCount: number;\n agents: AgentEntry[];\n}\n\n// \u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst REGISTRY_FILE = 'session-registry.json';\nconst HEARTBEAT_INTERVAL_MS = 5_000;\nconst STALE_TIMEOUT_MS = 30_000; // entry considered stale after 30s without heartbeat\n// A session that announced `closing` (heartbeat stopped) is dropped this long\n// after its last heartbeat, so the fleet view doesn't keep a dead client around.\nconst CLOSING_GRACE_MS = 15_000;\n// A live PID can outlast its terminal (for example, an orphaned WebSocket or\n// plugin handle keeps Node alive). Give a missed heartbeat one additional\n// timeout window, then remove it: the presence registry must reflect a live\n// surface, not merely an extant process id.\nconst LOST_GRACE_MS = 30_000;\n/** Subagents without any activity this long are not live, even if the owning session still heartbeats. */\nconst AGENT_STALE_MS = 5 * 60_000;\n// A held lock is released within milliseconds; anything older is a crashed\n// owner's leftover and is safe to break so writes never wedge permanently.\nconst STALE_LOCK_MS = 10_000;\nconst STALE_TMP_MS = 60_000;\nconst MAX_STALE_TMP_FILES = 20;\n\n// \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Parse registry file contents, tolerating corruption. A system crash can\n * leave the file zero-filled (NTFS journals the rename metadata but the data\n * blocks were never flushed \u2014 the file is its full size of NUL bytes), or a\n * torn write can leave invalid JSON. Treat anything unparsable \u2014 or parsable\n * but not a plain object \u2014 as an empty registry so the next write heals the\n * file instead of wedging every future write forever.\n */\nfunction parseRegistry(raw: string): Record<string, SessionRegistryEntry> {\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return parsed as Record<string, SessionRegistryEntry>;\n }\n } catch {\n /* corrupt file \u2014 fall through to empty */\n }\n return {};\n}\n\n// \u2500\u2500 Registry class \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class SessionRegistry {\n private readonly filePath: string;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private currentSessionId: string | null = null;\n /**\n * Last full entry this process registered. Kept so the heartbeat can\n * re-create our entry if it ever goes missing \u2014 e.g. our initial register()\n * write was dropped (a wedged lock), the file was reset, or we were pruned.\n */\n private lastEntry: SessionRegistryEntry | null = null;\n\n constructor(globalRoot: string) {\n this.filePath = path.join(globalRoot, REGISTRY_FILE);\n }\n\n // \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Register the current session. Call once on session start.\n * Starts the heartbeat timer.\n */\n async register(\n entry: Omit<SessionRegistryEntry, 'status' | 'lastHeartbeatAt' | 'agentCount' | 'agents'> & {\n agents?: AgentEntry[] | undefined;\n },\n ): Promise<void> {\n // Safe to call again on a project switch: the WebUI re-roots in place and\n // creates a fresh session id pointing at the new project. Clear the prior\n // heartbeat timer (otherwise each switch leaks a timer that keeps writing).\n // A process owns exactly one entry, so the same-pid dedup below drops our\n // own previous entry \u2014 the registry never carries a phantom session still\n // pointing at the old project's root/workingDir.\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n this.currentSessionId = entry.sessionId;\n const full: SessionRegistryEntry = {\n ...entry,\n status: 'active',\n lastHeartbeatAt: new Date().toISOString(),\n agentCount: entry.agents?.length ?? 0,\n agents: entry.agents ?? [],\n };\n this.lastEntry = full;\n await this.atomicUpdate((registry) => {\n // Prune dead entries that haven't heartbeated recently.\n // A just-created entry has no heartbeat yet \u2014 don't prune it.\n const now = Date.now();\n for (const [id, existing] of Object.entries(registry)) {\n if (existing.pid === entry.pid) {\n // Our own process owns exactly one entry. When re-registering under\n // a new session id (project switch re-roots in place), drop the\n // stale same-pid entry so it doesn't linger pointing at the old\n // project's root/workingDir.\n if (id !== entry.sessionId) delete registry[id];\n continue;\n }\n const heartbeatAge = now - new Date(existing.lastHeartbeatAt).getTime();\n if (heartbeatAge > STALE_TIMEOUT_MS && !pidAlive(existing.pid)) {\n delete registry[id];\n }\n }\n registry[entry.sessionId] = full;\n });\n\n // Start heartbeat\n /* v8 ignore start -- 5s heartbeat timer fires only in a live process, not under test */\n this.heartbeatTimer = setInterval(() => {\n void this.heartbeat();\n }, HEARTBEAT_INTERVAL_MS);\n /* v8 ignore stop */\n if (this.heartbeatTimer.unref) this.heartbeatTimer.unref();\n }\n\n /**\n * Update agent status for the current session. Call on every\n * significant status change (agent start, tool start, user wait, error).\n */\n async updateAgents(agents: AgentEntry[]): Promise<void> {\n if (!this.currentSessionId) return;\n // Derive session status from the agent collective.\n const hasRunning = agents.some((a) => a.status === 'running' || a.status === 'streaming');\n const hasWaiting = agents.some((a) => a.status === 'waiting_user');\n const hasError = agents.some((a) => a.status === 'error');\n const status: SessionLiveStatus = hasRunning || hasWaiting || hasError ? 'active' : 'idle';\n const nowIso = new Date().toISOString();\n\n // Keep the cached entry current so a heartbeat re-insert carries live agents.\n if (this.lastEntry) {\n this.lastEntry.agents = agents;\n this.lastEntry.agentCount = agents.length;\n this.lastEntry.status = status;\n this.lastEntry.lastHeartbeatAt = nowIso;\n }\n\n await this.atomicUpdate((registry) => {\n let entry = registry[this.currentSessionId!];\n if (!entry) {\n // Our entry vanished (dropped write / reset / pruned) \u2014 re-create it.\n /* v8 ignore next -- unreachable: register() sets lastEntry before any updateAgents */\n if (!this.lastEntry) return;\n entry = { ...this.lastEntry };\n registry[this.currentSessionId!] = entry;\n }\n entry.agents = agents;\n entry.agentCount = agents.length;\n entry.status = status;\n entry.lastHeartbeatAt = nowIso;\n });\n }\n\n /**\n * Mark the session as closing. Called during shutdown.\n * Stops the heartbeat timer.\n */\n async markClosing(): Promise<void> {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n if (!this.currentSessionId) return;\n await this.atomicUpdate((registry) => {\n const entry = registry[this.currentSessionId!];\n if (!entry) return;\n entry.status = 'closing';\n entry.lastHeartbeatAt = new Date().toISOString();\n });\n }\n\n /**\n * Remove the current session from the registry. Call on clean exit.\n */\n async unregister(): Promise<void> {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n if (!this.currentSessionId) return;\n const sid = this.currentSessionId;\n this.currentSessionId = null;\n await this.atomicUpdate((registry) => {\n delete registry[sid];\n });\n }\n\n /**\n * List all non-stale sessions. Prunes stale entries automatically.\n */\n async list(): Promise<SessionRegistryEntry[]> {\n const registry = await this.readAndPrune();\n return Object.values(registry);\n }\n\n /**\n * Get a single session entry by ID. Returns undefined if not found or stale.\n */\n async get(sessionId: string): Promise<SessionRegistryEntry | undefined> {\n const registry = await this.readAndPrune();\n return registry[sessionId];\n }\n\n /**\n * List all sessions for a specific project (by slug).\n */\n async listByProject(projectSlug: string): Promise<SessionRegistryEntry[]> {\n const all = await this.list();\n return all.filter((e) => e.projectSlug === projectSlug);\n }\n\n /**\n * Return the registry file path. Useful for WebUI to watch/read.\n */\n get registryPath(): string {\n return this.filePath;\n }\n\n // \u2500\u2500 Internal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async heartbeat(): Promise<void> {\n if (!this.currentSessionId) return;\n try {\n const sessionId = this.currentSessionId;\n const nowIso = new Date().toISOString();\n await this.atomicUpdate((registry) => {\n const entry = registry[sessionId];\n if (entry) {\n entry.lastHeartbeatAt = nowIso;\n // Status bound: if closing, don't revert\n if (entry.status !== 'closing') {\n const hasRunning = (entry.agents ?? []).some(\n (a) => a.status === 'running' || a.status === 'streaming',\n );\n entry.status = hasRunning ? 'active' : 'idle';\n }\n return;\n }\n if (this.lastEntry) {\n // Our entry is gone (initial register() dropped on a wedged lock, file\n // reset, or pruned). Re-create it through the locked path so a process\n // that booted into a broken registry still shows up once it heals.\n registry[sessionId] = { ...this.lastEntry, lastHeartbeatAt: nowIso };\n }\n });\n } catch {\n // Best-effort heartbeat \u2014 never throw\n }\n }\n\n private async readAndPrune(): Promise<Record<string, SessionRegistryEntry>> {\n try {\n const raw = await fs.readFile(this.filePath, 'utf8');\n const registry = parseRegistry(raw);\n const now = Date.now();\n let pruned = false;\n\n for (const [id, entry] of Object.entries(registry)) {\n const heartbeatAt = Date.parse(entry.lastHeartbeatAt);\n if (!Number.isFinite(heartbeatAt)) {\n delete registry[id];\n pruned = true;\n continue;\n }\n const heartbeatAge = now - heartbeatAt;\n\n // A live session heartbeat does not prove every cached subagent still\n // exists. Keep the leader as the surface itself, but reap workers whose\n // activity stopped several minutes ago (the common shadow-agent ghost).\n const agents = Array.isArray(entry.agents) ? entry.agents : [];\n const liveAgents = agents.filter((agent) => {\n if (agent.id === 'leader') return true;\n const lastActivityAt = Date.parse(agent.lastActivityAt);\n return Number.isFinite(lastActivityAt) && now - lastActivityAt <= AGENT_STALE_MS;\n });\n if (liveAgents.length !== agents.length || entry.agentCount !== liveAgents.length) {\n entry.agents = liveAgents;\n entry.agentCount = liveAgents.length;\n pruned = true;\n }\n\n // Cleanly-closed session: drop after a short grace so no dead client lingers.\n if (entry.status === 'closing' && heartbeatAge > CLOSING_GRACE_MS) {\n delete registry[id];\n pruned = true;\n continue;\n }\n if (heartbeatAge <= STALE_TIMEOUT_MS) continue;\n\n // A dead PID is definitive. A live PID can still be an orphaned Node\n // host, so it receives only a short lost grace before expiry.\n if (!pidAlive(entry.pid) || heartbeatAge > STALE_TIMEOUT_MS + LOST_GRACE_MS) {\n delete registry[id];\n pruned = true;\n continue;\n }\n if (entry.status !== 'lost') {\n entry.status = 'lost';\n pruned = true;\n }\n }\n\n if (pruned) {\n /* v8 ignore start -- best-effort prune write; the .catch only fires on a write failure */\n await this.writeAtomic(registry).catch(() => undefined);\n /* v8 ignore stop */\n }\n\n return registry;\n } catch {\n return {};\n }\n }\n\n private async atomicUpdate(\n fn: (registry: Record<string, SessionRegistryEntry>) => void,\n ): Promise<void> {\n const lockPath = `${this.filePath}.lock`;\n const maxRetries = 8;\n const retryDelayMs = 20;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n // Ensure directory exists\n await fs.mkdir(path.dirname(this.filePath), { recursive: true });\n\n // Acquire exclusive lock via O_CREAT | O_EXCL\n let lockHandle = await fs.open(lockPath, 'wx').catch(() => null);\n if (!lockHandle) {\n // Lock contended. A crashed process can leave its lock file behind\n // forever (the `finally` unlink never ran), which would wedge EVERY\n // future write \u2014 the registry silently stops updating. Break the lock\n // when its owner pid is dead or it has been held implausibly long\n // (legit holds are sub-millisecond), then retry the open once.\n if (await this.breakStaleLock(lockPath)) {\n /* v8 ignore start -- retry-open after breaking a stale lock; .catch only fires on contention */\n lockHandle = await fs.open(lockPath, 'wx').catch(() => null);\n /* v8 ignore stop */\n }\n if (!lockHandle) {\n await new Promise((r) => setTimeout(r, retryDelayMs * (attempt + 1)));\n continue;\n }\n }\n\n try {\n // Stamp the owner pid so other processes can detect a stale lock.\n /* v8 ignore start -- best-effort owner-pid stamp; .catch only fires on a write failure */\n await lockHandle.writeFile(String(process.pid)).catch(() => undefined);\n /* v8 ignore stop */\n const raw = await fs.readFile(this.filePath, 'utf8').catch(() => '{}');\n // Corruption-tolerant: a crash-zeroed or torn file must not abort\n // the write \u2014 starting from {} rewrites a healthy registry.\n const registry = parseRegistry(raw);\n fn(registry);\n await this.writeAtomicLocked(registry);\n return; // success\n } finally {\n await lockHandle.close();\n /* v8 ignore start -- best-effort lock cleanup in finally; .catch only fires if the lock vanished */\n await fs.unlink(lockPath).catch(() => undefined);\n /* v8 ignore stop */\n }\n } catch {\n // Best-effort \u2014 never throw from registry writes\n /* v8 ignore next -- defensive: a registry write failure must never propagate */\n return;\n }\n }\n // All retries exhausted \u2014 registry update dropped (non-critical)\n }\n\n /**\n * Break a contended lock if it is stale: the recorded owner pid is no longer\n * alive, or the lock is older than {@link STALE_LOCK_MS}. Returns true when the\n * lock was removed (caller should retry acquisition). Best-effort and\n * race-tolerant \u2014 a fresh lock (age ~0, live owner) is never broken, so the\n * common concurrent case self-heals on the next heartbeat.\n */\n private async breakStaleLock(lockPath: string): Promise<boolean> {\n try {\n const [stat, content] = await Promise.all([\n fs.stat(lockPath),\n /* v8 ignore start -- best-effort lock-content read; .catch only fires if the lock vanished */\n fs.readFile(lockPath, 'utf8').catch(() => ''),\n /* v8 ignore stop */\n ]);\n const ageMs = Date.now() - stat.mtimeMs;\n const ownerPid = Number.parseInt(content.trim(), 10);\n const ownerDead =\n Number.isInteger(ownerPid) && ownerPid > 0 && ownerPid !== process.pid && !pidAlive(ownerPid);\n if (ownerDead || ageMs > STALE_LOCK_MS) {\n /* v8 ignore start -- best-effort stale-lock removal; .catch only fires if the lock vanished */\n await fs.unlink(lockPath).catch(() => undefined);\n /* v8 ignore stop */\n return true;\n }\n return false;\n } catch {\n // stat failed \u2192 the lock vanished underneath us; let the caller retry.\n /* v8 ignore next -- defensive: a vanished lock between stat and read is fine */\n return true;\n }\n }\n\n private async writeAtomicLocked(registry: Record<string, SessionRegistryEntry>): Promise<void> {\n await this.pruneStaleTempFiles();\n await this.writeAtomicFile(registry);\n }\n\n /** Legacy write without lock \u2014 used by heartbeat for performance. */\n private async writeAtomic(registry: Record<string, SessionRegistryEntry>): Promise<void> {\n await this.pruneStaleTempFiles();\n await this.writeAtomicFile(registry);\n }\n\n private async writeAtomicFile(registry: Record<string, SessionRegistryEntry>): Promise<void> {\n const tmp = path.join(\n path.dirname(this.filePath),\n `.${path.basename(this.filePath)}.${randomUUID().slice(0, 8)}.tmp`,\n );\n try {\n // Write + fsync BEFORE the rename: without the fsync, a system crash\n // can journal the rename metadata while the data blocks were never\n // flushed, leaving a zero-filled (all-NUL) registry file on reboot.\n const handle = await fs.open(tmp, 'w');\n try {\n await handle.writeFile(JSON.stringify(registry, null, 2), 'utf8');\n await handle.sync().catch(() => undefined);\n } finally {\n await handle.close();\n }\n await fs.rename(tmp, this.filePath);\n } catch (err) {\n /* v8 ignore start -- rename-failure cleanup: best-effort tmp unlink + rethrow (atomicUpdate swallows it) */\n await fs.unlink(tmp).catch(() => undefined);\n throw err;\n /* v8 ignore stop */\n }\n }\n\n private async pruneStaleTempFiles(): Promise<void> {\n try {\n const dir = path.dirname(this.filePath);\n const base = path.basename(this.filePath);\n const now = Date.now();\n const stale: Array<{ name: string; mtimeMs: number }> = [];\n\n for (const name of await fs.readdir(dir)) {\n const isTemp =\n (name.startsWith(`${base}.`) || name.startsWith(`.${base}.`)) && name.endsWith('.tmp');\n if (!isTemp) continue;\n /* v8 ignore start -- best-effort temp stat; .catch(null)+continue only fire when the temp vanished */\n const stat = await fs.stat(path.join(dir, name)).catch(() => null);\n if (!stat) continue;\n /* v8 ignore stop */\n if (now - stat.mtimeMs > STALE_TMP_MS) stale.push({ name, mtimeMs: stat.mtimeMs });\n }\n\n stale.sort((a, b) => b.mtimeMs - a.mtimeMs);\n await Promise.all(\n stale.slice(MAX_STALE_TMP_FILES).map(async ({ name }) => {\n /* v8 ignore start -- best-effort temp removal; .catch only fires if the temp vanished */\n await fs.unlink(path.join(dir, name)).catch(() => undefined);\n /* v8 ignore stop */\n }),\n );\n } catch {\n // best-effort cleanup must not block registry heartbeats\n }\n }\n}\n\n/** Singleton \u2014 created once per process. */\nlet _instance: SessionRegistry | null = null;\n\nexport function getSessionRegistry(globalRoot?: string): SessionRegistry {\n if (!_instance && globalRoot) {\n _instance = new SessionRegistry(globalRoot);\n }\n if (!_instance) {\n /* v8 ignore next -- the singleton is initialized by the first call in every test/process */\n throw new Error('SessionRegistry not initialized. Call getSessionRegistry(globalRoot) first.');\n }\n return _instance;\n}\n\nexport function hasSessionRegistry(): boolean {\n return _instance !== null;\n}\n", "/**\n * AgentStatusTracker \u2014 subscribes to EventBus events and keeps the\n * SessionRegistry updated with live agent status.\n *\n * Created once per process during boot. Listens for:\n * - agent.run.started / agent.run.completed \u2192 agent status changes\n * - tool.started / tool.executed \u2192 current tool tracking\n * - brain.ask_human \u2192 waiting_user status\n * - fleet events (subagent spawn/start/done) \u2192 agent entries\n *\n * @module agent-status-tracker\n */\nimport type { EventBus } from './kernel/events.js';\nimport type {\n AgentEntry,\n AgentLiveStatus,\n SessionRegistry,\n} from './session-registry.js';\n\n/** A finished (idle/error) subagent older than this is reaped from the fleet view. */\nconst AGENT_REAP_MS = 30_000;\n/** How often the reaper sweeps for finished subagents. */\nconst AGENT_SWEEP_INTERVAL_MS = 10_000;\n/** Max chars of streamed assistant text kept in the registry (the live tail). */\nconst PARTIAL_TEXT_CAP = 1200;\n/** Min gap between registry flushes triggered purely by streamed text. */\nconst PARTIAL_FLUSH_THROTTLE_MS = 300;\n\nfunction clampPct(pct: number): number {\n if (!Number.isFinite(pct)) return 0;\n return Math.max(0, Math.min(100, pct));\n}\n\nexport interface AgentStatusTrackerOptions {\n events: EventBus;\n registry: SessionRegistry;\n /** Session id whose live agent state is mirrored by this tracker. */\n sessionId?: string | (() => string | undefined) | undefined;\n /** Leader agent name shown in the registry. Default: \"leader\". */\n leaderName?: string | undefined;\n /**\n * Best-effort callback fired after each registry write settles. Used to nudge\n * local WebUI servers (FleetNotifier) so cross-process status reaches the map\n * without waiting on their file-watch/poll. Never block or throw.\n */\n onUpdate?: (() => void) | undefined;\n}\n\nexport class AgentStatusTracker {\n private readonly events: EventBus;\n private readonly registry: SessionRegistry;\n private readonly sessionId: string | (() => string | undefined) | undefined;\n private readonly leaderName: string;\n\n // Live agent map: agentId \u2192 AgentEntry\n private agents = new Map<string, AgentEntry>();\n // Last full agent list flushed (leader + subagents). Lets external consumers\n // read the current state synchronously without re-deriving it.\n private lastAgents: AgentEntry[] = [];\n\n // Leader tracking\n private leaderStatus: AgentLiveStatus = 'idle';\n private leaderCurrentTool: string | undefined;\n private leaderIterations = 0;\n private leaderToolCalls = 0;\n private leaderCostUsd = 0;\n private leaderTokensIn = 0;\n private leaderTokensOut = 0;\n private leaderCtxPct: number | undefined;\n private leaderModel: string | undefined;\n private leaderPartialText = '';\n private leaderStartedAt: string | undefined;\n\n private unsubscribers: Array<() => void> = [];\n private readonly onUpdate: (() => void) | undefined;\n private sweepTimer: ReturnType<typeof setInterval> | null = null;\n private partialTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(opts: AgentStatusTrackerOptions) {\n this.events = opts.events;\n this.registry = opts.registry;\n this.sessionId = opts.sessionId;\n this.leaderName = opts.leaderName ?? 'leader';\n this.onUpdate = opts.onUpdate;\n }\n\n /** Current full agent list (leader + subagents) as of the last flush. */\n getAgents(): AgentEntry[] {\n return this.lastAgents.length > 0 ? [...this.lastAgents] : [];\n }\n\n start(): void {\n const on = (\n pattern: string,\n fn: (event: string, payload: unknown) => void,\n ): (() => void) =>\n this.events.onPattern(pattern, (event, payload) => {\n if (!this.acceptsSession(payload)) return;\n fn(event, payload);\n });\n\n // Leader events\n this.unsubscribers.push(\n on('agent.run.started', (_event, payload) => {\n const p = payload as { at?: string; model?: string; ctx?: unknown } | undefined;\n this.markLeaderStarted(p?.at);\n this.captureLeaderContext(p?.ctx);\n if (p?.model) this.leaderModel = p.model;\n this.leaderStatus = 'running';\n this.leaderIterations++;\n this.flush();\n }),\n );\n\n // Capture the leader's model + context fill from each iteration's context.\n this.unsubscribers.push(\n on('iteration.started', (_e, payload) => {\n const p = payload as { ctx?: unknown; index?: number } | undefined;\n const ctx = p?.ctx;\n this.markLeaderStarted();\n this.leaderStatus = 'running';\n if (typeof p?.index === 'number') {\n this.leaderIterations = Math.max(this.leaderIterations, p.index + 1);\n }\n if (!ctx) {\n this.flush();\n return;\n }\n this.captureLeaderContext(ctx);\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('agent.run.completed', (_event, payload) => {\n const p = payload as { status?: string; ctx?: unknown } | undefined;\n this.captureLeaderContext(p?.ctx);\n this.leaderStatus = p?.status === 'failed' ? 'error' : 'idle';\n this.leaderCurrentTool = undefined;\n this.leaderPartialText = '';\n if (this.leaderStatus === 'idle') this.leaderStartedAt = undefined;\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('agent.run.error', (_event, payload) => {\n const p = payload as { ctx?: unknown } | undefined;\n this.captureLeaderContext(p?.ctx);\n this.leaderStatus = 'error';\n this.leaderCurrentTool = undefined;\n this.leaderPartialText = '';\n this.flush();\n }),\n );\n\n // Tool events \u2014 track current tool\n this.unsubscribers.push(\n on('tool.started', (_event, payload) => {\n const p = payload as { name?: string } | undefined;\n if (p?.name) {\n this.markLeaderStarted();\n this.leaderCurrentTool = p.name;\n this.leaderToolCalls++;\n }\n this.leaderStatus = 'running';\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('tool.executed', () => {\n this.leaderCurrentTool = undefined;\n this.flush();\n }),\n );\n\n // Brain ask_human \u2192 waiting for user input\n this.unsubscribers.push(\n on('brain.ask_human', () => {\n this.markLeaderStarted();\n this.leaderStatus = 'waiting_user';\n this.flush();\n }),\n );\n\n // Streaming events\n this.unsubscribers.push(\n on('llm.stream_started', () => {\n this.markLeaderStarted();\n this.leaderStatus = 'streaming';\n // A new response is starting \u2014 drop the previous turn's live tail.\n this.leaderPartialText = '';\n this.flush();\n }),\n );\n\n // Live assistant text \u2014 accumulate the streamed tail so a cross-process\n // watcher sees the response form. Flushed on a throttle, NOT per token\n // (text_delta fires once per chunk \u2192 would thrash the shared registry).\n this.unsubscribers.push(\n on('provider.text_delta', (_e, payload) => {\n const p = payload as { text?: string; ctx?: unknown } | undefined;\n const text = p?.text;\n if (!text) return;\n this.markLeaderStarted();\n this.captureLeaderContext(p?.ctx);\n this.leaderStatus = 'streaming';\n const next = this.leaderPartialText + text;\n this.leaderPartialText =\n next.length > PARTIAL_TEXT_CAP ? next.slice(next.length - PARTIAL_TEXT_CAP) : next;\n this.schedulePartialFlush();\n }),\n );\n\n this.unsubscribers.push(\n on('provider.response', (_e, payload) => {\n const p = payload as { ctx?: unknown } | undefined;\n this.captureLeaderContext(p?.ctx);\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('provider.fallback', (_e, payload) => {\n const p = payload as { to?: { providerId?: string; model?: string } } | undefined;\n if (p?.to?.model) {\n this.leaderModel = p.to.providerId\n ? `${p.to.providerId}/${p.to.model}`\n : p.to.model;\n this.flush();\n }\n }),\n );\n\n this.unsubscribers.push(\n on('ctx.pct', (_e, payload) => {\n const p = payload as { load?: number } | undefined;\n if (typeof p?.load === 'number' && Number.isFinite(p.load)) {\n this.leaderCtxPct = clampPct(Math.round(p.load * 100));\n this.flush();\n }\n }),\n );\n\n // Leader token + cost accounting (per provider call \u2014 accumulate).\n this.unsubscribers.push(\n on('token.accounted', (_e, payload) => {\n const p = payload as\n | { usage?: { input?: number; output?: number }; cost?: { total?: number } }\n | undefined;\n if (!p) return;\n this.leaderTokensIn += p.usage?.input ?? 0;\n this.leaderTokensOut += p.usage?.output ?? 0;\n this.leaderCostUsd += p.cost?.total ?? 0;\n this.flush();\n }),\n );\n\n // \u2500\u2500 Subagent tracking \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // These are the real fleet lifecycle events (emitted by MultiAgentHost /\n // the subagent runner / director). The previous code listened to a\n // `fleet.subagent.*` namespace that nothing emits, so subagents never\n // reached the registry \u2014 only the leader showed up.\n const touch = (id: string): AgentEntry => {\n let entry = this.agents.get(id);\n if (!entry) {\n const now = new Date().toISOString();\n entry = { id, name: id, status: 'idle', iterations: 0, toolCalls: 0, startedAt: now, lastActivityAt: now };\n this.agents.set(id, entry);\n }\n entry.lastActivityAt = new Date().toISOString();\n return entry;\n };\n\n this.unsubscribers.push(\n on('subagent.spawned', (_e, payload) => {\n const p = payload as { subagentId?: string; name?: string; model?: string } | undefined;\n if (!p?.subagentId) return;\n const entry = touch(p.subagentId);\n entry.name = p.name?.trim() || entry.name;\n if (p.model) entry.model = p.model;\n /* v8 ignore next -- touch() always sets startedAt on creation */\n if (!entry.startedAt) entry.startedAt = new Date().toISOString();\n entry.status = 'running';\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('subagent.ctx_pct', (_e, payload) => {\n const p = payload as { subagentId?: string; load?: number } | undefined;\n if (!p?.subagentId) return;\n const entry = touch(p.subagentId);\n if (typeof p.load === 'number') entry.ctxPct = clampPct(Math.round(p.load * 100));\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('subagent.task_started', (_e, payload) => {\n const p = payload as { subagentId?: string } | undefined;\n if (!p?.subagentId) return;\n const entry = touch(p.subagentId);\n entry.status = 'running';\n /* v8 ignore next -- touch() always sets startedAt on creation */\n if (!entry.startedAt) entry.startedAt = new Date().toISOString();\n entry.iterations++;\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('subagent.tool_executed', (_e, payload) => {\n const p = payload as { subagentId?: string; name?: string } | undefined;\n if (!p?.subagentId) return;\n const entry = touch(p.subagentId);\n entry.status = 'running';\n /* v8 ignore next -- touch() always sets startedAt on creation */\n if (!entry.startedAt) entry.startedAt = new Date().toISOString();\n entry.currentTool = p.name;\n entry.toolCalls++;\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('subagent.iteration_summary', (_e, payload) => {\n const p = payload as\n | {\n subagentId?: string;\n iteration?: number;\n toolCalls?: number;\n currentTool?: string;\n costUsd?: number;\n partialText?: string;\n }\n | undefined;\n if (!p?.subagentId) return;\n const entry = touch(p.subagentId);\n entry.status = 'running';\n /* v8 ignore next -- touch() always sets startedAt on creation */\n if (!entry.startedAt) entry.startedAt = new Date().toISOString();\n if (typeof p.iteration === 'number') entry.iterations = p.iteration;\n if (typeof p.toolCalls === 'number') entry.toolCalls = p.toolCalls;\n if (typeof p.costUsd === 'number') entry.costUsd = p.costUsd;\n if (p.currentTool) entry.currentTool = p.currentTool;\n // Live streamed tail of THIS subagent's current response (the runner\n // already accumulates it) \u2014 capped to the same budget as the leader.\n if (typeof p.partialText === 'string') {\n entry.partialText =\n p.partialText.length > PARTIAL_TEXT_CAP\n ? p.partialText.slice(p.partialText.length - PARTIAL_TEXT_CAP)\n : p.partialText;\n }\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('subagent.task_completed', (_e, payload) => {\n const p = payload as\n | { subagentId?: string; status?: string; iterations?: number; toolCalls?: number }\n | undefined;\n if (!p?.subagentId) return;\n // Only update an agent we already know \u2014 a completion for an unseen\n // agent isn't worth materialising.\n const entry = this.agents.get(p.subagentId);\n if (!entry) return;\n entry.status = p.status === 'failed' || p.status === 'timeout' ? 'error' : 'idle';\n entry.currentTool = undefined;\n entry.partialText = undefined;\n if (typeof p.iterations === 'number') entry.iterations = p.iterations;\n if (typeof p.toolCalls === 'number') entry.toolCalls = p.toolCalls;\n entry.lastActivityAt = new Date().toISOString();\n this.flush();\n }),\n );\n\n this.unsubscribers.push(\n on('subagent.stopped', (_e, payload) => {\n const p = payload as { subagentId?: string } | undefined;\n if (!p?.subagentId) return;\n if (this.agents.delete(p.subagentId)) this.flush();\n }),\n );\n this.unsubscribers.push(\n on('subagent.removed', (_e, payload) => {\n const p = payload as { subagentId?: string } | undefined;\n if (!p?.subagentId) return;\n if (this.agents.delete(p.subagentId)) this.flush();\n }),\n );\n\n // Reap finished subagents so the fleet view doesn't fill with dead/idle\n // desks. The leader is synthesised in flush() (never in `this.agents`), so\n // it is never reaped \u2014 it represents the live session.\n this.sweepTimer = setInterval(() => this.sweep(), AGENT_SWEEP_INTERVAL_MS);\n if (typeof this.sweepTimer.unref === 'function') this.sweepTimer.unref();\n }\n\n stop(): void {\n for (const unsub of this.unsubscribers) {\n try { unsub(); } catch { /* ignore */ }\n }\n this.unsubscribers = [];\n if (this.sweepTimer) {\n clearInterval(this.sweepTimer);\n this.sweepTimer = null;\n }\n if (this.partialTimer) {\n clearTimeout(this.partialTimer);\n this.partialTimer = null;\n }\n }\n\n /**\n * Coalesce streamed-text flushes: at most one registry write per\n * {@link PARTIAL_FLUSH_THROTTLE_MS} while text streams in, so per-token\n * deltas never thrash the cross-process registry file.\n */\n private schedulePartialFlush(): void {\n if (this.partialTimer) return;\n this.partialTimer = setTimeout(() => {\n this.partialTimer = null;\n this.flush();\n }, PARTIAL_FLUSH_THROTTLE_MS);\n if (typeof this.partialTimer.unref === 'function') this.partialTimer.unref();\n }\n\n /**\n * Remove subagents that have been finished (idle/error) for longer than\n * {@link AGENT_REAP_MS}. Running / streaming / waiting_user agents are kept\n * regardless of age \u2014 only *not-working* agents are reaped.\n */\n private sweep(): void {\n const now = Date.now();\n let removed = false;\n for (const [id, a] of this.agents) {\n const finished = a.status !== 'running' && a.status !== 'streaming' && a.status !== 'waiting_user';\n const age = now - Date.parse(a.lastActivityAt);\n if (finished && Number.isFinite(age) && age > AGENT_REAP_MS) {\n this.agents.delete(id);\n removed = true;\n }\n }\n if (removed) this.flush();\n }\n\n private flush(): void {\n const leaderEntry: AgentEntry = {\n id: 'leader',\n name: this.leaderName,\n startedAt: this.leaderStartedAt,\n status: this.leaderStatus,\n currentTool: this.leaderCurrentTool,\n iterations: this.leaderIterations,\n toolCalls: this.leaderToolCalls,\n costUsd: this.leaderCostUsd,\n tokensIn: this.leaderTokensIn,\n tokensOut: this.leaderTokensOut,\n ctxPct: this.leaderCtxPct,\n model: this.leaderModel,\n partialText: this.leaderPartialText || undefined,\n lastActivityAt: new Date().toISOString(),\n };\n\n const allAgents = [leaderEntry, ...this.agents.values()];\n this.lastAgents = allAgents;\n // Broadcast the fresh agent list on the local bus so in-process consumers\n // (e.g. the HQ session-telemetry bridge) can build snapshots without\n // re-reading the shared registry file. Best-effort, never throws here.\n try {\n this.events.emit('session.agents_updated', {\n sessionId: this.currentSessionId(),\n agents: allAgents,\n });\n } catch {\n /* best-effort */\n }\n // Nudge local WebUIs only AFTER the write settles, so they re-read fresh\n // data. Best-effort \u2014 never let a notifier failure surface here.\n this.registry\n .updateAgents(allAgents)\n .then(() => {\n try {\n this.onUpdate?.();\n } catch {\n /* best-effort */\n }\n })\n .catch(() => undefined);\n }\n\n private currentSessionId(): string | undefined {\n return typeof this.sessionId === 'function' ? this.sessionId() : this.sessionId;\n }\n\n private acceptsSession(payload: unknown): boolean {\n const expected = this.currentSessionId();\n if (!expected) return true;\n if (typeof payload !== 'object' || payload === null) return true;\n const actual = (payload as { sessionId?: unknown }).sessionId;\n return typeof actual !== 'string' || actual.length === 0 || actual === expected;\n }\n\n private markLeaderStarted(startedAt?: string): void {\n if (\n this.leaderStartedAt &&\n (this.leaderStatus === 'running' ||\n this.leaderStatus === 'streaming' ||\n this.leaderStatus === 'waiting_user')\n ) {\n return;\n }\n this.leaderStartedAt = startedAt ?? new Date().toISOString();\n }\n\n private captureLeaderContext(ctx: unknown): void {\n if (typeof ctx !== 'object' || ctx === null) return;\n const c = ctx as {\n model?: unknown;\n lastRequestTokens?: unknown;\n meta?: Record<string, unknown> | undefined;\n provider?: { capabilities?: { maxContext?: unknown } | undefined } | undefined;\n };\n if (typeof c.model === 'string' && c.model.length > 0) this.leaderModel = c.model;\n\n const metaLimit = c.meta?.['effectiveMaxContext'];\n const providerMax = c.provider?.capabilities?.maxContext;\n const maxContext =\n typeof metaLimit === 'number' && metaLimit > 0\n ? metaLimit\n : typeof providerMax === 'number' && providerMax > 0\n ? providerMax\n : undefined;\n if (\n typeof c.lastRequestTokens === 'number' &&\n c.lastRequestTokens > 0 &&\n maxContext !== undefined\n ) {\n this.leaderCtxPct = clampPct(Math.round((c.lastRequestTokens / maxContext) * 100));\n }\n }\n}\n", "/**\n * FleetNotifier \u2014 push-on-write nudge to local WebUI servers.\n *\n * The cross-process source of truth is the file-based SessionRegistry, which\n * every WebUI server already watches (`fs.watch`, ~150ms) and polls (5s). This\n * notifier is a best-effort *latency* optimisation on top: after a process\n * writes its session/agent status to the registry, it sends a tiny\n * fire-and-forget `POST /api/fleet/ping` to every WebUI running against the\n * same project (discovered from `~/.wrongstack/webui-instances.json`). The\n * WebUI responds by re-broadcasting the (already-fresh) registry immediately \u2014\n * so a TUI/REPL agent's activity reaches the Fleet HQ map in ~milliseconds\n * instead of waiting on the watch/poll.\n *\n * Design notes:\n * - **Never the source of truth.** If no WebUI is running, the file system +\n * watch/poll still carry everything. Every failure here is swallowed.\n * - **Coalesced.** Bursts of events (tool calls, deltas) collapse into one POST\n * per ~50ms so we never hammer the WebUI per token.\n * - **Discovery cached** for a couple seconds to avoid a disk read per event.\n * - **Loopback only / self-excluded.** Targets the local instances file; a\n * `0.0.0.0`/`::` bind is dialled on `127.0.0.1`, and the caller's own pid is\n * skipped so a WebUI never pings itself.\n *\n * @module fleet-notifier\n */\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\n\nconst INSTANCES_FILE = 'webui-instances.json';\nconst DISCOVERY_TTL_MS = 2_500;\nconst COALESCE_MS = 50;\nconst POST_TIMEOUT_MS = 500;\n\ninterface InstanceRecordLike {\n pid: number;\n httpPort: number;\n host: string;\n projectRoot: string;\n}\n\nfunction pidAlive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException).code !== 'ESRCH';\n }\n}\n\n/** Normalise a project root for cross-process comparison (case-insensitive on Windows). */\nfunction normRoot(root: string): string {\n const resolved = path.resolve(root);\n return process.platform === 'win32' ? resolved.toLowerCase() : resolved;\n}\n\nexport interface FleetNotifierOptions {\n /** WrongStack global dir (`~/.wrongstack`) holding `webui-instances.json`. */\n baseDir: string;\n /** This process's project root \u2014 only WebUIs on the same project are pinged. */\n projectRoot: string;\n /** This process's pid, so a WebUI never pings itself. */\n selfPid?: number | undefined;\n /** Injectable POST for tests. Defaults to a timed `fetch`. */\n post?: ((url: string) => Promise<void>) | undefined;\n}\n\nexport class FleetNotifier {\n private readonly baseDir: string;\n private readonly projectRoot: string;\n private readonly selfPid: number;\n private readonly doPost: (url: string) => Promise<void>;\n\n private cache: { at: number; urls: string[] } | null = null;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private disposed = false;\n\n constructor(opts: FleetNotifierOptions) {\n this.baseDir = opts.baseDir;\n this.projectRoot = normRoot(opts.projectRoot);\n this.selfPid = opts.selfPid ?? process.pid;\n this.doPost = opts.post ?? defaultPost;\n }\n\n /** Coalesced, best-effort nudge. Safe to call on every status change. */\n notify(): void {\n if (this.disposed || this.timer) return;\n this.timer = setTimeout(() => {\n this.timer = null;\n void this.flush();\n }, COALESCE_MS);\n /* v8 ignore next -- timer.unref is always a function on Node Timeout objects */\n if (typeof this.timer.unref === 'function') this.timer.unref();\n }\n\n /** Resolve same-project WebUI ping URLs (cached briefly). Exposed for tests. */\n async endpoints(): Promise<string[]> {\n const now = Date.now();\n if (this.cache && now - this.cache.at < DISCOVERY_TTL_MS) return this.cache.urls;\n const urls = await this.discover();\n this.cache = { at: now, urls };\n return urls;\n }\n\n dispose(): void {\n this.disposed = true;\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n }\n\n private async flush(): Promise<void> {\n const urls = await this.endpoints();\n await Promise.all(urls.map((u) => this.doPost(u).catch(() => undefined)));\n }\n\n private async discover(): Promise<string[]> {\n try {\n const raw = await fs.readFile(path.join(this.baseDir, INSTANCES_FILE), 'utf8');\n const data = JSON.parse(raw) as { instances?: InstanceRecordLike[] };\n const list = Array.isArray(data?.instances) ? data.instances : [];\n return list\n .filter((i) => i && typeof i.httpPort === 'number')\n .filter((i) => i.pid !== this.selfPid)\n .filter((i) => normRoot(i.projectRoot) === this.projectRoot)\n .filter((i) => pidAlive(i.pid))\n .map((i) => {\n const host = i.host === '0.0.0.0' || i.host === '::' || !i.host ? '127.0.0.1' : i.host;\n return `http://${host}:${i.httpPort}/api/fleet/ping`;\n });\n } catch {\n // Missing/corrupt instances file \u2192 no WebUIs to notify.\n return [];\n }\n }\n}\n\nasync function defaultPost(url: string): Promise<void> {\n await fetch(url, {\n method: 'POST',\n signal: AbortSignal.timeout(POST_TIMEOUT_MS),\n });\n}\n", "import { expectDefined } from '../utils/expect-defined.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { CheckpointInfo, RewindResult, RewindResultExtended, SessionRewinder } from '../types/session-rewinder.js';\nimport type { SessionEvent, FileSnapshot } from '../types/session.js';\nimport { atomicWrite } from '../utils/atomic-write.js';\nimport { SessionError, ERROR_CODES } from '../types/errors.js';\nimport { sessionScopedPath } from '../utils/session-scoped-path.js';\n\nexport interface SessionRewinderOptions {\n sessionsDir: string;\n /** The project root directory; used to validate rewind targets stay inside it. */\n projectRoot: string;\n}\n\n/**\n * Rewind engine that reads session JSONL files and reverts file system\n * changes to any previous checkpoint.\n */\nexport class DefaultSessionRewinder implements SessionRewinder {\n constructor(private readonly sessionsDir: string, private readonly projectRoot: string) {}\n\n private sessionFile(sessionId: string): string {\n return sessionScopedPath(this.sessionsDir, sessionId, '.jsonl');\n }\n\n async listCheckpoints(sessionId: string): Promise<CheckpointInfo[]> {\n const file = this.sessionFile(sessionId);\n const raw = await fsp.readFile(file, 'utf8');\n const events = parseEvents(raw);\n\n // Build a map of promptIndex -> file snapshot count\n const fileCountMap = new Map<number, number>();\n for (const event of events) {\n if (event.type === 'file_snapshot') {\n const e = event as { promptIndex: number; files: FileSnapshot[] };\n fileCountMap.set(e.promptIndex, (fileCountMap.get(e.promptIndex) ?? 0) + e.files.length);\n }\n }\n\n const checkpoints: CheckpointInfo[] = [];\n for (const event of events) {\n if (event.type === 'checkpoint') {\n const e = event as { promptIndex: number; promptPreview: string; ts: string };\n checkpoints.push({\n promptIndex: e.promptIndex,\n promptPreview: e.promptPreview,\n ts: e.ts,\n fileCount: fileCountMap.get(e.promptIndex) ?? 0,\n });\n }\n }\n\n return checkpoints;\n }\n\n async rewindToCheckpoint(\n sessionId: string,\n checkpointIndex: number,\n ): Promise<RewindResultExtended> {\n const file = this.sessionFile(sessionId);\n const raw = await fsp.readFile(file, 'utf8');\n const events = parseEvents(raw);\n\n let targetIdx = -1;\n for (let i = 0; i < events.length; i++) {\n const event = expectDefined(events[i]);\n if (event.type === 'checkpoint') {\n const checkpointEvent = event as { promptIndex: number };\n if (checkpointEvent.promptIndex === checkpointIndex) {\n targetIdx = i;\n break;\n }\n }\n }\n\n if (targetIdx === -1) {\n throw new SessionError({\n message: `Checkpoint ${checkpointIndex} not found`,\n code: ERROR_CODES.SESSION_NOT_FOUND,\n context: { checkpointIndex },\n });\n }\n\n const snapshotsToRevert: Array<{ promptIndex: number; files: FileSnapshot[] }> = [];\n for (let i = targetIdx + 1; i < events.length; i++) {\n const event = expectDefined(events[i]);\n if (event.type === 'checkpoint') {\n break;\n }\n if (event.type === 'file_snapshot') {\n const snapshotEvent = event as { promptIndex: number; files: FileSnapshot[] };\n if (snapshotEvent.promptIndex >= checkpointIndex) {\n snapshotsToRevert.push({ promptIndex: snapshotEvent.promptIndex, files: snapshotEvent.files });\n }\n }\n }\n\n const result = await revertSnapshots(snapshotsToRevert, this.projectRoot);\n const removedEvents = events.length - targetIdx - 1;\n return { ...result, toPromptIndex: checkpointIndex, removedEvents };\n }\n\n async rewindLastN(sessionId: string, n: number): Promise<RewindResultExtended> {\n const file = this.sessionFile(sessionId);\n const raw = await fsp.readFile(file, 'utf8');\n const events = parseEvents(raw);\n\n const checkpoints: Array<{ promptIndex: number; ts: string }> = [];\n for (const event of events) {\n if (event.type === 'checkpoint') {\n checkpoints.push({ promptIndex: event.promptIndex, ts: event.ts });\n }\n }\n\n if (checkpoints.length === 0) {\n return { revertedFiles: [], errors: [], toPromptIndex: 0, removedEvents: 0 };\n }\n\n checkpoints.sort((a, b) => b.promptIndex - a.promptIndex);\n const targetIndex = checkpoints[n]?.promptIndex ?? 0;\n\n const snapshotsToRevert: Array<{ promptIndex: number; files: FileSnapshot[] }> = [];\n let shouldRevert = false;\n\n for (const event of events) {\n if (event.type === 'checkpoint' && event.promptIndex === targetIndex) {\n shouldRevert = true;\n continue;\n }\n if (shouldRevert && event.type === 'file_snapshot') {\n snapshotsToRevert.push({ promptIndex: event.promptIndex, files: event.files });\n }\n }\n\n const result = await revertSnapshots(snapshotsToRevert, this.projectRoot);\n return { ...result, toPromptIndex: targetIndex, removedEvents: snapshotsToRevert.length };\n }\n\n async rewindToStart(sessionId: string): Promise<RewindResultExtended> {\n const file = this.sessionFile(sessionId);\n const raw = await fsp.readFile(file, 'utf8');\n const events = parseEvents(raw);\n\n const allSnapshots: Array<{ promptIndex: number; files: FileSnapshot[] }> = [];\n for (const event of events) {\n if (event.type === 'file_snapshot') {\n allSnapshots.push({ promptIndex: event.promptIndex, files: event.files });\n }\n }\n\n if (allSnapshots.length === 0) {\n return { revertedFiles: [], errors: [], toPromptIndex: 0, removedEvents: 0 };\n }\n\n const result = await revertSnapshots(allSnapshots, this.projectRoot);\n return { ...result, toPromptIndex: 0, removedEvents: allSnapshots.length };\n }\n}\n\nfunction parseEvents(raw: string): SessionEvent[] {\n const lines = raw.split('\\n').filter((l) => l.trim());\n const events: SessionEvent[] = [];\n\n for (const line of lines) {\n try {\n const parsed = JSON.parse(line);\n if (\n parsed !== null &&\n typeof parsed === 'object' &&\n typeof (parsed as { type?: unknown | undefined }).type === 'string' &&\n typeof (parsed as { ts?: unknown | undefined }).ts === 'string'\n ) {\n events.push(parsed as SessionEvent);\n }\n } catch {\n // skip malformed\n }\n }\n\n return events;\n}\n\nasync function revertSnapshots(\n snapshots: Array<{ promptIndex: number; files: FileSnapshot[] }>,\n projectRoot: string,\n): Promise<RewindResult> {\n const revertedFiles: string[] = [];\n const errors: string[] = [];\n\n // Undo is the inverse of execution order. Reverse both event order and the\n // per-event file list so repeated edits of the same path restore the oldest\n // `before` content rather than stopping at an intermediate version.\n for (const snapshot of [...snapshots].reverse()) {\n for (const file of [...snapshot.files].reverse()) {\n try {\n // Guard: ensure the target path resolves inside the project root.\n // Without this, a maliciously recorded path (e.g., via path traversal\n // in a tool call that wasn't caught) could cause rewind to write\n // to arbitrary locations.\n const absPath = path.resolve(file.path);\n const root = path.resolve(projectRoot);\n const rel = path.relative(root, absPath);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n errors.push(`${file.path}: path resolves outside project root \u2014 skipping`);\n continue;\n }\n\n if (file.action === 'deleted') {\n // File was deleted \u2014 restore it from before\n if (file.before !== null) {\n // atomicWrite: torn restore would leave the user with a frankenstein file.\n await atomicWrite(file.path, file.before, { mode: 0o644 });\n revertedFiles.push(file.path);\n }\n } else if (file.action === 'created') {\n // File was created \u2014 delete it\n await fsp.unlink(file.path);\n revertedFiles.push(file.path);\n } else if (file.action === 'modified') {\n // File was modified \u2014 restore before content\n if (file.before !== null) {\n // atomicWrite: torn restore would leave the user with a frankenstein file.\n await atomicWrite(file.path, file.before, { mode: 0o644 });\n revertedFiles.push(file.path);\n }\n }\n } catch (err) {\n errors.push(`${file.path}: ${toErrorMessage(err)}`);\n }\n }\n }\n\n return { revertedFiles, errors };\n}\n", "import * as fsp from 'node:fs/promises';\nimport type { EventBus } from '../kernel/events.js';\nimport type { TodoItem } from '../core/context.js';\nimport type { ConversationState } from '../core/conversation-state.js';\nimport { atomicWrite } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/error.js';\n\n/**\n * On-disk checkpoint for `ctx.todos`. Written atomically every time the\n * todo list changes, read once on session resume. This is the missing\n * piece that lets `wstack resume <id>` rehydrate where the previous run\n * stopped instead of starting with an empty board.\n *\n * Schema is intentionally small \u2014 a single JSON object so a future\n * format bump is easy. The `version` field is the only contract; the\n * shape under `todos` mirrors `TodoItem` so reading is a straight assign.\n */\nexport interface TodosCheckpointFile {\n version: 1;\n sessionId: string;\n updatedAt: string;\n todos: TodoItem[];\n}\n\nexport type TodosCheckpointDetach = () => Promise<void>;\n\n/** Read a checkpoint from disk. Returns null when the file doesn't\n * exist or is corrupt \u2014 callers treat both cases as \"no prior state\".\n */\nexport async function loadTodosCheckpoint(\n filePath: string,\n events?: EventBus,\n traceId?: string,\n): Promise<TodoItem[] | null> {\n const t0 = Date.now();\n let raw: string;\n try {\n raw = await fsp.readFile(filePath, 'utf8');\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: traceId ?? '~boot~',\n store: 'todos',\n filePath,\n operation: 'load',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: true,\n });\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as TodosCheckpointFile;\n if (parsed?.version !== 1 || !Array.isArray(parsed.todos)) {\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'todos',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'invalid_schema',\n ...(traceId !== undefined && { traceId }),\n });\n return null;\n }\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'todos',\n filePath,\n operation: 'load',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(traceId !== undefined && { traceId }),\n });\n return parsed.todos.filter(\n (t): t is TodoItem =>\n !!t &&\n typeof t.id === 'string' &&\n typeof t.content === 'string' &&\n typeof t.status === 'string' &&\n (t.activeForm === undefined || typeof t.activeForm === 'string'),\n );\n } catch {\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'todos',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'parse_failed',\n ...(traceId !== undefined && { traceId }),\n });\n return null;\n }\n}\n\n/** Write the checkpoint atomically. Best-effort: a write failure is\n * logged but does not throw \u2014 losing one checkpoint shouldn't bring\n * down the agent run.\n */\nexport async function saveTodosCheckpoint(\n filePath: string,\n sessionId: string,\n todos: readonly TodoItem[],\n events?: EventBus,\n traceId?: string,\n warn?: (msg: string) => void,\n): Promise<void> {\n const t0 = Date.now();\n const payload: TodosCheckpointFile = {\n version: 1,\n sessionId,\n updatedAt: new Date().toISOString(),\n todos: [...todos],\n };\n try {\n await atomicWrite(filePath, JSON.stringify(payload, null, 2), { mode: 0o600 });\n events?.emit('storage.write', {\n sessionId: traceId ?? sessionId,\n store: 'todos',\n filePath,\n operation: 'save',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(traceId !== undefined && { traceId }),\n });\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: traceId ?? sessionId,\n store: 'todos',\n filePath,\n operation: 'save',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: false,\n });\n (warn ?? ((m) => console.warn(JSON.stringify({ level: 'warn', event: 'todos_checkpoint.save_failed', message: m, timestamp: new Date().toISOString() }))))(toErrorMessage(err));\n }\n}\n\n/**\n * Subscribe a `ConversationState` so every `todos_replaced` mutation\n * triggers an atomic write to disk. Returns the unsubscribe function.\n *\n * Writes are debounced by 150ms so a flurry of edits (e.g. the LLM\n * marking three items done in the same tool call) coalesces into one\n * disk hit.\n */\nexport function attachTodosCheckpoint(\n state: ConversationState,\n filePath: string,\n sessionId: string,\n events?: EventBus,\n traceId?: string,\n warn?: (msg: string) => void,\n): TodosCheckpointDetach {\n let timer: NodeJS.Timeout | null = null;\n let pending: readonly TodoItem[] | null = null;\n let writeChain: Promise<void> = Promise.resolve();\n\n const enqueueWrite = (todos: readonly TodoItem[]) => {\n writeChain = writeChain\n .then(() => saveTodosCheckpoint(filePath, sessionId, todos, events, traceId))\n /* v8 ignore start -- defensive: saveTodosCheckpoint swallows its own errors and never rejects */\n .catch((err) => {\n // Log and keep the chain alive \u2014 a failed write must not\n // poison the chain and silently stop all subsequent writes.\n const msg = toErrorMessage(err);\n (warn ?? ((m) => console.error(JSON.stringify({ level: 'error', event: 'todos_checkpoint.write_chain_failed', sessionId, message: m, timestamp: new Date().toISOString() }))))(msg);\n });\n /* v8 ignore stop */\n return writeChain;\n };\n\n const flush = () => {\n timer = null;\n if (pending) {\n const todos = pending;\n pending = null;\n return enqueueWrite(todos);\n }\n /* v8 ignore next -- defensive: flush is only invoked when a change is pending */\n return writeChain;\n };\n\n const unsubscribe = state.onChange((change) => {\n if (change.kind !== 'todos_replaced') return;\n pending = change.todos;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n void flush();\n }, 150);\n });\n return async () => {\n unsubscribe();\n if (timer) {\n clearTimeout(timer);\n // Flush any pending write before detach so callers can safely\n // unsubscribe at shutdown without losing the last update.\n await flush();\n } else {\n await writeChain;\n }\n };\n}\n", "import * as fsp from 'node:fs/promises';\nimport type { EventBus } from '../kernel/events.js';\nimport type { CompletedWorkEvidence } from '../types/context-evidence.js';\nimport { atomicWrite } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/error.js';\n\nexport interface CompletedWorkCheckpointFile {\n version: 1;\n sessionId: string;\n updatedAt: string;\n completedWork: CompletedWorkEvidence[];\n}\n\nexport async function loadCompletedWorkCheckpoint(\n filePath: string,\n events?: EventBus,\n traceId?: string,\n): Promise<CompletedWorkEvidence[] | null> {\n const t0 = Date.now();\n let raw: string;\n try {\n raw = await fsp.readFile(filePath, 'utf8');\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: traceId ?? '~boot~',\n store: 'completed-work',\n filePath,\n operation: 'load',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: true,\n });\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as CompletedWorkCheckpointFile;\n if (parsed?.version !== 1 || !Array.isArray(parsed.completedWork)) {\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'completed-work',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'invalid_schema',\n ...(traceId !== undefined && { traceId }),\n });\n return null;\n }\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'completed-work',\n filePath,\n operation: 'load',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(traceId !== undefined && { traceId }),\n });\n return parsed.completedWork.filter(isCompletedWorkEvidence);\n } catch {\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'completed-work',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'parse_failed',\n ...(traceId !== undefined && { traceId }),\n });\n return null;\n }\n}\n\nexport async function saveCompletedWorkCheckpoint(\n filePath: string,\n sessionId: string,\n completedWork: readonly CompletedWorkEvidence[],\n events?: EventBus,\n traceId?: string,\n): Promise<void> {\n const t0 = Date.now();\n const payload: CompletedWorkCheckpointFile = {\n version: 1,\n sessionId,\n updatedAt: new Date().toISOString(),\n completedWork: [...completedWork],\n };\n try {\n await atomicWrite(filePath, JSON.stringify(payload, null, 2), { mode: 0o600 });\n events?.emit('storage.write', {\n sessionId: traceId ?? sessionId,\n store: 'completed-work',\n filePath,\n operation: 'save',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(traceId !== undefined && { traceId }),\n });\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: traceId ?? sessionId,\n store: 'completed-work',\n filePath,\n operation: 'save',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: false,\n });\n console.warn(JSON.stringify({\n level: 'warn',\n event: 'completed_work_checkpoint.save_failed',\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }));\n }\n}\n\nfunction isCompletedWorkEvidence(value: unknown): value is CompletedWorkEvidence {\n if (!value || typeof value !== 'object') return false;\n const item = value as Partial<CompletedWorkEvidence>;\n return (\n typeof item.key === 'string' &&\n typeof item.source === 'string' &&\n ['todo', 'plan', 'task', 'verification', 'manual'].includes(item.source) &&\n typeof item.summary === 'string' &&\n typeof item.completedAt === 'number' &&\n (item.evidence === undefined || typeof item.evidence === 'string')\n );\n}\n", "import * as fsp from 'node:fs/promises';\nimport { randomUUID } from 'node:crypto';\nimport type { EventBus } from '../kernel/events.js';\nimport type { ConversationState } from '../core/conversation-state.js';\nimport { atomicWrite, withFileLock } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport { SessionError } from '../types/errors.js';\n\n/**\n * Plan items are the strategic counterpart to todos. Where `ctx.todos`\n * is the moment-to-moment task board the LLM mutates per-turn, a plan\n * captures the higher-level approach \u2014 the steps the user (or LLM)\n * laid out before any work began.\n *\n * Plans persist by default (per session) so a resumed session can show\n * \"you were on step 3 of 5\". Todos are derived/transient. Both can\n * coexist: think roadmap (plan) vs. sprint board (todos).\n */\nexport interface PlanItem {\n id: string;\n title: string;\n /** Optional longer-form context or rationale. */\n details?: string | undefined;\n status: 'open' | 'in_progress' | 'done';\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface PlanFile {\n version: 1;\n sessionId: string;\n title?: string | undefined;\n updatedAt: string;\n items: PlanItem[];\n}\n\nexport async function loadPlan(filePath: string, events?: EventBus): Promise<PlanFile | null> {\n const t0 = Date.now();\n let raw: string;\n try {\n raw = await fsp.readFile(filePath, 'utf8');\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: '~boot~',\n store: 'plan',\n filePath,\n operation: 'load',\n error: toErrorMessage(err),\n recoverable: true,\n });\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as PlanFile;\n if (parsed?.version !== 1 || !Array.isArray(parsed.items)) {\n events?.emit('storage.read', {\n sessionId: '~boot~',\n store: 'plan',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'invalid_schema',\n });\n return null;\n }\n events?.emit('storage.read', {\n sessionId: '~boot~',\n store: 'plan',\n filePath,\n operation: 'load',\n outcome: 'success',\n durationMs: Date.now() - t0,\n });\n return parsed;\n } catch {\n events?.emit('storage.read', {\n sessionId: '~boot~',\n store: 'plan',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'parse_failed',\n });\n return null;\n }\n}\n\n/**\n * Persist a plan. Returns `true` on success, `false` if the write failed\n * (still emits `storage.error` + warns \u2014 it does NOT throw, so callers that\n * treat a lost plan-save as non-fatal keep working). `mutatePlan` inspects the\n * result and throws so the plan TOOL can report `ok:false` instead of falsely\n * claiming the plan was persisted.\n */\nexport async function savePlan(\n filePath: string,\n plan: PlanFile,\n events?: EventBus,\n warn?: (msg: string) => void,\n): Promise<boolean> {\n const t0 = Date.now();\n try {\n await atomicWrite(filePath, JSON.stringify(plan, null, 2), { mode: 0o600 });\n events?.emit('storage.write', {\n sessionId: '~boot~',\n store: 'plan',\n filePath,\n operation: 'save',\n outcome: 'success',\n durationMs: Date.now() - t0,\n });\n return true;\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: '~boot~',\n store: 'plan',\n filePath,\n operation: 'save',\n error: toErrorMessage(err),\n recoverable: false,\n });\n (\n warn ??\n ((m) =>\n console.warn(\n JSON.stringify({\n level: 'warn',\n event: 'plan_store.save_failed',\n message: m,\n timestamp: new Date().toISOString(),\n }),\n ))\n )(toErrorMessage(err));\n return false;\n }\n}\n\n/** Create a new PlanFile when none exists on disk. */\nexport function emptyPlan(sessionId: string, title?: string): PlanFile {\n return {\n version: 1,\n sessionId,\n title,\n updatedAt: new Date().toISOString(),\n items: [],\n };\n}\n\nexport function addPlanItem(\n plan: PlanFile,\n title: string,\n details?: string | undefined,\n): { plan: PlanFile; item: PlanItem } {\n const now = new Date().toISOString();\n const item: PlanItem = {\n id: `plan_${Date.now()}_${randomUUID().slice(0, 6)}`,\n title,\n details,\n status: 'open',\n createdAt: now,\n updatedAt: now,\n };\n return {\n plan: { ...plan, items: [...plan.items, item], updatedAt: now },\n item,\n };\n}\n\nexport function removePlanItem(plan: PlanFile, idOrIndex: string): PlanFile {\n const idx = matchIndex(plan, idOrIndex);\n if (idx === -1) return plan;\n return {\n ...plan,\n items: plan.items.filter((_, i) => i !== idx),\n updatedAt: new Date().toISOString(),\n };\n}\n\nexport function setPlanItemStatus(\n plan: PlanFile,\n idOrIndex: string,\n status: PlanItem['status'],\n): PlanFile {\n const idx = matchIndex(plan, idOrIndex);\n if (idx === -1) return plan;\n const now = new Date().toISOString();\n const items = plan.items.map((it, i) => (i === idx ? { ...it, status, updatedAt: now } : it));\n return { ...plan, items, updatedAt: now };\n}\n\nexport function clearPlan(plan: PlanFile): PlanFile {\n return { ...plan, items: [], updatedAt: new Date().toISOString() };\n}\n\n/** Render the plan as a short markdown-ish string suitable for slash output. */\nexport function formatPlan(plan: PlanFile): string {\n if (plan.items.length === 0) return 'Plan is empty.';\n const lines: string[] = [];\n if (plan.title) lines.push(`# ${plan.title}`);\n plan.items.forEach((it, i) => {\n const mark = it.status === 'done' ? '[x]' : it.status === 'in_progress' ? '[~]' : '[ ]';\n lines.push(`${i + 1}. ${mark} ${it.title}`);\n if (it.details) {\n for (const line of it.details.split('\\n')) lines.push(` ${line}`);\n }\n });\n return lines.join('\\n');\n}\n\nfunction matchIndex(plan: PlanFile, idOrIndex: string): number {\n const asNum = Number.parseInt(idOrIndex, 10);\n if (!Number.isNaN(asNum) && asNum >= 1 && asNum <= plan.items.length) return asNum - 1;\n const byId = plan.items.findIndex((it) => it.id === idOrIndex);\n if (byId !== -1) return byId;\n const lower = idOrIndex.toLowerCase();\n return plan.items.findIndex((it) => it.title.toLowerCase().includes(lower));\n}\n\n/**\n * Promote a plan item to a set of todo items.\n * The plan item is marked 'in_progress' (if not already done) and its\n * title + details become the first todo; additional subtasks are appended.\n * Returns the derived todo list so the caller can pass it to `todoTool`\n * or `ctx.state.replaceTodos()`.\n */\nexport function deriveTodosFromPlanItem(\n plan: PlanFile,\n idOrIndex: string,\n subtasks?: string[] | undefined,\n): {\n plan: PlanFile;\n todos: Array<{\n id: string;\n content: string;\n status: 'pending' | 'in_progress' | 'completed';\n activeForm?: string | undefined;\n promotedFromPlan?: string | undefined;\n }>;\n} | null {\n const idx = matchIndex(plan, idOrIndex);\n if (idx === -1) return null;\n\n const item = plan.items[idx];\n /* v8 ignore next -- defensive: matchIndex returns a valid in-range index or -1 (handled above) */\n if (!item) return null;\n\n // Mark the plan item in_progress if it wasn't already done\n let updatedPlan = plan;\n if (item.status !== 'done') {\n updatedPlan = setPlanItemStatus(plan, idOrIndex, 'in_progress');\n }\n\n const todos: Array<{\n id: string;\n content: string;\n status: 'pending' | 'in_progress' | 'completed';\n activeForm?: string | undefined;\n promotedFromPlan?: string | undefined;\n }> = [];\n\n // First todo from the plan item itself\n todos.push({\n id: `todo_${Date.now()}_plan`,\n content: item.title,\n status: 'in_progress',\n activeForm: item.title,\n promotedFromPlan: item.id,\n });\n\n // Optional subtasks\n if (subtasks && subtasks.length > 0) {\n for (const st of subtasks) {\n todos.push({\n id: `todo_${Date.now()}_${randomUUID().slice(0, 6)}`,\n content: st,\n status: 'pending',\n promotedFromPlan: item.id,\n });\n }\n }\n\n return { plan: updatedPlan, todos };\n}\n\n/**\n * Load, modify, and save the plan file under a file-level lock.\n * Prevents races from parallel tool invocations (e.g. batch_tool_use).\n */\nexport async function mutatePlan(\n filePath: string,\n sessionId: string,\n fn: (plan: PlanFile) => PlanFile | Promise<PlanFile>,\n): Promise<PlanFile> {\n return withFileLock(filePath, async () => {\n const plan = (await loadPlan(filePath)) ?? emptyPlan(sessionId);\n const updated = await fn(plan);\n const persisted = await savePlan(filePath, updated);\n if (!persisted) {\n throw new SessionError({\n message: `Failed to persist plan to ${filePath} \u2014 the change was NOT saved.`,\n code: 'SESSION_WRITE_FAILED',\n sessionId,\n context: { filePath, operation: 'mutatePlan' },\n });\n }\n return updated;\n });\n}\n\n/**\n * Optional: attach a state-listener so meta operations (storing a plan\n * id on ctx.meta) trigger a save. Currently a stub \u2014 plans don't live\n * on Context, but this keeps the API surface symmetric with the todos\n * checkpoint so future refactors can flip plans into Context if needed.\n */\nexport function attachPlanCheckpoint(\n _state: ConversationState,\n _filePath: string,\n _sessionId: string,\n): () => void {\n return () => undefined;\n}\n", "/**\n * Plan templates \u2014 pre-defined plan skeletons for common workflows.\n *\n * Templates are stored in-memory (no disk I/O). Users instantiate them\n * via `/plan template use <name>` or `planTool(action: 'template_use')`.\n * Each template is a function that returns an array of item titles, so\n * dynamic content (dates, project names) can be injected later.\n */\n\nexport interface PlanTemplate {\n name: string;\n description: string;\n category: 'development' | 'release' | 'maintenance' | 'infrastructure';\n items: Array<{\n title: string;\n details?: string | undefined;\n }>;\n}\n\nconst templates: Record<string, PlanTemplate> = {\n 'new-feature': {\n name: 'new-feature',\n description: 'Standard workflow for adding a new feature',\n category: 'development',\n items: [\n { title: 'Write specification / design doc', details: 'Define scope, acceptance criteria, edge cases' },\n { title: 'Set up feature branch', details: 'git checkout -b feature/...' },\n { title: 'Implement core logic', details: 'TDD preferred \u2014 write tests first' },\n { title: 'Add unit tests', details: '>= 80% coverage for new code' },\n { title: 'Add integration tests', details: 'End-to-end happy path + error paths' },\n { title: 'Update documentation', details: 'README, API docs, changelog' },\n { title: 'Code review', details: 'Self-review before requesting review' },\n { title: 'Merge and deploy', details: 'CI green, tag release' },\n ],\n },\n 'bug-fix': {\n name: 'bug-fix',\n description: 'Systematic approach to fixing bugs',\n category: 'maintenance',\n items: [\n { title: 'Reproduce the bug', details: 'Minimal reproduction case' },\n { title: 'Root cause analysis', details: 'Trace through logs, debugger' },\n { title: 'Write failing test', details: 'Test must fail before fix' },\n { title: 'Implement fix', details: 'Smallest possible change' },\n { title: 'Verify fix', details: 'Test passes, reproduction no longer fails' },\n { title: 'Regression test', details: 'Ensure no related tests broken' },\n { title: 'Document in changelog', details: 'Brief description + issue link' },\n ],\n },\n 'refactor': {\n name: 'refactor',\n description: 'Safe refactoring workflow',\n category: 'maintenance',\n items: [\n { title: 'Identify refactoring target', details: 'Code smell, performance bottleneck, or tech debt' },\n { title: 'Ensure test coverage', details: 'Existing tests must pass before and after' },\n { title: 'Write characterization tests', details: 'Capture current behavior if tests weak' },\n { title: 'Apply refactoring', details: 'Small steps, frequent commits' },\n { title: 'Run full test suite', details: 'All tests must pass' },\n { title: 'Performance check', details: 'Ensure no regression' },\n { title: 'Code review', details: 'Explain the why, not just the what' },\n ],\n },\n 'release': {\n name: 'release',\n description: 'Preparing a new release',\n category: 'release',\n items: [\n { title: 'Version bump', details: 'package.json, lockfiles, tags' },\n { title: 'Update changelog', details: 'All changes since last release' },\n { title: 'Run full test suite', details: 'Unit + integration + e2e' },\n { title: 'Build artifacts', details: 'Docker images, bundles, binaries' },\n { title: 'Staging smoke tests', details: 'Deploy to staging, verify' },\n { title: 'Production deploy', details: 'Blue-green or canary' },\n { title: 'Post-deploy verification', details: 'Health checks, error rates' },\n { title: 'Announce release', details: 'Slack, email, GitHub release notes' },\n ],\n },\n 'security-audit': {\n name: 'security-audit',\n description: 'Security review and hardening',\n category: 'infrastructure',\n items: [\n { title: 'Dependency audit', details: 'npm audit, Snyk, Dependabot alerts' },\n { title: 'Secret scan', details: 'git-secrets, truffleHog, manual review' },\n { title: 'Access control review', details: 'IAM, roles, least privilege' },\n { title: 'Input validation audit', details: 'SQL injection, XSS, path traversal' },\n { title: 'Authentication review', details: 'Session management, MFA, password policy' },\n { title: 'Logging and monitoring', details: 'PII in logs, audit trails' },\n { title: 'Incident response plan', details: 'Runbooks, contacts, escalation' },\n ],\n },\n 'onboarding': {\n name: 'onboarding',\n description: 'New developer onboarding checklist',\n category: 'infrastructure',\n items: [\n { title: 'Repository access', details: 'GitHub/GitLab permissions' },\n { title: 'Local environment setup', details: 'Docker, dependencies, env files' },\n { title: 'Run tests locally', details: 'Verify green suite' },\n { title: 'Read architecture docs', details: 'ADR, README, onboarding guide' },\n { title: 'First commit', details: 'Docs fix or small improvement' },\n { title: 'Pair programming session', details: 'Walk through codebase with buddy' },\n { title: 'Deploy to staging', details: 'Verify CI/CD access' },\n ],\n },\n};\n\nexport function listPlanTemplates(): PlanTemplate[] {\n return Object.values(templates);\n}\n\nexport function getPlanTemplate(name: string): PlanTemplate | undefined {\n return templates[name];\n}\n\nexport function formatPlanTemplates(): string {\n const cats = new Map<PlanTemplate['category'], PlanTemplate[]>();\n for (const t of Object.values(templates)) {\n const arr = cats.get(t.category) ?? [];\n arr.push(t);\n cats.set(t.category, arr);\n }\n\n const lines: string[] = ['Available plan templates:'];\n for (const [cat, items] of cats) {\n lines.push(`\\n${cat}:`);\n for (const t of items) {\n lines.push(` ${t.name.padEnd(18)} \u2014 ${t.description}`);\n }\n }\n return lines.join('\\n');\n}\n", "import * as fsp from 'node:fs/promises';\nimport type { EventBus } from '../kernel/events.js';\nimport { atomicWrite, withFileLock } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport type { TaskItem } from '../utils/task-format.js';\nimport { SessionError } from '../types/errors.js';\n\n// ---------------------------------------------------------------------------\n// Task file persistence \u2014 one JSON file per session, resolved via\n// `sessionScopedPath(projectSessions, sessionId, '.tasks.json')`.\n//\n// Low-level load/save are exported for read-only consumers. Mutating callers\n// should use `mutateTasks` which wraps the entire read-modify-write cycle\n// under a file-level lock, preventing races from parallel tool invocations.\n// ---------------------------------------------------------------------------\n\nexport interface TaskFile {\n version: 1;\n sessionId: string;\n updatedAt: string;\n tasks: TaskItem[];\n}\n\nexport function emptyTaskFile(sessionId: string): TaskFile {\n return {\n version: 1,\n sessionId,\n updatedAt: new Date().toISOString(),\n tasks: [],\n };\n}\n\n/** Read the task file. Returns null when the file doesn't exist. */\nexport async function loadTasks(\n filePath: string,\n events?: EventBus,\n traceId?: string,\n): Promise<TaskFile | null> {\n const t0 = Date.now();\n let raw: string;\n try {\n raw = await fsp.readFile(filePath, 'utf8');\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: traceId ?? '~boot~',\n store: 'tasks',\n filePath,\n operation: 'load',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: true,\n });\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as TaskFile;\n if (parsed?.version !== 1 || !Array.isArray(parsed.tasks)) {\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'tasks',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'invalid_schema',\n ...(traceId !== undefined && { traceId }),\n });\n return null;\n }\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'tasks',\n filePath,\n operation: 'load',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(traceId !== undefined && { traceId }),\n });\n return parsed;\n } catch {\n events?.emit('storage.read', {\n sessionId: traceId ?? '~boot~',\n store: 'tasks',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'parse_failed',\n ...(traceId !== undefined && { traceId }),\n });\n return null;\n }\n}\n\n/**\n * Write the task file atomically. Prefer `mutateTasks` for read-modify-write\n * cycles \u2014 this low-level function does NOT acquire a lock.\n */\n/**\n * Persist the task file. Returns `true` on success, `false` if the write\n * failed (still emits `storage.error` + warns \u2014 does NOT throw). `mutateTasks`\n * inspects the result and throws so the task TOOL can report `ok:false`\n * instead of falsely claiming the tasks were saved.\n */\nexport async function saveTasks(\n filePath: string,\n tasks: TaskFile,\n events?: EventBus,\n traceId?: string,\n warn?: (msg: string) => void,\n): Promise<boolean> {\n const t0 = Date.now();\n try {\n tasks.updatedAt = new Date().toISOString();\n await atomicWrite(filePath, JSON.stringify(tasks, null, 2), { mode: 0o600 });\n events?.emit('storage.write', {\n sessionId: traceId ?? '~boot~',\n store: 'tasks',\n filePath,\n operation: 'save',\n outcome: 'success',\n durationMs: Date.now() - t0,\n ...(traceId !== undefined && { traceId }),\n });\n return true;\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: traceId ?? '~boot~',\n store: 'tasks',\n filePath,\n operation: 'save',\n outcome: 'failure',\n error: toErrorMessage(err),\n recoverable: false,\n ...(traceId !== undefined && { traceId }),\n });\n (\n warn ??\n ((m) =>\n console.warn(\n JSON.stringify({\n level: 'warn',\n event: 'task_store.save_failed',\n message: m,\n timestamp: new Date().toISOString(),\n }),\n ))\n )(toErrorMessage(err));\n return false;\n }\n}\n\n/**\n * Load, modify, and save the task file under a file-level lock.\n * `fn` receives the current TaskFile (or a fresh empty one) and must\n * return the mutated TaskFile (mutating in-place is fine \u2014 the returned\n * reference is what gets saved).\n *\n * This is the primary API for any code path that reads *and then writes*\n * the task file \u2014 it prevents races from parallel `batch_tool_use` calls.\n */\nexport async function mutateTasks(\n filePath: string,\n sessionId: string,\n fn: (file: TaskFile) => TaskFile | Promise<TaskFile>,\n events?: EventBus,\n traceId?: string,\n): Promise<TaskFile> {\n return withFileLock(filePath, async () => {\n const file = (await loadTasks(filePath, events, traceId)) ?? emptyTaskFile(sessionId);\n const updated = await fn(file);\n const persisted = await saveTasks(filePath, updated, events, traceId);\n if (!persisted) {\n throw new SessionError({\n message: `Failed to persist tasks to ${filePath} \u2014 the change was NOT saved.`,\n code: 'SESSION_WRITE_FAILED',\n sessionId,\n context: { filePath, operation: 'mutateTasks' },\n });\n }\n return updated;\n });\n}\n", "import * as fsp from 'node:fs/promises';\nimport { hostname } from 'node:os';\nimport { atomicWrite } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/error.js';\n\n/**\n * Director state checkpoint \u2014 written incrementally throughout a fleet\n * run so a crashed director can be inspected (and eventually resumed)\n * instead of leaving only a final `fleet.json` manifest after `shutdown()`.\n *\n * Schema is JSON-friendly and deliberately denormalized. Each mutation\n * triggers an atomic-write of the whole file \u2014 small payloads (typically\n * < 10 KB even with dozens of subagents) make this cheap.\n */\nexport interface DirectorSubagentState {\n id: string;\n name?: string | undefined;\n role?: string | undefined;\n provider?: string | undefined;\n model?: string | undefined;\n spawnedAt: string;\n}\n\nexport interface DirectorTaskState {\n taskId: string;\n subagentId?: string | undefined;\n description?: string | undefined;\n status: 'pending' | 'running' | 'completed' | 'failed' | 'stopped' | 'timeout';\n assignedAt?: string | undefined;\n completedAt?: string | undefined;\n iterations?: number | undefined;\n toolCalls?: number | undefined;\n durationMs?: number | undefined;\n error?: string | undefined;\n worktree?: DirectorTaskWorktreeState | undefined;\n}\n\nexport interface DirectorTaskWorktreeState {\n taskId: string;\n subagentId: string;\n handleId: string;\n dir: string;\n branch: string;\n baseBranch: string;\n status:\n | 'allocated'\n | 'fallback'\n | 'committed'\n | 'merged'\n | 'kept'\n | 'released'\n | 'conflict'\n | 'failed';\n commitSha?: string | undefined;\n conflictFiles?: string[] | undefined;\n error?: string | undefined;\n}\n\nexport interface DirectorStateSnapshot {\n version: 1;\n directorRunId: string;\n updatedAt: string;\n spawnCount: number;\n maxSpawns?: number | undefined;\n spawnDepth: number;\n maxSpawnDepth: number;\n directorBudget?:\n | {\n maxCostUsd?: number | undefined;\n maxTokens?: number | undefined;\n }\n | undefined;\n subagents: DirectorSubagentState[];\n tasks: DirectorTaskState[];\n /** Aggregated usage snapshot. Optional \u2014 populated by the Director on save when available. */\n usage?: unknown | undefined;\n}\n\nexport async function loadDirectorState(filePath: string): Promise<DirectorStateSnapshot | null> {\n let raw: string;\n try {\n raw = await fsp.readFile(filePath, 'utf8');\n } catch {\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as DirectorStateSnapshot;\n if (parsed?.version !== 1) return null;\n return parsed;\n } catch {\n return null;\n }\n}\n\n/**\n * Lock file entry written when a director starts. Prevents two directors\n * from resuming the same run \u2014 the second one sees the lock and refuses\n * rather than corrupting the checkpoint by writing concurrently.\n */\nexport interface DirectorStateLock {\n pid: number;\n hostname: string;\n startedAt: string;\n}\n\n/**\n * Write a lock file to claim this checkpoint. Returns false if the lock\n * is already held by a live process; returns true if the lock was acquired\n * (either the file didn't exist, or the previous holder is dead).\n */\nexport async function acquireDirectorStateLock(\n lockPath: string,\n processId = process.pid,\n): Promise<boolean> {\n let existing: string | undefined;\n try {\n existing = await fsp.readFile(lockPath, 'utf8');\n } catch {\n // No lock file \u2014 we're safe to claim\n }\n\n if (existing) {\n try {\n const lock = JSON.parse(existing) as DirectorStateLock;\n // Check if the process is still alive\n try {\n process.kill(lock.pid, 0);\n // Signal success means the process is alive \u2014 another director\n // owns this checkpoint. Refuse.\n return false;\n } catch {\n // ESRCH means the process is dead \u2014 stale lock. We'll overwrite.\n }\n } catch {\n // Malformed lock \u2014 treat as stale.\n }\n }\n\n const lock: DirectorStateLock = {\n pid: processId,\n hostname: hostname(),\n startedAt: new Date().toISOString(),\n };\n await atomicWrite(lockPath, JSON.stringify(lock), { mode: 0o600 });\n return true;\n}\n\n/**\n * Remove the lock file. Call this on graceful Director.shutdown() so the\n * next director run can claim the checkpoint without stale-lock checks.\n */\nexport async function releaseDirectorStateLock(lockPath: string): Promise<void> {\n try {\n await fsp.unlink(lockPath);\n } catch {\n // ignore\n }\n}\n\n/**\n * In-memory accumulator with atomic-write checkpoint. The Director keeps\n * an instance, mutates it on every spawn/assign/complete/fail event, and\n * the instance debounces writes so a burst of activity collapses into a\n * single disk hit.\n *\n * Supports crash recovery: use `loadDirectorState()` to read an existing\n * checkpoint, then call `DirectorStateCheckpoint.resume(snapshot)` to\n * re-attach to a fleet mid-flight. The lock mechanism ensures no two\n * directors can claim the same checkpoint.\n */\nexport class DirectorStateCheckpoint {\n private snapshot: DirectorStateSnapshot;\n private readonly filePath: string;\n private readonly lockPath: string;\n private timer: NodeJS.Timeout | null = null;\n private readonly debounceMs: number;\n private writing = false;\n private rewriteRequested = false;\n\n constructor(\n filePath: string,\n init: {\n directorRunId: string;\n maxSpawns?: number | undefined;\n spawnDepth: number;\n maxSpawnDepth: number;\n directorBudget?:\n | {\n maxCostUsd?: number | undefined;\n maxTokens?: number | undefined;\n }\n | undefined;\n },\n debounceMs = 250,\n ) {\n this.filePath = filePath;\n // Lock file lives alongside the checkpoint \u2014 `<path>.lock`\n this.lockPath = `${filePath}.lock`;\n this.debounceMs = debounceMs;\n this.snapshot = {\n version: 1,\n directorRunId: init.directorRunId,\n updatedAt: new Date().toISOString(),\n spawnCount: 0,\n maxSpawns: init.maxSpawns,\n spawnDepth: init.spawnDepth,\n maxSpawnDepth: init.maxSpawnDepth,\n directorBudget: init.directorBudget,\n subagents: [],\n tasks: [],\n };\n }\n\n /**\n * Attempt to acquire the lock for this checkpoint. Call this before\n * resuming a crashed director run. If it returns false, another\n * director process is still running this fleet \u2014 do not resume.\n */\n async acquireLock(): Promise<boolean> {\n return acquireDirectorStateLock(this.lockPath);\n }\n\n /**\n * Release the lock on graceful shutdown. Call `flush()` first to ensure\n * the final checkpoint state is on disk before removing the lock.\n * Without this, the next resume will see a stale-lock and refuse.\n */\n async releaseLock(): Promise<void> {\n return releaseDirectorStateLock(this.lockPath);\n }\n\n /**\n * Resume from a snapshot previously loaded via `loadDirectorState()`.\n * Use this when `--resume <runId>` is triggered \u2014 the snapshot has\n * the full fleet state (subagents, tasks) from before the crash; the\n * checkpoint continues from there.\n */\n resume(snapshot: DirectorStateSnapshot): void {\n this.snapshot = snapshot;\n }\n\n current(): DirectorStateSnapshot {\n return this.snapshot;\n }\n\n recordSpawn(sub: DirectorSubagentState, spawnCount: number): void {\n this.snapshot = {\n ...this.snapshot,\n spawnCount,\n subagents: [...this.snapshot.subagents.filter((s) => s.id !== sub.id), sub],\n };\n this.bumpUpdatedAt();\n this.schedule();\n }\n\n recordTaskAssigned(task: DirectorTaskState): void {\n const exists = this.snapshot.tasks.some((t) => t.taskId === task.taskId);\n this.snapshot = {\n ...this.snapshot,\n tasks: exists\n ? this.snapshot.tasks.map((t) => (t.taskId === task.taskId ? { ...t, ...task } : t))\n : [...this.snapshot.tasks, task],\n };\n this.bumpUpdatedAt();\n this.schedule();\n }\n\n recordTaskStatus(\n taskId: string,\n patch: Partial<DirectorTaskState> & { status: DirectorTaskState['status'] },\n ): void {\n this.snapshot = {\n ...this.snapshot,\n tasks: this.snapshot.tasks.map((t) => (t.taskId === taskId ? { ...t, ...patch } : t)),\n };\n this.bumpUpdatedAt();\n this.schedule();\n }\n\n recordTaskWorktree(taskId: string, worktree: DirectorTaskWorktreeState): void {\n const exists = this.snapshot.tasks.some((t) => t.taskId === taskId);\n this.snapshot = {\n ...this.snapshot,\n tasks: exists\n ? this.snapshot.tasks.map((t) => (t.taskId === taskId ? { ...t, worktree } : t))\n : [\n ...this.snapshot.tasks,\n {\n taskId,\n subagentId: worktree.subagentId,\n status: 'running',\n worktree,\n },\n ],\n };\n this.bumpUpdatedAt();\n this.schedule();\n }\n\n setUsage(usage: unknown): void {\n this.snapshot = { ...this.snapshot, usage };\n this.bumpUpdatedAt();\n this.schedule();\n }\n\n /** Force a synchronous flush \u2014 used by Director.shutdown(). */\n async flush(): Promise<void> {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n await this.persist();\n // If a rewrite was requested while we waited, persist() scheduled\n // a follow-up write. Loop until no more rewrites are requested so\n // shutdown doesn't return before the most recent state lands on disk.\n /* v8 ignore start -- concurrency-defensive: persist()'s finally clears the flag in single-threaded flow */\n while (this.rewriteRequested) {\n this.rewriteRequested = false;\n await this.persist();\n }\n /* v8 ignore stop */\n }\n\n private bumpUpdatedAt(): void {\n this.snapshot = { ...this.snapshot, updatedAt: new Date().toISOString() };\n }\n\n private schedule(): void {\n if (this.timer) return;\n this.timer = setTimeout(() => {\n this.timer = null;\n void this.persist();\n }, this.debounceMs);\n }\n\n private async persist(): Promise<void> {\n if (this.writing) {\n // A write is already in flight \u2014 defer to a follow-up flush so the\n // most recent state still lands. Without this guard, simultaneous\n // burst mutations can drop the latest snapshot if rename races.\n this.rewriteRequested = true;\n return;\n }\n this.writing = true;\n try {\n await atomicWrite(this.filePath, JSON.stringify(this.snapshot, null, 2), {\n mode: 0o600,\n });\n } catch (err) {\n console.warn(\n JSON.stringify({\n level: 'warn',\n event: 'director_state.checkpoint_write_failed',\n message: toErrorMessage(err),\n timestamp: new Date().toISOString(),\n }),\n );\n } finally {\n this.writing = false;\n /* v8 ignore start -- concurrency-defensive: rewriteRequested is only set by an overlapping persist() */\n if (this.rewriteRequested) {\n this.rewriteRequested = false;\n this.schedule();\n }\n /* v8 ignore stop */\n }\n }\n}\n", "import * as fsp from 'node:fs/promises';\nimport type { EventBus } from '../kernel/events.js';\nimport { atomicWrite, withFileLock } from '../utils/atomic-write.js';\nimport { toErrorMessage } from '../utils/error.js';\nimport { color } from '../utils/color.js';\nimport { resolveWstackPaths } from '../utils/wstack-paths.js';\nimport { FsError, ERROR_CODES } from '../types/errors.js';\n\n/**\n * Long-running autonomous mission. A goal survives across sessions and\n * drives the EternalAutonomyEngine \u2014 every iteration of the engine\n * consults the goal to choose what to do next.\n *\n * Storage: `~/.wrongstack/projects/<hash>/goal.json`. Persistent and\n * project-scoped on purpose: the goal belongs to the codebase, not the\n * REPL session.\n */\n\nexport interface JournalEntry {\n /** ISO timestamp of the iteration. */\n at: string;\n /** Sequential iteration counter (1-based, monotonically increasing). */\n iteration: number;\n /** Source that produced the action ('todo' | 'git' | 'brainstorm' | 'resume' | 'manual' | 'parallel'). */\n source: 'todo' | 'git' | 'brainstorm' | 'resume' | 'manual' | 'parallel';\n /** Short one-line description of what the iteration set out to do. */\n task: string;\n /** Outcome status. */\n status: 'success' | 'failure' | 'aborted' | 'skipped';\n /** Optional free-form note (error message, summary, etc.). */\n note?: string | undefined;\n /** Optional token usage delta for this iteration. */\n tokens?: { input: number; output: number } | undefined;\n /** Optional USD cost delta for this iteration (provider-estimated). */\n costUsd?: number | undefined;\n}\n\nexport interface GoalFile {\n version: 1;\n /** The raw mission statement as entered by the user. */\n goal: string;\n /**\n * LLM-refined version of the goal \u2014 unambiguous, with concrete\n * deliverables and acceptance criteria.\n */\n refinedGoal?: string | undefined;\n /**\n * Concrete, verifiable deliverables extracted from the refined goal.\n */\n deliverables?: string[] | undefined;\n /**\n * Estimated completion 0-100. Updated by the engine after each\n * iteration. Null means \"not yet assessed\".\n */\n progress?: number | undefined;\n /** Human-readable note explaining the current progress estimate. */\n progressNote?: string | undefined;\n /**\n * Time-series of progress measurements for trend analysis.\n * Last 200 entries retained. Use `recordProgress()` to append.\n */\n progressHistory?: ProgressSnapshot[] | undefined;\n /**\n * Computed trend from recent progress measurements.\n * 'accelerating' | 'steady' | 'stalling' | undefined.\n */\n progressTrend?: 'accelerating' | 'steady' | 'stalling' | undefined;\n /** When the goal was first set or last replaced. */\n setAt: string;\n /** Updated on every iteration completion. */\n lastActivityAt: string;\n /** Total iterations the engine has run against this goal (cumulative). */\n iterations: number;\n /** Engine lifecycle state \u2014 'running' means another process owns this goal. */\n engineState: 'idle' | 'running' | 'stopped';\n /**\n * Mission-level lifecycle.\n */\n goalState?: 'active' | 'paused' | 'completed' | 'abandoned' | undefined;\n /**\n * Per-todo attempt counter.\n */\n todoAttempts?: Record<string, number>;\n /** Bounded ring buffer of recent iterations (newest last). */\n journal: JournalEntry[];\n}\n\n/** Cap on persisted journal entries \u2014 older entries are evicted FIFO. */\nexport const MAX_JOURNAL_ENTRIES = 500;\n\n/**\n * Resolve the goal file path for a given project root.\n *\n * SINGLE canonical location: the per-project directory that\n * `resolveWstackPaths()` uses for everything else (sessions, memory, specs) \u2014\n * `~/.wrongstack/projects/<slug>/goal.json`. This is the same path the `/goal`\n * slash command writes via `opts.paths.projectGoal`, so every reader/writer\n * (the eternal/parallel autonomy engines, the CLI autonomy commands, the TUI\n * F9 panel, and `/goal` itself) now agree on one file.\n *\n * Previously this returned a SEPARATE hash-based dir (`projects/<hash>/`), which\n * disagreed with `/goal` and littered the home dir with thousands of stray\n * `<hash>/goal.json` directories that held nothing else.\n */\nexport function goalFilePath(projectRoot: string): string {\n return resolveWstackPaths({ projectRoot }).projectGoal;\n}\n\nexport async function loadGoal(\n filePath: string,\n events?: EventBus,\n warn?: (msg: string) => void,\n): Promise<GoalFile | null> {\n const t0 = Date.now();\n let raw: string;\n try {\n raw = await fsp.readFile(filePath, 'utf8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') {\n events?.emit('storage.read', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'load',\n outcome: 'success',\n durationMs: Date.now() - t0,\n });\n return null; // file doesn't exist \u2014 not an error\n }\n events?.emit('storage.error', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'load',\n error: toErrorMessage(err),\n recoverable: false,\n });\n throw err; // permission errors etc. should surface\n }\n try {\n const parsed = JSON.parse(raw) as GoalFile;\n if (parsed?.version !== 1 || typeof parsed.goal !== 'string' || !Array.isArray(parsed.journal)) {\n (warn ?? ((msg) => console.warn(JSON.stringify({ level: 'warn', event: 'goal_store.invalid_schema', path: filePath, message: msg, timestamp: new Date().toISOString() }))))(\n 'invalid schema \u2014 consider deleting and re-creating',\n );\n events?.emit('storage.read', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'invalid_schema',\n });\n return null;\n }\n events?.emit('storage.read', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'load',\n outcome: 'success',\n durationMs: Date.now() - t0,\n });\n return parsed;\n } catch {\n (warn ?? ((msg) => console.warn(JSON.stringify({ level: 'warn', event: 'goal_store.parse_failed', path: filePath, message: msg, timestamp: new Date().toISOString() }))))(\n 'JSON parse failed \u2014 consider deleting and re-creating',\n );\n events?.emit('storage.read', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'load',\n outcome: 'failure',\n durationMs: Date.now() - t0,\n error: 'parse_failed',\n });\n return null;\n }\n}\n\nexport async function saveGoal(filePath: string, goal: GoalFile, events?: EventBus): Promise<void> {\n const t0 = Date.now();\n try {\n await atomicWrite(filePath, JSON.stringify(goal, null, 2), { mode: 0o600 });\n events?.emit('storage.write', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'save',\n outcome: 'success',\n durationMs: Date.now() - t0,\n });\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'save',\n error: toErrorMessage(err),\n recoverable: false,\n });\n throw new FsError({\n message: toErrorMessage(err),\n code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,\n path: filePath,\n cause: err,\n });\n }\n}\n\n/**\n * Atomically load, modify, and save a goal file under a file lock.\n * Prevents lost-update races when the autonomy engine and CLI /goal commands\n * write concurrently (both eternal and parallel engines may run simultaneously).\n *\n * `fn` receives the current GoalFile (or `null` if no goal exists yet)\n * and must return the updated GoalFile (or `null` to delete).\n */\nexport async function updateGoal(\n filePath: string,\n fn: (current: GoalFile | null) => GoalFile | null,\n events?: EventBus,\n): Promise<void> {\n const t0 = Date.now();\n await withFileLock(filePath, async () => {\n const current = await loadGoal(filePath, events);\n const next = fn(current);\n if (next) {\n await saveGoal(filePath, next, events);\n } else {\n try {\n await fsp.unlink(filePath);\n events?.emit('storage.write', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'delete',\n outcome: 'success',\n durationMs: Date.now() - t0,\n });\n } catch (err) {\n events?.emit('storage.error', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'delete',\n error: toErrorMessage(err),\n recoverable: true,\n });\n // best-effort \u2014 file may not exist\n }\n }\n events?.emit('storage.write', {\n sessionId: '~boot~',\n store: 'goal',\n filePath,\n operation: 'update',\n outcome: 'success',\n durationMs: Date.now() - t0,\n });\n });\n}\n\nexport function emptyGoal(goal: string): GoalFile {\n const now = new Date().toISOString();\n return {\n version: 1,\n goal,\n setAt: now,\n lastActivityAt: now,\n iterations: 0,\n engineState: 'idle',\n goalState: 'active',\n todoAttempts: {},\n journal: [],\n };\n}\n\n/**\n * Set progress estimate on a goal. Returns a new GoalFile.\n * Clamps progress to 0-100.\n */\nexport function setProgress(\n goal: GoalFile,\n progress: number,\n note?: string,\n): GoalFile {\n const clamped = Math.min(100, Math.max(0, progress));\n return {\n ...goal,\n progress: clamped,\n progressNote: note ?? clamped + '% complete',\n };\n}\n\n/**\n * Append a journal entry, bumping iteration counters and trimming the\n * ring buffer. Returns a new GoalFile \u2014 does not mutate the argument.\n */\nexport function appendJournal(goal: GoalFile, entry: Omit<JournalEntry, 'iteration' | 'at'>): GoalFile {\n const iteration = goal.iterations + 1;\n const at = new Date().toISOString();\n const full: JournalEntry = { ...entry, iteration, at };\n const journal = [...goal.journal, full];\n const trimmed = journal.length > MAX_JOURNAL_ENTRIES\n ? journal.slice(journal.length - MAX_JOURNAL_ENTRIES)\n : journal;\n return {\n ...goal,\n iterations: iteration,\n lastActivityAt: at,\n journal: trimmed,\n };\n}\n\n/**\n * Aggregate cumulative cost + tokens across all journal entries.\n */\nexport function summarizeUsage(goal: GoalFile): {\n totalCostUsd: number;\n totalInputTokens: number;\n totalOutputTokens: number;\n iterationsWithUsage: number;\n} {\n let totalCostUsd = 0;\n let totalInputTokens = 0;\n let totalOutputTokens = 0;\n let iterationsWithUsage = 0;\n for (const e of goal.journal) {\n if (typeof e.costUsd === 'number') totalCostUsd += e.costUsd;\n if (e.tokens) {\n totalInputTokens += e.tokens.input;\n totalOutputTokens += e.tokens.output;\n }\n if (typeof e.costUsd === 'number' || e.tokens) iterationsWithUsage++;\n }\n return { totalCostUsd, totalInputTokens, totalOutputTokens, iterationsWithUsage };\n}\n\nconst DOLLAR = '\\u0024';\n\n/** Format the goal + recent journal as a human-readable status block. */\nexport function formatGoal(goal: GoalFile, journalLimit = 10): string {\n const lines: string[] = [];\n\n // Header \u2014 show refined goal, with original as annotation if different\n const displayGoal = goal.refinedGoal || goal.goal;\n lines.push(color.bold('Goal') + ': ' + displayGoal);\n if (goal.refinedGoal && goal.refinedGoal !== goal.goal) {\n const snippet = goal.goal.length > 60 ? goal.goal.slice(0, 60) + '\u2026' : goal.goal;\n lines.push(color.dim(' (original: \"' + snippet + '\")'));\n }\n\n // Progress bar (20-segment)\n if (typeof goal.progress === 'number') {\n const pct = Math.min(100, Math.max(0, Math.round(goal.progress)));\n const filled = Math.round(pct / 5);\n const empty = 20 - filled;\n const bar = color.green('\u2588'.repeat(filled)) + color.dim('\u2591'.repeat(empty));\n lines.push('Progress: ' + bar + ' ' + color.bold(pct + '%'));\n if (goal.progressNote) {\n lines.push(' ' + color.dim(goal.progressNote));\n }\n // Trend indicator\n if (goal.progressTrend) {\n const trendIcon = goal.progressTrend === 'accelerating' ? '\uD83D\uDE80'\n : goal.progressTrend === 'stalling' ? '\u26A0\uFE0F'\n : '\u27A1\uFE0F';\n lines.push(' Trend: ' + trendIcon + ' ' + goal.progressTrend);\n }\n }\n\n // Deliverables checklist\n if (goal.deliverables && goal.deliverables.length > 0) {\n lines.push('');\n lines.push(color.bold('Deliverables:'));\n for (const d of goal.deliverables) {\n const done = /^\\[[x\u2713]\\]|\u2705|\\(done\\)/i.test(d);\n const marker = done ? color.green('\u2713') : color.dim('\u25CB');\n lines.push(' ' + marker + ' ' + d);\n }\n }\n\n lines.push('');\n lines.push('Set: ' + goal.setAt);\n lines.push('Last activity: ' + goal.lastActivityAt);\n lines.push('Iterations: ' + goal.iterations);\n const stateLabel = goal.goalState ?? 'active';\n lines.push('State: ' + stateLabel + (goal.iterations > 0 ? ' (iteration #' + goal.iterations + ')' : ''));\n lines.push('Engine: ' + goal.engineState);\n const usage = summarizeUsage(goal);\n if (usage.iterationsWithUsage > 0) {\n const spent = 'Spent: ' + DOLLAR + usage.totalCostUsd.toFixed(4)\n + ' (in ' + usage.totalInputTokens + ' / out ' + usage.totalOutputTokens\n + ' tokens across ' + usage.iterationsWithUsage + ' iterations)';\n lines.push(spent);\n }\n if (goal.journal.length > 0) {\n lines.push('');\n lines.push('Recent journal (last ' + Math.min(journalLimit, goal.journal.length) + '):');\n const tail = goal.journal.slice(-journalLimit);\n for (const e of tail) {\n const mark = e.status === 'success' ? '\u2713' : e.status === 'failure' ? '\u2717' : e.status === 'aborted' ? '\u2298' : '\u00B7';\n const note = e.note ? ' \u2014 ' + e.note : '';\n const cost = typeof e.costUsd === 'number' ? ' (' + DOLLAR + e.costUsd.toFixed(4) + ')' : '';\n lines.push(' #' + e.iteration + ' ' + mark + ' [' + e.source + '] ' + e.task + cost + note);\n }\n }\n return lines.join('\\n');\n}\n\n/** A single progress measurement at a point in time. */\nexport interface ProgressSnapshot {\n at: string;\n progress: number;\n note?: string | undefined;\n}\n\n/**\n * Parse [PROGRESS: N%] from agent final text.\n * Supports formats:\n * [PROGRESS: 45%]\n * [PROGRESS: 45%] \u2014 3/5 deliverables done\n * [progress: 100%]\n * Returns null if no match.\n */\nexport function parseProgressFromText(text: string): { progress: number; note?: string } | null {\n const re = /\\[progress:\\s*(\\d{1,3})%\\]\\s*(?:[\u2014-]\\s*(.+))?/i;\n const m = text.match(re);\n if (!m) return null;\n // Regex match guarantees capture group 1 exists, but use ?? fallback to\n // satisfy noUncheckedIndexedAccess without a non-null assertion.\n const progress = Math.min(100, Math.max(0, Number.parseInt(m[1] ?? '0', 10)));\n const note = m[2]?.trim() || undefined;\n return note === undefined ? { progress } : { progress, note };\n}\n\n/**\n * Record a progress measurement. Returns a new GoalFile with:\n * - progress + progressNote updated\n * - progressHistory appended (last 200 entries kept)\n * - progress trend computed (accelerating/steady/stalling)\n */\nexport function recordProgress(\n goal: GoalFile,\n progress: number,\n note?: string,\n): GoalFile {\n const clamped = Math.min(100, Math.max(0, progress));\n const history = [...(goal.progressHistory ?? []), { at: new Date().toISOString(), progress: clamped, note }];\n // Keep last 200 snapshots\n const trimmed = history.length > 200 ? history.slice(-200) : history;\n\n return {\n ...goal,\n progress: clamped,\n progressNote: note ?? `${clamped}% complete`,\n progressHistory: trimmed,\n progressTrend: computeTrend(trimmed),\n };\n}\n\n/** Max progress history entries to retain. */\nexport const MAX_PROGRESS_HISTORY = 200;\n\nfunction computeTrend(history: ProgressSnapshot[]): 'accelerating' | 'steady' | 'stalling' | undefined {\n if (history.length < 3) return undefined;\n const recent = history.slice(-5);\n const deltas: number[] = [];\n for (let i = 1; i < recent.length; i++) {\n deltas.push((recent[i]?.progress ?? 0) - (recent[i - 1]?.progress ?? 0));\n }\n /* v8 ignore next -- unreachable: history.length>=3 guard above guarantees >=2 deltas */\n if (deltas.length < 2) return undefined;\n const avgDelta = deltas.reduce((a, b) => a + b, 0) / deltas.length;\n if (avgDelta > 2) return 'accelerating';\n if (avgDelta < -1) return 'stalling';\n return 'steady';\n}\n", "/**\n * Goal-to-Kanban bridge module.\n *\n * Automatically creates and manages a kanban board for each `/goal set` mission.\n * The board mirrors the goal's deliverables as tasks flowing through\n * Backlog \u2192 In Progress \u2192 Done columns, and the autonomy engines update\n * task status as work progresses.\n *\n * API:\n * createGoalKanbanBoard(projectRoot, goalFile, goalId) \u2192 boardId\n * findGoalKanbanBoard(projectRoot, boardId) \u2192 KanbanBoard | null\n * deleteGoalKanbanBoard(projectRoot, boardId) \u2192 void\n * formatGoalKanbanPreview(goalFile) \u2192 string (rich display)\n * formatGoalKanbanChoicePrompt() \u2192 string (selection screen)\n */\n\nimport {\n addColumn,\n addTask,\n createBoard,\n getBoard,\n listBoards,\n removeBoard,\n} from '@wrongstack/kanban';\nimport type { GoalFile } from './goal-store.js';\nimport { color } from '../utils/color.js';\n\n// \u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst GOAL_BOARD_TAG_PREFIX = 'goal:';\n\nconst DEFAULT_COLUMNS = [\n { title: 'Backlog', order: 0 },\n { title: 'In Progress', order: 1 },\n { title: 'Done', order: 2 },\n] as const;\n\n// \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Create (or update) a kanban board for the given goal.\n *\n * - If the goal file already carries a `kanbanBoardId` and the board still\n * exists, this is a no-op (returns the existing id).\n * - Otherwise a new board is created with three columns and one task per\n * deliverable. The board id is stored on `goalFile.kanbanBoardId`.\n *\n * Returns the board id. Returns null when the project root is unavailable.\n */\nexport async function createGoalKanbanBoard(\n projectRoot: string,\n goalFile: GoalFile,\n): Promise<string | null> {\n if (!projectRoot) return null;\n\n // Reuse an existing link if the board is still alive\n const existingId = (goalFile as GoalFileWithKanban).kanbanBoardId;\n if (existingId) {\n const existing = await getBoard(projectRoot, existingId).catch(() => null);\n if (existing) return existingId;\n }\n\n // Build a short goal suffix for the board title (max ~60 chars)\n const displayGoal = (goalFile.refinedGoal || goalFile.goal).replace(/\\s+/g, ' ').trim();\n const titleSuffix = displayGoal.length > 80 ? displayGoal.slice(0, 77) + '\u2026' : displayGoal;\n\n const board = await createBoard(projectRoot, {\n title: `\uD83C\uDFAF ${titleSuffix}`,\n description: `Kanban board auto-created for goal: ${displayGoal}`,\n tags: [goalTag(goalFile)],\n });\n\n // Add the three columns if the board was created with a default column\n // (kanban createBoard normally provides 'backlog' as the starter column).\n // We rename/replace by adding our own columns, then removing the starter.\n const backlogCol = board.columns.find((c) =>\n ['backlog', 'todo', 'open'].includes(c.id.toLowerCase()),\n );\n const existingColumnIds = new Set(board.columns.map((c) => c.id));\n\n for (const col of DEFAULT_COLUMNS) {\n if (!existingColumnIds.has(col.title.toLowerCase().replace(/\\s+/g, '-'))) {\n const result = await addColumn(projectRoot, board.id, { title: col.title });\n if (result?.column && backlogCol) {\n // Move column to correct order \u2014 addColumn appends, so we re-order\n // by setting order directly. This is best-effort.\n }\n }\n }\n\n // Add one task per deliverable\n const targetColumnId = board.columns[0]?.id ?? 'backlog';\n if (goalFile.deliverables && goalFile.deliverables.length > 0) {\n for (const d of goalFile.deliverables) {\n const cleaned = d.replace(/^\\[[x\u2713]\\]|\u2705|\\(done\\)\\s*/i, '').trim();\n if (cleaned) {\n await addTask(projectRoot, board.id, {\n title: cleaned,\n columnId: targetColumnId,\n description: `Deliverable for goal: ${displayGoal}`,\n priority: 'medium',\n }).catch(() => {\n // Best-effort: a task-add failure for one deliverable won't crash the goal\n });\n }\n }\n }\n\n // Store the board id on the goal file by mutating the passed reference\n (goalFile as GoalFileWithKanban).kanbanBoardId = board.id;\n\n return board.id;\n}\n\n/**\n * Find a kanban board by id. Returns null when the board doesn't exist\n * (e.g. manually deleted). The caller should handle this gracefully.\n */\nexport async function findGoalKanbanBoard(\n projectRoot: string,\n boardId: string,\n) {\n if (!projectRoot || !boardId) return null;\n try {\n return await getBoard(projectRoot, boardId);\n } catch {\n return null;\n }\n}\n\n/**\n * Delete the kanban board linked to a goal. Best-effort.\n */\nexport async function deleteGoalKanbanBoard(\n projectRoot: string,\n boardId: string,\n): Promise<void> {\n if (!projectRoot || !boardId) return;\n await removeBoard(projectRoot, boardId).catch(() => {});\n}\n\n/**\n * Find a goal-linked kanban board by its goal tag.\n * Used when the goal file's kanbanBoardId is missing but the board exists.\n */\nexport async function findGoalBoardByTag(\n projectRoot: string,\n goalFile: GoalFile,\n) {\n if (!projectRoot) return null;\n const tag = goalTag(goalFile);\n const boards = await listBoards(projectRoot);\n const matched = boards.find((b) => b.tags?.includes(tag));\n if (!matched) return null;\n return getBoard(projectRoot, matched.id).catch(() => null);\n}\n\n/**\n * Build a formatted \"Goal Kanban\" preview block showing deliverables as\n * a kanban-column layout. This is the \"Goal event\" displayed after the\n * deliverables list.\n */\nexport function formatGoalKanbanPreview(\n goalFile: GoalFile,\n boardId?: string | null,\n taskCount?: number,\n): string {\n const lines: string[] = [];\n const displayGoal = (goalFile.refinedGoal || goalFile.goal)\n .replace(/\\s+/g, ' ')\n .trim();\n\n // \u2500\u2500 Header \u2500\u2500\n lines.push('');\n lines.push(color.bold(color.cyan('\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550')));\n lines.push(color.bold(color.cyan(' \uD83C\uDFAF GOAL KANBAN \u2014 Mission Board')));\n lines.push(color.bold(color.cyan('\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550')));\n lines.push('');\n lines.push(` ${color.bold('Mission:')} ${color.bold(displayGoal)}`);\n if (boardId) {\n lines.push(` ${color.dim(`Board: ${boardId.slice(0, 12)}\u2026`)}`);\n }\n if (typeof taskCount === 'number') {\n lines.push(` ${color.dim(`${taskCount} task(s)`)}`);\n }\n lines.push('');\n\n // \u2500\u2500 Deliverables as kanban columns \u2500\u2500\n const deliverables = goalFile.deliverables ?? [];\n const doneDeliverables = deliverables.filter((d) =>\n /^\\[[x\u2713]\\]|\u2705|\\(done\\)/i.test(d),\n ).length;\n const totalDeliverables = deliverables.length;\n\n if (totalDeliverables > 0) {\n // Backlog section (todo items)\n const backlog = deliverables.filter(\n (d) => !/^\\[[x\u2713]\\]|\u2705|\\(done\\)|\uD83D\uDD04|in.progress/i.test(d),\n );\n const inProgress = deliverables.filter((d) => /\uD83D\uDD04|in.progress/i.test(d));\n const done = deliverables.filter((d) => /^\\[[x\u2713]\\]|\u2705|\\(done\\)/i.test(d));\n\n const renderColumn = (title: string, items: string[], icon: string, accent: (s: string) => string) => {\n if (items.length === 0) {\n lines.push(` ${accent(color.bold(`${icon} ${title}`))} ${color.dim('(empty)')}`);\n return;\n }\n lines.push(` ${accent(color.bold(`${icon} ${title} (${items.length})`))}`);\n for (const item of items) {\n const cleaned = item\n .replace(/^\\[[x\u2713]\\]|\u2705|\\(done\\)|\uD83D\uDD04|in.progress\\s*/gi, '')\n .trim();\n lines.push(` \u25CB ${cleaned}`);\n }\n };\n\n renderColumn('Backlog', backlog, '\uD83D\uDCCB', (s) => color.dim(s));\n renderColumn('In Progress', inProgress, '\uD83D\uDD04', (s) => color.cyan(s));\n renderColumn('Done', done, '\u2705', (s) => color.green(s));\n\n lines.push('');\n lines.push(\n ` ${color.dim(`Progress: ${doneDeliverables}/${totalDeliverables} deliverables complete`)}`,\n );\n }\n\n // \u2500\u2500 Progress bar (when available) \u2500\u2500\n if (typeof goalFile.progress === 'number') {\n const pct = Math.min(100, Math.max(0, Math.round(goalFile.progress)));\n const filled = Math.round(pct / 5);\n const empty = 20 - filled;\n const bar = color.green('\u2588'.repeat(filled)) + color.dim('\u2591'.repeat(empty));\n lines.push(` ${color.dim('Overall:')} ${bar} ${color.bold(`${pct}%`)}`);\n if (goalFile.progressNote) {\n lines.push(` ${color.dim(goalFile.progressNote)}`);\n }\n }\n if (typeof goalFile.progress === 'undefined' && totalDeliverables > 0) {\n const pct = Math.round((doneDeliverables / totalDeliverables) * 100);\n const filled = Math.round(pct / 5);\n const empty = 20 - filled;\n const bar = (pct > 0 ? color.green('\u2588'.repeat(filled)) : '') + color.dim('\u2591'.repeat(empty));\n lines.push(` ${color.dim('Overall:')} ${bar} ${color.bold(`${pct}%`)}`);\n }\n\n lines.push('');\n lines.push(color.bold(color.cyan('\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550')));\n\n return lines.join('\\n');\n}\n\n/**\n * Build the \"Goal event\" banner \u2014 shown after deliverables in `/goal set`.\n * A compact, visually rich summary that doubles as the \"goal event\" trigger.\n */\nexport function formatGoalEvent(\n goalFile: GoalFile,\n boardId?: string | null,\n): string {\n const lines: string[] = [];\n\n lines.push('');\n lines.push(color.bold(color.green('\u250C\u2500\u2500\u2500 Goal Event \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510')));\n lines.push(color.bold(color.green('\u2502')) + ' \uD83C\uDFAF Mission locked and ready');\n if (boardId) {\n lines.push(\n color.bold(color.green('\u2502')) +\n ` \uD83D\uDCCB Kanban board created: ${color.cyan(boardId.slice(0, 12) + '\u2026')}`,\n );\n }\n const deliverables = goalFile.deliverables ?? [];\n lines.push(\n color.bold(color.green('\u2502')) +\n ` \uD83D\uDCE6 ${deliverables.length} deliverable${deliverables.length !== 1 ? 's' : ''} extracted`,\n );\n if (typeof goalFile.progress === 'number') {\n lines.push(\n color.bold(color.green('\u2502')) + ` \uD83D\uDCCA Progress: ${goalFile.progress}%`,\n );\n }\n lines.push(color.bold(color.green('\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518')));\n\n return lines.join('\\n');\n}\n\n/**\n * Build the autonomy choice prompt \u2014 shown after the goal event.\n * Presents the user with two modes and asks for a selection.\n */\nexport function formatGoalAutonomyChoice(): string {\n const lines: string[] = [];\n\n lines.push('');\n lines.push(color.bold(color.magenta('\u2550\u2550\u2550 Choose Autonomy Mode \u2550\u2550\u2550')));\n lines.push('');\n lines.push(\n ` ${color.bold('1')}. ${color.red('Autonomy Eternal')}` +\n ` ${color.dim('\u2014 Single-agent sense/decide/execute/reflect loop')}`,\n );\n lines.push(\n ` ${color.bold('2')}. ${color.magenta('Eternal Parallel')}` +\n ` ${color.dim('\u2014 Multi-agent fan-out (4-8 subagents per tick)')}`,\n );\n lines.push('');\n lines.push(` ${color.dim('Enter 1 or 2 (or press Enter to skip):')}`);\n\n return lines.join('\\n');\n}\n\n/**\n * Parse a user choice for autonomy mode.\n * Returns 'eternal' | 'eternal-parallel' | null (skip).\n */\nexport function parseAutonomyChoice(input: string): 'eternal' | 'eternal-parallel' | null {\n const trimmed = input.trim().toLowerCase();\n if (trimmed === '1' || trimmed === 'eternal' || trimmed === 'e') return 'eternal';\n if (trimmed === '2' || trimmed === 'parallel' || trimmed === 'p') return 'eternal-parallel';\n if (trimmed === '' || trimmed === 'skip' || trimmed === 's' || trimmed === '0') return null;\n return null;\n}\n\n// \u2500\u2500 Internal helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction goalTag(goalFile: GoalFile): string {\n // Use the goal's first 24 chars as a stable tag component\n const goal = (goalFile.refinedGoal || goalFile.goal).replace(/\\s+/g, '-').slice(0, 24).toLowerCase();\n return `${GOAL_BOARD_TAG_PREFIX}${goal}`;\n}\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Extended GoalFile with optional kanban board reference.\n */\nexport interface GoalFileWithKanban extends GoalFile {\n kanbanBoardId?: string | undefined;\n}\n", "import { createHash } from 'node:crypto';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { PromptEntry, PromptVariable } from '../types/prompt.js';\nimport { atomicWrite, ensureDir } from '../utils/atomic-write.js';\nimport { slugify } from '../utils/slug.js';\nimport { ulid } from '../utils/ulid.js';\nimport type { WstackPaths } from '../utils/wstack-paths.js';\n\nexport type { PromptEntry, PromptSource, PromptVariable } from '../types/prompt.js';\n\nexport interface PromptStore {\n list(): Promise<PromptEntry[]>;\n get(id: string): Promise<PromptEntry | null>;\n save(entry: PromptEntry): Promise<void>;\n delete(id: string): Promise<boolean>;\n find(query: string): Promise<PromptEntry[]>;\n}\n\n/** Current on-disk schema version. */\nconst SCHEMA_VERSION = 2;\n\ninterface RawPromptFile {\n version: number;\n entry: unknown;\n}\n\n/** sha256 of a prompt's content \u2014 used for builtin/synced integrity checks. */\nexport function promptChecksum(content: string): string {\n return createHash('sha256').update(content, 'utf8').digest('hex');\n}\n\n/**\n * Upgrade any persisted prompt record (v1 or v2) to a fully-populated v2\n * `PromptEntry`. Pure \u2014 does not touch disk. v1 records (only\n * `id/title/content/tags/createdAt/updatedAt`) get sensible defaults:\n * slug from title, empty description, `uncategorized` category, `user` source.\n */\nexport function migratePromptEntry(raw: unknown): PromptEntry | null {\n if (!raw || typeof raw !== 'object') return null;\n const r = raw as Record<string, unknown>;\n if (typeof r['id'] !== 'string' || typeof r['title'] !== 'string') return null;\n\n const title = r['title'];\n const content = typeof r['content'] === 'string' ? r['content'] : '';\n const now = typeof r['createdAt'] === 'string' ? r['createdAt'] : new Date(0).toISOString();\n const tags = Array.isArray(r['tags'])\n ? (r['tags'].filter((t) => typeof t === 'string') as string[])\n : [];\n\n return {\n id: r['id'],\n slug: typeof r['slug'] === 'string' && r['slug'].length > 0 ? r['slug'] : slugify(title),\n title,\n description: typeof r['description'] === 'string' ? r['description'] : '',\n content,\n category:\n typeof r['category'] === 'string' && r['category'].length > 0\n ? r['category']\n : 'uncategorized',\n tags,\n source: isPromptSource(r['source']) ? r['source'] : 'user',\n favorite: r['favorite'] === true,\n variables: normalizeVariables(r['variables']),\n author: typeof r['author'] === 'string' ? r['author'] : undefined,\n version: typeof r['version'] === 'string' ? r['version'] : undefined,\n license: typeof r['license'] === 'string' ? r['license'] : undefined,\n checksum: typeof r['checksum'] === 'string' ? r['checksum'] : undefined,\n forkedFrom: typeof r['forkedFrom'] === 'string' ? r['forkedFrom'] : undefined,\n createdAt: now,\n updatedAt: typeof r['updatedAt'] === 'string' ? r['updatedAt'] : now,\n };\n}\n\nfunction isPromptSource(v: unknown): v is PromptEntry['source'] {\n return v === 'builtin' || v === 'user' || v === 'project' || v === 'synced';\n}\n\nfunction normalizeVariables(v: unknown): PromptVariable[] | undefined {\n if (!Array.isArray(v)) return undefined;\n const out: PromptVariable[] = [];\n for (const item of v) {\n if (\n item &&\n typeof item === 'object' &&\n typeof (item as Record<string, unknown>)['name'] === 'string'\n ) {\n const o = item as Record<string, unknown>;\n const enumVals =\n Array.isArray(o['enum']) && o['enum'].every((x) => typeof x === 'string')\n ? (o['enum'] as string[])\n : undefined;\n out.push({\n name: o['name'] as string,\n description: typeof o['description'] === 'string' ? o['description'] : undefined,\n default: typeof o['default'] === 'string' ? o['default'] : undefined,\n required: o['required'] === true ? true : undefined,\n enum: enumVals && enumVals.length > 0 ? enumVals : undefined,\n multiline: o['multiline'] === true ? true : undefined,\n });\n }\n }\n return out.length > 0 ? out : undefined;\n}\n\n/**\n * DefaultPromptStore \u2014 file-per-prompt JSON in a single directory (the global\n * `~/.wrongstack/prompts` by default, or a layer dir the loader passes in).\n * Reads tolerate legacy v1 files via `migratePromptEntry`; writes always emit\n * the current v2 schema.\n */\nexport class DefaultPromptStore implements PromptStore {\n private readonly dir: string;\n\n constructor(pathsOrDir: WstackPaths | string) {\n this.dir = typeof pathsOrDir === 'string' ? pathsOrDir : pathsOrDir.globalPrompts;\n }\n\n async list(): Promise<PromptEntry[]> {\n await ensureDir(this.dir);\n const entries: PromptEntry[] = [];\n try {\n const files = await fs.readdir(this.dir);\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n try {\n const raw: RawPromptFile = JSON.parse(\n await fs.readFile(path.join(this.dir, file), 'utf8'),\n );\n const migrated = migratePromptEntry(raw.entry);\n if (migrated) entries.push(migrated);\n } catch {\n // skip corrupt files\n }\n }\n } catch {\n // dir doesn't exist yet\n }\n return entries.sort(\n (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),\n );\n }\n\n async get(id: string): Promise<PromptEntry | null> {\n const file = path.join(this.dir, `${id}.json`);\n try {\n const raw: RawPromptFile = JSON.parse(await fs.readFile(file, 'utf8'));\n return migratePromptEntry(raw.entry);\n } catch {\n return null;\n }\n }\n\n async save(entry: PromptEntry): Promise<void> {\n await ensureDir(this.dir);\n const file = path.join(this.dir, `${entry.id}.json`);\n const raw: RawPromptFile = { version: SCHEMA_VERSION, entry };\n await atomicWrite(file, JSON.stringify(raw, null, 2));\n }\n\n async delete(id: string): Promise<boolean> {\n const file = path.join(this.dir, `${id}.json`);\n try {\n await fs.unlink(file);\n return true;\n } catch {\n return false;\n }\n }\n\n async find(query: string): Promise<PromptEntry[]> {\n const all = await this.list();\n const lower = query.toLowerCase();\n return all.filter(\n (e) =>\n e.title.toLowerCase().includes(lower) ||\n e.description.toLowerCase().includes(lower) ||\n e.content.toLowerCase().includes(lower) ||\n e.category.toLowerCase().includes(lower) ||\n e.tags.some((t) => t.toLowerCase().includes(lower)),\n );\n }\n\n /** Create a new v2 entry and return it. Does NOT persist \u2014 call save() afterwards. */\n createNew(\n title: string,\n content: string,\n tags: string[] = [],\n extra: Partial<\n Omit<PromptEntry, 'id' | 'title' | 'content' | 'tags' | 'createdAt' | 'updatedAt'>\n > = {},\n ): PromptEntry {\n const now = new Date().toISOString();\n return {\n id: ulid(),\n slug: extra.slug && extra.slug.length > 0 ? extra.slug : slugify(title),\n title,\n description: extra.description ?? '',\n content,\n category: extra.category ?? 'uncategorized',\n tags,\n source: extra.source ?? 'user',\n favorite: extra.favorite ?? false,\n variables: extra.variables,\n author: extra.author,\n version: extra.version,\n license: extra.license,\n checksum: extra.checksum,\n forkedFrom: extra.forkedFrom,\n createdAt: now,\n updatedAt: now,\n };\n }\n}\n", "import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { atomicWrite, ensureDir } from '../utils/atomic-write.js';\n\nexport interface PromptUsage {\n count: number;\n lastUsedAt: string;\n}\n\ninterface RawUsageFile {\n version: 1;\n usage: Record<string, PromptUsage>;\n}\n\n/**\n * Tracks how often each prompt is inserted, keyed by slug, in a single JSON\n * file (`~/.wrongstack/prompt-usage.json`). Kept SEPARATE from the prompt\n * entries so usage can be recorded for read-only builtin prompts without\n * copy-on-writing them into the user layer. Surfaces \"recent / most-used\"\n * views and a gentle search-ranking boost.\n */\nexport class PromptUsageStore {\n constructor(private readonly file: string) {}\n\n async load(): Promise<Record<string, PromptUsage>> {\n try {\n const raw: RawUsageFile = JSON.parse(await fs.readFile(this.file, 'utf8'));\n if (raw && typeof raw === 'object' && raw.usage && typeof raw.usage === 'object') {\n return raw.usage;\n }\n } catch {\n // missing or corrupt \u2192 empty\n }\n return {};\n }\n\n async record(slug: string, at: string = new Date().toISOString()): Promise<PromptUsage> {\n const usage = await this.load();\n const prev = usage[slug];\n const next: PromptUsage = { count: (prev?.count ?? 0) + 1, lastUsedAt: at };\n usage[slug] = next;\n await ensureDir(path.dirname(this.file));\n await atomicWrite(this.file, JSON.stringify({ version: 1, usage } satisfies RawUsageFile, null, 2));\n return next;\n }\n\n async get(slug: string): Promise<PromptUsage | undefined> {\n return (await this.load())[slug];\n }\n\n /** Slugs ordered by most-recently-used (then by count), capped at `limit`. */\n async recent(limit = 15): Promise<{ slug: string; usage: PromptUsage }[]> {\n const usage = await this.load();\n return Object.entries(usage)\n .map(([slug, u]) => ({ slug, usage: u }))\n .sort(\n (a, b) =>\n new Date(b.usage.lastUsedAt).getTime() - new Date(a.usage.lastUsedAt).getTime() ||\n b.usage.count - a.usage.count,\n )\n .slice(0, limit);\n }\n\n /** Slugs ordered by usage count (then recency), capped at `limit`. */\n async top(limit = 15): Promise<{ slug: string; usage: PromptUsage }[]> {\n const usage = await this.load();\n return Object.entries(usage)\n .map(([slug, u]) => ({ slug, usage: u }))\n .sort(\n (a, b) =>\n b.usage.count - a.usage.count ||\n new Date(b.usage.lastUsedAt).getTime() - new Date(a.usage.lastUsedAt).getTime(),\n )\n .slice(0, limit);\n }\n}\n", "import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { atomicWrite, ensureDir } from '../utils/atomic-write.js';\nimport type { SecretScrubber } from '../types/secret-scrubber.js';\n\n/**\n * On-disk shape of the per-project TUI input history file. Newest entry\n * first. `version` is forward-compat \u2014 a future schema bump can migrate.\n */\ninterface InputHistoryFile {\n version: 1;\n updatedAt: string;\n entries: string[];\n}\n\nexport const INPUT_HISTORY_DEFAULT_MAX = 100;\n\n/**\n * Per-project TUI prompt input history.\n *\n * Backed by a single JSON file at `~/.wrongstack/projects/<slug>/input-\n * history.json`. Entries are newest-first, capped at `maxEntries`. Before\n * any entry is written to disk it is passed through a `SecretScrubber` and\n * dropped if it still contains a redacted marker \u2014 leaking a pasted API\n * key into a history file would be a real incident, so the scrubber is\n * mandatory, not optional.\n *\n * The store is intentionally small and synchronous-ish (load once at TUI\n * boot, save debounced on change). It mirrors PromptUsageStore's shape so\n * the two read as a family.\n */\nexport class InputHistoryStore {\n /**\n * @param file Absolute path to the per-project input-history.json.\n * @param scrubber SecretScrubber used to filter secrets before write.\n * @param maxEntries Cap on the number of entries persisted. Default 100.\n */\n constructor(\n private readonly file: string,\n private readonly scrubber: SecretScrubber,\n private readonly maxEntries: number = INPUT_HISTORY_DEFAULT_MAX,\n ) {}\n\n /**\n * Load the persisted entries. Missing or corrupt file \u2192 empty list\n * (never throws \u2014 history is best-effort).\n */\n async load(): Promise<string[]> {\n try {\n const raw: InputHistoryFile = JSON.parse(await fs.readFile(this.file, 'utf8'));\n if (\n raw &&\n typeof raw === 'object' &&\n Array.isArray(raw.entries) &&\n raw.entries.every((e) => typeof e === 'string')\n ) {\n return raw.entries.slice(0, this.maxEntries);\n }\n } catch {\n // missing or corrupt \u2192 empty\n }\n return [];\n }\n\n /**\n * Persist `entries` to disk after scrubbing each one. Entries that still\n * contain a redaction marker AFTER scrubbing are dropped \u2014 a half-scrubbed\n * prompt like \"export API_KEY=***REDACTED***\" is useless as history and\n * keeping it risks confusing the agent. Dedup is the caller's job (the\n * reducer already dedups); this method only scrubs, caps, and writes.\n */\n async save(entries: string[]): Promise<void> {\n const cleaned = this.scrubAndFilter(entries);\n await ensureDir(path.dirname(this.file));\n const payload: InputHistoryFile = {\n version: 1,\n updatedAt: new Date().toISOString(),\n entries: cleaned,\n };\n await atomicWrite(this.file, JSON.stringify(payload, null, 2));\n }\n\n /** Truncate the file to an empty entry list (used by /clear). */\n async clear(): Promise<void> {\n await ensureDir(path.dirname(this.file));\n const payload: InputHistoryFile = { version: 1, updatedAt: new Date().toISOString(), entries: [] };\n await atomicWrite(this.file, JSON.stringify(payload, null, 2));\n }\n\n /**\n * Run each entry through the scrubber and drop any that still contain a\n * redaction marker. Internal so the test suite can pin the policy.\n */\n private scrubAndFilter(entries: string[]): string[] {\n const out: string[] = [];\n for (const entry of entries) {\n const scrubbed = this.scrubber.scrub(entry);\n if (this.looksRedacted(scrubbed)) continue;\n out.push(scrubbed);\n }\n return out.slice(0, this.maxEntries);\n }\n\n /**\n * Heuristic: did the scrubber actually redact anything? DefaultSecretScrubber\n * replaces matches with \"***REDACTED***\", so a residual marker means a secret\n * was detected. Subclasses of SecretScrubber that use a different sentinel\n * can be accommodated by widening this check in the future; for now the\n * marker check covers the shipped scrubber.\n */\n private looksRedacted(text: string): boolean {\n return text.includes('***REDACTED***');\n }\n}\n", "import { expectDefined } from '../utils/expect-defined.js';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { createHash } from 'node:crypto';\nimport type { WstackPaths } from '../utils/wstack-paths.js';\nimport type { SyncCategory, SyncConfig } from '../types/config.js';\nimport { FsError, WrongStackError, ERROR_CODES } from '../types/errors.js';\nexport const ALL_SYNC_CATEGORIES: SyncCategory[] = ['settings', 'skills', 'prompts', 'memory', 'history'];\n\nexport interface SyncResult {\n ok: boolean;\n action: 'push' | 'pull';\n categories: SyncCategory[];\n committedAt?: string | undefined;\n message: string;\n}\n\ninterface SyncStateFile {\n version: 1;\n sha: string;\n lastSyncedAt: string;\n localRev: string;\n}\n\n/**\n * CloudSync \u2014 push/pull user-selected ~/.wrongstack categories to a\n * private GitHub repo. No git CLI needed; uses GitHub REST API via fetch.\n * The token is stored encrypted via SecretVault (field named `githubToken`\n * so the vault walker picks it up automatically).\n */\nexport class CloudSync {\n private readonly statePath: string;\n private state: SyncStateFile | null = null;\n\n constructor(\n private readonly paths: WstackPaths,\n private readonly getConfig: () => SyncConfig | null,\n private readonly setConfig: (c: SyncConfig) => Promise<void>,\n ) {\n this.statePath = path.join(paths.globalRoot, 'sync-state.json');\n }\n\n // \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async status(): Promise<string> {\n const cfg = this.getConfig();\n if (!cfg?.enabled) {\n return 'CloudSync: disabled. Run `/sync enable` to activate.';\n }\n const last = this.state?.lastSyncedAt;\n const since = last ? timeAgo(last) : 'never';\n return [\n `CloudSync: enabled`,\n ` repo: ${cfg.repo}`,\n ` categories: ${cfg.categories.join(', ')}`,\n ` last sync: ${since}`,\n ].join('\\n');\n }\n\n async enable(_repo: string, _categories: SyncCategory[]): Promise<string> {\n // Persisted by the slash command via configStore.update.\n return 'Enable via /sync enable.';\n }\n\n async disable(): Promise<string> {\n const cfg = this.getConfig();\n if (!cfg) return 'CloudSync is not configured.';\n const next = { ...cfg, enabled: false };\n await this.setConfig(next);\n return 'CloudSync disabled. Local data kept.';\n }\n\n async push(token: string): Promise<SyncResult> {\n const cfg = this.getConfig();\n if (!cfg?.enabled) return { ok: false, action: 'push', categories: [], message: 'Not enabled.' };\n\n const parts = cfg.repo.split('/');\n const owner = expectDefined(parts[0]);\n const repoName = expectDefined(parts[1]);\n const branch = 'main';\n const baseTreeSha = this.state?.sha;\n\n const { treeEntries, rev } = await this.buildLocalTree(cfg.categories);\n const newTreeSha = await this.createGitTree(token, owner, repoName, treeEntries, baseTreeSha);\n\n const commitSha = await this.createCommit(\n token, owner, repoName, newTreeSha,\n baseTreeSha,\n `Sync ${cfg.categories.join(', ')} \u2014 ${new Date().toISOString()}`,\n );\n\n try {\n await this.updateRef(token, owner, repoName, branch, commitSha);\n } catch (err) {\n // 422 = not a fast forward \u2014 remote branch moved. Fetch latest SHA and retry.\n if (err instanceof Error && err.message.includes('422')) {\n const remote = await this.getRef(token, owner, repoName, branch);\n const currentSha = remote.object.sha;\n const rebasedCommitSha = await this.createCommit(\n token, owner, repoName, newTreeSha,\n currentSha,\n `Sync ${cfg.categories.join(', ')} \u2014 ${new Date().toISOString()}`,\n );\n await this.updateRef(token, owner, repoName, branch, rebasedCommitSha);\n } else {\n throw err;\n }\n }\n\n const syncState: SyncStateFile = {\n version: 1,\n sha: commitSha,\n lastSyncedAt: new Date().toISOString(),\n localRev: rev,\n };\n await fs.writeFile(this.statePath, JSON.stringify(syncState, null, 2), 'utf8');\n this.state = syncState;\n\n return {\n ok: true,\n action: 'push',\n categories: cfg.categories,\n committedAt: commitSha,\n message: `Pushed ${cfg.categories.join(', ')} to ${cfg.repo}. Commit: ${commitSha.slice(0, 7)}`,\n };\n }\n\n async pull(token: string): Promise<SyncResult> {\n const cfg = this.getConfig();\n if (!cfg?.enabled) return { ok: false, action: 'pull', categories: [], message: 'Not enabled.' };\n\n const pullParts = cfg.repo.split('/');\n const owner = expectDefined(pullParts[0]);\n const repoName = expectDefined(pullParts[1]);\n\n const branchData = await this.getRef(token, owner, repoName, 'main');\n const currentSha = branchData.object.sha;\n\n const commitData = await this.getCommit(token, owner, repoName, currentSha);\n const treeSha = commitData.tree.sha;\n\n const treeEntries = await this.getTreeEntries(token, owner, repoName, treeSha);\n\n for (const entry of treeEntries) {\n if (entry.type !== 'blob') continue;\n\n // Paths look like \"data/{category}/...\" \u2014 extract the category\n const segments = entry.path.split('/');\n if (segments[0] !== 'data' || !segments[1]) continue;\n const cat = segments[1] as SyncCategory;\n if (!['settings', 'skills', 'prompts', 'memory', 'history'].includes(cat)) continue;\n\n const localPath = this.categoryToPath(cat);\n if (!localPath) continue;\n\n // Reconstruct relative path under the category dir. Remote trees are\n // untrusted input: a compromised sync repo could contain paths like\n // data/skills/../../config.json. Keep every write inside the selected\n // category root, and only allow subpaths for directory-backed categories.\n const rel = segments.slice(2).join('/');\n const destPath = resolvePulledCategoryPath(cat, localPath, rel, entry.path);\n\n const blobData = await this.getBlob(token, owner, repoName, entry.sha);\n await fs.mkdir(path.dirname(destPath), { recursive: true });\n await fs.writeFile(destPath, Buffer.from(blobData, 'base64'));\n }\n\n const localRev = await this.hashLocalCategories(cfg.categories);\n const syncState: SyncStateFile = {\n version: 1,\n sha: currentSha,\n lastSyncedAt: new Date().toISOString(),\n localRev,\n };\n await fs.writeFile(this.statePath, JSON.stringify(syncState, null, 2), 'utf8');\n this.state = syncState;\n\n return {\n ok: true,\n action: 'pull',\n categories: cfg.categories,\n committedAt: currentSha,\n message: `Pulled ${cfg.categories.join(', ')} from ${cfg.repo}. Commit: ${currentSha.slice(0, 7)}`,\n };\n }\n\n async hasLocalChanges(): Promise<boolean> {\n if (!this.state) return true;\n const cfg = this.getConfig();\n if (!cfg) return true;\n const current = await this.hashLocalCategories(cfg.categories);\n return current !== this.state.localRev;\n }\n\n async loadState(): Promise<void> {\n try {\n const raw = await fs.readFile(this.statePath, 'utf8');\n this.state = JSON.parse(raw) as SyncStateFile;\n } catch {\n this.state = null;\n }\n }\n\n // \u2500\u2500 GitHub API helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async githubFetch(\n token: string,\n owner: string,\n repo: string,\n method: 'GET' | 'POST' | 'PUT' | 'PATCH',\n pathSegment: string,\n body?: unknown | undefined,\n ): Promise<unknown> {\n const url = `https://api.github.com/repos/${owner}/${repo}${pathSegment}`;\n const init: RequestInit = {\n signal: AbortSignal.timeout(15_000),\n method,\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n 'Content-Type': 'application/json',\n },\n };\n if (body !== undefined) init.body = JSON.stringify(body);\n const res = await fetch(url, init);\n\n if (!res.ok) {\n const errText = await res.text();\n throw new WrongStackError({\n message: `GitHub API ${method} ${pathSegment} failed (${res.status}): ${errText}`,\n code: ERROR_CODES.UNKNOWN,\n subsystem: 'general',\n context: { method, pathSegment, status: res.status, repo: `${owner}/${repo}` },\n });\n }\n\n const text = await res.text();\n return text ? JSON.parse(text) : {};\n }\n\n private async getRef(token: string, owner: string, repo: string, ref: string) {\n return (await this.githubFetch(token, owner, repo, 'GET', `/git/refs/heads/${ref}`)) as {\n object: { sha: string };\n };\n }\n\n private async updateRef(token: string, owner: string, repo: string, ref: string, sha: string) {\n await this.githubFetch(token, owner, repo, 'PATCH', `/git/refs/heads/${ref}`, {\n sha,\n force: false,\n });\n }\n\n private async getCommit(token: string, owner: string, repo: string, sha: string) {\n return (await this.githubFetch(token, owner, repo, 'GET', `/git/commits/${sha}`)) as {\n tree: { sha: string };\n message: string;\n };\n }\n\n private async getTreeEntries(token: string, owner: string, repo: string, treeSha: string) {\n return (await this.githubFetch(token, owner, repo, 'GET', `/git/trees/${treeSha}?recursive=1`)) as Array<{\n path: string;\n sha: string;\n type: 'blob' | 'tree';\n }>;\n }\n\n private async createCommit(\n token: string, owner: string, repo: string,\n treeSha: string, parentSha?: string | undefined, message = 'sync',\n ) {\n const body: Record<string, unknown> = { message, tree: treeSha };\n if (parentSha) body.parents = [parentSha];\n const result = (await this.githubFetch(token, owner, repo, 'POST', '/git/commits', body)) as { sha: string };\n return result.sha;\n }\n\n private async createGitTree(\n token: string, owner: string, repo: string,\n entries: Array<{ path: string; content: string; mode: string }>,\n baseTreeSha?: string | undefined,\n ): Promise<string> {\n const tree = entries.map((e) => ({\n path: e.path,\n mode: e.mode,\n type: 'blob',\n content: e.content,\n }));\n const body: Record<string, unknown> = { tree };\n if (baseTreeSha) body.base_tree = baseTreeSha;\n const result = (await this.githubFetch(token, owner, repo, 'POST', '/git/trees', body)) as { sha: string };\n return result.sha;\n }\n\n private async getBlob(token: string, owner: string, repo: string, sha: string): Promise<string> {\n const result = (await this.githubFetch(token, owner, repo, 'GET', `/git/blobs/${sha}`)) as { content: string };\n return result.content;\n }\n\n // \u2500\u2500 Local file helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async buildLocalTree(categories: SyncCategory[]): Promise<{\n treeEntries: Array<{ path: string; content: string; mode: string }>;\n rev: string;\n }> {\n const entries: Array<{ path: string; content: string; mode: string }> = [];\n const hashes: string[] = [];\n\n for (const cat of categories) {\n const localPath = this.categoryToPath(cat);\n if (!localPath) continue;\n try {\n const stat = await fs.stat(localPath);\n if (stat.isDirectory()) {\n const files = await this.walkDir(localPath, localPath);\n for (const file of files) {\n const content = await fs.readFile(file, 'utf8');\n const rel = path.relative(localPath, file).replace(/\\\\/g, '/');\n entries.push({ path: `data/${cat}/${rel}`, content, mode: '100644' });\n hashes.push(content);\n }\n } else {\n const content = await fs.readFile(localPath, 'utf8');\n entries.push({ path: `data/${cat}`, content, mode: '100644' });\n hashes.push(content);\n }\n } catch {\n // skip missing files/dirs\n }\n }\n\n const rev = createHash('sha256').update(hashes.join('')).digest('hex').slice(0, 12);\n return { treeEntries: entries, rev };\n }\n\n private async hashLocalCategories(categories: SyncCategory[]): Promise<string> {\n const hashes: string[] = [];\n for (const cat of categories) {\n const localPath = this.categoryToPath(cat);\n if (!localPath) continue;\n try {\n const stat = await fs.stat(localPath);\n if (stat.isDirectory()) {\n const files = await this.walkDir(localPath, localPath);\n for (const file of files) {\n const content = await fs.readFile(file);\n hashes.push(content.toString('base64') + file);\n }\n } else {\n const content = await fs.readFile(localPath);\n hashes.push(content.toString('base64') + localPath);\n }\n } catch {\n // skip\n }\n }\n return createHash('sha256').update(hashes.join('')).digest('hex').slice(0, 12);\n }\n\n private categoryToPath(cat: SyncCategory): string | null {\n switch (cat) {\n case 'settings': return this.paths.globalConfig;\n case 'skills': return this.paths.globalSkills;\n case 'prompts': return this.paths.globalPrompts;\n case 'memory': return this.paths.globalMemory;\n case 'history': return this.paths.historyFile;\n /* v8 ignore next -- unreachable: SyncCategory is exhaustively matched above */\n default: return null;\n }\n }\n\n private async walkDir(dir: string, base: string): Promise<string[]> {\n const results: string[] = [];\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...(await this.walkDir(full, base)));\n } else {\n results.push(full);\n }\n }\n return results;\n }\n}\n\nfunction resolvePulledCategoryPath(\n cat: SyncCategory,\n localPath: string,\n rel: string,\n remotePath: string,\n): string {\n const directoryBacked = cat === 'skills' || cat === 'prompts';\n if (!directoryBacked) {\n if (rel) throw new FsError({\n message: `Refusing nested CloudSync path for file category: ${remotePath}`,\n code: ERROR_CODES.FS_DELETE_FAILED,\n path: remotePath,\n context: { reason: 'nested_file_category', category: cat },\n });\n return localPath;\n }\n\n if (!rel) return localPath;\n const normalizedRel = path.normalize(rel);\n const traversesUp = normalizedRel === '..' || normalizedRel.startsWith(`..${path.sep}`);\n if (path.isAbsolute(normalizedRel) || traversesUp) {\n throw new FsError({\n message: `Refusing CloudSync path traversal: ${remotePath}`,\n code: ERROR_CODES.FS_DELETE_FAILED,\n path: remotePath,\n context: { reason: 'path_traversal', normalizedRel },\n });\n }\n\n const dest = path.resolve(localPath, normalizedRel);\n const root = path.resolve(localPath);\n const relative = path.relative(root, dest);\n /* v8 ignore start -- unreachable: the normalizedRel '..' guard above already rejects traversal */\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n throw new FsError({\n message: `Refusing CloudSync path outside category root: ${remotePath}`,\n code: ERROR_CODES.FS_DELETE_FAILED,\n path: remotePath,\n context: { reason: 'outside_category_root', category: cat },\n });\n }\n /* v8 ignore stop */\n return dest;\n}\n\nfunction timeAgo(iso: string): string {\n const diff = Date.now() - new Date(iso).getTime();\n const mins = Math.floor(diff / 60_000);\n if (mins < 1) return 'just now';\n if (mins < 60) return `${mins}m ago`;\n const hrs = Math.floor(mins / 60);\n if (hrs < 24) return `${hrs}h ago`;\n const days = Math.floor(hrs / 24);\n return `${days}d ago`;\n}\n", "import type { SessionEvent, SessionWriter } from '../types/session.js';\n\nexport type AuditLevel = 'minimal' | 'standard' | 'full';\n\n/**\n * Configuration for sampling high-volume events inside the bridge.\n * This allows callers (CLI, TUI, WebUI, plugins) to tune how aggressively\n * noisy events like tool progress are persisted.\n */\nexport interface ToolProgressSamplingOptions {\n /**\n * How often to persist 'log' and 'partial_output' progress events.\n * - 1 = every message (no sampling)\n * - 8 = keep the first message + every 8th after that (default)\n */\n sampleRate?: number | undefined;\n}\n\nexport interface SessionSamplingOptions {\n /** Controls sampling behavior for `tool_progress` events (only relevant at auditLevel 'full'). */\n toolProgress?: ToolProgressSamplingOptions | undefined;\n}\n\nexport interface SessionEventBridgeOptions {\n /** Sampling rules for high-volume audit events. */\n sampling?: SessionSamplingOptions | undefined;\n}\n\n/**\n * Small, safe helper that wraps a SessionWriter and enforces the\n * configured auditLevel.\n *\n * All appends are best-effort. Failures are swallowed (with optional\n * diagnostics) so they never crash the agent loop.\n */\nexport interface SessionEventBridge {\n /** Append an event if allowed by the current audit level. */\n append(event: SessionEvent): Promise<void>;\n /** Batch-append events allowed by the current audit level. */\n appendBatch(events: SessionEvent[]): Promise<void>;\n\n /** Current audit level. Reflects the latest {@link setAuditLevel} value. */\n readonly level: AuditLevel;\n\n /**\n * Change the audit level on a live bridge. Subsequent appends are filtered\n * by the new level \u2014 used by the TUI `/settings` picker to apply an\n * `auditLevel` change to the running session without a restart.\n */\n setAuditLevel(level: AuditLevel): void;\n\n /** Returns true if an event of this type should be written at the current level. */\n allows(type: SessionEvent['type']): boolean;\n}\n\n/** Core events that are always written regardless of auditLevel. */\nconst CORE_RECONSTRUCT_EVENTS = new Set<SessionEvent['type']>([\n 'session_start',\n 'session_resumed',\n 'session_forked',\n 'user_input',\n 'llm_response',\n 'tool_result',\n 'context_snapshot',\n 'checkpoint',\n 'file_snapshot',\n 'file_observation',\n 'rewound',\n 'in_flight_start',\n 'in_flight_end',\n 'session_end',\n]);\n\n/**\n * Events that are considered \"standard\" audit detail.\n * These are lightweight and high-value for forensics.\n */\nconst STANDARD_AUDIT_EVENTS = new Set<SessionEvent['type']>([\n 'llm_request',\n 'tool_use',\n 'tool_call_start',\n 'tool_call_end',\n 'compaction',\n 'error',\n 'message_truncated',\n 'provider_retry',\n 'provider_error',\n 'skill_activated',\n 'skill_deactivated',\n]);\n\n/**\n * Events that are only allowed at 'full' audit level because they can be\n * very high volume (e.g. streaming tool output).\n */\nconst FULL_ONLY_EVENTS = new Set<SessionEvent['type']>([\n 'tool_progress',\n]);\n\n/**\n * \"full\" level allows everything (including potentially heavy events\n * that plugins or future code may emit).\n */\nfunction isAllowed(type: SessionEvent['type'], level: AuditLevel): boolean {\n if (CORE_RECONSTRUCT_EVENTS.has(type)) return true;\n if (level === 'minimal') return false;\n\n if (STANDARD_AUDIT_EVENTS.has(type)) return true;\n if (level === 'standard') return false;\n\n // 'full' level events (high volume)\n if (FULL_ONLY_EVENTS.has(type)) {\n return level === 'full';\n }\n\n // 'full' \u2014 allow everything else\n return true;\n}\n\n/**\n * Create a safe, audit-level-aware bridge around a SessionWriter.\n *\n * The bridge can also apply sampling for high-volume events (e.g. `tool_progress`)\n * when `auditLevel` is set to `'full'`.\n *\n * @example\n * const bridge = createSessionEventBridge(sessionWriter, 'full', {\n * sampling: {\n * toolProgress: { sampleRate: 5 } // more aggressive sampling\n * }\n * });\n */\nexport function createSessionEventBridge(\n writer:\n | SessionWriter\n | (() => SessionWriter | undefined | null)\n | undefined\n | null,\n level: AuditLevel = 'standard',\n options: SessionEventBridgeOptions = {},\n): SessionEventBridge {\n // Mutable so setAuditLevel() can re-tune a live bridge in place.\n let currentLevel: AuditLevel = level ?? 'standard';\n\n // Accept either a writer instance or a getter. A getter lets long-lived\n // hosts (CLI/TUI/WebUI) resolve the CURRENT writer on every append \u2014 when\n // the user resumes another session mid-run, audit events follow the swap\n // instead of being silently dropped into the old, closed writer.\n const resolveWriter: () => SessionWriter | undefined | null =\n typeof writer === 'function' ? writer : () => writer;\n\n // Internal sampling state for high-volume events (e.g. tool_progress).\n // Keyed by tool call id (or name as fallback) to keep sampling per-call.\n const progressCounters = new Map<string, number>();\n\n const toolProgressConfig = options.sampling?.toolProgress ?? {};\n const TOOL_PROGRESS_SAMPLE_RATE = toolProgressConfig.sampleRate ?? 8;\n\n /**\n * Decide whether a high-volume event should be sampled in.\n * Currently only implements sampling for 'tool_progress'.\n */\n function shouldSample(event: SessionEvent): boolean {\n if (event.type !== 'tool_progress') return true;\n\n const progEvent = event as Extract<SessionEvent, { type: 'tool_progress' }>;\n const innerType = progEvent.event?.type;\n\n // Always let through high-signal structured events\n if (innerType === 'warning' || innerType === 'metric' || innerType === 'file_changed') {\n return true;\n }\n\n // Sample noisy text streams (log / partial_output)\n if (innerType === 'log' || innerType === 'partial_output') {\n const key = progEvent.id || progEvent.name;\n const count = (progressCounters.get(key) || 0) + 1;\n progressCounters.set(key, count);\n\n // Always keep the first message + every Nth after that\n return count === 1 || (count % TOOL_PROGRESS_SAMPLE_RATE === 0);\n }\n\n return true;\n }\n\n return {\n get level() {\n return currentLevel;\n },\n\n setAuditLevel(next) {\n currentLevel = next ?? 'standard';\n },\n\n allows(type) {\n return isAllowed(type, currentLevel);\n },\n\n async append(event) {\n const target = resolveWriter();\n if (!target) return;\n if (!isAllowed(event.type, currentLevel)) return;\n\n // Apply sampling for high-volume events (only at 'full' level)\n if (!shouldSample(event)) return;\n\n try {\n await target.append(event);\n } catch (_err) {\n // Best-effort: never let session logging break the agent.\n // The existing FileSessionWriter already does throttled warnings,\n // but we keep this wrapper silent by default to avoid log spam.\n // Callers that care can listen to EventBus 'session.damaged' etc.\n }\n },\n\n async appendBatch(events) {\n const target = resolveWriter();\n if (!target || events.length === 0) return;\n const allowed = events.filter(\n (e) => isAllowed(e.type, currentLevel) && shouldSample(e),\n );\n if (allowed.length === 0) return;\n try {\n await target.appendBatch(allowed);\n } catch {\n // best-effort \u2014 same contract as append()\n }\n },\n };\n}\n\n/** Convenience re-export of the allowed core set for tests/docs. */\nexport { CORE_RECONSTRUCT_EVENTS, STANDARD_AUDIT_EVENTS };\n\n/**\n * Safely extract the auditLevel from a (possibly partial) Config object.\n * Falls back to 'standard' if not present or invalid.\n */\nexport function resolveAuditLevel(\n cfg?: { session?: { auditLevel?: AuditLevel | undefined } | undefined } | null,\n): AuditLevel {\n const raw = cfg?.session?.auditLevel;\n if (raw === 'minimal' || raw === 'standard' || raw === 'full') {\n return raw;\n }\n return 'standard';\n}\n\n/**\n * Fully resolves the session logging configuration with sensible defaults.\n * This is the recommended way for the CLI / TUI / WebUI to read session config.\n */\nexport function resolveSessionLoggingConfig(\n cfg?: {\n session?: {\n auditLevel?: AuditLevel | undefined;\n sampling?: {\n toolProgress?: { sampleRate?: number | undefined };\n };\n };\n } | null,\n): {\n auditLevel: AuditLevel;\n sampling: {\n toolProgress: { sampleRate: number };\n };\n} {\n const session = cfg?.session ?? {};\n\n const auditLevel = resolveAuditLevel(cfg);\n\n const toolProgressSampleRate =\n session.sampling?.toolProgress?.sampleRate ?? 8;\n\n return {\n auditLevel,\n sampling: {\n toolProgress: {\n sampleRate: Math.max(1, Math.floor(toolProgressSampleRate)),\n },\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["createHash", "fsp", "path", "randomBytes", "fs", "path", "open", "path", "slugify", "projectHash", "randomBytes", "stat", "resolve", "path", "createHash", "fsp", "path", "createHash", "relative", "stat", "resolve", "createHash", "fsp", "path", "isInside", "relative", "stat", "createHash", "createHash", "stat", "entry", "fsp", "path", "randomBytes", "fsp", "path", "randomBytes", "randomUUID", "fs", "path", "stat", "randomUUID", "fs", "readFileSync", "statSync", "path", "fs", "path", "fs", "path", "deepMerge", "stat", "fsp", "os", "path", "fs", "stat", "randomUUID", "fs", "randomUUID", "fs", "path", "createHash", "storageErrorString", "FILE_VERSION", "offset", "stat", "hasNonWhitespace", "fs", "path", "stat", "boundary", "createHash", "randomUUID", "fs", "randomUUID", "createHash", "stableStringify", "sortKeys", "fs", "path", "randomUUID", "projectSlug", "stat", "fs", "path", "pidAlive", "fsp", "path", "fsp", "fsp", "fsp", "randomUUID", "randomUUID", "fsp", "fsp", "hostname", "lock", "hostname", "fsp", "createHash", "fs", "path", "createHash", "fs", "path", "fs", "path", "fs", "path", "createHash", "stat", "createHash", "relative"]
|
|
7
7
|
}
|