@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
package/dist/app-sidecar.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* POST /prompt → { text } ; runs AgentSession.prompt(text)
|
|
9
9
|
* POST /cancel → aborts the in-flight run
|
|
10
10
|
*
|
|
11
|
-
* The agent spine (
|
|
11
|
+
* The agent spine (@prestyj/ai → @prestyj/agent → @prestyj/core) and every tool are reused
|
|
12
12
|
* unchanged via AgentSession — this file is only a network seam.
|
|
13
13
|
*/
|
|
14
14
|
import http from "node:http";
|
|
@@ -24,6 +24,7 @@ import { runSubagentWorkerMode } from "./modes/subagent-worker-mode.js";
|
|
|
24
24
|
import { setStreamDiagnostic } from "@prestyj/agent";
|
|
25
25
|
import { AgentSession } from "./core/agent-session.js";
|
|
26
26
|
import { RunLifecycle } from "./core/run-lifecycle.js";
|
|
27
|
+
import { RunClaim } from "./core/run-claim.js";
|
|
27
28
|
import { CHAT_AGENT_IDS, chatAgentSessionsDir, createChatAgent, parseChatAgentId, switchChatAgent, } from "./chat-agents/index.js";
|
|
28
29
|
import { buildJiwaTools, JiwaStore } from "./chat-agents/jiwa.js";
|
|
29
30
|
import { buildMemoryTools, MemoryStore } from "./chat-agents/memory.js";
|
|
@@ -33,20 +34,22 @@ import { parseAutopilotVerdict } from "./core/autopilot-verdict.js";
|
|
|
33
34
|
import { isWorkflowCommandText, countAssistantMessages, shouldStartAutopilotCycle, extractTurnToolCalls, isMechanicalOnlyTurn, } from "./core/autopilot-gate.js";
|
|
34
35
|
import { driveAutopilotCycle, frameAutopilotInjection } from "./core/autopilot-cycle.js";
|
|
35
36
|
import { validateNolanModelPref, effectiveNolanModel, } from "./core/nolan-model.js";
|
|
36
|
-
import { normalizeAutopilotMarkersForHistory, normalizeAppMarkersForHistory, normalizeNolanTurnsForHistory, restoreUserRow, restoreAssistantTexts, autopilotMarkerCopySeed, } from "./core/session-history.js";
|
|
37
|
+
import { normalizeAutopilotMarkersForHistory, normalizeAppMarkersForHistory, normalizeNolanTurnsForHistory, getHistoryMessageVisibility, replayMessagesInOrder, restoreUserRow, restoreAssistantTexts, resolveRestoredCommand, autopilotMarkerCopySeed, } from "./core/session-history.js";
|
|
37
38
|
import { sessionToMarkdown, defaultExportFilename, } from "./core/session-export.js";
|
|
38
39
|
import { AuthStorage } from "./core/auth-storage.js";
|
|
39
40
|
import { cleanupToolOutputs } from "./tools/overflow.js";
|
|
40
41
|
import { readCappedBody } from "./utils/http-body.js";
|
|
41
|
-
import { fetchSubscriptionUsage,
|
|
42
|
+
import { fetchSubscriptionUsage, SubscriptionUsageError, XIAOMI_CREDITS_KEY, dualAuthProvider, oauthStorageKey, discoverLocalModels, findProbedModel, formatLocalModelId, parseLocalModelId, probeEndpoint, toModelInfo as localModelInfo, } from "@prestyj/core";
|
|
42
43
|
import { LocalEndpointError, addCustomEndpoint, listAllEndpoints, removeCustomEndpoint, syncEndpointCredentials, } from "./core/local-endpoint-store.js";
|
|
43
44
|
import { loginAnthropic } from "./core/oauth/anthropic.js";
|
|
44
45
|
import { loginOpenAI } from "./core/oauth/openai.js";
|
|
45
46
|
import { loginGemini } from "./core/oauth/gemini.js";
|
|
46
47
|
import { loginKimi } from "./core/oauth/kimi.js";
|
|
47
|
-
import {
|
|
48
|
+
import { loginXai } from "./core/oauth/xai.js";
|
|
49
|
+
import { AUTH_PROVIDERS, authPriorityNote, describeAuthMethods, } from "./core/auth-providers.js";
|
|
48
50
|
import { ensureAppDirs, loadSavedSettings } from "./config.js";
|
|
49
51
|
import { SettingsManager } from "./core/settings-manager.js";
|
|
52
|
+
import { installPlugin, listInstalledPlugins, removePlugin, } from "./core/extensions/plugin-bundles.js";
|
|
50
53
|
import { getModel, getDefaultThinkingLevel, getContextWindow, getAllModels, clearRuntimeModels, registerRuntimeModels, } from "./core/model-registry.js";
|
|
51
54
|
import { resolveStartOrFallback } from "./core/resolve-start.js";
|
|
52
55
|
import { getGitBranch, getGitDirtyFileCount, isGitRepo } from "./utils/git.js";
|
|
@@ -59,18 +62,24 @@ import { discoverProjects } from "./core/project-discovery.js";
|
|
|
59
62
|
import { listSidecarSessions } from "./app-sidecar-sessions.js";
|
|
60
63
|
import { loadTasksSync, saveTasksSync, pruneDoneTasksSync, getNextPendingTask, markTaskInProgress, } from "./core/task-store.js";
|
|
61
64
|
import { initLogger, log } from "./core/logger.js";
|
|
65
|
+
import { installTerminationHandlers } from "./core/shutdown.js";
|
|
62
66
|
import { RADIO_STATIONS, getCurrentStation, getRadioVolume, playRadio, setRadioVolume, stopRadio, } from "./core/radio.js";
|
|
63
67
|
import { enrichProcessPath } from "./core/shell-path.js";
|
|
64
68
|
import { downscaleForPreview, shrinkToFit, validateVisionImage } from "./utils/image.js";
|
|
65
69
|
import { startServeMode } from "./modes/serve-mode.js";
|
|
66
70
|
import { loadTelegramConfig, saveTelegramConfig, verifyBotToken } from "./core/telegram-config.js";
|
|
67
|
-
import { loadServers, addServer, removeServer, getServer, parseMcpAddCommand, MCPClientManager, McpOAuthStore, } from "./core/mcp/index.js";
|
|
71
|
+
import { loadServers, addServer, removeServer, getServer, parseMcpAddCommand, MCPClientManager, McpOAuthStore, createElicitationBridge, } from "./core/mcp/index.js";
|
|
68
72
|
import { buildSnapshot, levelForXp, rankForLevel } from "./core/progress/ranks.js";
|
|
69
73
|
import { loadProgress, peekProgress, updateProgress } from "./core/progress/store.js";
|
|
70
74
|
import { awardPrompt, awardCommits } from "./core/progress/engine.js";
|
|
71
75
|
import { detectNewCommits, repoKey } from "./core/progress/git-xp.js";
|
|
72
76
|
import { rebuildFromSessions } from "./core/progress/rebuild.js";
|
|
73
77
|
import { captureSidecarError, flushSidecarErrors, shouldCaptureToolFailure, shouldCaptureUsagePollingError, wrapSidecarHandler, } from "./core/sidecar-error-reporter.js";
|
|
78
|
+
const AUTOMATION_PROVENANCE = {
|
|
79
|
+
source: "runtime",
|
|
80
|
+
kind: "automation",
|
|
81
|
+
visibility: "hidden",
|
|
82
|
+
};
|
|
74
83
|
const ALL_PROVIDERS = [
|
|
75
84
|
// US
|
|
76
85
|
"anthropic",
|
|
@@ -87,6 +96,12 @@ const ALL_PROVIDERS = [
|
|
|
87
96
|
"sakana",
|
|
88
97
|
"openrouter",
|
|
89
98
|
];
|
|
99
|
+
function stringArray(value) {
|
|
100
|
+
if (!Array.isArray(value))
|
|
101
|
+
return undefined;
|
|
102
|
+
const out = value.filter((v) => typeof v === "string" && v.trim().length > 0);
|
|
103
|
+
return out.length > 0 ? out : undefined;
|
|
104
|
+
}
|
|
90
105
|
function appSettingsFile() {
|
|
91
106
|
return path.join(os.homedir(), ".ezcoder", "ezcoder-app.json");
|
|
92
107
|
}
|
|
@@ -110,6 +125,8 @@ async function loadAppSettings() {
|
|
|
110
125
|
projectModels: raw.projectModels && typeof raw.projectModels === "object" ? raw.projectModels : undefined,
|
|
111
126
|
autopilot: raw.autopilot && typeof raw.autopilot === "object" ? raw.autopilot : undefined,
|
|
112
127
|
nolanModels: raw.nolanModels && typeof raw.nolanModels === "object" ? raw.nolanModels : undefined,
|
|
128
|
+
projectRoots: stringArray(raw.projectRoots),
|
|
129
|
+
hiddenProjects: stringArray(raw.hiddenProjects),
|
|
113
130
|
};
|
|
114
131
|
}
|
|
115
132
|
catch {
|
|
@@ -363,30 +380,6 @@ function detectHookKind(text) {
|
|
|
363
380
|
return "regrounding";
|
|
364
381
|
return null;
|
|
365
382
|
}
|
|
366
|
-
// Separator AgentSession.prompt() inserts between a command's prompt body and
|
|
367
|
-
// the user's trailing args. Must stay in sync with the expansion there.
|
|
368
|
-
const COMMAND_ARGS_SEP = "\n\n## User Instructions\n\n";
|
|
369
|
-
/**
|
|
370
|
-
* Reverse a prompt-template command's expansion. When a `/name` command runs,
|
|
371
|
-
* the agent persists the FULL expanded prompt body as the user message — so on
|
|
372
|
-
* resume the raw body would render instead of the short `/name` chip the user
|
|
373
|
-
* saw live. Given the candidate commands (built-in + custom) and a restored
|
|
374
|
-
* message body, recover the original `/name [args]` invocation. Returns null
|
|
375
|
-
* when the text isn't a known command body (an ordinary user message).
|
|
376
|
-
*/
|
|
377
|
-
function detectPromptCommand(text, candidates) {
|
|
378
|
-
for (const c of candidates) {
|
|
379
|
-
if (!c.prompt)
|
|
380
|
-
continue;
|
|
381
|
-
if (text === c.prompt)
|
|
382
|
-
return `/${c.name}`;
|
|
383
|
-
if (text.startsWith(c.prompt + COMMAND_ARGS_SEP)) {
|
|
384
|
-
const args = text.slice(c.prompt.length + COMMAND_ARGS_SEP.length).trim();
|
|
385
|
-
return args ? `/${c.name} ${args}` : `/${c.name}`;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
return null;
|
|
389
|
-
}
|
|
390
383
|
/** A short transport summary for display (URL for http/sse, command+args for stdio). */
|
|
391
384
|
function mcpRowSummary(config) {
|
|
392
385
|
if (config.url)
|
|
@@ -459,6 +452,8 @@ async function runJsonModeIfRequested() {
|
|
|
459
452
|
model: { type: "string" },
|
|
460
453
|
"max-turns": { type: "string" },
|
|
461
454
|
"system-prompt": { type: "string" },
|
|
455
|
+
"agent-prompt": { type: "string" },
|
|
456
|
+
"agent-context": { type: "string" },
|
|
462
457
|
tools: { type: "string" },
|
|
463
458
|
"mcp-servers": { type: "string" },
|
|
464
459
|
"prompt-cache-key": { type: "string" },
|
|
@@ -492,6 +487,8 @@ async function runJsonModeIfRequested() {
|
|
|
492
487
|
model: values.model ?? "claude-opus-5",
|
|
493
488
|
cwd: process.cwd(),
|
|
494
489
|
systemPrompt: values["system-prompt"],
|
|
490
|
+
agentPrompt: values["agent-prompt"],
|
|
491
|
+
agentContext: values["agent-context"] === "none" ? "none" : undefined,
|
|
495
492
|
maxTurns: maxTurnsRaw ? parseInt(maxTurnsRaw, 10) : undefined,
|
|
496
493
|
allowedTools,
|
|
497
494
|
allowedMcpServers,
|
|
@@ -595,9 +592,23 @@ async function main() {
|
|
|
595
592
|
await auth.load();
|
|
596
593
|
// Every window's session lives here as an in-process object, keyed by the id
|
|
597
594
|
// the daemon hands back from POST /session. The Rust shell routes each proxy
|
|
598
|
-
// request to its window's session via the `x-
|
|
595
|
+
// request to its window's session via the `x-ez-session` header (and the
|
|
599
596
|
// `?session=` query for the SSE /events stream).
|
|
600
597
|
const sessions = new Map();
|
|
598
|
+
/**
|
|
599
|
+
* Fan one frame out to every window.
|
|
600
|
+
*
|
|
601
|
+
* For state that is genuinely global rather than per-session — `~/.ezcoder/auth.json`
|
|
602
|
+
* is shared by all windows, so connecting a provider in one must refresh the
|
|
603
|
+
* model picker in all of them. Mirrors the memory/jiwa/progress fan-outs.
|
|
604
|
+
*/
|
|
605
|
+
const broadcastAll = (type, data) => {
|
|
606
|
+
for (const ctx of sessions.values())
|
|
607
|
+
ctx.broadcast(type, data);
|
|
608
|
+
};
|
|
609
|
+
// Providers currently mid-OAuth in some window. Daemon-level so two windows
|
|
610
|
+
// cannot race two browser flows for the same provider into one auth file.
|
|
611
|
+
const oauthInFlightProviders = new Set();
|
|
601
612
|
const memoryStore = new MemoryStore({
|
|
602
613
|
onChange: ({ memories }) => {
|
|
603
614
|
for (const ctx of sessions.values()) {
|
|
@@ -621,6 +632,13 @@ async function main() {
|
|
|
621
632
|
});
|
|
622
633
|
const usageCache = new Map();
|
|
623
634
|
const usageRequests = new Map();
|
|
635
|
+
// Last snapshot that actually carried windows, per provider. Replayed while a
|
|
636
|
+
// fetch is failing so the title meter never blinks out of existence. Bounded
|
|
637
|
+
// by USAGE_LAST_GOOD_MAX_AGE_MS — a provider that never recovers must stop
|
|
638
|
+
// reporting rather than freeze a percentage (and a long-past reset time) on
|
|
639
|
+
// screen forever. The 429 backoff alone runs to 24h, far past any usefulness.
|
|
640
|
+
const usageLastGood = new Map();
|
|
641
|
+
const USAGE_LAST_GOOD_MAX_AGE_MS = 30 * 60_000;
|
|
624
642
|
// 429 backoff: quota endpoints are auxiliary UI data. Honor Retry-After when
|
|
625
643
|
// provided; otherwise retain the unavailable snapshot for 30 minutes. Clamp
|
|
626
644
|
// the provider value so a malformed header can neither hammer the endpoint
|
|
@@ -633,8 +651,11 @@ async function main() {
|
|
|
633
651
|
const displayName = provider === "anthropic" ? "Anthropic" : provider === "openai" ? "Codex" : "Kimi";
|
|
634
652
|
// Kimi plan usage is tracked on the OAuth credential specifically — the
|
|
635
653
|
// Moonshot platform API key is metered per-token, not per plan window.
|
|
636
|
-
const authKey = provider
|
|
654
|
+
const authKey = oauthStorageKey(provider) ?? provider;
|
|
637
655
|
if (!(await auth.hasProviderAuth(authKey))) {
|
|
656
|
+
// Logged out: drop the replay cache so a later login on a DIFFERENT
|
|
657
|
+
// account can never inherit the previous one's numbers.
|
|
658
|
+
usageLastGood.delete(provider);
|
|
638
659
|
return { provider, displayName, connected: false, windows: [], fetchedAt: Date.now() };
|
|
639
660
|
}
|
|
640
661
|
try {
|
|
@@ -651,7 +672,14 @@ async function main() {
|
|
|
651
672
|
// A provider can revoke an access token before its stored expiry. Refresh
|
|
652
673
|
// once on 401, matching inference auth recovery, then retry the usage call.
|
|
653
674
|
if (error instanceof SubscriptionUsageError && error.status === 401) {
|
|
654
|
-
|
|
675
|
+
// Name the rejected token. This poller runs on a timer alongside live
|
|
676
|
+
// agent runs, so an unconditional refresh here would invalidate the
|
|
677
|
+
// token those runs are holding and fail them mid-turn — a background
|
|
678
|
+
// status check must never be able to log out the foreground.
|
|
679
|
+
credentials = await auth.resolveCredentials(authKey, {
|
|
680
|
+
forceRefresh: true,
|
|
681
|
+
rejectedToken: credentials.accessToken,
|
|
682
|
+
});
|
|
655
683
|
const snapshot = {
|
|
656
684
|
...(await fetchSubscriptionUsage(provider, credentials)),
|
|
657
685
|
connected: true,
|
|
@@ -678,6 +706,18 @@ async function main() {
|
|
|
678
706
|
...(backoffMs !== undefined && { backoffMs: String(backoffMs) }),
|
|
679
707
|
});
|
|
680
708
|
const connected = await auth.hasProviderAuth(authKey);
|
|
709
|
+
// Transient failures (notably the 429s these auxiliary quota endpoints
|
|
710
|
+
// hand out) must not blank the meter. Keep serving the last good
|
|
711
|
+
// snapshot, flagged `stale`, so the bar stays put instead of flickering
|
|
712
|
+
// out for the whole backoff window and back in on the next success.
|
|
713
|
+
// Past the max age it's dropped — no data beats confidently wrong data.
|
|
714
|
+
const lastGood = usageLastGood.get(provider);
|
|
715
|
+
if (lastGood && Date.now() - lastGood.fetchedAt >= USAGE_LAST_GOOD_MAX_AGE_MS) {
|
|
716
|
+
usageLastGood.delete(provider);
|
|
717
|
+
}
|
|
718
|
+
else if (connected && lastGood) {
|
|
719
|
+
return { ...lastGood, stale: true };
|
|
720
|
+
}
|
|
681
721
|
return {
|
|
682
722
|
provider,
|
|
683
723
|
displayName,
|
|
@@ -699,6 +739,11 @@ async function main() {
|
|
|
699
739
|
usageRequests.set(provider, request);
|
|
700
740
|
try {
|
|
701
741
|
const result = await request;
|
|
742
|
+
// Never re-store a replay — it would keep its original `fetchedAt`, but
|
|
743
|
+
// writing it back muddies the "last GOOD" contract for no gain.
|
|
744
|
+
if (result.connected && !result.error && !result.stale && result.windows.length > 0) {
|
|
745
|
+
usageLastGood.set(provider, result);
|
|
746
|
+
}
|
|
702
747
|
// Anthropic can return utilization before it assigns reset timestamps
|
|
703
748
|
// (notably before the account's first active request). Retry that partial
|
|
704
749
|
// snapshot quickly; complete snapshots keep the normal one-minute cache.
|
|
@@ -719,10 +764,10 @@ async function main() {
|
|
|
719
764
|
usageRequests.delete(provider);
|
|
720
765
|
}
|
|
721
766
|
}
|
|
722
|
-
/** Resolve the target session id: the `x-
|
|
767
|
+
/** Resolve the target session id: the `x-ez-session` header, else a
|
|
723
768
|
* `?session=` query param (used by the SSE /events connection). */
|
|
724
769
|
function sessionIdFromReq(req, url) {
|
|
725
|
-
const header = req.headers["x-
|
|
770
|
+
const header = req.headers["x-ez-session"];
|
|
726
771
|
if (typeof header === "string" && header.length > 0)
|
|
727
772
|
return header;
|
|
728
773
|
try {
|
|
@@ -740,7 +785,7 @@ async function main() {
|
|
|
740
785
|
res.writeHead(204, {
|
|
741
786
|
"access-control-allow-origin": "*",
|
|
742
787
|
"access-control-allow-methods": "GET, POST, DELETE, OPTIONS",
|
|
743
|
-
"access-control-allow-headers": "content-type, x-
|
|
788
|
+
"access-control-allow-headers": "content-type, x-ez-session",
|
|
744
789
|
});
|
|
745
790
|
res.end();
|
|
746
791
|
return;
|
|
@@ -766,7 +811,15 @@ async function main() {
|
|
|
766
811
|
const sessionPath = typeof body.sessionPath === "string" && body.sessionPath ? body.sessionPath : undefined;
|
|
767
812
|
const id = randomUUID();
|
|
768
813
|
try {
|
|
769
|
-
const ctx = await createSession({
|
|
814
|
+
const ctx = await createSession({
|
|
815
|
+
auth,
|
|
816
|
+
paths,
|
|
817
|
+
progress,
|
|
818
|
+
memoryStore,
|
|
819
|
+
jiwaStore,
|
|
820
|
+
broadcastAll,
|
|
821
|
+
oauthInFlightProviders,
|
|
822
|
+
}, { id, mode, chatAgent, cwd: sessionCwd, sessionPath });
|
|
770
823
|
sessions.set(id, ctx);
|
|
771
824
|
log("INFO", "app-sidecar", "session created", {
|
|
772
825
|
id,
|
|
@@ -839,23 +892,28 @@ async function main() {
|
|
|
839
892
|
log("INFO", "app-sidecar", "daemon listening", { port: String(addr.port), host });
|
|
840
893
|
});
|
|
841
894
|
const shellPid = process.ppid;
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
895
|
+
// Session teardown awaits MCP servers, LSP servers and third-party extension
|
|
896
|
+
// `deactivate()` hooks. Any can hang, so a deadline guarantees process exit.
|
|
897
|
+
const shutdown = installTerminationHandlers({
|
|
898
|
+
scope: "app-sidecar",
|
|
899
|
+
teardown: async () => {
|
|
900
|
+
clearInterval(parentWatch);
|
|
901
|
+
// Radio playback is app-wide (one stream across all windows).
|
|
902
|
+
stopRadio();
|
|
903
|
+
// Close the ~/.ezcoder progress fs.watch handle.
|
|
904
|
+
progress.dispose();
|
|
905
|
+
await Promise.all([...sessions.values()].map((c) => c.dispose().catch(() => { })));
|
|
906
|
+
server.close();
|
|
907
|
+
},
|
|
908
|
+
onTimeout: (timeoutMs) => {
|
|
909
|
+
// Radio is audible, so it must stop even when the rest is wedged.
|
|
910
|
+
stopRadio();
|
|
911
|
+
log("WARN", "app-sidecar", "daemon teardown hung; exiting on deadline", {
|
|
912
|
+
timeoutMs: String(timeoutMs),
|
|
913
|
+
sessions: String(sessions.size),
|
|
914
|
+
});
|
|
915
|
+
},
|
|
916
|
+
});
|
|
859
917
|
process.once("exit", stopRadio);
|
|
860
918
|
// Tauri can disappear without delivering a signal (force-quit, dev runner
|
|
861
919
|
// teardown, crash). Detect reparenting or a dead shell so the daemon and its
|
|
@@ -871,7 +929,7 @@ async function main() {
|
|
|
871
929
|
}
|
|
872
930
|
}
|
|
873
931
|
if (!parentAlive)
|
|
874
|
-
|
|
932
|
+
shutdown();
|
|
875
933
|
}, 1_000);
|
|
876
934
|
parentWatch.unref?.();
|
|
877
935
|
}
|
|
@@ -1042,7 +1100,7 @@ async function createProgressManager(agentDir, broadcastAll) {
|
|
|
1042
1100
|
* logger, PATH, shared auth file, and radio live at the daemon level.
|
|
1043
1101
|
*/
|
|
1044
1102
|
async function createSession(deps, opts) {
|
|
1045
|
-
const { auth, progress, memoryStore, jiwaStore } = deps;
|
|
1103
|
+
const { auth, progress, memoryStore, jiwaStore, broadcastAll, oauthInFlightProviders } = deps;
|
|
1046
1104
|
const paths = deps.paths;
|
|
1047
1105
|
const mode = opts.mode;
|
|
1048
1106
|
let chatAgent = opts.chatAgent;
|
|
@@ -1137,7 +1195,7 @@ async function createSession(deps, opts) {
|
|
|
1137
1195
|
"Start it again and retry, or pick another model.");
|
|
1138
1196
|
}
|
|
1139
1197
|
// Turn any thrown value into the same clear headline/message/guidance shape
|
|
1140
|
-
// the TUI shows (see
|
|
1198
|
+
// the TUI shows (see @prestyj/ai's formatError) instead of a bare `err.message`, log
|
|
1141
1199
|
// the full detail, and broadcast it under `type` ("error" or "nolan_error").
|
|
1142
1200
|
// Without this the webview only ever saw a raw provider string like
|
|
1143
1201
|
// `400 {"code":"400",...}` with no "is this me or them / when does it reset"
|
|
@@ -1178,6 +1236,17 @@ async function createSession(deps, opts) {
|
|
|
1178
1236
|
})
|
|
1179
1237
|
.catch(() => { });
|
|
1180
1238
|
}
|
|
1239
|
+
// ── MCP elicitation bridge ─────────────────────────────────
|
|
1240
|
+
// An MCP server can ask for user input in the middle of a tool call. The
|
|
1241
|
+
// bridge parks the promise; we broadcast the prompt over SSE and resolve it
|
|
1242
|
+
// when the webview POSTs /mcp/elicit/:id.
|
|
1243
|
+
const elicitations = createElicitationBridge({
|
|
1244
|
+
broadcast: (prompt) => broadcast("mcp_elicit", prompt),
|
|
1245
|
+
onTimeout: (prompt) => log("WARN", "app-sidecar", "MCP elicitation timed out", {
|
|
1246
|
+
id: prompt.id,
|
|
1247
|
+
server: prompt.server,
|
|
1248
|
+
}),
|
|
1249
|
+
});
|
|
1181
1250
|
// The session file path to resume (passed by the daemon's POST /session);
|
|
1182
1251
|
// empty/unset starts a fresh session.
|
|
1183
1252
|
const resumeSessionPath = opts.sessionPath;
|
|
@@ -1191,6 +1260,7 @@ async function createSession(deps, opts) {
|
|
|
1191
1260
|
signal: abort.signal,
|
|
1192
1261
|
// Keep MCP startup off the readiness path in both modes.
|
|
1193
1262
|
backgroundMcpConnect: true,
|
|
1263
|
+
onMcpElicit: elicitations.onElicit,
|
|
1194
1264
|
// Keep restore-time auto-compaction off the readiness path too: its summary
|
|
1195
1265
|
// LLM call (30s timeout) used to freeze waitForReady — and with it the whole
|
|
1196
1266
|
// window (project picker, session list) — whenever a resumed session was
|
|
@@ -1513,6 +1583,10 @@ async function createSession(deps, opts) {
|
|
|
1513
1583
|
recordApprovedPlanMarkers(d.text);
|
|
1514
1584
|
});
|
|
1515
1585
|
session.eventBus.on("thinking_delta", (d) => broadcast("thinking_delta", d));
|
|
1586
|
+
// The agent consumed queued steering at a turn boundary. Re-broadcast as the
|
|
1587
|
+
// usual `queued` depth update so the webview drops the pending affordance the
|
|
1588
|
+
// moment the message lands in the loop, not at run_end.
|
|
1589
|
+
session.eventBus.on("queue_drained", (d) => broadcast("queued", { count: d.count, messages: session.listQueuedMessages() }));
|
|
1516
1590
|
session.eventBus.on("tool_call_start", (d) => {
|
|
1517
1591
|
toolCallNames.set(d.toolCallId, d.name);
|
|
1518
1592
|
broadcast("tool_call_start", d);
|
|
@@ -1564,10 +1638,29 @@ async function createSession(deps, opts) {
|
|
|
1564
1638
|
session.eventBus.on("compaction_start", (d) => broadcast("compaction_start", d));
|
|
1565
1639
|
session.eventBus.on("compaction_end", (d) => broadcast("compaction_end", d));
|
|
1566
1640
|
let running = false;
|
|
1641
|
+
// Closes the window between `/prompt` deciding to start a run and `runAgent`
|
|
1642
|
+
// flipping `running` — that stretch awaits, so Node yields inside it. See
|
|
1643
|
+
// RunClaim.
|
|
1644
|
+
const runClaim = new RunClaim();
|
|
1567
1645
|
const runLifecycle = new RunLifecycle((runState) => {
|
|
1568
1646
|
running = runState !== "idle";
|
|
1569
1647
|
if (runState === "cancelling")
|
|
1570
1648
|
broadcast("run_cancelling", { runState });
|
|
1649
|
+
},
|
|
1650
|
+
// Durable run journal. Fire-and-forget on purpose: an unwritten journal
|
|
1651
|
+
// entry is a missed crash hint, while a journal write that throws inside
|
|
1652
|
+
// begin()/settle() would break run ownership itself.
|
|
1653
|
+
{
|
|
1654
|
+
started: (generation) => void session.persistRunStarted(generation).catch((err) => {
|
|
1655
|
+
log("WARN", "app-sidecar", "failed to journal run start", {
|
|
1656
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1657
|
+
});
|
|
1658
|
+
}),
|
|
1659
|
+
finished: (generation, outcome) => void session.persistRunFinished(generation, outcome).catch((err) => {
|
|
1660
|
+
log("WARN", "app-sidecar", "failed to journal run finish", {
|
|
1661
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1662
|
+
});
|
|
1663
|
+
}),
|
|
1571
1664
|
});
|
|
1572
1665
|
const cancelledRunEndGenerations = new Set();
|
|
1573
1666
|
let pendingCancelDrain = null;
|
|
@@ -1643,7 +1736,7 @@ async function createSession(deps, opts) {
|
|
|
1643
1736
|
// with `@Nolan …`; he reads EZ Coder's transcript (one-way — EZ Coder never sees
|
|
1644
1737
|
// Nolan's) and hands back runnable prompts + mentorship. Created lazily on the
|
|
1645
1738
|
// first `@Nolan` so windows that never use Nolan pay zero cost. His events ride the
|
|
1646
|
-
// SAME SSE stream with `
|
|
1739
|
+
// SAME SSE stream with `nolan_`-prefixed types, routed to the Nolan bubble.
|
|
1647
1740
|
let nolanSession = null;
|
|
1648
1741
|
let nolanAbort = new AbortController();
|
|
1649
1742
|
let nolanRunning = false;
|
|
@@ -1673,7 +1766,7 @@ async function createSession(deps, opts) {
|
|
|
1673
1766
|
return { provider: st.provider, model: st.model };
|
|
1674
1767
|
}
|
|
1675
1768
|
/** Footer payload: Nolan's effective model + whether it's a pin. Merged into
|
|
1676
|
-
* /state, the SSE ready frame, and every
|
|
1769
|
+
* /state, the SSE ready frame, and every nolan_model_change broadcast. */
|
|
1677
1770
|
function nolanStatePayload() {
|
|
1678
1771
|
const st = session.getState();
|
|
1679
1772
|
return effectiveNolanModel(nolanModelOverride, { provider: st.provider, model: st.model });
|
|
@@ -1689,13 +1782,13 @@ async function createSession(deps, opts) {
|
|
|
1689
1782
|
if (st.provider === provider && st.model === model)
|
|
1690
1783
|
return;
|
|
1691
1784
|
await nolanSession.switchModel(provider, model);
|
|
1692
|
-
log("INFO", "app-sidecar", "
|
|
1785
|
+
log("INFO", "app-sidecar", "nolan session model synced", { provider, model });
|
|
1693
1786
|
}
|
|
1694
1787
|
async function ensureNolanSession() {
|
|
1695
1788
|
if (nolanSession)
|
|
1696
1789
|
return nolanSession;
|
|
1697
1790
|
const target = nolanCurrentModel();
|
|
1698
|
-
const
|
|
1791
|
+
const nolanAgent = new AgentSession({
|
|
1699
1792
|
provider: target.provider,
|
|
1700
1793
|
model: target.model,
|
|
1701
1794
|
cwd,
|
|
@@ -1704,38 +1797,41 @@ async function createSession(deps, opts) {
|
|
|
1704
1797
|
allowedMcpServers: NOLAN_ALLOWED_MCP_SERVERS,
|
|
1705
1798
|
transient: true,
|
|
1706
1799
|
signal: nolanAbort.signal,
|
|
1707
|
-
// Nolan
|
|
1800
|
+
// Nolan belongs to this window, so route MCP elicitation prompts there.
|
|
1801
|
+
// Keeping daemon sessions interactive also lets them share one pooled MCP connection.
|
|
1802
|
+
onMcpElicit: elicitations.onElicit,
|
|
1803
|
+
// Nolan's bursty, spread-out turns outlast the default cache
|
|
1708
1804
|
// TTL regardless of the user's global speedProfile pick.
|
|
1709
1805
|
forceLongCacheRetention: true,
|
|
1710
1806
|
});
|
|
1711
|
-
await
|
|
1712
|
-
// Bridge Nolan's bus to the shared SSE fan-out with
|
|
1807
|
+
await nolanAgent.initialize();
|
|
1808
|
+
// Bridge Nolan's bus to the shared SSE fan-out with nolan_-prefixed types so the
|
|
1713
1809
|
// webview routes them to the Nolan bubble, never EZ Coder's.
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1810
|
+
nolanAgent.eventBus.on("text_delta", (d) => broadcast("nolan_text_delta", d));
|
|
1811
|
+
nolanAgent.eventBus.on("thinking_delta", (d) => broadcast("nolan_thinking_delta", d));
|
|
1812
|
+
nolanAgent.eventBus.on("tool_call_start", (d) => {
|
|
1717
1813
|
nolanToolCallNames.set(d.toolCallId, d.name);
|
|
1718
1814
|
broadcast("nolan_tool_call_start", d);
|
|
1719
1815
|
});
|
|
1720
|
-
|
|
1721
|
-
|
|
1816
|
+
nolanAgent.eventBus.on("tool_call_update", (d) => broadcast("nolan_tool_call_update", d));
|
|
1817
|
+
nolanAgent.eventBus.on("tool_call_end", (d) => {
|
|
1722
1818
|
nolanToolCallNames.delete(d.toolCallId);
|
|
1723
1819
|
broadcast("nolan_tool_call_end", d);
|
|
1724
1820
|
});
|
|
1725
1821
|
// Native server tools (Anthropic web_search) stream text both before AND
|
|
1726
1822
|
// after them in the same turn; forward so the webview can break the bubble
|
|
1727
1823
|
// (otherwise "...work.Local tools..." glues together). Mirrors the build bus.
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
broadcastError("nolan_error", "
|
|
1824
|
+
nolanAgent.eventBus.on("server_tool_call", (d) => broadcast("nolan_server_tool_call", d));
|
|
1825
|
+
nolanAgent.eventBus.on("turn_end", (d) => broadcast("nolan_turn_end", d));
|
|
1826
|
+
nolanAgent.eventBus.on("error", (d) => {
|
|
1827
|
+
broadcastError("nolan_error", "nolan error", d.error);
|
|
1732
1828
|
});
|
|
1733
|
-
nolanSession =
|
|
1734
|
-
log("INFO", "app-sidecar", "
|
|
1829
|
+
nolanSession = nolanAgent;
|
|
1830
|
+
log("INFO", "app-sidecar", "nolan session ready", {
|
|
1735
1831
|
provider: target.provider,
|
|
1736
1832
|
model: target.model,
|
|
1737
1833
|
});
|
|
1738
|
-
return
|
|
1834
|
+
return nolanAgent;
|
|
1739
1835
|
}
|
|
1740
1836
|
// ── Autopilot Nolan (auto-reviewer) ──────────────────────────
|
|
1741
1837
|
// A THIRD read-only AgentSession, separate from chat Nolan. In autopilot mode
|
|
@@ -1758,13 +1854,13 @@ async function createSession(deps, opts) {
|
|
|
1758
1854
|
if (st.provider === provider && st.model === model)
|
|
1759
1855
|
return;
|
|
1760
1856
|
await nolanAutoSession.switchModel(provider, model);
|
|
1761
|
-
log("INFO", "app-sidecar", "
|
|
1857
|
+
log("INFO", "app-sidecar", "nolan autopilot session model synced", { provider, model });
|
|
1762
1858
|
}
|
|
1763
1859
|
async function ensureNolanAutoSession() {
|
|
1764
1860
|
if (nolanAutoSession)
|
|
1765
1861
|
return nolanAutoSession;
|
|
1766
1862
|
const target = nolanCurrentModel();
|
|
1767
|
-
const
|
|
1863
|
+
const nolanAgent = new AgentSession({
|
|
1768
1864
|
provider: target.provider,
|
|
1769
1865
|
model: target.model,
|
|
1770
1866
|
cwd,
|
|
@@ -1773,26 +1869,32 @@ async function createSession(deps, opts) {
|
|
|
1773
1869
|
allowedMcpServers: NOLAN_ALLOWED_MCP_SERVERS,
|
|
1774
1870
|
transient: true,
|
|
1775
1871
|
signal: nolanAutoAbort.signal,
|
|
1872
|
+
// Route this window's MCP elicitation prompts through the shared bridge.
|
|
1873
|
+
onMcpElicit: elicitations.onElicit,
|
|
1776
1874
|
// Autopilot review rounds routinely span the injected EZ Coder run
|
|
1777
1875
|
// (often >5 min) regardless of the user's global speedProfile pick.
|
|
1778
1876
|
forceLongCacheRetention: true,
|
|
1779
1877
|
});
|
|
1780
1878
|
// Nolan is already the independent autopilot reviewer; recursively running
|
|
1781
1879
|
// his own Ideal self-review adds latency and can corrupt the verdict shape.
|
|
1782
|
-
|
|
1783
|
-
await
|
|
1880
|
+
nolanAgent.setIdealReviewSuppressed(true);
|
|
1881
|
+
await nolanAgent.initialize();
|
|
1784
1882
|
// Deliberately no bus bridge: the review is silent. Errors surface via the
|
|
1785
1883
|
// runAutopilotReview try/catch as autopilot_error frames.
|
|
1786
|
-
nolanAutoSession =
|
|
1787
|
-
log("INFO", "app-sidecar", "
|
|
1884
|
+
nolanAutoSession = nolanAgent;
|
|
1885
|
+
log("INFO", "app-sidecar", "nolan autopilot session ready", {
|
|
1788
1886
|
provider: target.provider,
|
|
1789
1887
|
model: target.model,
|
|
1790
1888
|
});
|
|
1791
|
-
return
|
|
1889
|
+
return nolanAgent;
|
|
1792
1890
|
}
|
|
1793
1891
|
function abortOwnedWork() {
|
|
1794
1892
|
cancelGeneration++;
|
|
1795
1893
|
abort.abort();
|
|
1894
|
+
// An MCP tool call parked on user input is not cancelled by the signal —
|
|
1895
|
+
// the promise lives in the bridge. Release it, or the aborted turn's tool
|
|
1896
|
+
// call never returns.
|
|
1897
|
+
elicitations.cancelAll();
|
|
1796
1898
|
// Stop a run-all sweep and every async child through AgentSession's signal.
|
|
1797
1899
|
taskRunAll = false;
|
|
1798
1900
|
autopilotCancelled = true;
|
|
@@ -1804,9 +1906,9 @@ async function createSession(deps, opts) {
|
|
|
1804
1906
|
nolanAutoAbort = new AbortController();
|
|
1805
1907
|
nolanAutoSession?.setSignal(nolanAutoAbort.signal);
|
|
1806
1908
|
}
|
|
1807
|
-
function finishOwnedGeneration(generation, emitCancelledFallback) {
|
|
1909
|
+
function finishOwnedGeneration(generation, emitCancelledFallback, outcome = "completed") {
|
|
1808
1910
|
const cancelled = runLifecycle.isCancellationRequested(generation);
|
|
1809
|
-
const settlement = runLifecycle.settle(generation);
|
|
1911
|
+
const settlement = runLifecycle.settle(generation, outcome);
|
|
1810
1912
|
if (!settlement.settled)
|
|
1811
1913
|
return cancelled;
|
|
1812
1914
|
// A replacement signal is safe only after the provider-backed owner settled.
|
|
@@ -1884,8 +1986,9 @@ async function createSession(deps, opts) {
|
|
|
1884
1986
|
});
|
|
1885
1987
|
}
|
|
1886
1988
|
}
|
|
1887
|
-
if (ownsGeneration)
|
|
1888
|
-
finishOwnedGeneration(generation, false);
|
|
1989
|
+
if (ownsGeneration) {
|
|
1990
|
+
finishOwnedGeneration(generation, false, runSucceeded ? "completed" : "failed");
|
|
1991
|
+
}
|
|
1889
1992
|
// A cancelled injected run is still owned by the surrounding autopilot
|
|
1890
1993
|
// cycle; its outer finalizer emits the one terminal cancelled run_end.
|
|
1891
1994
|
if (!(cancelled && !ownsGeneration)) {
|
|
@@ -1904,7 +2007,10 @@ async function createSession(deps, opts) {
|
|
|
1904
2007
|
// completed ones so they drop out of the Tasks modal automatically (users
|
|
1905
2008
|
// never have to delete finished tasks by hand).
|
|
1906
2009
|
broadcast("tasks_list", { tasks: pruneDoneTasksSync(cwd) });
|
|
1907
|
-
broadcast("queued", {
|
|
2010
|
+
broadcast("queued", {
|
|
2011
|
+
count: session.getQueuedCount(),
|
|
2012
|
+
messages: session.listQueuedMessages(),
|
|
2013
|
+
});
|
|
1908
2014
|
broadcast("extras", footerExtras());
|
|
1909
2015
|
}
|
|
1910
2016
|
}
|
|
@@ -1918,7 +2024,7 @@ async function createSession(deps, opts) {
|
|
|
1918
2024
|
autopilotReviewing = true;
|
|
1919
2025
|
broadcast("autopilot_review_start", {});
|
|
1920
2026
|
try {
|
|
1921
|
-
const
|
|
2027
|
+
const nolanAgent = await ensureNolanAutoSession();
|
|
1922
2028
|
const digest = buildNolanAutopilotContext({
|
|
1923
2029
|
cwd,
|
|
1924
2030
|
gitBranch,
|
|
@@ -1927,8 +2033,8 @@ async function createSession(deps, opts) {
|
|
|
1927
2033
|
injectedPrompts: [...injectedAutopilotPrompts],
|
|
1928
2034
|
workflowCommands: await loadWorkflowCommandSpecs(),
|
|
1929
2035
|
});
|
|
1930
|
-
await
|
|
1931
|
-
return parseAutopilotVerdict(lastAssistantText(
|
|
2036
|
+
await nolanAgent.prompt(digest);
|
|
2037
|
+
return parseAutopilotVerdict(lastAssistantText(nolanAgent.getMessages()));
|
|
1932
2038
|
}
|
|
1933
2039
|
catch (err) {
|
|
1934
2040
|
if (!autopilotCancelled)
|
|
@@ -1958,7 +2064,7 @@ async function createSession(deps, opts) {
|
|
|
1958
2064
|
autopilotReviewing = true;
|
|
1959
2065
|
broadcast("autopilot_review_start", {});
|
|
1960
2066
|
try {
|
|
1961
|
-
const
|
|
2067
|
+
const nolanAgent = await ensureNolanAutoSession();
|
|
1962
2068
|
// Re-read the plan file (the run may have revised it in place); fall
|
|
1963
2069
|
// back to the content captured at exit_plan time.
|
|
1964
2070
|
const planContent = await fs.readFile(planPath, "utf-8").catch(() => pendingPlanContent);
|
|
@@ -1971,10 +2077,10 @@ async function createSession(deps, opts) {
|
|
|
1971
2077
|
workflowCommands: await loadWorkflowCommandSpecs(),
|
|
1972
2078
|
planContent,
|
|
1973
2079
|
});
|
|
1974
|
-
await
|
|
2080
|
+
await nolanAgent.prompt(digest);
|
|
1975
2081
|
if (autopilotCancelled || planGeneration !== genAtStart)
|
|
1976
2082
|
return null;
|
|
1977
|
-
return parseAutopilotVerdict(lastAssistantText(
|
|
2083
|
+
return parseAutopilotVerdict(lastAssistantText(nolanAgent.getMessages()));
|
|
1978
2084
|
}
|
|
1979
2085
|
catch (err) {
|
|
1980
2086
|
// User action mid-review (manual Accept aborts the nolanAuto run): drop
|
|
@@ -2000,7 +2106,7 @@ async function createSession(deps, opts) {
|
|
|
2000
2106
|
const IMPLEMENT_PLAN_PROMPT = "The plan has been approved. Implement it now, following each step in order.";
|
|
2001
2107
|
// Drive the review→prompt→review loop for one finished user turn. Only ever
|
|
2002
2108
|
// called after shouldStartAutopilotCycle approves the turn (POST /prompt or
|
|
2003
|
-
// the stranded-queue drain) — never from the task runner, resume, /
|
|
2109
|
+
// the stranded-queue drain) — never from the task runner, resume, /nolan, or
|
|
2004
2110
|
// error paths, so there's no recursion and no guard tangle. The loop's
|
|
2005
2111
|
// control flow lives in driveAutopilotCycle (core/autopilot-cycle.ts) so
|
|
2006
2112
|
// every exit path is unit-tested; this only wires the real dependencies.
|
|
@@ -2064,7 +2170,7 @@ async function createSession(deps, opts) {
|
|
|
2064
2170
|
// label stays the clean prompt.
|
|
2065
2171
|
const framed = frameAutopilotInjection(IMPLEMENT_PLAN_PROMPT);
|
|
2066
2172
|
injectedAutopilotPrompts.push(framed);
|
|
2067
|
-
return runAgent(IMPLEMENT_PLAN_PROMPT, () => session.prompt(framed));
|
|
2173
|
+
return runAgent(IMPLEMENT_PLAN_PROMPT, () => session.prompt(framed, AUTOMATION_PROVENANCE));
|
|
2068
2174
|
},
|
|
2069
2175
|
// Lean context per user turn: wipe prior review history so each new
|
|
2070
2176
|
// turn starts cheap, while within this cycle the few review messages
|
|
@@ -2093,19 +2199,21 @@ async function createSession(deps, opts) {
|
|
|
2093
2199
|
},
|
|
2094
2200
|
// Autopilot-injected run: EZ Coder receives the framed prompt (no human
|
|
2095
2201
|
// is watching this turn) while run_start keeps the clean label.
|
|
2096
|
-
runPrompt: (body) => runAgent(body, () => session.prompt(frameAutopilotInjection(body))),
|
|
2202
|
+
runPrompt: (body) => runAgent(body, () => session.prompt(frameAutopilotInjection(body), AUTOMATION_PROVENANCE)),
|
|
2097
2203
|
emit: (event) => {
|
|
2098
2204
|
// Persist the terminal verdict marker so a resumed session renders the
|
|
2099
2205
|
// same Nolan bubble the live run showed instead of dropping it or
|
|
2100
2206
|
// falling back to the raw verdict text (e.g. ALL_CLEAR).
|
|
2101
2207
|
if (event.type === "autopilot_done") {
|
|
2102
2208
|
// Broadcast the SAME copySeed the persisted marker will produce on
|
|
2103
|
-
// resume, so the live all-clear wording matches the resumed one
|
|
2104
|
-
//
|
|
2209
|
+
// resume, so the live all-clear wording matches the resumed one.
|
|
2210
|
+
// Must use the PERSISTED count — that's what persistAutopilotMarker
|
|
2211
|
+
// anchors against, and it trails the in-memory list after a run
|
|
2212
|
+
// whose messages never made it to disk.
|
|
2105
2213
|
const seed = autopilotMarkerCopySeed({
|
|
2106
2214
|
version: 1,
|
|
2107
2215
|
phase: "done",
|
|
2108
|
-
afterMessageCount: session.
|
|
2216
|
+
afterMessageCount: session.getPersistedTranscriptCount(),
|
|
2109
2217
|
});
|
|
2110
2218
|
broadcast(event.type, { ...event.data, copySeed: seed });
|
|
2111
2219
|
void session.persistAutopilotMarker("done");
|
|
@@ -2150,7 +2258,10 @@ async function createSession(deps, opts) {
|
|
|
2150
2258
|
const next = session.takeNextQueuedMessage();
|
|
2151
2259
|
if (!next)
|
|
2152
2260
|
return;
|
|
2153
|
-
broadcast("queued", {
|
|
2261
|
+
broadcast("queued", {
|
|
2262
|
+
count: session.getQueuedCount(),
|
|
2263
|
+
messages: session.listQueuedMessages(),
|
|
2264
|
+
});
|
|
2154
2265
|
if (!next.text.trim() && next.attachments.length === 0)
|
|
2155
2266
|
continue;
|
|
2156
2267
|
// A queued message draining as a fresh turn supersedes any pending
|
|
@@ -2236,7 +2347,7 @@ async function createSession(deps, opts) {
|
|
|
2236
2347
|
const shortId = task.id.slice(0, 8);
|
|
2237
2348
|
const completionHint = `\n\n---\nWhen you have fully completed this task, call the tasks tool to mark it done:\n` +
|
|
2238
2349
|
`tasks({ action: "done", id: "${shortId}" })`;
|
|
2239
|
-
await runAgent(task.title, () => session.prompt(task.prompt + completionHint));
|
|
2350
|
+
await runAgent(task.title, () => session.prompt(task.prompt + completionHint, AUTOMATION_PROVENANCE));
|
|
2240
2351
|
// The agent typically marks the task done via the tasks tool during the run;
|
|
2241
2352
|
// prune completed tasks and push the refreshed list so the modal drops them.
|
|
2242
2353
|
broadcast("tasks_list", { tasks: pruneDoneTasksSync(cwd) });
|
|
@@ -2279,10 +2390,52 @@ async function createSession(deps, opts) {
|
|
|
2279
2390
|
// Native (Rust) writes to auth.json bypass this daemon's cache; re-read so
|
|
2280
2391
|
// the reported connection state matches what's actually on disk.
|
|
2281
2392
|
await auth.reload();
|
|
2282
|
-
const providers = await Promise.all(AUTH_PROVIDERS.map(async (p) =>
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2393
|
+
const providers = await Promise.all(AUTH_PROVIDERS.map(async (p) => {
|
|
2394
|
+
const dual = dualAuthProvider(p.value);
|
|
2395
|
+
// Which methods hold a credential right now. Dual-auth providers can hold
|
|
2396
|
+
// both at once, and the app needs them separately: one "connected" bit
|
|
2397
|
+
// cannot express "OAuth signed in, key also on file as backup", nor offer
|
|
2398
|
+
// a per-method disconnect.
|
|
2399
|
+
const oauthKey = dual?.oauthKey;
|
|
2400
|
+
const hasOAuth = oauthKey ? await auth.hasCredentials(oauthKey) : false;
|
|
2401
|
+
const apiKeyKeys = p.apiKeyVariants?.map((v) => v.key) ?? [p.value];
|
|
2402
|
+
const hasApiKey = p.methods.includes("apikey")
|
|
2403
|
+
? (await Promise.all(apiKeyKeys.map((k) => auth.hasCredentials(k)))).some(Boolean)
|
|
2404
|
+
: false;
|
|
2405
|
+
const connectedMethods = [];
|
|
2406
|
+
// OAuth-only providers (Anthropic/OpenAI/Gemini) store under the provider
|
|
2407
|
+
// id itself, so `hasProviderAuth` is what proves their OAuth connection.
|
|
2408
|
+
if (p.methods.includes("oauth") &&
|
|
2409
|
+
(hasOAuth || (!dual && (await auth.hasCredentials(p.value)))))
|
|
2410
|
+
connectedMethods.push("oauth");
|
|
2411
|
+
if (hasApiKey)
|
|
2412
|
+
connectedMethods.push("apikey");
|
|
2413
|
+
// Which one a request would actually use, mirroring AuthStorage's
|
|
2414
|
+
// resolution: OAuth wins unless its usage window is exhausted AND a key
|
|
2415
|
+
// is configured to cover it.
|
|
2416
|
+
const exhaustedUntil = oauthKey
|
|
2417
|
+
? ((await auth.getCredentials(oauthKey))?.usageExhaustedUntil ?? 0)
|
|
2418
|
+
: 0;
|
|
2419
|
+
const oauthSidelined = hasOAuth && Date.now() < exhaustedUntil && hasApiKey;
|
|
2420
|
+
const activeMethod = connectedMethods.includes("oauth")
|
|
2421
|
+
? oauthSidelined
|
|
2422
|
+
? "apikey"
|
|
2423
|
+
: "oauth"
|
|
2424
|
+
: connectedMethods[0];
|
|
2425
|
+
// `methodDetails` is the server-side lookup table behind
|
|
2426
|
+
// `methodGuidance` — shipping both would duplicate every string on the
|
|
2427
|
+
// wire for no consumer.
|
|
2428
|
+
const { methodDetails: _table, ...wireMeta } = p;
|
|
2429
|
+
return {
|
|
2430
|
+
...wireMeta,
|
|
2431
|
+
connected: await auth.hasProviderAuth(p.value),
|
|
2432
|
+
connectedMethods,
|
|
2433
|
+
...(activeMethod ? { activeMethod } : {}),
|
|
2434
|
+
...(oauthSidelined ? { oauthExhaustedUntil: exhaustedUntil } : {}),
|
|
2435
|
+
...(authPriorityNote(p.value) ? { priorityNote: authPriorityNote(p.value) } : {}),
|
|
2436
|
+
methodGuidance: describeAuthMethods(p.value),
|
|
2437
|
+
};
|
|
2438
|
+
}));
|
|
2286
2439
|
return { providers };
|
|
2287
2440
|
}
|
|
2288
2441
|
// Background tasks have no event source (the bash tool just spawns them), so
|
|
@@ -2469,6 +2622,45 @@ async function createSession(deps, opts) {
|
|
|
2469
2622
|
});
|
|
2470
2623
|
return;
|
|
2471
2624
|
}
|
|
2625
|
+
if (method === "GET" && url === "/plugins") {
|
|
2626
|
+
void listInstalledPlugins(paths.extensionsDir)
|
|
2627
|
+
.then((plugins) => json(res, 200, { plugins }))
|
|
2628
|
+
.catch((error) => {
|
|
2629
|
+
captureSidecarError(error, "app-sidecar.plugins.list");
|
|
2630
|
+
json(res, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
2631
|
+
});
|
|
2632
|
+
return;
|
|
2633
|
+
}
|
|
2634
|
+
if (method === "POST" && url === "/plugins/install") {
|
|
2635
|
+
void readBody(req, res).then(async (raw) => {
|
|
2636
|
+
if (raw === null)
|
|
2637
|
+
return;
|
|
2638
|
+
try {
|
|
2639
|
+
const bundlePath = JSON.parse(raw).bundlePath;
|
|
2640
|
+
if (typeof bundlePath !== "string" || !path.isAbsolute(bundlePath)) {
|
|
2641
|
+
json(res, 400, { error: "bundlePath must be an absolute path" });
|
|
2642
|
+
return;
|
|
2643
|
+
}
|
|
2644
|
+
const plugin = await installPlugin(bundlePath, paths.extensionsDir);
|
|
2645
|
+
json(res, 200, { plugin, restartRequired: true });
|
|
2646
|
+
}
|
|
2647
|
+
catch (error) {
|
|
2648
|
+
captureSidecarError(error, "app-sidecar.plugins.install");
|
|
2649
|
+
json(res, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
2650
|
+
}
|
|
2651
|
+
});
|
|
2652
|
+
return;
|
|
2653
|
+
}
|
|
2654
|
+
if (method === "DELETE" && url.startsWith("/plugins/")) {
|
|
2655
|
+
const pluginId = decodeURIComponent(url.slice("/plugins/".length));
|
|
2656
|
+
void removePlugin(pluginId, paths.extensionsDir)
|
|
2657
|
+
.then(() => json(res, 200, { removed: pluginId, restartRequired: true }))
|
|
2658
|
+
.catch((error) => {
|
|
2659
|
+
captureSidecarError(error, "app-sidecar.plugins.remove");
|
|
2660
|
+
json(res, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
2661
|
+
});
|
|
2662
|
+
return;
|
|
2663
|
+
}
|
|
2472
2664
|
if (method === "GET" && url === "/settings") {
|
|
2473
2665
|
// `configured` is true only when the user explicitly saved a projects root
|
|
2474
2666
|
// (the ezcoder-app.json file exists with a value) — not when we fall back to the
|
|
@@ -2555,9 +2747,53 @@ async function createSession(deps, opts) {
|
|
|
2555
2747
|
});
|
|
2556
2748
|
return;
|
|
2557
2749
|
}
|
|
2750
|
+
// Dismiss a project from the picker (or restore it with hidden:false). The
|
|
2751
|
+
// path is kept rather than the row: sessions in scratch dirs keep
|
|
2752
|
+
// re-surfacing, so the user's decision has to outlive any one scan.
|
|
2753
|
+
if (method === "POST" && url === "/projects/hidden") {
|
|
2754
|
+
void readBody(req, res).then(async (raw) => {
|
|
2755
|
+
if (raw === null)
|
|
2756
|
+
return;
|
|
2757
|
+
let body;
|
|
2758
|
+
try {
|
|
2759
|
+
body = JSON.parse(raw);
|
|
2760
|
+
}
|
|
2761
|
+
catch {
|
|
2762
|
+
json(res, 400, { error: "invalid JSON body" });
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
const target = body.path?.trim();
|
|
2766
|
+
if (!target) {
|
|
2767
|
+
json(res, 400, { error: "path is required" });
|
|
2768
|
+
return;
|
|
2769
|
+
}
|
|
2770
|
+
const key = path.resolve(target);
|
|
2771
|
+
try {
|
|
2772
|
+
const settings = await loadAppSettings();
|
|
2773
|
+
const current = new Set((settings.hiddenProjects ?? []).map((p) => path.resolve(p)));
|
|
2774
|
+
if (body.hidden === false)
|
|
2775
|
+
current.delete(key);
|
|
2776
|
+
else
|
|
2777
|
+
current.add(key);
|
|
2778
|
+
settings.hiddenProjects = current.size > 0 ? Array.from(current) : undefined;
|
|
2779
|
+
await saveAppSettings(settings);
|
|
2780
|
+
json(res, 200, { hidden: Array.from(current) });
|
|
2781
|
+
}
|
|
2782
|
+
catch (err) {
|
|
2783
|
+
captureSidecarError(err, "app-sidecar.projects.hidden");
|
|
2784
|
+
json(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
2785
|
+
}
|
|
2786
|
+
});
|
|
2787
|
+
return;
|
|
2788
|
+
}
|
|
2558
2789
|
if (method === "GET" && url === "/projects") {
|
|
2559
|
-
//
|
|
2560
|
-
void
|
|
2790
|
+
// Session stores plus configured project folders, including unopened projects.
|
|
2791
|
+
void loadAppSettings()
|
|
2792
|
+
.then(({ projectsRoot, projectRoots, hiddenProjects }) => discoverProjects({
|
|
2793
|
+
projectsRoot,
|
|
2794
|
+
extraRoots: projectRoots,
|
|
2795
|
+
hiddenPaths: hiddenProjects,
|
|
2796
|
+
}))
|
|
2561
2797
|
.then((projects) => json(res, 200, { projects }))
|
|
2562
2798
|
.catch((err) => {
|
|
2563
2799
|
captureSidecarError(err, "app-sidecar.projects.discover");
|
|
@@ -2667,7 +2903,7 @@ async function createSession(deps, opts) {
|
|
|
2667
2903
|
// Nolan (mentor) turns to interleave: group by the non-system message count
|
|
2668
2904
|
// they were recorded after, so each lands right after that message. A
|
|
2669
2905
|
// turn becomes two wire rows: the `@Nolan` question (user) + Nolan's reply
|
|
2670
|
-
// (assistant), both flagged `
|
|
2906
|
+
// (assistant), both flagged `nolan` so the webview tints them.
|
|
2671
2907
|
// Deduped; stale anchors are clamped to the last message (Nolan turns
|
|
2672
2908
|
// carry real conversation, so they render at the end instead of
|
|
2673
2909
|
// vanishing).
|
|
@@ -2683,16 +2919,16 @@ async function createSession(deps, opts) {
|
|
|
2683
2919
|
return;
|
|
2684
2920
|
nolanByCount.delete(count);
|
|
2685
2921
|
for (const turn of turns) {
|
|
2686
|
-
history.push({ role: "user", text: `@Nolan ${turn.question}`,
|
|
2687
|
-
history.push({ role: "assistant", text: turn.reply,
|
|
2922
|
+
history.push({ role: "user", text: `@Nolan ${turn.question}`, nolan: true });
|
|
2923
|
+
history.push({ role: "assistant", text: turn.reply, nolan: true });
|
|
2688
2924
|
}
|
|
2689
2925
|
};
|
|
2690
2926
|
// Autopilot verdict markers to interleave, same anchor scheme as Nolan
|
|
2691
2927
|
// turns — each becomes a single assistant row the webview renders
|
|
2692
2928
|
// exactly like the live `autopilot` item (never a raw verdict string).
|
|
2693
|
-
//
|
|
2694
|
-
//
|
|
2695
|
-
//
|
|
2929
|
+
// Normalization pulls anchors left over from an unrebased compaction
|
|
2930
|
+
// back to where the marker was actually written, so stale all-clear
|
|
2931
|
+
// bubbles no longer bunch at the bottom of a reopened session.
|
|
2696
2932
|
const restoredMessageCount = messages.filter((m) => m.role !== "system").length;
|
|
2697
2933
|
const autopilotByCount = new Map();
|
|
2698
2934
|
for (const marker of normalizeAutopilotMarkersForHistory(session.getAutopilotMarkers(), restoredMessageCount)) {
|
|
@@ -2776,6 +3012,21 @@ async function createSession(deps, opts) {
|
|
|
2776
3012
|
},
|
|
2777
3013
|
});
|
|
2778
3014
|
}
|
|
3015
|
+
else if (marker.kind === "interrupted_run") {
|
|
3016
|
+
// Rendered as an error row: the run's tools already changed the
|
|
3017
|
+
// repo, so the user needs to see it and decide what to do. We
|
|
3018
|
+
// never replay it — that would duplicate those changes.
|
|
3019
|
+
history.push({
|
|
3020
|
+
role: "assistant",
|
|
3021
|
+
text: "",
|
|
3022
|
+
error: {
|
|
3023
|
+
scope: "interrupted_run",
|
|
3024
|
+
headline: "A run was interrupted",
|
|
3025
|
+
message: "EZ Coder stopped mid-run, so this turn is incomplete. Any files its tools already changed are still on disk.",
|
|
3026
|
+
guidance: "Review the working tree, then re-send the request if you still want it.",
|
|
3027
|
+
},
|
|
3028
|
+
});
|
|
3029
|
+
}
|
|
2779
3030
|
}
|
|
2780
3031
|
};
|
|
2781
3032
|
let nonSystemCount = 0;
|
|
@@ -2784,10 +3035,11 @@ async function createSession(deps, opts) {
|
|
|
2784
3035
|
flushNolan(0);
|
|
2785
3036
|
flushAutopilot(0);
|
|
2786
3037
|
flushAppMarkers(0);
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
3038
|
+
await replayMessagesInOrder(messages, async (msg, count) => {
|
|
3039
|
+
nonSystemCount = count;
|
|
3040
|
+
const visibility = getHistoryMessageVisibility(msg);
|
|
3041
|
+
if (visibility === "hidden")
|
|
3042
|
+
return;
|
|
2791
3043
|
if (msg.role === "tool") {
|
|
2792
3044
|
// Tool result messages: check for ImageContent blocks (screenshots,
|
|
2793
3045
|
// generated images) and emit a toolImages entry.
|
|
@@ -2831,20 +3083,39 @@ async function createSession(deps, opts) {
|
|
|
2831
3083
|
});
|
|
2832
3084
|
}
|
|
2833
3085
|
}
|
|
2834
|
-
|
|
3086
|
+
return;
|
|
2835
3087
|
}
|
|
2836
3088
|
// User or assistant message — text/hook/command/compacted extraction,
|
|
2837
3089
|
// plus sub-agent group detection for assistant tool_calls.
|
|
2838
3090
|
if (msg.role === "user") {
|
|
2839
3091
|
// Rebuild the live bubble: strip the steering wrapper, drop
|
|
2840
3092
|
// attachment/file notes the model saw but the bubble never showed.
|
|
2841
|
-
const restored = restoreUserRow(msg.content);
|
|
3093
|
+
const restored = restoreUserRow(msg.content, msg.provenance);
|
|
2842
3094
|
const text = restored.text;
|
|
2843
|
-
const hook = detectHookKind(text);
|
|
2844
|
-
const compacted =
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
3095
|
+
const hook = msg.provenance ? null : detectHookKind(text);
|
|
3096
|
+
const compacted = visibility === "summary" ||
|
|
3097
|
+
(!msg.provenance && !hook && text.startsWith("[Previous conversation summary]"));
|
|
3098
|
+
const hint = userHintByCount.get(nonSystemCount);
|
|
3099
|
+
// The typed invocation persisted alongside the prompt is
|
|
3100
|
+
// authoritative. Reversing the expanded body only works while the
|
|
3101
|
+
// template is byte-identical, and templates drift (edited
|
|
3102
|
+
// `.ezcoder/commands/*.md`, reworded built-ins, app-vs-CLI phrasing) —
|
|
3103
|
+
// after which the resumed session dumped the raw multi-KB body
|
|
3104
|
+
// instead of the `/name` chip. Older sessions have no hint, so the
|
|
3105
|
+
// body match stays as the fallback.
|
|
3106
|
+
const command = !hook && !compacted
|
|
3107
|
+
? resolveRestoredCommand(typeof hint?.command === "string" ? hint.command : null, text, commandCandidates)
|
|
3108
|
+
: null;
|
|
3109
|
+
// Autopilot injected this turn — live showed only the Nolan-tinted
|
|
3110
|
+
// marker for it, never a user bubble. Emitting one here would print
|
|
3111
|
+
// the injected instruction a second time, unstyled.
|
|
3112
|
+
//
|
|
3113
|
+
// Pushed background-status updates are skipped for the same reason:
|
|
3114
|
+
// the live run rendered no bubble for them, so showing them here
|
|
3115
|
+
// would fill a reopened session with machine-facing status lines
|
|
3116
|
+
// the user never saw while working.
|
|
3117
|
+
if ((!msg.provenance || (!restored.autopilotInjected && !restored.notification)) &&
|
|
3118
|
+
(text.trim() || restored.images.length > 0)) {
|
|
2848
3119
|
history.push({
|
|
2849
3120
|
role: "user",
|
|
2850
3121
|
text: command ?? text,
|
|
@@ -2907,12 +3178,13 @@ async function createSession(deps, opts) {
|
|
|
2907
3178
|
});
|
|
2908
3179
|
}
|
|
2909
3180
|
}
|
|
2910
|
-
|
|
2911
|
-
// after
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
3181
|
+
}, (count) => {
|
|
3182
|
+
// Markers flush after every physical message, including tools and
|
|
3183
|
+
// provenance-hidden runtime context.
|
|
3184
|
+
flushNolan(count);
|
|
3185
|
+
flushAutopilot(count);
|
|
3186
|
+
flushAppMarkers(count);
|
|
3187
|
+
});
|
|
2916
3188
|
// Flush remaining Nolan turns whose anchor is at/after the message count so
|
|
2917
3189
|
// none are dropped. Autopilot/app markers beyond the restored message
|
|
2918
3190
|
// count were already filtered above; any remaining marker here is valid.
|
|
@@ -2973,7 +3245,12 @@ async function createSession(deps, opts) {
|
|
|
2973
3245
|
return;
|
|
2974
3246
|
}
|
|
2975
3247
|
if (method === "POST" && url === "/prompt") {
|
|
2976
|
-
|
|
3248
|
+
// Per-request: only the prompt that actually claimed the start may release
|
|
3249
|
+
// it. A bare release would let an early-returning request (bad JSON, or a
|
|
3250
|
+
// prompt that queued) clear a claim another request is still holding.
|
|
3251
|
+
let claimedStart = false;
|
|
3252
|
+
void readBody(req, res)
|
|
3253
|
+
.then(async (raw) => {
|
|
2977
3254
|
if (raw === null)
|
|
2978
3255
|
return;
|
|
2979
3256
|
let text;
|
|
@@ -2993,14 +3270,17 @@ async function createSession(deps, opts) {
|
|
|
2993
3270
|
json(res, 400, { error: "empty prompt" });
|
|
2994
3271
|
return;
|
|
2995
3272
|
}
|
|
2996
|
-
if (runLifecycle.running &&
|
|
3273
|
+
if (runLifecycle.running &&
|
|
3274
|
+
runLifecycle.isCancellationRequested(runLifecycle.generation)) {
|
|
2997
3275
|
json(res, 409, {
|
|
2998
3276
|
error: runLifecycle.state === "cancelling" ? "run_cancelling" : "cancel_failed",
|
|
2999
3277
|
runState: runLifecycle.state,
|
|
3000
3278
|
});
|
|
3001
3279
|
return;
|
|
3002
3280
|
}
|
|
3003
|
-
|
|
3281
|
+
// `runClaim` covers the gap before `runAgent` flips `running`: a
|
|
3282
|
+
// prompt arriving in that window must queue, not start a second run.
|
|
3283
|
+
if (running || runClaim.active || autopilotActive) {
|
|
3004
3284
|
// Queue prompts as mid-run steering (mirrors the CLI). Also queue while
|
|
3005
3285
|
// an autopilot cycle is active but between injected runs (build idle,
|
|
3006
3286
|
// Nolan reviewing) so the message never starts a run that collides with
|
|
@@ -3009,20 +3289,47 @@ async function createSession(deps, opts) {
|
|
|
3009
3289
|
// path as a non-queued attachment prompt when it drains.
|
|
3010
3290
|
const prepared = attachments.length > 0 ? await prepareAttachments(cwd, attachments) : [];
|
|
3011
3291
|
const count = session.queueMessage(text, prepared);
|
|
3012
|
-
broadcast("queued", { count });
|
|
3292
|
+
broadcast("queued", { count, messages: session.listQueuedMessages() });
|
|
3013
3293
|
json(res, 202, { queued: true, count });
|
|
3014
3294
|
return;
|
|
3015
3295
|
}
|
|
3296
|
+
// Claim the run NOW, synchronously. Everything below this line may
|
|
3297
|
+
// yield, and `running` does not flip until runAgent begins.
|
|
3298
|
+
claimedStart = runClaim.claim();
|
|
3016
3299
|
json(res, 202, { accepted: true });
|
|
3300
|
+
// Gate inputs captured around the run: whether this turn is a workflow
|
|
3301
|
+
// slash command (attachment prompts skip slash expansion entirely), and
|
|
3302
|
+
// how many assistant messages the run actually adds. Computed even when
|
|
3303
|
+
// autopilot is currently off — the toggle can flip ON mid-run, and the
|
|
3304
|
+
// gate reads the post-run value.
|
|
3305
|
+
const workflowCommand = attachments.length === 0 &&
|
|
3306
|
+
isWorkflowCommandText(text, await loadWorkflowCommandSpecs());
|
|
3307
|
+
// Does this input actually expand into a persisted user message? Asked
|
|
3308
|
+
// of the session itself, because only it knows whether the command
|
|
3309
|
+
// resolves here (name/alias casing, custom `.ezcoder/commands`, non-coder
|
|
3310
|
+
// agents that don't expand at all). A looser guess would anchor the
|
|
3311
|
+
// hint at +1 with no message to land on — decorating an unrelated
|
|
3312
|
+
// later bubble with the wrong `/name`.
|
|
3313
|
+
const expandsToTemplate = attachments.length === 0 && (await session.willExpandPromptTemplate(text));
|
|
3017
3314
|
// Webview display hint for this prompt's user bubble (nolanSent shimmer
|
|
3018
|
-
// label / enhancer highlight segments
|
|
3019
|
-
// the user message the prompt below
|
|
3020
|
-
// skip this (their position in the run
|
|
3021
|
-
|
|
3315
|
+
// label / enhancer highlight segments / the `/name` a command was typed
|
|
3316
|
+
// as). Anchored +1 so it attaches to the user message the prompt below
|
|
3317
|
+
// is about to push. Queued prompts skip this (their position in the run
|
|
3318
|
+
// is unpredictable).
|
|
3319
|
+
//
|
|
3320
|
+
// Recording the invocation matters because the agent persists the
|
|
3321
|
+
// EXPANDED template as the user message. Resume used to recover
|
|
3322
|
+
// `/name` by matching that body against the current templates, which
|
|
3323
|
+
// silently fails the moment a template is edited or reworded — the
|
|
3324
|
+
// reopened session then rendered the raw multi-KB prompt instead of
|
|
3325
|
+
// the command chip.
|
|
3326
|
+
if (expandsToTemplate ||
|
|
3327
|
+
(meta && (meta.nolanSent === true || Array.isArray(meta.enhancements)))) {
|
|
3022
3328
|
void session
|
|
3023
3329
|
.persistAppMarker("user_hint", {
|
|
3024
|
-
...(
|
|
3025
|
-
...(
|
|
3330
|
+
...(expandsToTemplate ? { command: text.trim() } : {}),
|
|
3331
|
+
...(meta?.nolanSent === true ? { nolanSent: true } : {}),
|
|
3332
|
+
...(Array.isArray(meta?.enhancements) ? { enhancements: meta.enhancements } : {}),
|
|
3026
3333
|
}, 1)
|
|
3027
3334
|
.catch(() => { });
|
|
3028
3335
|
}
|
|
@@ -3033,12 +3340,6 @@ async function createSession(deps, opts) {
|
|
|
3033
3340
|
// feedback, anything) supersedes the pending plan — the bump also
|
|
3034
3341
|
// invalidates any in-flight Nolan plan review.
|
|
3035
3342
|
clearPendingPlan();
|
|
3036
|
-
// Gate inputs captured around the run: whether this turn is a workflow
|
|
3037
|
-
// slash command (attachment prompts skip slash expansion entirely), and
|
|
3038
|
-
// how many assistant messages the run actually adds. Computed even when
|
|
3039
|
-
// autopilot is currently off — the toggle can flip ON mid-run, and the
|
|
3040
|
-
// gate reads the post-run value.
|
|
3041
|
-
const workflowCommand = attachments.length === 0 && isWorkflowCommandText(text, await loadWorkflowCommandSpecs());
|
|
3042
3343
|
const assistantsBefore = countAssistantMessages(session.getMessages());
|
|
3043
3344
|
const messagesBefore = session.getMessages().length;
|
|
3044
3345
|
await runAgent(text, async () => {
|
|
@@ -3090,14 +3391,18 @@ async function createSession(deps, opts) {
|
|
|
3090
3391
|
// A prompt sent while Nolan was reviewing (build idle) queued but had no
|
|
3091
3392
|
// run to steer into — run it now as a fresh turn so it never strands.
|
|
3092
3393
|
await runStrandedQueue();
|
|
3394
|
+
})
|
|
3395
|
+
.finally(() => {
|
|
3396
|
+
if (claimedStart)
|
|
3397
|
+
runClaim.release();
|
|
3093
3398
|
});
|
|
3094
3399
|
return;
|
|
3095
3400
|
}
|
|
3096
3401
|
// Nolan Grout (mentor): an independent read-only advisory run on the nolanSession.
|
|
3097
|
-
// Runs concurrently with a build run — its events are
|
|
3402
|
+
// Runs concurrently with a build run — its events are nolan_-prefixed so the
|
|
3098
3403
|
// webview keeps the bubbles separate. The context digest is assembled fresh
|
|
3099
3404
|
// from the BUILD session's transcript each turn (one-way mirror).
|
|
3100
|
-
if (method === "POST" && url === "/
|
|
3405
|
+
if (method === "POST" && url === "/nolan/prompt") {
|
|
3101
3406
|
if (mode === "chat") {
|
|
3102
3407
|
json(res, 404, { error: "Nolan is not available in EZ Chat." });
|
|
3103
3408
|
return;
|
|
@@ -3125,18 +3430,18 @@ async function createSession(deps, opts) {
|
|
|
3125
3430
|
nolanRunning = true;
|
|
3126
3431
|
broadcast("nolan_run_start", { text });
|
|
3127
3432
|
try {
|
|
3128
|
-
const
|
|
3433
|
+
const nolanAgent = await ensureNolanSession();
|
|
3129
3434
|
const digest = await buildNolanContext(session, cwd, gitBranch, text, await loadWorkflowCommandSpecs(), injectedAutopilotPrompts);
|
|
3130
|
-
await
|
|
3435
|
+
await nolanAgent.prompt(digest);
|
|
3131
3436
|
// Record the turn against the BUILD session so it persists + survives
|
|
3132
3437
|
// resume (advisory custom entry, never an LLM message). Reply is Nolan's
|
|
3133
3438
|
// last assistant message; skip persistence if he produced nothing.
|
|
3134
|
-
const reply = lastAssistantText(
|
|
3439
|
+
const reply = lastAssistantText(nolanAgent.getMessages());
|
|
3135
3440
|
if (reply.trim())
|
|
3136
3441
|
await session.persistNolanTurn(text, reply);
|
|
3137
3442
|
}
|
|
3138
3443
|
catch (err) {
|
|
3139
|
-
broadcastError("nolan_error", "
|
|
3444
|
+
broadcastError("nolan_error", "nolan run failed", err);
|
|
3140
3445
|
}
|
|
3141
3446
|
finally {
|
|
3142
3447
|
nolanRunning = false;
|
|
@@ -3149,7 +3454,7 @@ async function createSession(deps, opts) {
|
|
|
3149
3454
|
});
|
|
3150
3455
|
return;
|
|
3151
3456
|
}
|
|
3152
|
-
if (method === "POST" && url === "/
|
|
3457
|
+
if (method === "POST" && url === "/nolan/cancel") {
|
|
3153
3458
|
nolanAbort.abort();
|
|
3154
3459
|
nolanAbort = new AbortController();
|
|
3155
3460
|
nolanSession?.setSignal(nolanAbort.signal);
|
|
@@ -3524,7 +3829,7 @@ async function createSession(deps, opts) {
|
|
|
3524
3829
|
// no-op event (keeps footer/event tests from treating an EZ Coder
|
|
3525
3830
|
// switch as a Nolan switch).
|
|
3526
3831
|
if (!nolanModelOverride)
|
|
3527
|
-
broadcast("
|
|
3832
|
+
broadcast("nolan_model_change", nolanStatePayload());
|
|
3528
3833
|
// The new model usually has a different context window — push extras so
|
|
3529
3834
|
// the footer's context meter rescales immediately.
|
|
3530
3835
|
broadcast("extras", footerExtras());
|
|
@@ -3536,7 +3841,7 @@ async function createSession(deps, opts) {
|
|
|
3536
3841
|
// { model: null } / "" to clear (Nolan resumes following EZ Coder). Applies
|
|
3537
3842
|
// to BOTH Nolan sessions (chat + autopilot reviewer); a switch landing while
|
|
3538
3843
|
// either is mid-run defers via the pending-model mechanics.
|
|
3539
|
-
if (method === "POST" && url === "/
|
|
3844
|
+
if (method === "POST" && url === "/nolan/model") {
|
|
3540
3845
|
void readBody(req, res).then(async (raw) => {
|
|
3541
3846
|
if (raw === null)
|
|
3542
3847
|
return;
|
|
@@ -3571,17 +3876,49 @@ async function createSession(deps, opts) {
|
|
|
3571
3876
|
await saveNolanModelPref(cwd, nolanModelOverride);
|
|
3572
3877
|
await syncNolanModel(target.provider, target.id);
|
|
3573
3878
|
await syncNolanAutoModel(target.provider, target.id);
|
|
3574
|
-
log("INFO", "app-sidecar", "
|
|
3879
|
+
log("INFO", "app-sidecar", "nolan model pinned", {
|
|
3575
3880
|
provider: target.provider,
|
|
3576
3881
|
model: target.id,
|
|
3577
3882
|
});
|
|
3578
3883
|
}
|
|
3579
3884
|
const payload = nolanStatePayload();
|
|
3580
|
-
broadcast("
|
|
3885
|
+
broadcast("nolan_model_change", payload);
|
|
3581
3886
|
json(res, 200, payload);
|
|
3582
3887
|
});
|
|
3583
3888
|
return;
|
|
3584
3889
|
}
|
|
3890
|
+
// Pending queued steering, for the composer's cancel affordance.
|
|
3891
|
+
if (method === "GET" && url === "/queued") {
|
|
3892
|
+
json(res, 200, { queued: session.listQueuedMessages() });
|
|
3893
|
+
return;
|
|
3894
|
+
}
|
|
3895
|
+
// Cancel one pending queued message by id.
|
|
3896
|
+
if (method === "POST" && url === "/queued/cancel") {
|
|
3897
|
+
void readBody(req, res).then((raw) => {
|
|
3898
|
+
if (raw === null)
|
|
3899
|
+
return;
|
|
3900
|
+
let id;
|
|
3901
|
+
try {
|
|
3902
|
+
id = JSON.parse(raw).id ?? "";
|
|
3903
|
+
}
|
|
3904
|
+
catch {
|
|
3905
|
+
json(res, 400, { error: "invalid JSON body" });
|
|
3906
|
+
return;
|
|
3907
|
+
}
|
|
3908
|
+
if (!id.trim()) {
|
|
3909
|
+
json(res, 400, { error: "missing queued message id" });
|
|
3910
|
+
return;
|
|
3911
|
+
}
|
|
3912
|
+
// `false` means it already drained into the run between render and
|
|
3913
|
+
// click. That is a race, not an error, so report it as a normal result
|
|
3914
|
+
// and let the client reconcile from the fresh list.
|
|
3915
|
+
const cancelled = session.cancelQueuedMessage(id);
|
|
3916
|
+
const queued = session.listQueuedMessages();
|
|
3917
|
+
broadcast("queued", { count: queued.length, messages: queued });
|
|
3918
|
+
json(res, 200, { cancelled, queued });
|
|
3919
|
+
});
|
|
3920
|
+
return;
|
|
3921
|
+
}
|
|
3585
3922
|
if (method === "POST" && url === "/kill") {
|
|
3586
3923
|
void readBody(req, res).then(async (raw) => {
|
|
3587
3924
|
if (raw === null)
|
|
@@ -3605,6 +3942,33 @@ async function createSession(deps, opts) {
|
|
|
3605
3942
|
});
|
|
3606
3943
|
return;
|
|
3607
3944
|
}
|
|
3945
|
+
// Import a Claude Code / Codex / Cursor transcript into a resumable GG
|
|
3946
|
+
// Coder session. The importer never throws — it returns a typed failure so
|
|
3947
|
+
// the app can show the reason verbatim.
|
|
3948
|
+
if (method === "POST" && url === "/import-transcript") {
|
|
3949
|
+
void readBody(req, res).then(async (raw) => {
|
|
3950
|
+
if (raw === null)
|
|
3951
|
+
return;
|
|
3952
|
+
let body;
|
|
3953
|
+
try {
|
|
3954
|
+
body = JSON.parse(raw);
|
|
3955
|
+
}
|
|
3956
|
+
catch {
|
|
3957
|
+
json(res, 400, { error: "invalid JSON body" });
|
|
3958
|
+
return;
|
|
3959
|
+
}
|
|
3960
|
+
const filePath = body.path?.trim();
|
|
3961
|
+
if (!filePath) {
|
|
3962
|
+
json(res, 400, { error: "missing transcript path" });
|
|
3963
|
+
return;
|
|
3964
|
+
}
|
|
3965
|
+
const result = await session.importForeignTranscript(filePath, {
|
|
3966
|
+
...(body.cwd ? { cwd: body.cwd } : {}),
|
|
3967
|
+
});
|
|
3968
|
+
json(res, result.ok ? 200 : 400, result);
|
|
3969
|
+
});
|
|
3970
|
+
return;
|
|
3971
|
+
}
|
|
3608
3972
|
if (method === "POST" && url === "/thinking") {
|
|
3609
3973
|
const st = session.getState();
|
|
3610
3974
|
const next = getNextThinkingLevel(st.provider, st.model, session.getThinkingLevel());
|
|
@@ -3639,7 +4003,7 @@ async function createSession(deps, opts) {
|
|
|
3639
4003
|
const generation = runLifecycle.generation;
|
|
3640
4004
|
if (!pendingCancelDrain || pendingCancelDrain.generation !== generation) {
|
|
3641
4005
|
pendingCancelDrain = { generation, text: session.drainQueue() };
|
|
3642
|
-
broadcast("queued", { count: 0 });
|
|
4006
|
+
broadcast("queued", { count: 0, messages: [] });
|
|
3643
4007
|
}
|
|
3644
4008
|
const result = await runLifecycle.cancel(CANCEL_TIMEOUT_MS);
|
|
3645
4009
|
const drained = pendingCancelDrain.text;
|
|
@@ -3799,7 +4163,17 @@ async function createSession(deps, opts) {
|
|
|
3799
4163
|
...(baseUrl ? { baseUrl } : {}),
|
|
3800
4164
|
};
|
|
3801
4165
|
await auth.setCredentials(storageKey, creds);
|
|
3802
|
-
|
|
4166
|
+
// auth.json is shared by every window, so this is a global change:
|
|
4167
|
+
// close their login modals and refresh their provider lists too.
|
|
4168
|
+
broadcastAll("auth_done", { provider });
|
|
4169
|
+
// `auth_done` means "a login succeeded" (modals close on it).
|
|
4170
|
+
// `auth_change` means "auth.json changed" — which a DISCONNECT also is,
|
|
4171
|
+
// so connection state has one signal that covers both directions.
|
|
4172
|
+
broadcastAll("auth_change", { provider });
|
|
4173
|
+
// A newly connected provider unlocks its models. `/models` filters on
|
|
4174
|
+
// who is logged in, so every window's picker is now stale — without
|
|
4175
|
+
// this the new models don't appear until the session is reopened.
|
|
4176
|
+
broadcastAll("models_change", {});
|
|
3803
4177
|
json(res, 200, { ok: true });
|
|
3804
4178
|
});
|
|
3805
4179
|
return;
|
|
@@ -3825,7 +4199,18 @@ async function createSession(deps, opts) {
|
|
|
3825
4199
|
json(res, 409, { error: "a login is already in progress" });
|
|
3826
4200
|
return;
|
|
3827
4201
|
}
|
|
4202
|
+
// A login writes the shared ~/.ezcoder/auth.json, so two windows racing the
|
|
4203
|
+
// same provider means two browser tabs and two token exchanges whose
|
|
4204
|
+
// writes clobber each other. The per-session flag above cannot see
|
|
4205
|
+
// that — guard the provider daemon-wide as well.
|
|
4206
|
+
if (oauthInFlightProviders.has(provider)) {
|
|
4207
|
+
json(res, 409, {
|
|
4208
|
+
error: `a ${meta.label} login is already in progress in another window`,
|
|
4209
|
+
});
|
|
4210
|
+
return;
|
|
4211
|
+
}
|
|
3828
4212
|
oauthInFlight = true;
|
|
4213
|
+
oauthInFlightProviders.add(provider);
|
|
3829
4214
|
json(res, 202, { accepted: true });
|
|
3830
4215
|
void (async () => {
|
|
3831
4216
|
const cb = authCallbacks();
|
|
@@ -3838,18 +4223,29 @@ async function createSession(deps, opts) {
|
|
|
3838
4223
|
creds = await loginOpenAI(cb);
|
|
3839
4224
|
else if (provider === "gemini")
|
|
3840
4225
|
creds = await loginGemini(cb);
|
|
3841
|
-
else if (provider === "moonshot") {
|
|
3842
|
-
|
|
3843
|
-
|
|
4226
|
+
else if (provider === "moonshot" || provider === "xai") {
|
|
4227
|
+
// Subscription OAuth (Kimi plan / SuperGrok-X Premium) stores under
|
|
4228
|
+
// a distinct key so it can coexist with the provider's API key.
|
|
4229
|
+
creds = provider === "moonshot" ? await loginKimi(cb) : await loginXai(cb);
|
|
4230
|
+
storageKey = dualAuthProvider(provider).oauthKey;
|
|
3844
4231
|
}
|
|
3845
4232
|
else {
|
|
3846
4233
|
throw new Error(`OAuth not implemented for ${provider}`);
|
|
3847
4234
|
}
|
|
3848
4235
|
await auth.setCredentials(storageKey, creds);
|
|
3849
|
-
|
|
4236
|
+
// Terminal outcome of a GLOBAL change: every window's login modal
|
|
4237
|
+
// should close and its provider list refresh, not just the one
|
|
4238
|
+
// that started the flow.
|
|
4239
|
+
broadcastAll("auth_done", { provider });
|
|
4240
|
+
broadcastAll("auth_change", { provider });
|
|
4241
|
+
// The OAuth provider's models just became selectable everywhere.
|
|
4242
|
+
broadcastAll("models_change", {});
|
|
3850
4243
|
}
|
|
3851
4244
|
catch (err) {
|
|
3852
4245
|
captureSidecarError(err, "app-sidecar.auth.oauth", { provider });
|
|
4246
|
+
// Deliberately session-scoped: this is the outcome of ONE window's
|
|
4247
|
+
// attempt. Another window that never pressed Connect has nothing to
|
|
4248
|
+
// show an error about, and its modal correctly still offers login.
|
|
3853
4249
|
broadcast("auth_error", {
|
|
3854
4250
|
provider,
|
|
3855
4251
|
message: err instanceof Error ? err.message : String(err),
|
|
@@ -3857,6 +4253,7 @@ async function createSession(deps, opts) {
|
|
|
3857
4253
|
}
|
|
3858
4254
|
finally {
|
|
3859
4255
|
oauthInFlight = false;
|
|
4256
|
+
oauthInFlightProviders.delete(provider);
|
|
3860
4257
|
pendingCode = null;
|
|
3861
4258
|
}
|
|
3862
4259
|
})();
|
|
@@ -3885,27 +4282,72 @@ async function createSession(deps, opts) {
|
|
|
3885
4282
|
});
|
|
3886
4283
|
return;
|
|
3887
4284
|
}
|
|
4285
|
+
if (method === "POST" && url.startsWith("/mcp/elicit/")) {
|
|
4286
|
+
const id = decodeURIComponent(url.slice("/mcp/elicit/".length));
|
|
4287
|
+
void readBody(req, res).then((raw) => {
|
|
4288
|
+
if (raw === null)
|
|
4289
|
+
return;
|
|
4290
|
+
let result;
|
|
4291
|
+
try {
|
|
4292
|
+
const parsed = JSON.parse(raw);
|
|
4293
|
+
if (parsed.action !== "accept" &&
|
|
4294
|
+
parsed.action !== "decline" &&
|
|
4295
|
+
parsed.action !== "cancel") {
|
|
4296
|
+
json(res, 400, { error: "action must be accept, decline, or cancel" });
|
|
4297
|
+
return;
|
|
4298
|
+
}
|
|
4299
|
+
result =
|
|
4300
|
+
parsed.action === "accept"
|
|
4301
|
+
? { action: "accept", content: parsed.content ?? {} }
|
|
4302
|
+
: { action: parsed.action };
|
|
4303
|
+
}
|
|
4304
|
+
catch {
|
|
4305
|
+
json(res, 400, { error: "invalid JSON body" });
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
4308
|
+
// Unknown id means it already timed out or was cancelled by an abort —
|
|
4309
|
+
// the tool call has moved on, so the answer has nowhere to go.
|
|
4310
|
+
if (!elicitations.settle(id, result)) {
|
|
4311
|
+
json(res, 409, { error: "no elicitation is awaiting a response" });
|
|
4312
|
+
return;
|
|
4313
|
+
}
|
|
4314
|
+
json(res, 200, { ok: true });
|
|
4315
|
+
});
|
|
4316
|
+
return;
|
|
4317
|
+
}
|
|
3888
4318
|
if (method === "POST" && url === "/auth/logout") {
|
|
3889
4319
|
void readBody(req, res).then(async (raw) => {
|
|
3890
4320
|
if (raw === null)
|
|
3891
4321
|
return;
|
|
3892
4322
|
let provider;
|
|
4323
|
+
let logoutMethod;
|
|
3893
4324
|
try {
|
|
3894
|
-
|
|
4325
|
+
const body = JSON.parse(raw);
|
|
4326
|
+
provider = body.provider ?? "";
|
|
4327
|
+
logoutMethod =
|
|
4328
|
+
body.method === "oauth" || body.method === "apikey" ? body.method : undefined;
|
|
3895
4329
|
}
|
|
3896
4330
|
catch {
|
|
3897
4331
|
json(res, 400, { error: "invalid JSON body" });
|
|
3898
4332
|
return;
|
|
3899
4333
|
}
|
|
3900
|
-
|
|
3901
|
-
//
|
|
3902
|
-
//
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
4334
|
+
const dual = dualAuthProvider(provider);
|
|
4335
|
+
// A dual-auth provider can be disconnected one method at a time: dropping
|
|
4336
|
+
// a spent API key should not sign the user out of their subscription, and
|
|
4337
|
+
// vice versa. Omitting `method` still clears everything (the plain
|
|
4338
|
+
// "Disconnect" action).
|
|
4339
|
+
if (logoutMethod !== "apikey") {
|
|
4340
|
+
await auth.clearCredentials(dual ? dual.oauthKey : provider);
|
|
4341
|
+
}
|
|
4342
|
+
if (logoutMethod !== "oauth") {
|
|
4343
|
+
// Non-dual providers store their only credential under the provider id,
|
|
4344
|
+
// so this covers both them and a dual provider's API key.
|
|
4345
|
+
await auth.clearCredentials(provider);
|
|
4346
|
+
// Xiaomi's API Credits credential lives under a distinct key — clear it
|
|
4347
|
+
// too so "disconnect" fully removes both the Token Plan and Credits keys.
|
|
4348
|
+
if (provider === "xiaomi")
|
|
4349
|
+
await auth.clearCredentials(XIAOMI_CREDITS_KEY);
|
|
4350
|
+
}
|
|
3909
4351
|
broadcast("auth_done", { provider });
|
|
3910
4352
|
json(res, 200, { ok: true });
|
|
3911
4353
|
});
|
|
@@ -4270,6 +4712,7 @@ async function createSession(deps, opts) {
|
|
|
4270
4712
|
json(res, 404, { error: "not found" });
|
|
4271
4713
|
}
|
|
4272
4714
|
async function dispose() {
|
|
4715
|
+
elicitations.cancelAll();
|
|
4273
4716
|
tasksPollStopped = true;
|
|
4274
4717
|
if (tasksPoll)
|
|
4275
4718
|
clearTimeout(tasksPoll);
|