@prestyj/cli 5.10.0 → 5.12.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/README.md +3 -4
- package/assets/skills/compliance-guard/SKILL.md +251 -0
- package/assets/skills/compliance-guard/references/artifacts.md +186 -0
- package/assets/skills/compliance-guard/references/eu-uk.md +151 -0
- package/assets/skills/compliance-guard/references/exposure-triage.md +161 -0
- package/assets/skills/compliance-guard/references/lawsuit-vectors.md +141 -0
- package/assets/skills/compliance-guard/references/provenance.md +42 -0
- package/assets/skills/compliance-guard/references/sector-gates.md +173 -0
- package/assets/skills/compliance-guard/references/security-baseline.md +130 -0
- package/assets/skills/compliance-guard/references/trigger-map.md +163 -0
- package/assets/skills/compliance-guard/references/us.md +164 -0
- package/dist/app-sidecar-auth-broadcast.test.d.ts +2 -0
- package/dist/app-sidecar-auth-broadcast.test.d.ts.map +1 -0
- package/dist/app-sidecar-auth-broadcast.test.js +348 -0
- package/dist/app-sidecar-auth-broadcast.test.js.map +1 -0
- package/dist/app-sidecar-sessions.d.ts +5 -2
- package/dist/app-sidecar-sessions.d.ts.map +1 -1
- package/dist/app-sidecar-sessions.js +34 -8
- package/dist/app-sidecar-sessions.js.map +1 -1
- package/dist/app-sidecar-sessions.test.js +96 -1
- package/dist/app-sidecar-sessions.test.js.map +1 -1
- package/dist/app-sidecar.js +616 -173
- package/dist/app-sidecar.js.map +1 -1
- package/dist/chat-agents/jiwa.test.js +1 -1
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +33 -36
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/command-routing.d.ts +1 -1
- package/dist/cli/command-routing.d.ts.map +1 -1
- package/dist/cli/command-routing.js +1 -0
- package/dist/cli/command-routing.js.map +1 -1
- package/dist/cli/command-routing.test.js +1 -0
- package/dist/cli/command-routing.test.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +179 -48
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +18 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +45 -76
- package/dist/config.js.map +1 -1
- package/dist/config.test.js +30 -13
- package/dist/config.test.js.map +1 -1
- package/dist/core/agent-notifications.d.ts +62 -0
- package/dist/core/agent-notifications.d.ts.map +1 -0
- package/dist/core/agent-notifications.js +92 -0
- package/dist/core/agent-notifications.js.map +1 -0
- package/dist/core/agent-notifications.test.d.ts +2 -0
- package/dist/core/agent-notifications.test.d.ts.map +1 -0
- package/dist/core/agent-notifications.test.js +97 -0
- package/dist/core/agent-notifications.test.js.map +1 -0
- package/dist/core/agent-session-checkpoint-flush.test.d.ts +2 -0
- package/dist/core/agent-session-checkpoint-flush.test.d.ts.map +1 -0
- package/dist/core/agent-session-checkpoint-flush.test.js +201 -0
- package/dist/core/agent-session-checkpoint-flush.test.js.map +1 -0
- package/dist/core/agent-session-compaction.test.js +323 -6
- package/dist/core/agent-session-compaction.test.js.map +1 -1
- package/dist/core/agent-session-crash-durability.test.d.ts +2 -0
- package/dist/core/agent-session-crash-durability.test.d.ts.map +1 -0
- package/dist/core/agent-session-crash-durability.test.js +102 -0
- package/dist/core/agent-session-crash-durability.test.js.map +1 -0
- package/dist/core/agent-session-marker-anchors.test.js +181 -2
- package/dist/core/agent-session-marker-anchors.test.js.map +1 -1
- package/dist/core/agent-session-model-switch.test.d.ts +2 -0
- package/dist/core/agent-session-model-switch.test.d.ts.map +1 -0
- package/dist/core/agent-session-model-switch.test.js +161 -0
- package/dist/core/agent-session-model-switch.test.js.map +1 -0
- package/dist/core/agent-session-notifications.test.d.ts +2 -0
- package/dist/core/agent-session-notifications.test.d.ts.map +1 -0
- package/dist/core/agent-session-notifications.test.js +114 -0
- package/dist/core/agent-session-notifications.test.js.map +1 -0
- package/dist/core/agent-session-process-gate.test.d.ts +2 -0
- package/dist/core/agent-session-process-gate.test.d.ts.map +1 -0
- package/dist/core/agent-session-process-gate.test.js +98 -0
- package/dist/core/agent-session-process-gate.test.js.map +1 -0
- package/dist/core/agent-session-queue.test.js +65 -0
- package/dist/core/agent-session-queue.test.js.map +1 -1
- package/dist/core/agent-session-review-coverage.test.js +80 -23
- package/dist/core/agent-session-review-coverage.test.js.map +1 -1
- package/dist/core/agent-session-slash-restore.test.d.ts +2 -0
- package/dist/core/agent-session-slash-restore.test.d.ts.map +1 -0
- package/dist/core/agent-session-slash-restore.test.js +121 -0
- package/dist/core/agent-session-slash-restore.test.js.map +1 -0
- package/dist/core/agent-session-tool-tiers.test.d.ts +2 -0
- package/dist/core/agent-session-tool-tiers.test.d.ts.map +1 -0
- package/dist/core/agent-session-tool-tiers.test.js +138 -0
- package/dist/core/agent-session-tool-tiers.test.js.map +1 -0
- package/dist/core/agent-session.d.ts +257 -13
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +1118 -241
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agents.d.ts +29 -1
- package/dist/core/agents.d.ts.map +1 -1
- package/dist/core/agents.js +47 -97
- package/dist/core/agents.js.map +1 -1
- package/dist/core/agents.test.js +87 -7
- package/dist/core/agents.test.js.map +1 -1
- package/dist/core/api-benchmark.d.ts +3 -3
- package/dist/core/api-benchmark.js +7 -7
- package/dist/core/auth-providers.d.ts +37 -1
- package/dist/core/auth-providers.d.ts.map +1 -1
- package/dist/core/auth-providers.js +83 -3
- package/dist/core/auth-providers.js.map +1 -1
- package/dist/core/auto-update.test.js +1 -1
- package/dist/core/autopilot-gate.d.ts +2 -2
- package/dist/core/autopilot-gate.js +1 -1
- package/dist/core/bash-spawn-benchmark.js +6 -6
- package/dist/core/bundled-agents.d.ts +3 -0
- package/dist/core/bundled-agents.d.ts.map +1 -0
- package/dist/core/bundled-agents.js +252 -0
- package/dist/core/bundled-agents.js.map +1 -0
- package/dist/core/cache-warm-benchmark.d.ts +2 -2
- package/dist/core/cache-warm-benchmark.js +5 -5
- package/dist/core/code-retrieval-chunkers.d.ts +27 -0
- package/dist/core/code-retrieval-chunkers.d.ts.map +1 -0
- package/dist/core/code-retrieval-chunkers.js +264 -0
- package/dist/core/code-retrieval-chunkers.js.map +1 -0
- package/dist/core/code-retrieval-chunkers.test.d.ts +2 -0
- package/dist/core/code-retrieval-chunkers.test.d.ts.map +1 -0
- package/dist/core/code-retrieval-chunkers.test.js +203 -0
- package/dist/core/code-retrieval-chunkers.test.js.map +1 -0
- package/dist/core/code-retrieval.d.ts +6 -0
- package/dist/core/code-retrieval.d.ts.map +1 -1
- package/dist/core/code-retrieval.js +38 -1
- package/dist/core/code-retrieval.js.map +1 -1
- package/dist/core/compaction/active-context.test.js +21 -0
- package/dist/core/compaction/active-context.test.js.map +1 -1
- package/dist/core/compaction/compactor.d.ts +108 -8
- package/dist/core/compaction/compactor.d.ts.map +1 -1
- package/dist/core/compaction/compactor.js +423 -119
- package/dist/core/compaction/compactor.js.map +1 -1
- package/dist/core/compaction/compactor.test.js +360 -8
- package/dist/core/compaction/compactor.test.js.map +1 -1
- package/dist/core/compaction/policy.d.ts +17 -0
- package/dist/core/compaction/policy.d.ts.map +1 -0
- package/dist/core/compaction/policy.js +22 -0
- package/dist/core/compaction/policy.js.map +1 -0
- package/dist/core/compaction/policy.test.d.ts +2 -0
- package/dist/core/compaction/policy.test.d.ts.map +1 -0
- package/dist/core/compaction/policy.test.js +35 -0
- package/dist/core/compaction/policy.test.js.map +1 -0
- package/dist/core/compaction/query-aware-selector.d.ts +33 -0
- package/dist/core/compaction/query-aware-selector.d.ts.map +1 -0
- package/dist/core/compaction/query-aware-selector.js +202 -0
- package/dist/core/compaction/query-aware-selector.js.map +1 -0
- package/dist/core/compaction/query-aware-selector.test.d.ts +2 -0
- package/dist/core/compaction/query-aware-selector.test.d.ts.map +1 -0
- package/dist/core/compaction/query-aware-selector.test.js +96 -0
- package/dist/core/compaction/query-aware-selector.test.js.map +1 -0
- package/dist/core/compaction/tool-result-pruner.d.ts +24 -11
- package/dist/core/compaction/tool-result-pruner.d.ts.map +1 -1
- package/dist/core/compaction/tool-result-pruner.js +98 -26
- package/dist/core/compaction/tool-result-pruner.js.map +1 -1
- package/dist/core/compaction/tool-result-pruner.test.js +178 -6
- package/dist/core/compaction/tool-result-pruner.test.js.map +1 -1
- package/dist/core/encode-cwd.test.js +1 -1
- package/dist/core/event-bus.d.ts +23 -0
- package/dist/core/event-bus.d.ts.map +1 -1
- package/dist/core/event-bus.js +10 -0
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +28 -8
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/plugin-bundles.d.ts +23 -0
- package/dist/core/extensions/plugin-bundles.d.ts.map +1 -0
- package/dist/core/extensions/plugin-bundles.js +228 -0
- package/dist/core/extensions/plugin-bundles.js.map +1 -0
- package/dist/core/extensions/plugin-bundles.test.d.ts +2 -0
- package/dist/core/extensions/plugin-bundles.test.d.ts.map +1 -0
- package/dist/core/extensions/plugin-bundles.test.js +110 -0
- package/dist/core/extensions/plugin-bundles.test.js.map +1 -0
- package/dist/core/fast-apply-benchmark.d.ts +2 -2
- package/dist/core/fast-apply-benchmark.js +6 -6
- package/dist/core/foreign-session-import.d.ts +120 -0
- package/dist/core/foreign-session-import.d.ts.map +1 -0
- package/dist/core/foreign-session-import.js +598 -0
- package/dist/core/foreign-session-import.js.map +1 -0
- package/dist/core/foreign-session-import.test.d.ts +2 -0
- package/dist/core/foreign-session-import.test.d.ts.map +1 -0
- package/dist/core/foreign-session-import.test.js +220 -0
- package/dist/core/foreign-session-import.test.js.map +1 -0
- package/dist/core/foreign-transcript-blocks.d.ts +51 -0
- package/dist/core/foreign-transcript-blocks.d.ts.map +1 -0
- package/dist/core/foreign-transcript-blocks.js +151 -0
- package/dist/core/foreign-transcript-blocks.js.map +1 -0
- package/dist/core/goal-lifecycle-smoke.test.js +2 -2
- package/dist/core/goal-store.js +1 -1
- package/dist/core/goal-store.test.js +2 -2
- package/dist/core/goal-verifier.test.js +2 -2
- package/dist/core/goal-worker-dev-server-lifecycle.test.js +1 -1
- package/dist/core/goal-worker.test.js +2 -2
- package/dist/core/hashline-edit-benchmark.d.ts +2 -2
- package/dist/core/hashline-edit-benchmark.js +7 -7
- package/dist/core/ideal-review.d.ts +20 -1
- package/dist/core/ideal-review.d.ts.map +1 -1
- package/dist/core/ideal-review.js +37 -2
- package/dist/core/ideal-review.js.map +1 -1
- package/dist/core/ideal-review.test.js +99 -3
- package/dist/core/ideal-review.test.js.map +1 -1
- package/dist/core/local-endpoint-store.test.js +1 -1
- package/dist/core/logger.d.ts +1 -1
- package/dist/core/logger.js +2 -2
- package/dist/core/logger.js.map +1 -1
- package/dist/core/loop-breaker.d.ts.map +1 -1
- package/dist/core/loop-breaker.js +1 -0
- package/dist/core/loop-breaker.js.map +1 -1
- package/dist/core/lsp/client-navigation.test.d.ts +2 -0
- package/dist/core/lsp/client-navigation.test.d.ts.map +1 -0
- package/dist/core/lsp/client-navigation.test.js +135 -0
- package/dist/core/lsp/client-navigation.test.js.map +1 -0
- package/dist/core/lsp/client.d.ts +84 -0
- package/dist/core/lsp/client.d.ts.map +1 -1
- package/dist/core/lsp/client.js +190 -3
- package/dist/core/lsp/client.js.map +1 -1
- package/dist/core/lsp/integration.test.js +2 -2
- package/dist/core/lsp/manager-navigation.test.d.ts +2 -0
- package/dist/core/lsp/manager-navigation.test.d.ts.map +1 -0
- package/dist/core/lsp/manager-navigation.test.js +121 -0
- package/dist/core/lsp/manager-navigation.test.js.map +1 -0
- package/dist/core/lsp/manager.d.ts +81 -9
- package/dist/core/lsp/manager.d.ts.map +1 -1
- package/dist/core/lsp/manager.js +183 -64
- package/dist/core/lsp/manager.js.map +1 -1
- package/dist/core/lsp/manager.test.js +18 -0
- package/dist/core/lsp/manager.test.js.map +1 -1
- package/dist/core/lsp/pool.d.ts +113 -0
- package/dist/core/lsp/pool.d.ts.map +1 -0
- package/dist/core/lsp/pool.js +255 -0
- package/dist/core/lsp/pool.js.map +1 -0
- package/dist/core/lsp/pool.test.d.ts +2 -0
- package/dist/core/lsp/pool.test.d.ts.map +1 -0
- package/dist/core/lsp/pool.test.js +286 -0
- package/dist/core/lsp/pool.test.js.map +1 -0
- package/dist/core/lsp/servers.d.ts.map +1 -1
- package/dist/core/lsp/servers.js +43 -3
- package/dist/core/lsp/servers.js.map +1 -1
- package/dist/core/lsp/servers.test.d.ts +2 -0
- package/dist/core/lsp/servers.test.d.ts.map +1 -0
- package/dist/core/lsp/servers.test.js +67 -0
- package/dist/core/lsp/servers.test.js.map +1 -0
- package/dist/core/lsp/windows.test.js +6 -18
- package/dist/core/lsp/windows.test.js.map +1 -1
- package/dist/core/mcp/catalog-cache.d.ts +63 -0
- package/dist/core/mcp/catalog-cache.d.ts.map +1 -0
- package/dist/core/mcp/catalog-cache.js +163 -0
- package/dist/core/mcp/catalog-cache.js.map +1 -0
- package/dist/core/mcp/catalog-cache.test.d.ts +2 -0
- package/dist/core/mcp/catalog-cache.test.d.ts.map +1 -0
- package/dist/core/mcp/catalog-cache.test.js +99 -0
- package/dist/core/mcp/catalog-cache.test.js.map +1 -0
- package/dist/core/mcp/client-session-recovery.test.d.ts +2 -0
- package/dist/core/mcp/client-session-recovery.test.d.ts.map +1 -0
- package/dist/core/mcp/client-session-recovery.test.js +231 -0
- package/dist/core/mcp/client-session-recovery.test.js.map +1 -0
- package/dist/core/mcp/client.d.ts +158 -0
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +478 -45
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/content.d.ts +19 -0
- package/dist/core/mcp/content.d.ts.map +1 -0
- package/dist/core/mcp/content.js +172 -0
- package/dist/core/mcp/content.js.map +1 -0
- package/dist/core/mcp/content.test.d.ts +2 -0
- package/dist/core/mcp/content.test.d.ts.map +1 -0
- package/dist/core/mcp/content.test.js +135 -0
- package/dist/core/mcp/content.test.js.map +1 -0
- package/dist/core/mcp/deferred-catalog.d.ts +1 -1
- package/dist/core/mcp/deferred-catalog.js +1 -1
- package/dist/core/mcp/elicitation-bridge.d.ts +41 -0
- package/dist/core/mcp/elicitation-bridge.d.ts.map +1 -0
- package/dist/core/mcp/elicitation-bridge.js +52 -0
- package/dist/core/mcp/elicitation-bridge.js.map +1 -0
- package/dist/core/mcp/elicitation.test.d.ts +2 -0
- package/dist/core/mcp/elicitation.test.d.ts.map +1 -0
- package/dist/core/mcp/elicitation.test.js +158 -0
- package/dist/core/mcp/elicitation.test.js.map +1 -0
- package/dist/core/mcp/index.d.ts +5 -1
- package/dist/core/mcp/index.d.ts.map +1 -1
- package/dist/core/mcp/index.js +2 -0
- package/dist/core/mcp/index.js.map +1 -1
- package/dist/core/mcp/legacy-negotiation.test.d.ts +2 -0
- package/dist/core/mcp/legacy-negotiation.test.d.ts.map +1 -0
- package/dist/core/mcp/legacy-negotiation.test.js +80 -0
- package/dist/core/mcp/legacy-negotiation.test.js.map +1 -0
- package/dist/core/mcp/oauth-provider.d.ts +1 -2
- package/dist/core/mcp/oauth-provider.d.ts.map +1 -1
- package/dist/core/mcp/oauth-provider.js +6 -2
- package/dist/core/mcp/oauth-provider.js.map +1 -1
- package/dist/core/mcp/oauth-store.d.ts +1 -1
- package/dist/core/mcp/oauth-store.d.ts.map +1 -1
- package/dist/core/mcp/oauth-store.js.map +1 -1
- package/dist/core/mcp/oauth-store.test.js +2 -2
- package/dist/core/mcp/resolve-stdio.test.js +5 -5
- package/dist/core/mcp/session-elicitation-wiring.test.d.ts +2 -0
- package/dist/core/mcp/session-elicitation-wiring.test.d.ts.map +1 -0
- package/dist/core/mcp/session-elicitation-wiring.test.js +138 -0
- package/dist/core/mcp/session-elicitation-wiring.test.js.map +1 -0
- package/dist/core/mcp/shared-pool.d.ts +158 -0
- package/dist/core/mcp/shared-pool.d.ts.map +1 -0
- package/dist/core/mcp/shared-pool.js +235 -0
- package/dist/core/mcp/shared-pool.js.map +1 -0
- package/dist/core/mcp/shared-pool.test.d.ts +2 -0
- package/dist/core/mcp/shared-pool.test.d.ts.map +1 -0
- package/dist/core/mcp/shared-pool.test.js +425 -0
- package/dist/core/mcp/shared-pool.test.js.map +1 -0
- package/dist/core/mcp/store.test.js +5 -5
- package/dist/core/mcp/types.d.ts +12 -0
- package/dist/core/mcp/types.d.ts.map +1 -1
- package/dist/core/mcp/when-connected.test.d.ts +2 -0
- package/dist/core/mcp/when-connected.test.d.ts.map +1 -0
- package/dist/core/mcp/when-connected.test.js +46 -0
- package/dist/core/mcp/when-connected.test.js.map +1 -0
- package/dist/core/nolan-context.d.ts.map +1 -1
- package/dist/core/nolan-context.js +8 -0
- package/dist/core/nolan-context.js.map +1 -1
- package/dist/core/nolan-context.test.js +40 -0
- package/dist/core/nolan-context.test.js.map +1 -1
- package/dist/core/nolan-model.test.js +1 -1
- package/dist/core/nolan-prompt.js +8 -5
- package/dist/core/nolan-prompt.js.map +1 -1
- package/dist/core/nolan-prompt.test.js +5 -0
- package/dist/core/nolan-prompt.test.js.map +1 -1
- package/dist/core/oauth/xai.d.ts +2 -0
- package/dist/core/oauth/xai.d.ts.map +1 -0
- package/dist/core/oauth/xai.js +4 -0
- package/dist/core/oauth/xai.js.map +1 -0
- package/dist/core/persistent-shell.d.ts +3 -2
- package/dist/core/persistent-shell.d.ts.map +1 -1
- package/dist/core/persistent-shell.js +11 -4
- package/dist/core/persistent-shell.js.map +1 -1
- package/dist/core/persistent-shell.test.js +6 -6
- package/dist/core/process-gate.d.ts +45 -0
- package/dist/core/process-gate.d.ts.map +1 -0
- package/dist/core/process-gate.js +79 -0
- package/dist/core/process-gate.js.map +1 -0
- package/dist/core/process-gate.test.d.ts +2 -0
- package/dist/core/process-gate.test.d.ts.map +1 -0
- package/dist/core/process-gate.test.js +87 -0
- package/dist/core/process-gate.test.js.map +1 -0
- package/dist/core/process-manager-dev-server-repro.test.js +51 -12
- package/dist/core/process-manager-dev-server-repro.test.js.map +1 -1
- package/dist/core/process-manager-notifications.test.d.ts +2 -0
- package/dist/core/process-manager-notifications.test.d.ts.map +1 -0
- package/dist/core/process-manager-notifications.test.js +227 -0
- package/dist/core/process-manager-notifications.test.js.map +1 -0
- package/dist/core/process-manager.d.ts +72 -1
- package/dist/core/process-manager.d.ts.map +1 -1
- package/dist/core/process-manager.js +247 -4
- package/dist/core/process-manager.js.map +1 -1
- package/dist/core/progress/git-xp.test.js +2 -2
- package/dist/core/progress/ranks.d.ts +14 -3
- package/dist/core/progress/ranks.d.ts.map +1 -1
- package/dist/core/progress/ranks.js +181 -12
- package/dist/core/progress/ranks.js.map +1 -1
- package/dist/core/progress/ranks.test.js +48 -7
- package/dist/core/progress/ranks.test.js.map +1 -1
- package/dist/core/progress/rebuild.test.js +1 -1
- package/dist/core/progress/store.test.js +1 -1
- package/dist/core/progress/types.d.ts +2 -1
- package/dist/core/progress/types.d.ts.map +1 -1
- package/dist/core/project-discovery.d.ts +31 -6
- package/dist/core/project-discovery.d.ts.map +1 -1
- package/dist/core/project-discovery.js +349 -79
- package/dist/core/project-discovery.js.map +1 -1
- package/dist/core/project-discovery.test.js +128 -4
- package/dist/core/project-discovery.test.js.map +1 -1
- package/dist/core/prompt-commands.js +3 -3
- package/dist/core/radio.d.ts +1 -1
- package/dist/core/radio.d.ts.map +1 -1
- package/dist/core/radio.js +90 -6
- package/dist/core/radio.js.map +1 -1
- package/dist/core/radio.test.js +15 -2
- package/dist/core/radio.test.js.map +1 -1
- package/dist/core/regrounding.d.ts.map +1 -1
- package/dist/core/regrounding.js +1 -0
- package/dist/core/regrounding.js.map +1 -1
- package/dist/core/run-claim.d.ts +37 -0
- package/dist/core/run-claim.d.ts.map +1 -0
- package/dist/core/run-claim.js +46 -0
- package/dist/core/run-claim.js.map +1 -0
- package/dist/core/run-claim.test.d.ts +2 -0
- package/dist/core/run-claim.test.d.ts.map +1 -0
- package/dist/core/run-claim.test.js +107 -0
- package/dist/core/run-claim.test.js.map +1 -0
- package/dist/core/run-journal.test.d.ts +2 -0
- package/dist/core/run-journal.test.d.ts.map +1 -0
- package/dist/core/run-journal.test.js +195 -0
- package/dist/core/run-journal.test.js.map +1 -0
- package/dist/core/run-lifecycle.d.ts +22 -3
- package/dist/core/run-lifecycle.d.ts.map +1 -1
- package/dist/core/run-lifecycle.js +12 -3
- package/dist/core/run-lifecycle.js.map +1 -1
- package/dist/core/sandbox-domains.d.ts +14 -0
- package/dist/core/sandbox-domains.d.ts.map +1 -0
- package/dist/core/sandbox-domains.js +76 -0
- package/dist/core/sandbox-domains.js.map +1 -0
- package/dist/core/sandbox-feedback.d.ts +22 -0
- package/dist/core/sandbox-feedback.d.ts.map +1 -0
- package/dist/core/sandbox-feedback.js +69 -0
- package/dist/core/sandbox-feedback.js.map +1 -0
- package/dist/core/sandbox-feedback.test.d.ts +2 -0
- package/dist/core/sandbox-feedback.test.d.ts.map +1 -0
- package/dist/core/sandbox-feedback.test.js +32 -0
- package/dist/core/sandbox-feedback.test.js.map +1 -0
- package/dist/core/sandbox.d.ts +91 -0
- package/dist/core/sandbox.d.ts.map +1 -0
- package/dist/core/sandbox.js +254 -0
- package/dist/core/sandbox.js.map +1 -0
- package/dist/core/sandbox.test.d.ts +2 -0
- package/dist/core/sandbox.test.d.ts.map +1 -0
- package/dist/core/sandbox.test.js +157 -0
- package/dist/core/sandbox.test.js.map +1 -0
- package/dist/core/semantic-search-benchmark.d.ts +2 -2
- package/dist/core/semantic-search-benchmark.js +5 -5
- package/dist/core/session-compaction.d.ts +6 -0
- package/dist/core/session-compaction.d.ts.map +1 -1
- package/dist/core/session-compaction.js +11 -1
- package/dist/core/session-compaction.js.map +1 -1
- package/dist/core/session-compaction.test.js +19 -2
- package/dist/core/session-compaction.test.js.map +1 -1
- package/dist/core/session-export.d.ts.map +1 -1
- package/dist/core/session-export.js +12 -4
- package/dist/core/session-export.js.map +1 -1
- package/dist/core/session-history.d.ts +95 -6
- package/dist/core/session-history.d.ts.map +1 -1
- package/dist/core/session-history.js +261 -17
- package/dist/core/session-history.js.map +1 -1
- package/dist/core/session-history.test.js +173 -2
- package/dist/core/session-history.test.js.map +1 -1
- package/dist/core/session-manager.d.ts +223 -6
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +579 -74
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-manager.test.js +154 -10
- package/dist/core/session-manager.test.js.map +1 -1
- package/dist/core/session-notification-visibility.test.d.ts +2 -0
- package/dist/core/session-notification-visibility.test.d.ts.map +1 -0
- package/dist/core/session-notification-visibility.test.js +113 -0
- package/dist/core/session-notification-visibility.test.js.map +1 -0
- package/dist/core/session-preview.d.ts +5 -4
- package/dist/core/session-preview.d.ts.map +1 -1
- package/dist/core/session-preview.js +12 -5
- package/dist/core/session-preview.js.map +1 -1
- package/dist/core/session-restore-display.test.js +25 -0
- package/dist/core/session-restore-display.test.js.map +1 -1
- package/dist/core/session-storage-fd-leak.test.d.ts +2 -0
- package/dist/core/session-storage-fd-leak.test.d.ts.map +1 -0
- package/dist/core/session-storage-fd-leak.test.js +116 -0
- package/dist/core/session-storage-fd-leak.test.js.map +1 -0
- package/dist/core/session-storage.d.ts +22 -2
- package/dist/core/session-storage.d.ts.map +1 -1
- package/dist/core/session-storage.js +72 -9
- package/dist/core/session-storage.js.map +1 -1
- package/dist/core/session-storage.test.js +8 -3
- package/dist/core/session-storage.test.js.map +1 -1
- package/dist/core/settings-manager.d.ts +10 -2
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +36 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/shell-path.js +1 -1
- package/dist/core/shell.d.ts +1 -1
- package/dist/core/shell.js +2 -2
- package/dist/core/shell.test.js +2 -2
- package/dist/core/shutdown.d.ts +71 -0
- package/dist/core/shutdown.d.ts.map +1 -0
- package/dist/core/shutdown.js +144 -0
- package/dist/core/shutdown.js.map +1 -0
- package/dist/core/shutdown.test.d.ts +2 -0
- package/dist/core/shutdown.test.d.ts.map +1 -0
- package/dist/core/shutdown.test.js +160 -0
- package/dist/core/shutdown.test.js.map +1 -0
- package/dist/core/sidecar-error-reporter.js +1 -1
- package/dist/core/sidecar-error-reporter.test.js +2 -2
- package/dist/core/skills-routing.test.js +96 -0
- package/dist/core/skills-routing.test.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +3 -0
- package/dist/core/skills.js.map +1 -1
- package/dist/core/steering.d.ts +12 -0
- package/dist/core/steering.d.ts.map +1 -1
- package/dist/core/steering.js +16 -0
- package/dist/core/steering.js.map +1 -1
- package/dist/core/subagent-manager.d.ts +22 -0
- package/dist/core/subagent-manager.d.ts.map +1 -1
- package/dist/core/subagent-manager.js +84 -13
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/core/subagent-manager.test.js +57 -15
- package/dist/core/subagent-manager.test.js.map +1 -1
- package/dist/core/subagent-store.test.js +3 -3
- package/dist/core/task-store.js +1 -1
- package/dist/core/verification-evidence.d.ts +17 -0
- package/dist/core/verification-evidence.d.ts.map +1 -0
- package/dist/core/verification-evidence.js +283 -0
- package/dist/core/verification-evidence.js.map +1 -0
- package/dist/core/verification-evidence.test.d.ts +2 -0
- package/dist/core/verification-evidence.test.d.ts.map +1 -0
- package/dist/core/verification-evidence.test.js +103 -0
- package/dist/core/verification-evidence.test.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interactive.d.ts.map +1 -1
- package/dist/interactive.js +17 -3
- package/dist/interactive.js.map +1 -1
- package/dist/modes/acp-mode.d.ts +122 -0
- package/dist/modes/acp-mode.d.ts.map +1 -0
- package/dist/modes/acp-mode.js +1366 -0
- package/dist/modes/acp-mode.js.map +1 -0
- package/dist/modes/acp-mode.test.d.ts +2 -0
- package/dist/modes/acp-mode.test.d.ts.map +1 -0
- package/dist/modes/acp-mode.test.js +1065 -0
- package/dist/modes/acp-mode.test.js.map +1 -0
- package/dist/modes/agent-home-mode.d.ts.map +1 -1
- package/dist/modes/agent-home-mode.js +15 -11
- package/dist/modes/agent-home-mode.js.map +1 -1
- package/dist/modes/index.d.ts +1 -0
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +1 -0
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/json-mode.d.ts +36 -0
- package/dist/modes/json-mode.d.ts.map +1 -1
- package/dist/modes/json-mode.js +39 -0
- package/dist/modes/json-mode.js.map +1 -1
- package/dist/modes/json-mode.test.d.ts +2 -0
- package/dist/modes/json-mode.test.d.ts.map +1 -0
- package/dist/modes/json-mode.test.js +84 -0
- package/dist/modes/json-mode.test.js.map +1 -0
- package/dist/modes/serve-mode.d.ts +1 -1
- package/dist/modes/serve-mode.d.ts.map +1 -1
- package/dist/modes/serve-mode.js +14 -9
- package/dist/modes/serve-mode.js.map +1 -1
- package/dist/modes/subagent-worker-mode.d.ts +8 -0
- package/dist/modes/subagent-worker-mode.d.ts.map +1 -1
- package/dist/modes/subagent-worker-mode.js +91 -11
- package/dist/modes/subagent-worker-mode.js.map +1 -1
- package/dist/modes/subagent-worker-mode.test.d.ts +2 -0
- package/dist/modes/subagent-worker-mode.test.d.ts.map +1 -0
- package/dist/modes/subagent-worker-mode.test.js +52 -0
- package/dist/modes/subagent-worker-mode.test.js.map +1 -0
- package/dist/session.d.ts +29 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +41 -0
- package/dist/session.js.map +1 -1
- package/dist/session.test.js +1 -1
- package/dist/system-prompt.d.ts +37 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +123 -11
- package/dist/system-prompt.js.map +1 -1
- package/dist/system-prompt.test.js +90 -11
- package/dist/system-prompt.test.js.map +1 -1
- package/dist/system-prompt.tiering.test.d.ts +2 -0
- package/dist/system-prompt.tiering.test.d.ts.map +1 -0
- package/dist/system-prompt.tiering.test.js +105 -0
- package/dist/system-prompt.tiering.test.js.map +1 -0
- package/dist/tools/bash.d.ts +2 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +55 -16
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/code-nav.d.ts +30 -0
- package/dist/tools/code-nav.d.ts.map +1 -0
- package/dist/tools/code-nav.js +320 -0
- package/dist/tools/code-nav.js.map +1 -0
- package/dist/tools/code-nav.test.d.ts +2 -0
- package/dist/tools/code-nav.test.d.ts.map +1 -0
- package/dist/tools/code-nav.test.js +136 -0
- package/dist/tools/code-nav.test.js.map +1 -0
- package/dist/tools/edit.test.js +1 -1
- package/dist/tools/generate-image.d.ts +1 -1
- package/dist/tools/goals.d.ts +4 -4
- package/dist/tools/goals.test.js +2 -2
- package/dist/tools/grep-parity.test.d.ts +2 -0
- package/dist/tools/grep-parity.test.d.ts.map +1 -0
- package/dist/tools/grep-parity.test.js +92 -0
- package/dist/tools/grep-parity.test.js.map +1 -0
- package/dist/tools/grep-recall.test.d.ts +2 -0
- package/dist/tools/grep-recall.test.d.ts.map +1 -0
- package/dist/tools/grep-recall.test.js +114 -0
- package/dist/tools/grep-recall.test.js.map +1 -0
- package/dist/tools/grep-spawn.test.d.ts +2 -0
- package/dist/tools/grep-spawn.test.d.ts.map +1 -0
- package/dist/tools/grep-spawn.test.js +66 -0
- package/dist/tools/grep-spawn.test.js.map +1 -0
- package/dist/tools/grep.d.ts +13 -3
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +342 -41
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/grep.test.js +17 -14
- package/dist/tools/grep.test.js.map +1 -1
- package/dist/tools/index.d.ts +20 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +28 -18
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/index.test.d.ts +2 -0
- package/dist/tools/index.test.d.ts.map +1 -0
- package/dist/tools/index.test.js +55 -0
- package/dist/tools/index.test.js.map +1 -0
- package/dist/tools/plan-mode.test.js +1 -1
- package/dist/tools/prompt-hints.d.ts +9 -0
- package/dist/tools/prompt-hints.d.ts.map +1 -1
- package/dist/tools/prompt-hints.js +61 -8
- package/dist/tools/prompt-hints.js.map +1 -1
- package/dist/tools/read-only-bash.d.ts +3 -0
- package/dist/tools/read-only-bash.d.ts.map +1 -1
- package/dist/tools/read-only-bash.js +7 -9
- package/dist/tools/read-only-bash.js.map +1 -1
- package/dist/tools/safe-env.js +1 -1
- package/dist/tools/search-code.d.ts.map +1 -1
- package/dist/tools/search-code.js +7 -6
- package/dist/tools/search-code.js.map +1 -1
- package/dist/tools/search-code.test.js +8 -2
- package/dist/tools/search-code.test.js.map +1 -1
- package/dist/tools/skill.js +2 -1
- package/dist/tools/skill.js.map +1 -1
- package/dist/tools/subagent-control.d.ts.map +1 -1
- package/dist/tools/subagent-control.js +25 -6
- package/dist/tools/subagent-control.js.map +1 -1
- package/dist/tools/subagent-control.test.js +34 -1
- package/dist/tools/subagent-control.test.js.map +1 -1
- package/dist/tools/subagent-shared.d.ts +26 -1
- package/dist/tools/subagent-shared.d.ts.map +1 -1
- package/dist/tools/subagent-shared.js +40 -7
- package/dist/tools/subagent-shared.js.map +1 -1
- package/dist/tools/subagent-shared.test.js +43 -9
- package/dist/tools/subagent-shared.test.js.map +1 -1
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +54 -16
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/subagent.test.js +68 -3
- package/dist/tools/subagent.test.js.map +1 -1
- package/dist/tools/task-output.d.ts.map +1 -1
- package/dist/tools/task-output.js +3 -1
- package/dist/tools/task-output.js.map +1 -1
- package/dist/tools/task-send.test.js +19 -10
- package/dist/tools/task-send.test.js.map +1 -1
- package/dist/tools/tool-search.d.ts +12 -1
- package/dist/tools/tool-search.d.ts.map +1 -1
- package/dist/tools/tool-search.js +24 -4
- package/dist/tools/tool-search.js.map +1 -1
- package/dist/tools/tool-search.test.d.ts +2 -0
- package/dist/tools/tool-search.test.d.ts.map +1 -0
- package/dist/tools/tool-search.test.js +69 -0
- package/dist/tools/tool-search.test.js.map +1 -0
- package/dist/tools/tool-tiers.d.ts +42 -0
- package/dist/tools/tool-tiers.d.ts.map +1 -0
- package/dist/tools/tool-tiers.js +78 -0
- package/dist/tools/tool-tiers.js.map +1 -0
- package/dist/tools/tool-tiers.test.d.ts +2 -0
- package/dist/tools/tool-tiers.test.d.ts.map +1 -0
- package/dist/tools/tool-tiers.test.js +48 -0
- package/dist/tools/tool-tiers.test.js.map +1 -0
- package/dist/tools/web-fetch.d.ts +1 -1
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +5 -2
- package/dist/tools/web-fetch.js.map +1 -1
- package/dist/tools/write.test.js +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +27 -1
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/Banner.d.ts.map +1 -1
- package/dist/ui/components/Banner.js +24 -16
- package/dist/ui/components/Banner.js.map +1 -1
- package/dist/ui/components/ToolExecution.js +42 -34
- package/dist/ui/components/ToolExecution.js.map +1 -1
- package/dist/ui/components/force-color.d.ts +2 -0
- package/dist/ui/components/force-color.d.ts.map +1 -0
- package/dist/ui/components/force-color.js +11 -0
- package/dist/ui/components/force-color.js.map +1 -0
- package/dist/ui/components/themed-output.test.d.ts +2 -0
- package/dist/ui/components/themed-output.test.d.ts.map +1 -0
- package/dist/ui/components/themed-output.test.js +111 -0
- package/dist/ui/components/themed-output.test.js.map +1 -0
- package/dist/ui/error-item.d.ts +1 -1
- package/dist/ui/error-item.js +3 -3
- package/dist/ui/error-item.js.map +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts +3 -0
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +45 -6
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/login.js +1 -1
- package/dist/ui/login.js.map +1 -1
- package/dist/ui/render.d.ts +1 -1
- package/dist/ui/render.js +6 -6
- package/dist/ui/theme/dark-ansi.json +6 -0
- package/dist/ui/theme/dark-daltonized.json +6 -0
- package/dist/ui/theme/dark.json +6 -0
- package/dist/ui/theme/light-ansi.json +6 -0
- package/dist/ui/theme/light-daltonized.json +6 -0
- package/dist/ui/theme/light.json +6 -0
- package/dist/ui/theme/theme.d.ts +6 -0
- package/dist/ui/theme/theme.d.ts.map +1 -1
- package/dist/ui/tui-terminal-recorder.test.js +1 -1
- package/dist/utils/git.test.js +3 -3
- package/dist/utils/github.test.js +3 -3
- package/dist/utils/github.test.js.map +1 -1
- package/dist/utils/image.d.ts +26 -3
- package/dist/utils/image.d.ts.map +1 -1
- package/dist/utils/image.js +83 -14
- package/dist/utils/image.js.map +1 -1
- package/dist/utils/image.test.d.ts +2 -0
- package/dist/utils/image.test.d.ts.map +1 -0
- package/dist/utils/image.test.js +139 -0
- package/dist/utils/image.test.js.map +1 -0
- package/package.json +7 -6
|
@@ -5,6 +5,7 @@ import crypto from "node:crypto";
|
|
|
5
5
|
import { environmentSecrets, redactValue, } from "@prestyj/ai";
|
|
6
6
|
import { log } from "./logger.js";
|
|
7
7
|
import { encodeCwd } from "./encode-cwd.js";
|
|
8
|
+
import { getUserSessionPrompt } from "./session-preview.js";
|
|
8
9
|
import { archiveColdSession, archiveSessionPath, cleanupOldSessionTemps, COLD_SESSION_AGE_DAYS, emptyStorageNormalizationMetrics, hydrateSessionEntry, isSessionPath, normalizeSessionEntryForStorage, openSessionReadStream, plainSessionPath, resolveSessionPath, sessionAssetDir, thawSessionArchive, } from "./session-storage.js";
|
|
9
10
|
export const DISPLAY_ITEM_CUSTOM_KIND = "display_item";
|
|
10
11
|
export const TURN_METRIC_CUSTOM_KIND = "turn_metric";
|
|
@@ -71,12 +72,29 @@ export const AUTOPILOT_MARKER_CUSTOM_KIND = "autopilot_marker";
|
|
|
71
72
|
* so the LLM never sees it, anchored by `afterMessageCount` so the host can
|
|
72
73
|
* interleave it back into the transcript on resume. */
|
|
73
74
|
export const APP_MARKER_CUSTOM_KIND = "app_transcript_marker";
|
|
75
|
+
/**
|
|
76
|
+
* Run journal — a matched pair of custom entries bracketing every provider run.
|
|
77
|
+
*
|
|
78
|
+
* Same not-on-the-DAG treatment as Nolan turns (`parentId: null`): the model never
|
|
79
|
+
* sees them, and they can't race the message branch's leaf pointer. A
|
|
80
|
+
* `run_started` with no matching `run_finished` is the on-disk signature of a
|
|
81
|
+
* run that died mid-flight — the host surfaces it on load instead of silently
|
|
82
|
+
* resuming work whose tools already half-mutated the repo.
|
|
83
|
+
*
|
|
84
|
+
* `generation` is the `RunLifecycle` generation, so the pairing inherits that
|
|
85
|
+
* class's generation-safety: a stale run cannot close a newer one's journal.
|
|
86
|
+
*/
|
|
87
|
+
export const RUN_STARTED_CUSTOM_KIND = "run_started";
|
|
88
|
+
export const RUN_FINISHED_CUSTOM_KIND = "run_finished";
|
|
74
89
|
function isCompletedItemLike(value) {
|
|
75
90
|
return (typeof value === "object" &&
|
|
76
91
|
value !== null &&
|
|
77
92
|
typeof value.kind === "string" &&
|
|
78
93
|
typeof value.id === "string");
|
|
79
94
|
}
|
|
95
|
+
const COMPACTION_COORDINATION_DIR = ".compaction-coordination";
|
|
96
|
+
const CORRUPT_LEASE_STALE_MS = 60_000;
|
|
97
|
+
const LEASE_POLL_MS = 50;
|
|
80
98
|
// ── Session Manager ────────────────────────────────────────
|
|
81
99
|
export class SessionManager {
|
|
82
100
|
static activePathsByRoot = new Map();
|
|
@@ -88,6 +106,115 @@ export class SessionManager {
|
|
|
88
106
|
constructor(sessionsDir) {
|
|
89
107
|
this.sessionsDir = path.resolve(sessionsDir);
|
|
90
108
|
}
|
|
109
|
+
coordinationKey(conversationId) {
|
|
110
|
+
return crypto.createHash("sha256").update(conversationId).digest("hex");
|
|
111
|
+
}
|
|
112
|
+
coordinationRoot() {
|
|
113
|
+
return path.join(this.sessionsDir, COMPACTION_COORDINATION_DIR);
|
|
114
|
+
}
|
|
115
|
+
async leaseOwner(lockPath) {
|
|
116
|
+
try {
|
|
117
|
+
const parsed = JSON.parse(await fs.readFile(path.join(lockPath, "owner.json"), "utf-8"));
|
|
118
|
+
return typeof parsed.token === "string" &&
|
|
119
|
+
typeof parsed.pid === "number" &&
|
|
120
|
+
typeof parsed.createdAt === "string"
|
|
121
|
+
? parsed
|
|
122
|
+
: null;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
processIsAlive(pid) {
|
|
129
|
+
try {
|
|
130
|
+
process.kill(pid, 0);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
return error.code !== "ESRCH";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async waitForLease(signal) {
|
|
138
|
+
if (signal?.aborted)
|
|
139
|
+
throw new DOMException("Aborted", "AbortError");
|
|
140
|
+
await new Promise((resolve, reject) => {
|
|
141
|
+
const finish = () => {
|
|
142
|
+
signal?.removeEventListener("abort", onAbort);
|
|
143
|
+
resolve();
|
|
144
|
+
};
|
|
145
|
+
const timer = setTimeout(finish, LEASE_POLL_MS);
|
|
146
|
+
const onAbort = () => {
|
|
147
|
+
clearTimeout(timer);
|
|
148
|
+
signal?.removeEventListener("abort", onAbort);
|
|
149
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
150
|
+
};
|
|
151
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
152
|
+
timer.unref?.();
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/** Serialize compaction work across processes for one logical conversation. */
|
|
156
|
+
async withCompactionLease(conversationId, signal, work) {
|
|
157
|
+
const root = this.coordinationRoot();
|
|
158
|
+
await fs.mkdir(root, { recursive: true });
|
|
159
|
+
const lockPath = path.join(root, `${this.coordinationKey(conversationId)}.lock`);
|
|
160
|
+
const token = crypto.randomUUID();
|
|
161
|
+
while (true) {
|
|
162
|
+
try {
|
|
163
|
+
await fs.mkdir(lockPath);
|
|
164
|
+
const owner = {
|
|
165
|
+
token,
|
|
166
|
+
pid: process.pid,
|
|
167
|
+
createdAt: new Date().toISOString(),
|
|
168
|
+
};
|
|
169
|
+
await fs.writeFile(path.join(lockPath, "owner.json"), JSON.stringify(owner), "utf-8");
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (error.code !== "EEXIST")
|
|
174
|
+
throw error;
|
|
175
|
+
const owner = await this.leaseOwner(lockPath);
|
|
176
|
+
const stat = await fs.stat(lockPath).catch(() => null);
|
|
177
|
+
const corruptAndOld = !owner && stat !== null && Date.now() - stat.mtimeMs > CORRUPT_LEASE_STALE_MS;
|
|
178
|
+
const deadOwner = owner !== null && !this.processIsAlive(owner.pid);
|
|
179
|
+
if (deadOwner || corruptAndOld) {
|
|
180
|
+
await fs.rm(lockPath, { recursive: true, force: true });
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
await this.waitForLease(signal);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
return await work();
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
const owner = await this.leaseOwner(lockPath);
|
|
191
|
+
if (owner?.token === token)
|
|
192
|
+
await fs.rm(lockPath, { recursive: true, force: true });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async readCompactionAttemptState(conversationId) {
|
|
196
|
+
const statePath = path.join(this.coordinationRoot(), `${this.coordinationKey(conversationId)}.state.json`);
|
|
197
|
+
try {
|
|
198
|
+
const state = JSON.parse(await fs.readFile(statePath, "utf-8"));
|
|
199
|
+
return typeof state.fingerprint === "string" &&
|
|
200
|
+
typeof state.policyKey === "string" &&
|
|
201
|
+
["success", "failed", "noop"].includes(state.outcome) &&
|
|
202
|
+
typeof state.updatedAt === "string"
|
|
203
|
+
? state
|
|
204
|
+
: null;
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async writeCompactionAttemptState(conversationId, state) {
|
|
211
|
+
const root = this.coordinationRoot();
|
|
212
|
+
await fs.mkdir(root, { recursive: true });
|
|
213
|
+
const statePath = path.join(root, `${this.coordinationKey(conversationId)}.state.json`);
|
|
214
|
+
const temporaryPath = `${statePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
215
|
+
await fs.writeFile(temporaryPath, JSON.stringify(state), "utf-8");
|
|
216
|
+
await fs.rename(temporaryPath, statePath);
|
|
217
|
+
}
|
|
91
218
|
/**
|
|
92
219
|
* Session persistence must never crash a live session. Disk-full (ENOSPC),
|
|
93
220
|
* permission, or quota errors during transcript writes are reported once
|
|
@@ -124,6 +251,12 @@ export class SessionManager {
|
|
|
124
251
|
version: 2,
|
|
125
252
|
id,
|
|
126
253
|
conversationId: options?.conversationId ?? id,
|
|
254
|
+
generation: options?.generation ?? 0,
|
|
255
|
+
...(options?.parentSessionId ? { parentSessionId: options.parentSessionId } : {}),
|
|
256
|
+
...(options?.sourceFingerprint ? { sourceFingerprint: options.sourceFingerprint } : {}),
|
|
257
|
+
...(typeof options?.retainedMessageCount === "number"
|
|
258
|
+
? { retainedMessageCount: options.retainedMessageCount }
|
|
259
|
+
: {}),
|
|
127
260
|
...(safePreview ? { preview: safePreview } : {}),
|
|
128
261
|
timestamp,
|
|
129
262
|
cwd,
|
|
@@ -163,48 +296,119 @@ export class SessionManager {
|
|
|
163
296
|
async load(sessionPath) {
|
|
164
297
|
// Resuming is a write operation, so transparently thaw a gzip archive and
|
|
165
298
|
// return the effective plain path every future append must use.
|
|
166
|
-
const
|
|
167
|
-
const
|
|
299
|
+
const canonicalPath = (await this.resolveCanonicalSession(sessionPath)) ?? sessionPath;
|
|
300
|
+
const effectivePath = await thawSessionArchive(canonicalPath);
|
|
301
|
+
return this.loadPhysicalCheckpoint(effectivePath);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Load the contiguous checkpoint ancestry ending at the canonical newest file.
|
|
305
|
+
*
|
|
306
|
+
* The returned order is oldest → newest. A missing, corrupt, cyclic, or
|
|
307
|
+
* cross-conversation parent stops traversal at the oldest readable checkpoint,
|
|
308
|
+
* allowing display callers to retain that checkpoint's compaction summary as a
|
|
309
|
+
* fallback. Parent archives are read in place rather than thawed because this
|
|
310
|
+
* API is for history reconstruction, not resuming writes.
|
|
311
|
+
*/
|
|
312
|
+
async loadCheckpointChain(sessionPath) {
|
|
313
|
+
const requestedSummary = await this.readSessionSummary(sessionPath);
|
|
314
|
+
// A resumed session can compact under a different cwd than its parent (for
|
|
315
|
+
// example, a remote ACP client reconnecting from another workspace). The
|
|
316
|
+
// physical ancestry is therefore machine-wide, not confined to the newest
|
|
317
|
+
// checkpoint's encoded-cwd directory.
|
|
318
|
+
const directories = await this.storageDirectories();
|
|
319
|
+
const candidates = (await Promise.all(directories.map((directory) => this.sessionCandidates(directory)))).flat();
|
|
320
|
+
const summaries = (await Promise.all(candidates.map((candidate) => this.readSessionSummary(candidate)))).filter((summary) => summary !== null);
|
|
321
|
+
const requestedIdentity = requestedSummary?.conversationId ??
|
|
322
|
+
summaries.find((summary) => summary.id === sessionPath)?.conversationId;
|
|
323
|
+
const newestSummary = requestedIdentity
|
|
324
|
+
? summaries
|
|
325
|
+
.filter((summary) => summary.conversationId === requestedIdentity)
|
|
326
|
+
.sort((left, right) => SessionManager.compareCheckpoints(right, left))[0]
|
|
327
|
+
: undefined;
|
|
328
|
+
const newestPath = newestSummary?.path ?? sessionPath;
|
|
329
|
+
// Resuming the newest checkpoint is a write operation, so thaw only that
|
|
330
|
+
// generation. Historical parents stay archived and read-only.
|
|
331
|
+
const newest = await this.loadPhysicalCheckpoint(await thawSessionArchive(newestPath));
|
|
332
|
+
const newestConversationId = newest.header.conversationId ?? newest.header.id;
|
|
333
|
+
const byPhysicalId = new Map(summaries.map((summary) => [summary.id, summary.path]));
|
|
334
|
+
const newestResolvedPath = path.resolve(newest.path);
|
|
335
|
+
const visited = new Set([newest.header.id]);
|
|
336
|
+
const chain = [newest];
|
|
337
|
+
let current = newest;
|
|
338
|
+
while (current.header.parentSessionId) {
|
|
339
|
+
const parentId = current.header.parentSessionId;
|
|
340
|
+
if (visited.has(parentId))
|
|
341
|
+
break;
|
|
342
|
+
const parentPath = byPhysicalId.get(parentId);
|
|
343
|
+
if (!parentPath || path.resolve(parentPath) === newestResolvedPath)
|
|
344
|
+
break;
|
|
345
|
+
try {
|
|
346
|
+
const parent = await this.loadPhysicalCheckpoint(parentPath, true);
|
|
347
|
+
if ((parent.header.conversationId ?? parent.header.id) !== newestConversationId)
|
|
348
|
+
break;
|
|
349
|
+
visited.add(parentId);
|
|
350
|
+
chain.unshift(parent);
|
|
351
|
+
current = parent;
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return chain;
|
|
358
|
+
}
|
|
359
|
+
async loadPhysicalCheckpoint(sessionPath, rejectMalformedLines = false) {
|
|
360
|
+
const effectivePath = await resolveSessionPath(sessionPath);
|
|
361
|
+
const { stream, close } = await openSessionReadStream(effectivePath);
|
|
168
362
|
let header = null;
|
|
169
363
|
const entries = [];
|
|
170
364
|
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
365
|
+
// rejectMalformedLines can throw mid-transcript; close() on the way out.
|
|
366
|
+
try {
|
|
367
|
+
for await (const line of rl) {
|
|
368
|
+
if (!line)
|
|
369
|
+
continue;
|
|
370
|
+
try {
|
|
371
|
+
const parsed = JSON.parse(line);
|
|
372
|
+
if (parsed.type === "session") {
|
|
373
|
+
if (parsed.version === 2) {
|
|
374
|
+
header = parsed;
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
const v1 = parsed;
|
|
378
|
+
header = {
|
|
379
|
+
type: "session",
|
|
380
|
+
version: 2,
|
|
381
|
+
id: v1.id,
|
|
382
|
+
conversationId: v1.id,
|
|
383
|
+
generation: 0,
|
|
384
|
+
timestamp: v1.timestamp,
|
|
385
|
+
cwd: v1.cwd,
|
|
386
|
+
provider: v1.provider,
|
|
387
|
+
model: v1.model,
|
|
388
|
+
leafId: null,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
continue;
|
|
179
392
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
version: 2,
|
|
185
|
-
id: v1.id,
|
|
186
|
-
conversationId: v1.id,
|
|
187
|
-
timestamp: v1.timestamp,
|
|
188
|
-
cwd: v1.cwd,
|
|
189
|
-
provider: v1.provider,
|
|
190
|
-
model: v1.model,
|
|
191
|
-
leafId: null,
|
|
192
|
-
};
|
|
393
|
+
const entry = parsed;
|
|
394
|
+
if (entry.type === "message" && !entry.id) {
|
|
395
|
+
entry.id = crypto.randomUUID();
|
|
396
|
+
entry.parentId = null;
|
|
193
397
|
}
|
|
194
|
-
|
|
398
|
+
entries.push(await hydrateSessionEntry(entry, effectivePath));
|
|
195
399
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
400
|
+
catch (error) {
|
|
401
|
+
if (rejectMalformedLines)
|
|
402
|
+
throw error;
|
|
403
|
+
// Skip malformed JSON lines — cold migration preserves their raw bytes
|
|
404
|
+
// so a future recovery tool still has a chance to repair them.
|
|
200
405
|
}
|
|
201
|
-
entries.push(await hydrateSessionEntry(entry, effectivePath));
|
|
202
|
-
}
|
|
203
|
-
catch {
|
|
204
|
-
// Skip malformed JSON lines — cold migration preserves their raw bytes
|
|
205
|
-
// so a future recovery tool still has a chance to repair them.
|
|
206
406
|
}
|
|
207
407
|
}
|
|
408
|
+
finally {
|
|
409
|
+
rl.close();
|
|
410
|
+
close();
|
|
411
|
+
}
|
|
208
412
|
if (!header) {
|
|
209
413
|
throw new Error(`Invalid session file: no header found in ${sessionPath}`);
|
|
210
414
|
}
|
|
@@ -213,30 +417,50 @@ export class SessionManager {
|
|
|
213
417
|
async readSessionInfo(candidatePath) {
|
|
214
418
|
try {
|
|
215
419
|
const resolvedPath = await resolveSessionPath(candidatePath);
|
|
216
|
-
const { stream } = await openSessionReadStream(resolvedPath);
|
|
420
|
+
const { stream, close } = await openSessionReadStream(resolvedPath);
|
|
217
421
|
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
218
422
|
let first = null;
|
|
219
423
|
let messageCount = 0;
|
|
220
424
|
let lastActivity = null;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
425
|
+
let preview;
|
|
426
|
+
try {
|
|
427
|
+
for await (const line of rl) {
|
|
428
|
+
if (!line)
|
|
429
|
+
continue;
|
|
430
|
+
try {
|
|
431
|
+
const parsed = JSON.parse(line);
|
|
432
|
+
if (!first) {
|
|
433
|
+
if (parsed.type !== "session")
|
|
434
|
+
break;
|
|
435
|
+
first = parsed;
|
|
436
|
+
// v2 headers usually carry the preview already; when they do,
|
|
437
|
+
// nothing below has to look for one.
|
|
438
|
+
preview = parsed.preview?.trim() || undefined;
|
|
439
|
+
}
|
|
440
|
+
else if (parsed.type === "message") {
|
|
441
|
+
messageCount += 1;
|
|
442
|
+
if (parsed.timestamp)
|
|
443
|
+
lastActivity = parsed.timestamp;
|
|
444
|
+
// Recover a title for the sessions whose header predates `preview`.
|
|
445
|
+
// Free here: this pass already reads every line. `getUserSessionPrompt`
|
|
446
|
+
// rejects compaction summaries and autopilot/status injections, so a
|
|
447
|
+
// session is titled by what its user actually asked.
|
|
448
|
+
if (!preview && parsed.message?.role === "user") {
|
|
449
|
+
preview =
|
|
450
|
+
getUserSessionPrompt(parsed.message.content, parsed.message.provenance)
|
|
451
|
+
?.replace(/\s+/g, " ")
|
|
452
|
+
.trim() || undefined;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
230
455
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (parsed.timestamp)
|
|
234
|
-
lastActivity = parsed.timestamp;
|
|
456
|
+
catch {
|
|
457
|
+
// Skip malformed lines while retaining readable entries around them.
|
|
235
458
|
}
|
|
236
459
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
460
|
+
}
|
|
461
|
+
finally {
|
|
462
|
+
rl.close();
|
|
463
|
+
close();
|
|
240
464
|
}
|
|
241
465
|
if (!first || first.type !== "session")
|
|
242
466
|
return null;
|
|
@@ -245,11 +469,13 @@ export class SessionManager {
|
|
|
245
469
|
conversationId: first.version === 2
|
|
246
470
|
? (first.conversationId ?? first.id)
|
|
247
471
|
: first.id,
|
|
472
|
+
generation: first.version === 2 ? (first.generation ?? 0) : 0,
|
|
248
473
|
path: resolvedPath,
|
|
249
474
|
timestamp: first.timestamp,
|
|
250
475
|
lastActivity: lastActivity ?? first.timestamp,
|
|
251
476
|
cwd: first.cwd,
|
|
252
477
|
messageCount,
|
|
478
|
+
...(preview ? { preview: preview.slice(0, 100) } : {}),
|
|
253
479
|
};
|
|
254
480
|
}
|
|
255
481
|
catch {
|
|
@@ -266,9 +492,95 @@ export class SessionManager {
|
|
|
266
492
|
}
|
|
267
493
|
return files.filter(isSessionPath).map((file) => path.join(directory, file));
|
|
268
494
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
495
|
+
/**
|
|
496
|
+
* Read just enough of a session file to summarize it.
|
|
497
|
+
*
|
|
498
|
+
* Stops at the first user prompt (or the first message, when the header
|
|
499
|
+
* already carries a preview) instead of parsing the whole transcript — this
|
|
500
|
+
* is what makes listing every session on the machine cheap. Files whose
|
|
501
|
+
* first user message is far down (long tool runs before the user speaks)
|
|
502
|
+
* are capped rather than allowed to stall the list.
|
|
503
|
+
*/
|
|
504
|
+
async readSessionSummary(candidatePath) {
|
|
505
|
+
try {
|
|
506
|
+
const resolvedPath = await resolveSessionPath(candidatePath);
|
|
507
|
+
const { stream, close } = await openSessionReadStream(resolvedPath);
|
|
508
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
509
|
+
let first = null;
|
|
510
|
+
let preview;
|
|
511
|
+
let hasMessages = false;
|
|
512
|
+
let lines = 0;
|
|
513
|
+
const MAX_SCAN_LINES = 500;
|
|
514
|
+
try {
|
|
515
|
+
for await (const line of rl) {
|
|
516
|
+
if (!line)
|
|
517
|
+
continue;
|
|
518
|
+
if (++lines > MAX_SCAN_LINES)
|
|
519
|
+
break;
|
|
520
|
+
try {
|
|
521
|
+
const parsed = JSON.parse(line);
|
|
522
|
+
if (!first) {
|
|
523
|
+
if (parsed.type !== "session")
|
|
524
|
+
break;
|
|
525
|
+
first = parsed;
|
|
526
|
+
preview = parsed.preview?.trim() || undefined;
|
|
527
|
+
}
|
|
528
|
+
else if (parsed.type === "message") {
|
|
529
|
+
hasMessages = true;
|
|
530
|
+
// Header preview + any message: done. Otherwise keep scanning for
|
|
531
|
+
// the first user-authored prompt to title by.
|
|
532
|
+
if (preview)
|
|
533
|
+
break;
|
|
534
|
+
if (parsed.message?.role === "user") {
|
|
535
|
+
preview =
|
|
536
|
+
getUserSessionPrompt(parsed.message.content, parsed.message.provenance)
|
|
537
|
+
?.replace(/\s+/g, " ")
|
|
538
|
+
.trim() || undefined;
|
|
539
|
+
if (preview)
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
catch {
|
|
545
|
+
// Skip malformed lines while retaining readable entries around them.
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
finally {
|
|
550
|
+
// close() destroys the gunzip AND its source. Destroying only the
|
|
551
|
+
// stream orphans the underlying fd — this listing runs over every
|
|
552
|
+
// session on the machine, so that leaked one fd per archive per call.
|
|
553
|
+
rl.close();
|
|
554
|
+
close();
|
|
555
|
+
}
|
|
556
|
+
if (!first || first.type !== "session")
|
|
557
|
+
return null;
|
|
558
|
+
const stat = await fs.stat(resolvedPath);
|
|
559
|
+
return {
|
|
560
|
+
id: first.id,
|
|
561
|
+
conversationId: first.version === 2
|
|
562
|
+
? (first.conversationId ?? first.id)
|
|
563
|
+
: first.id,
|
|
564
|
+
generation: first.version === 2 ? (first.generation ?? 0) : 0,
|
|
565
|
+
path: resolvedPath,
|
|
566
|
+
timestamp: first.timestamp,
|
|
567
|
+
lastActivity: stat.mtime.toISOString(),
|
|
568
|
+
cwd: first.cwd,
|
|
569
|
+
hasMessages,
|
|
570
|
+
...(preview ? { preview: preview.slice(0, 100) } : {}),
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
catch {
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Keep the newest file per conversation and sort newest first.
|
|
579
|
+
*
|
|
580
|
+
* A conversation can span several files (compaction forks a fresh one), so
|
|
581
|
+
* without this collapse a resumed thread appears once per checkpoint.
|
|
582
|
+
*/
|
|
583
|
+
static dedupeByConversation(summaries) {
|
|
272
584
|
const byConversation = new Map();
|
|
273
585
|
const seenResolvedPaths = new Set();
|
|
274
586
|
for (const summary of summaries) {
|
|
@@ -276,25 +588,109 @@ export class SessionManager {
|
|
|
276
588
|
continue;
|
|
277
589
|
seenResolvedPaths.add(summary.path);
|
|
278
590
|
const current = byConversation.get(summary.conversationId);
|
|
279
|
-
if (!current || summary
|
|
280
|
-
|
|
281
|
-
byConversation.set(summary.conversationId, info);
|
|
591
|
+
if (!current || SessionManager.compareCheckpoints(summary, current) > 0) {
|
|
592
|
+
byConversation.set(summary.conversationId, summary);
|
|
282
593
|
}
|
|
283
594
|
}
|
|
284
|
-
return [...byConversation.values()]
|
|
595
|
+
return [...byConversation.values()]
|
|
596
|
+
.sort((a, b) => b.lastActivity.localeCompare(a.lastActivity) ||
|
|
597
|
+
b.timestamp.localeCompare(a.timestamp) ||
|
|
598
|
+
b.path.localeCompare(a.path))
|
|
599
|
+
.map(({ conversationId: _conversationId, generation: _generation, ...info }) => info);
|
|
600
|
+
}
|
|
601
|
+
static compareCheckpoints(left, right) {
|
|
602
|
+
return ((left.generation ?? 0) - (right.generation ?? 0) ||
|
|
603
|
+
left.lastActivity.localeCompare(right.lastActivity) ||
|
|
604
|
+
left.timestamp.localeCompare(right.timestamp) ||
|
|
605
|
+
left.path.localeCompare(right.path));
|
|
606
|
+
}
|
|
607
|
+
async list(cwd) {
|
|
608
|
+
return this.summarize(await this.sessionCandidates(this.dirForCwd(cwd)));
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* One project's sessions, newest first, using the early-exit summary read.
|
|
612
|
+
*
|
|
613
|
+
* For callers that render a list rather than exact message counts — on a
|
|
614
|
+
* project with hundreds of sessions this is the difference between instant
|
|
615
|
+
* and a noticeable stall.
|
|
616
|
+
*/
|
|
617
|
+
async listSummaries(cwd) {
|
|
618
|
+
const candidates = await this.sessionCandidates(this.dirForCwd(cwd));
|
|
619
|
+
const summaries = await Promise.all(candidates.map((file) => this.readSessionSummary(file)));
|
|
620
|
+
return SessionManager.dedupeByConversation(summaries);
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Every session on this machine, across every project, newest first.
|
|
624
|
+
*
|
|
625
|
+
* A remote client is not browsing one checkout the way the TUI is — it is
|
|
626
|
+
* asking "what have I been working on?", and the answer spans projects. Each
|
|
627
|
+
* entry carries its own `cwd`, so the caller can group by project. Uses the
|
|
628
|
+
* early-exit summary read, because "every session on the machine" is exactly
|
|
629
|
+
* where a full parse of each file becomes a multi-second stall.
|
|
630
|
+
*/
|
|
631
|
+
async listAllSummaries() {
|
|
632
|
+
const directories = await this.storageDirectories();
|
|
633
|
+
const candidates = await Promise.all(directories.map((directory) => this.sessionCandidates(directory)));
|
|
634
|
+
const summaries = await Promise.all(candidates.flat().map((file) => this.readSessionSummary(file)));
|
|
635
|
+
return SessionManager.dedupeByConversation(summaries);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Summarize session files, keeping the newest file per conversation.
|
|
639
|
+
*
|
|
640
|
+
* A conversation can span several files (compaction forks a fresh one), so
|
|
641
|
+
* without this collapse a resumed thread appears once per checkpoint.
|
|
642
|
+
*/
|
|
643
|
+
async summarize(candidates) {
|
|
644
|
+
const summaries = await Promise.all(candidates.map((file) => this.readSessionInfo(file)));
|
|
645
|
+
return SessionManager.dedupeByConversation(summaries);
|
|
285
646
|
}
|
|
286
647
|
async getMostRecent(cwd) {
|
|
287
648
|
const sessions = await this.list(cwd);
|
|
288
649
|
return sessions.find((session) => session.messageCount > 0)?.path ?? null;
|
|
289
650
|
}
|
|
651
|
+
/** Resolve an id, conversation id, or stale physical path to the newest checkpoint. */
|
|
652
|
+
async resolveCanonicalSession(requested, cwd) {
|
|
653
|
+
const looksLikePath = path.isAbsolute(requested) || requested.includes(path.sep) || isSessionPath(requested);
|
|
654
|
+
let candidates;
|
|
655
|
+
let requestedSummary = null;
|
|
656
|
+
if (looksLikePath) {
|
|
657
|
+
requestedSummary = await this.readSessionSummary(requested);
|
|
658
|
+
if (!requestedSummary)
|
|
659
|
+
return null;
|
|
660
|
+
candidates = await this.sessionCandidates(path.dirname(requestedSummary.path));
|
|
661
|
+
}
|
|
662
|
+
else if (cwd) {
|
|
663
|
+
candidates = await this.sessionCandidates(this.dirForCwd(cwd));
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
const directories = await this.storageDirectories();
|
|
667
|
+
candidates = (await Promise.all(directories.map((dir) => this.sessionCandidates(dir)))).flat();
|
|
668
|
+
}
|
|
669
|
+
const summaries = (await Promise.all(candidates.map((candidate) => this.readSessionSummary(candidate)))).filter((summary) => summary !== null);
|
|
670
|
+
if (requestedSummary && !summaries.some((summary) => summary.path === requestedSummary?.path)) {
|
|
671
|
+
summaries.push(requestedSummary);
|
|
672
|
+
}
|
|
673
|
+
const identityMatches = requestedSummary
|
|
674
|
+
? [requestedSummary]
|
|
675
|
+
: summaries.filter((summary) => summary.id === requested || summary.conversationId === requested);
|
|
676
|
+
if (identityMatches.length === 0)
|
|
677
|
+
return null;
|
|
678
|
+
const requestedIdentity = identityMatches.sort((a, b) => SessionManager.compareCheckpoints(b, a))[0].conversationId;
|
|
679
|
+
const checkpoints = summaries.filter((summary) => summary.conversationId === requestedIdentity);
|
|
680
|
+
return (checkpoints.sort((a, b) => SessionManager.compareCheckpoints(b, a))[0]?.path ??
|
|
681
|
+
identityMatches[0].path);
|
|
682
|
+
}
|
|
290
683
|
async findById(cwd, sessionId) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
684
|
+
return this.resolveCanonicalSession(sessionId, cwd);
|
|
685
|
+
}
|
|
686
|
+
/** Locate and canonicalize a session identity across every project directory. */
|
|
687
|
+
async findAnyById(sessionId, cwd) {
|
|
688
|
+
if (cwd) {
|
|
689
|
+
const local = await this.resolveCanonicalSession(sessionId, cwd);
|
|
690
|
+
if (local)
|
|
691
|
+
return local;
|
|
296
692
|
}
|
|
297
|
-
return
|
|
693
|
+
return this.resolveCanonicalSession(sessionId);
|
|
298
694
|
}
|
|
299
695
|
async storageDirectories() {
|
|
300
696
|
let entries;
|
|
@@ -305,7 +701,7 @@ export class SessionManager {
|
|
|
305
701
|
return [];
|
|
306
702
|
}
|
|
307
703
|
return entries
|
|
308
|
-
.filter((entry) => entry.isDirectory())
|
|
704
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))
|
|
309
705
|
.map((entry) => path.join(this.sessionsDir, entry.name));
|
|
310
706
|
}
|
|
311
707
|
async logicalSessionBases(directory) {
|
|
@@ -471,6 +867,84 @@ export class SessionManager {
|
|
|
471
867
|
};
|
|
472
868
|
await this.appendEntry(sessionPath, entry);
|
|
473
869
|
}
|
|
870
|
+
/** Open the run journal for one `RunLifecycle` generation. */
|
|
871
|
+
async appendRunStarted(sessionPath, payload) {
|
|
872
|
+
await this.appendEntry(sessionPath, {
|
|
873
|
+
type: "custom",
|
|
874
|
+
kind: RUN_STARTED_CUSTOM_KIND,
|
|
875
|
+
id: crypto.randomUUID(),
|
|
876
|
+
parentId: null,
|
|
877
|
+
timestamp: new Date().toISOString(),
|
|
878
|
+
data: payload,
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
/** Close the run journal for one generation. Its absence marks a crashed run. */
|
|
882
|
+
async appendRunFinished(sessionPath, payload) {
|
|
883
|
+
await this.appendEntry(sessionPath, {
|
|
884
|
+
type: "custom",
|
|
885
|
+
kind: RUN_FINISHED_CUSTOM_KIND,
|
|
886
|
+
id: crypto.randomUUID(),
|
|
887
|
+
parentId: null,
|
|
888
|
+
timestamp: new Date().toISOString(),
|
|
889
|
+
data: payload,
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Reconstruct the run journal in file order, pairing each `run_started` with
|
|
894
|
+
* the `run_finished` carrying the same generation.
|
|
895
|
+
*
|
|
896
|
+
* Generations are NOT unique across a session file. `RunLifecycle` counts
|
|
897
|
+
* from zero per instance, and a resumed session builds a fresh one — so the
|
|
898
|
+
* first run after every app restart is generation 1 again. Pairing therefore
|
|
899
|
+
* tracks only the runs still OPEN: a `run_finished` closes its generation and
|
|
900
|
+
* releases the number, and a later `run_started` reusing it opens a new run.
|
|
901
|
+
*
|
|
902
|
+
* Without that release, a crash in a resumed session was invisible: the
|
|
903
|
+
* reused `run_started` looked like a duplicate and was dropped, which is
|
|
904
|
+
* exactly the case this journal exists to catch.
|
|
905
|
+
*
|
|
906
|
+
* A repeat `run_started` for a generation that is still open IS ignored, so a
|
|
907
|
+
* truncated or replayed log reports one unfinished run — never a phantom pile.
|
|
908
|
+
*/
|
|
909
|
+
getRunJournal(entries) {
|
|
910
|
+
const runs = [];
|
|
911
|
+
const openByGeneration = new Map();
|
|
912
|
+
for (const entry of entries) {
|
|
913
|
+
if (entry.type !== "custom")
|
|
914
|
+
continue;
|
|
915
|
+
if (entry.kind === RUN_STARTED_CUSTOM_KIND) {
|
|
916
|
+
const p = entry.data;
|
|
917
|
+
if (p?.version !== 1 || typeof p.generation !== "number")
|
|
918
|
+
continue;
|
|
919
|
+
if (openByGeneration.has(p.generation))
|
|
920
|
+
continue;
|
|
921
|
+
const run = {
|
|
922
|
+
generation: p.generation,
|
|
923
|
+
startedAt: typeof p.startedAt === "string" ? p.startedAt : entry.timestamp,
|
|
924
|
+
afterMessageCount: typeof p.afterMessageCount === "number" ? p.afterMessageCount : 0,
|
|
925
|
+
};
|
|
926
|
+
openByGeneration.set(p.generation, run);
|
|
927
|
+
runs.push(run);
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
if (entry.kind === RUN_FINISHED_CUSTOM_KIND) {
|
|
931
|
+
const p = entry.data;
|
|
932
|
+
if (p?.version !== 1 || typeof p.generation !== "number")
|
|
933
|
+
continue;
|
|
934
|
+
const run = openByGeneration.get(p.generation);
|
|
935
|
+
if (!run || !p.outcome)
|
|
936
|
+
continue;
|
|
937
|
+
run.outcome = p.outcome;
|
|
938
|
+
// Closed — the number is free for the next app run to reuse.
|
|
939
|
+
openByGeneration.delete(p.generation);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
return runs;
|
|
943
|
+
}
|
|
944
|
+
/** Runs that opened the journal but never closed it — i.e. crashed mid-flight. */
|
|
945
|
+
getUnfinishedRuns(entries) {
|
|
946
|
+
return this.getRunJournal(entries).filter((run) => run.outcome === undefined);
|
|
947
|
+
}
|
|
474
948
|
async updateLeaf(sessionPath, leafId) {
|
|
475
949
|
try {
|
|
476
950
|
const writablePath = await thawSessionArchive(sessionPath);
|
|
@@ -540,11 +1014,36 @@ export class SessionManager {
|
|
|
540
1014
|
return isCompletedItemLike(item) ? [item] : [];
|
|
541
1015
|
});
|
|
542
1016
|
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Walk entries in file order, tracking how many branch (non-system) messages
|
|
1019
|
+
* have been written so far, and hand each custom entry that count. This is
|
|
1020
|
+
* the independent position estimate behind {@link RecordedPosition}.
|
|
1021
|
+
*/
|
|
1022
|
+
mapCustomEntriesInFileOrder(entries, leafId, project) {
|
|
1023
|
+
// Only branch messages count — an off-branch fork's entries are not part of
|
|
1024
|
+
// the restored transcript the anchors are measured against.
|
|
1025
|
+
const onBranch = leafId
|
|
1026
|
+
? new Set(this.getBranch(entries, leafId).map((e) => e.id))
|
|
1027
|
+
: new Set(entries.map((e) => e.id));
|
|
1028
|
+
const out = [];
|
|
1029
|
+
let messagesSoFar = 0;
|
|
1030
|
+
for (const entry of entries) {
|
|
1031
|
+
if (entry.type === "message") {
|
|
1032
|
+
if (onBranch.has(entry.id) && entry.message.role !== "system")
|
|
1033
|
+
messagesSoFar++;
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1036
|
+
if (entry.type !== "custom")
|
|
1037
|
+
continue;
|
|
1038
|
+
out.push(...project(entry, messagesSoFar));
|
|
1039
|
+
}
|
|
1040
|
+
return out;
|
|
1041
|
+
}
|
|
543
1042
|
/** Read all persisted Nolan turns in file order. Returns them regardless of
|
|
544
1043
|
* branch (Nolan turns are not chained into the DAG), validated + normalized. */
|
|
545
|
-
getNolanTurns(entries) {
|
|
546
|
-
return
|
|
547
|
-
if (entry.
|
|
1044
|
+
getNolanTurns(entries, leafId) {
|
|
1045
|
+
return this.mapCustomEntriesInFileOrder(entries, leafId, (entry, recordedAfterMessageCount) => {
|
|
1046
|
+
if (entry.kind !== NOLAN_TURN_CUSTOM_KIND)
|
|
548
1047
|
return [];
|
|
549
1048
|
const p = entry.data;
|
|
550
1049
|
if (p?.version === 1 && typeof p.question === "string" && typeof p.reply === "string") {
|
|
@@ -554,6 +1053,7 @@ export class SessionManager {
|
|
|
554
1053
|
question: p.question,
|
|
555
1054
|
reply: p.reply,
|
|
556
1055
|
afterMessageCount: typeof p.afterMessageCount === "number" ? p.afterMessageCount : 0,
|
|
1056
|
+
recordedAfterMessageCount,
|
|
557
1057
|
},
|
|
558
1058
|
];
|
|
559
1059
|
}
|
|
@@ -562,9 +1062,9 @@ export class SessionManager {
|
|
|
562
1062
|
}
|
|
563
1063
|
/** Read all persisted app transcript markers in file order, validated +
|
|
564
1064
|
* normalized (same not-on-the-DAG treatment as Nolan turns). */
|
|
565
|
-
getAppMarkers(entries) {
|
|
566
|
-
return
|
|
567
|
-
if (entry.
|
|
1065
|
+
getAppMarkers(entries, leafId) {
|
|
1066
|
+
return this.mapCustomEntriesInFileOrder(entries, leafId, (entry, recordedAfterMessageCount) => {
|
|
1067
|
+
if (entry.kind !== APP_MARKER_CUSTOM_KIND)
|
|
568
1068
|
return [];
|
|
569
1069
|
const p = entry.data;
|
|
570
1070
|
const kind = p?.kind;
|
|
@@ -574,13 +1074,17 @@ export class SessionManager {
|
|
|
574
1074
|
kind === "error" ||
|
|
575
1075
|
kind === "user_hint" ||
|
|
576
1076
|
kind === "compaction" ||
|
|
577
|
-
kind === "agent_handoff"
|
|
1077
|
+
kind === "agent_handoff" ||
|
|
1078
|
+
kind === "model_switch" ||
|
|
1079
|
+
kind === "import" ||
|
|
1080
|
+
kind === "interrupted_run")) {
|
|
578
1081
|
return [
|
|
579
1082
|
{
|
|
580
1083
|
version: 1,
|
|
581
1084
|
kind,
|
|
582
1085
|
afterMessageCount: typeof p.afterMessageCount === "number" ? p.afterMessageCount : 0,
|
|
583
1086
|
data: typeof p.data === "object" && p.data !== null ? p.data : {},
|
|
1087
|
+
recordedAfterMessageCount,
|
|
584
1088
|
},
|
|
585
1089
|
];
|
|
586
1090
|
}
|
|
@@ -598,9 +1102,9 @@ export class SessionManager {
|
|
|
598
1102
|
}
|
|
599
1103
|
/** Read all persisted autopilot markers in file order, validated + normalized
|
|
600
1104
|
* (same not-on-the-DAG treatment as Nolan turns). */
|
|
601
|
-
getAutopilotMarkers(entries) {
|
|
602
|
-
return
|
|
603
|
-
if (entry.
|
|
1105
|
+
getAutopilotMarkers(entries, leafId) {
|
|
1106
|
+
return this.mapCustomEntriesInFileOrder(entries, leafId, (entry, recordedAfterMessageCount) => {
|
|
1107
|
+
if (entry.kind !== AUTOPILOT_MARKER_CUSTOM_KIND)
|
|
604
1108
|
return [];
|
|
605
1109
|
const p = entry.data;
|
|
606
1110
|
const phase = p?.phase;
|
|
@@ -617,6 +1121,7 @@ export class SessionManager {
|
|
|
617
1121
|
...(typeof p.reason === "string" ? { reason: p.reason } : {}),
|
|
618
1122
|
...(typeof p.body === "string" ? { body: p.body } : {}),
|
|
619
1123
|
afterMessageCount: typeof p.afterMessageCount === "number" ? p.afterMessageCount : 0,
|
|
1124
|
+
recordedAfterMessageCount,
|
|
620
1125
|
},
|
|
621
1126
|
];
|
|
622
1127
|
}
|