@prestyj/cli 5.10.0 → 5.11.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/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 +282 -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 +95 -0
- package/dist/app-sidecar-sessions.test.js.map +1 -1
- package/dist/app-sidecar.js +497 -128
- package/dist/app-sidecar.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 +167 -47
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -0
- package/dist/config.js.map +1 -1
- package/dist/config.test.js +10 -1
- 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 +92 -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.d.ts +214 -10
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +978 -212
- package/dist/core/agent-session.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/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/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/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/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.d.ts +15 -0
- package/dist/core/lsp/client.d.ts.map +1 -1
- package/dist/core/lsp/client.js +39 -1
- package/dist/core/lsp/client.js.map +1 -1
- package/dist/core/lsp/manager.d.ts +47 -9
- package/dist/core/lsp/manager.d.ts.map +1 -1
- package/dist/core/lsp/manager.js +96 -69
- 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 +2 -14
- 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 +476 -35
- package/dist/core/mcp/client.js.map +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/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/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-prompt.js +3 -0
- 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/persistent-shell.d.ts +3 -2
- package/dist/core/persistent-shell.d.ts.map +1 -1
- package/dist/core/persistent-shell.js +10 -3
- package/dist/core/persistent-shell.js.map +1 -1
- 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-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 +217 -0
- package/dist/core/process-manager-notifications.test.js.map +1 -0
- package/dist/core/process-manager.d.ts +50 -1
- package/dist/core/process-manager.d.ts.map +1 -1
- package/dist/core/process-manager.js +189 -2
- package/dist/core/process-manager.js.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 +287 -31
- package/dist/core/project-discovery.js.map +1 -1
- package/dist/core/project-discovery.test.js +124 -0
- package/dist/core/project-discovery.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/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 +18 -1
- 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 +515 -26
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-manager.test.js +147 -3
- 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.d.ts.map +1 -1
- package/dist/core/session-storage.js +39 -3
- package/dist/core/session-storage.js.map +1 -1
- package/dist/core/session-storage.test.js +6 -1
- package/dist/core/session-storage.test.js.map +1 -1
- package/dist/core/settings-manager.d.ts +8 -2
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +26 -0
- package/dist/core/settings-manager.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 +13 -0
- package/dist/core/subagent-manager.d.ts.map +1 -1
- package/dist/core/subagent-manager.js +39 -1
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/core/subagent-manager.test.js +43 -7
- package/dist/core/subagent-manager.test.js.map +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/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/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 +28 -0
- package/dist/modes/json-mode.d.ts.map +1 -1
- package/dist/modes/json-mode.js +37 -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/subagent-worker-mode.d.ts +3 -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/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/goals.d.ts +2 -2
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +2 -1
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/index.d.ts +13 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +20 -17
- 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/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/subagent-control.d.ts.map +1 -1
- package/dist/tools/subagent-control.js +6 -2
- package/dist/tools/subagent-control.js.map +1 -1
- package/dist/tools/subagent-shared.d.ts +6 -0
- package/dist/tools/subagent-shared.d.ts.map +1 -1
- package/dist/tools/subagent-shared.js +6 -0
- package/dist/tools/subagent-shared.js.map +1 -1
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +43 -8
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/subagent.test.js +65 -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/tool-search.d.ts +12 -1
- package/dist/tools/tool-search.d.ts.map +1 -1
- package/dist/tools/tool-search.js +22 -3
- 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/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/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/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +18 -4
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- 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
|
@@ -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,7 +34,7 @@ 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";
|
|
@@ -47,6 +48,7 @@ import { loginKimi } from "./core/oauth/kimi.js";
|
|
|
47
48
|
import { AUTH_PROVIDERS } from "./core/auth-providers.js";
|
|
48
49
|
import { ensureAppDirs, loadSavedSettings } from "./config.js";
|
|
49
50
|
import { SettingsManager } from "./core/settings-manager.js";
|
|
51
|
+
import { installPlugin, listInstalledPlugins, removePlugin, } from "./core/extensions/plugin-bundles.js";
|
|
50
52
|
import { getModel, getDefaultThinkingLevel, getContextWindow, getAllModels, clearRuntimeModels, registerRuntimeModels, } from "./core/model-registry.js";
|
|
51
53
|
import { resolveStartOrFallback } from "./core/resolve-start.js";
|
|
52
54
|
import { getGitBranch, getGitDirtyFileCount, isGitRepo } from "./utils/git.js";
|
|
@@ -64,13 +66,18 @@ import { enrichProcessPath } from "./core/shell-path.js";
|
|
|
64
66
|
import { downscaleForPreview, shrinkToFit, validateVisionImage } from "./utils/image.js";
|
|
65
67
|
import { startServeMode } from "./modes/serve-mode.js";
|
|
66
68
|
import { loadTelegramConfig, saveTelegramConfig, verifyBotToken } from "./core/telegram-config.js";
|
|
67
|
-
import { loadServers, addServer, removeServer, getServer, parseMcpAddCommand, MCPClientManager, McpOAuthStore, } from "./core/mcp/index.js";
|
|
69
|
+
import { loadServers, addServer, removeServer, getServer, parseMcpAddCommand, MCPClientManager, McpOAuthStore, createElicitationBridge, } from "./core/mcp/index.js";
|
|
68
70
|
import { buildSnapshot, levelForXp, rankForLevel } from "./core/progress/ranks.js";
|
|
69
71
|
import { loadProgress, peekProgress, updateProgress } from "./core/progress/store.js";
|
|
70
72
|
import { awardPrompt, awardCommits } from "./core/progress/engine.js";
|
|
71
73
|
import { detectNewCommits, repoKey } from "./core/progress/git-xp.js";
|
|
72
74
|
import { rebuildFromSessions } from "./core/progress/rebuild.js";
|
|
73
75
|
import { captureSidecarError, flushSidecarErrors, shouldCaptureToolFailure, shouldCaptureUsagePollingError, wrapSidecarHandler, } from "./core/sidecar-error-reporter.js";
|
|
76
|
+
const AUTOMATION_PROVENANCE = {
|
|
77
|
+
source: "runtime",
|
|
78
|
+
kind: "automation",
|
|
79
|
+
visibility: "hidden",
|
|
80
|
+
};
|
|
74
81
|
const ALL_PROVIDERS = [
|
|
75
82
|
// US
|
|
76
83
|
"anthropic",
|
|
@@ -87,6 +94,12 @@ const ALL_PROVIDERS = [
|
|
|
87
94
|
"sakana",
|
|
88
95
|
"openrouter",
|
|
89
96
|
];
|
|
97
|
+
function stringArray(value) {
|
|
98
|
+
if (!Array.isArray(value))
|
|
99
|
+
return undefined;
|
|
100
|
+
const out = value.filter((v) => typeof v === "string" && v.trim().length > 0);
|
|
101
|
+
return out.length > 0 ? out : undefined;
|
|
102
|
+
}
|
|
90
103
|
function appSettingsFile() {
|
|
91
104
|
return path.join(os.homedir(), ".ezcoder", "ezcoder-app.json");
|
|
92
105
|
}
|
|
@@ -110,6 +123,8 @@ async function loadAppSettings() {
|
|
|
110
123
|
projectModels: raw.projectModels && typeof raw.projectModels === "object" ? raw.projectModels : undefined,
|
|
111
124
|
autopilot: raw.autopilot && typeof raw.autopilot === "object" ? raw.autopilot : undefined,
|
|
112
125
|
nolanModels: raw.nolanModels && typeof raw.nolanModels === "object" ? raw.nolanModels : undefined,
|
|
126
|
+
projectRoots: stringArray(raw.projectRoots),
|
|
127
|
+
hiddenProjects: stringArray(raw.hiddenProjects),
|
|
113
128
|
};
|
|
114
129
|
}
|
|
115
130
|
catch {
|
|
@@ -363,30 +378,6 @@ function detectHookKind(text) {
|
|
|
363
378
|
return "regrounding";
|
|
364
379
|
return null;
|
|
365
380
|
}
|
|
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
381
|
/** A short transport summary for display (URL for http/sse, command+args for stdio). */
|
|
391
382
|
function mcpRowSummary(config) {
|
|
392
383
|
if (config.url)
|
|
@@ -598,6 +589,20 @@ async function main() {
|
|
|
598
589
|
// request to its window's session via the `x-gg-session` header (and the
|
|
599
590
|
// `?session=` query for the SSE /events stream).
|
|
600
591
|
const sessions = new Map();
|
|
592
|
+
/**
|
|
593
|
+
* Fan one frame out to every window.
|
|
594
|
+
*
|
|
595
|
+
* For state that is genuinely global rather than per-session — `~/.ezcoder/auth.json`
|
|
596
|
+
* is shared by all windows, so connecting a provider in one must refresh the
|
|
597
|
+
* model picker in all of them. Mirrors the memory/jiwa/progress fan-outs.
|
|
598
|
+
*/
|
|
599
|
+
const broadcastAll = (type, data) => {
|
|
600
|
+
for (const ctx of sessions.values())
|
|
601
|
+
ctx.broadcast(type, data);
|
|
602
|
+
};
|
|
603
|
+
// Providers currently mid-OAuth in some window. Daemon-level so two windows
|
|
604
|
+
// cannot race two browser flows for the same provider into one auth file.
|
|
605
|
+
const oauthInFlightProviders = new Set();
|
|
601
606
|
const memoryStore = new MemoryStore({
|
|
602
607
|
onChange: ({ memories }) => {
|
|
603
608
|
for (const ctx of sessions.values()) {
|
|
@@ -621,6 +626,13 @@ async function main() {
|
|
|
621
626
|
});
|
|
622
627
|
const usageCache = new Map();
|
|
623
628
|
const usageRequests = new Map();
|
|
629
|
+
// Last snapshot that actually carried windows, per provider. Replayed while a
|
|
630
|
+
// fetch is failing so the title meter never blinks out of existence. Bounded
|
|
631
|
+
// by USAGE_LAST_GOOD_MAX_AGE_MS — a provider that never recovers must stop
|
|
632
|
+
// reporting rather than freeze a percentage (and a long-past reset time) on
|
|
633
|
+
// screen forever. The 429 backoff alone runs to 24h, far past any usefulness.
|
|
634
|
+
const usageLastGood = new Map();
|
|
635
|
+
const USAGE_LAST_GOOD_MAX_AGE_MS = 30 * 60_000;
|
|
624
636
|
// 429 backoff: quota endpoints are auxiliary UI data. Honor Retry-After when
|
|
625
637
|
// provided; otherwise retain the unavailable snapshot for 30 minutes. Clamp
|
|
626
638
|
// the provider value so a malformed header can neither hammer the endpoint
|
|
@@ -635,6 +647,9 @@ async function main() {
|
|
|
635
647
|
// Moonshot platform API key is metered per-token, not per plan window.
|
|
636
648
|
const authKey = provider === "moonshot" ? MOONSHOT_OAUTH_KEY : provider;
|
|
637
649
|
if (!(await auth.hasProviderAuth(authKey))) {
|
|
650
|
+
// Logged out: drop the replay cache so a later login on a DIFFERENT
|
|
651
|
+
// account can never inherit the previous one's numbers.
|
|
652
|
+
usageLastGood.delete(provider);
|
|
638
653
|
return { provider, displayName, connected: false, windows: [], fetchedAt: Date.now() };
|
|
639
654
|
}
|
|
640
655
|
try {
|
|
@@ -678,6 +693,18 @@ async function main() {
|
|
|
678
693
|
...(backoffMs !== undefined && { backoffMs: String(backoffMs) }),
|
|
679
694
|
});
|
|
680
695
|
const connected = await auth.hasProviderAuth(authKey);
|
|
696
|
+
// Transient failures (notably the 429s these auxiliary quota endpoints
|
|
697
|
+
// hand out) must not blank the meter. Keep serving the last good
|
|
698
|
+
// snapshot, flagged `stale`, so the bar stays put instead of flickering
|
|
699
|
+
// out for the whole backoff window and back in on the next success.
|
|
700
|
+
// Past the max age it's dropped — no data beats confidently wrong data.
|
|
701
|
+
const lastGood = usageLastGood.get(provider);
|
|
702
|
+
if (lastGood && Date.now() - lastGood.fetchedAt >= USAGE_LAST_GOOD_MAX_AGE_MS) {
|
|
703
|
+
usageLastGood.delete(provider);
|
|
704
|
+
}
|
|
705
|
+
else if (connected && lastGood) {
|
|
706
|
+
return { ...lastGood, stale: true };
|
|
707
|
+
}
|
|
681
708
|
return {
|
|
682
709
|
provider,
|
|
683
710
|
displayName,
|
|
@@ -699,6 +726,11 @@ async function main() {
|
|
|
699
726
|
usageRequests.set(provider, request);
|
|
700
727
|
try {
|
|
701
728
|
const result = await request;
|
|
729
|
+
// Never re-store a replay — it would keep its original `fetchedAt`, but
|
|
730
|
+
// writing it back muddies the "last GOOD" contract for no gain.
|
|
731
|
+
if (result.connected && !result.error && !result.stale && result.windows.length > 0) {
|
|
732
|
+
usageLastGood.set(provider, result);
|
|
733
|
+
}
|
|
702
734
|
// Anthropic can return utilization before it assigns reset timestamps
|
|
703
735
|
// (notably before the account's first active request). Retry that partial
|
|
704
736
|
// snapshot quickly; complete snapshots keep the normal one-minute cache.
|
|
@@ -766,7 +798,15 @@ async function main() {
|
|
|
766
798
|
const sessionPath = typeof body.sessionPath === "string" && body.sessionPath ? body.sessionPath : undefined;
|
|
767
799
|
const id = randomUUID();
|
|
768
800
|
try {
|
|
769
|
-
const ctx = await createSession({
|
|
801
|
+
const ctx = await createSession({
|
|
802
|
+
auth,
|
|
803
|
+
paths,
|
|
804
|
+
progress,
|
|
805
|
+
memoryStore,
|
|
806
|
+
jiwaStore,
|
|
807
|
+
broadcastAll,
|
|
808
|
+
oauthInFlightProviders,
|
|
809
|
+
}, { id, mode, chatAgent, cwd: sessionCwd, sessionPath });
|
|
770
810
|
sessions.set(id, ctx);
|
|
771
811
|
log("INFO", "app-sidecar", "session created", {
|
|
772
812
|
id,
|
|
@@ -1042,7 +1082,7 @@ async function createProgressManager(agentDir, broadcastAll) {
|
|
|
1042
1082
|
* logger, PATH, shared auth file, and radio live at the daemon level.
|
|
1043
1083
|
*/
|
|
1044
1084
|
async function createSession(deps, opts) {
|
|
1045
|
-
const { auth, progress, memoryStore, jiwaStore } = deps;
|
|
1085
|
+
const { auth, progress, memoryStore, jiwaStore, broadcastAll, oauthInFlightProviders } = deps;
|
|
1046
1086
|
const paths = deps.paths;
|
|
1047
1087
|
const mode = opts.mode;
|
|
1048
1088
|
let chatAgent = opts.chatAgent;
|
|
@@ -1178,6 +1218,17 @@ async function createSession(deps, opts) {
|
|
|
1178
1218
|
})
|
|
1179
1219
|
.catch(() => { });
|
|
1180
1220
|
}
|
|
1221
|
+
// ── MCP elicitation bridge ─────────────────────────────────
|
|
1222
|
+
// An MCP server can ask for user input in the middle of a tool call. The
|
|
1223
|
+
// bridge parks the promise; we broadcast the prompt over SSE and resolve it
|
|
1224
|
+
// when the webview POSTs /mcp/elicit/:id.
|
|
1225
|
+
const elicitations = createElicitationBridge({
|
|
1226
|
+
broadcast: (prompt) => broadcast("mcp_elicit", prompt),
|
|
1227
|
+
onTimeout: (prompt) => log("WARN", "app-sidecar", "MCP elicitation timed out", {
|
|
1228
|
+
id: prompt.id,
|
|
1229
|
+
server: prompt.server,
|
|
1230
|
+
}),
|
|
1231
|
+
});
|
|
1181
1232
|
// The session file path to resume (passed by the daemon's POST /session);
|
|
1182
1233
|
// empty/unset starts a fresh session.
|
|
1183
1234
|
const resumeSessionPath = opts.sessionPath;
|
|
@@ -1191,6 +1242,7 @@ async function createSession(deps, opts) {
|
|
|
1191
1242
|
signal: abort.signal,
|
|
1192
1243
|
// Keep MCP startup off the readiness path in both modes.
|
|
1193
1244
|
backgroundMcpConnect: true,
|
|
1245
|
+
onMcpElicit: elicitations.onElicit,
|
|
1194
1246
|
// Keep restore-time auto-compaction off the readiness path too: its summary
|
|
1195
1247
|
// LLM call (30s timeout) used to freeze waitForReady — and with it the whole
|
|
1196
1248
|
// window (project picker, session list) — whenever a resumed session was
|
|
@@ -1513,6 +1565,10 @@ async function createSession(deps, opts) {
|
|
|
1513
1565
|
recordApprovedPlanMarkers(d.text);
|
|
1514
1566
|
});
|
|
1515
1567
|
session.eventBus.on("thinking_delta", (d) => broadcast("thinking_delta", d));
|
|
1568
|
+
// The agent consumed queued steering at a turn boundary. Re-broadcast as the
|
|
1569
|
+
// usual `queued` depth update so the webview drops the pending affordance the
|
|
1570
|
+
// moment the message lands in the loop, not at run_end.
|
|
1571
|
+
session.eventBus.on("queue_drained", (d) => broadcast("queued", { count: d.count, messages: session.listQueuedMessages() }));
|
|
1516
1572
|
session.eventBus.on("tool_call_start", (d) => {
|
|
1517
1573
|
toolCallNames.set(d.toolCallId, d.name);
|
|
1518
1574
|
broadcast("tool_call_start", d);
|
|
@@ -1564,10 +1620,29 @@ async function createSession(deps, opts) {
|
|
|
1564
1620
|
session.eventBus.on("compaction_start", (d) => broadcast("compaction_start", d));
|
|
1565
1621
|
session.eventBus.on("compaction_end", (d) => broadcast("compaction_end", d));
|
|
1566
1622
|
let running = false;
|
|
1623
|
+
// Closes the window between `/prompt` deciding to start a run and `runAgent`
|
|
1624
|
+
// flipping `running` — that stretch awaits, so Node yields inside it. See
|
|
1625
|
+
// RunClaim.
|
|
1626
|
+
const runClaim = new RunClaim();
|
|
1567
1627
|
const runLifecycle = new RunLifecycle((runState) => {
|
|
1568
1628
|
running = runState !== "idle";
|
|
1569
1629
|
if (runState === "cancelling")
|
|
1570
1630
|
broadcast("run_cancelling", { runState });
|
|
1631
|
+
},
|
|
1632
|
+
// Durable run journal. Fire-and-forget on purpose: an unwritten journal
|
|
1633
|
+
// entry is a missed crash hint, while a journal write that throws inside
|
|
1634
|
+
// begin()/settle() would break run ownership itself.
|
|
1635
|
+
{
|
|
1636
|
+
started: (generation) => void session.persistRunStarted(generation).catch((err) => {
|
|
1637
|
+
log("WARN", "app-sidecar", "failed to journal run start", {
|
|
1638
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1639
|
+
});
|
|
1640
|
+
}),
|
|
1641
|
+
finished: (generation, outcome) => void session.persistRunFinished(generation, outcome).catch((err) => {
|
|
1642
|
+
log("WARN", "app-sidecar", "failed to journal run finish", {
|
|
1643
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1644
|
+
});
|
|
1645
|
+
}),
|
|
1571
1646
|
});
|
|
1572
1647
|
const cancelledRunEndGenerations = new Set();
|
|
1573
1648
|
let pendingCancelDrain = null;
|
|
@@ -1643,7 +1718,7 @@ async function createSession(deps, opts) {
|
|
|
1643
1718
|
// with `@Nolan …`; he reads EZ Coder's transcript (one-way — EZ Coder never sees
|
|
1644
1719
|
// Nolan's) and hands back runnable prompts + mentorship. Created lazily on the
|
|
1645
1720
|
// first `@Nolan` so windows that never use Nolan pay zero cost. His events ride the
|
|
1646
|
-
// SAME SSE stream with `
|
|
1721
|
+
// SAME SSE stream with `nolan_`-prefixed types, routed to the Nolan bubble.
|
|
1647
1722
|
let nolanSession = null;
|
|
1648
1723
|
let nolanAbort = new AbortController();
|
|
1649
1724
|
let nolanRunning = false;
|
|
@@ -1673,7 +1748,7 @@ async function createSession(deps, opts) {
|
|
|
1673
1748
|
return { provider: st.provider, model: st.model };
|
|
1674
1749
|
}
|
|
1675
1750
|
/** Footer payload: Nolan's effective model + whether it's a pin. Merged into
|
|
1676
|
-
* /state, the SSE ready frame, and every
|
|
1751
|
+
* /state, the SSE ready frame, and every nolan_model_change broadcast. */
|
|
1677
1752
|
function nolanStatePayload() {
|
|
1678
1753
|
const st = session.getState();
|
|
1679
1754
|
return effectiveNolanModel(nolanModelOverride, { provider: st.provider, model: st.model });
|
|
@@ -1689,13 +1764,13 @@ async function createSession(deps, opts) {
|
|
|
1689
1764
|
if (st.provider === provider && st.model === model)
|
|
1690
1765
|
return;
|
|
1691
1766
|
await nolanSession.switchModel(provider, model);
|
|
1692
|
-
log("INFO", "app-sidecar", "
|
|
1767
|
+
log("INFO", "app-sidecar", "nolan session model synced", { provider, model });
|
|
1693
1768
|
}
|
|
1694
1769
|
async function ensureNolanSession() {
|
|
1695
1770
|
if (nolanSession)
|
|
1696
1771
|
return nolanSession;
|
|
1697
1772
|
const target = nolanCurrentModel();
|
|
1698
|
-
const
|
|
1773
|
+
const nolanAgent = new AgentSession({
|
|
1699
1774
|
provider: target.provider,
|
|
1700
1775
|
model: target.model,
|
|
1701
1776
|
cwd,
|
|
@@ -1704,38 +1779,41 @@ async function createSession(deps, opts) {
|
|
|
1704
1779
|
allowedMcpServers: NOLAN_ALLOWED_MCP_SERVERS,
|
|
1705
1780
|
transient: true,
|
|
1706
1781
|
signal: nolanAbort.signal,
|
|
1707
|
-
// Nolan
|
|
1782
|
+
// Nolan belongs to this window, so route MCP elicitation prompts there.
|
|
1783
|
+
// Keeping daemon sessions interactive also lets them share one pooled MCP connection.
|
|
1784
|
+
onMcpElicit: elicitations.onElicit,
|
|
1785
|
+
// Nolan's bursty, spread-out turns outlast the default cache
|
|
1708
1786
|
// TTL regardless of the user's global speedProfile pick.
|
|
1709
1787
|
forceLongCacheRetention: true,
|
|
1710
1788
|
});
|
|
1711
|
-
await
|
|
1712
|
-
// Bridge Nolan's bus to the shared SSE fan-out with
|
|
1789
|
+
await nolanAgent.initialize();
|
|
1790
|
+
// Bridge Nolan's bus to the shared SSE fan-out with nolan_-prefixed types so the
|
|
1713
1791
|
// webview routes them to the Nolan bubble, never EZ Coder's.
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1792
|
+
nolanAgent.eventBus.on("text_delta", (d) => broadcast("nolan_text_delta", d));
|
|
1793
|
+
nolanAgent.eventBus.on("thinking_delta", (d) => broadcast("nolan_thinking_delta", d));
|
|
1794
|
+
nolanAgent.eventBus.on("tool_call_start", (d) => {
|
|
1717
1795
|
nolanToolCallNames.set(d.toolCallId, d.name);
|
|
1718
1796
|
broadcast("nolan_tool_call_start", d);
|
|
1719
1797
|
});
|
|
1720
|
-
|
|
1721
|
-
|
|
1798
|
+
nolanAgent.eventBus.on("tool_call_update", (d) => broadcast("nolan_tool_call_update", d));
|
|
1799
|
+
nolanAgent.eventBus.on("tool_call_end", (d) => {
|
|
1722
1800
|
nolanToolCallNames.delete(d.toolCallId);
|
|
1723
1801
|
broadcast("nolan_tool_call_end", d);
|
|
1724
1802
|
});
|
|
1725
1803
|
// Native server tools (Anthropic web_search) stream text both before AND
|
|
1726
1804
|
// after them in the same turn; forward so the webview can break the bubble
|
|
1727
1805
|
// (otherwise "...work.Local tools..." glues together). Mirrors the build bus.
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
broadcastError("nolan_error", "
|
|
1806
|
+
nolanAgent.eventBus.on("server_tool_call", (d) => broadcast("nolan_server_tool_call", d));
|
|
1807
|
+
nolanAgent.eventBus.on("turn_end", (d) => broadcast("nolan_turn_end", d));
|
|
1808
|
+
nolanAgent.eventBus.on("error", (d) => {
|
|
1809
|
+
broadcastError("nolan_error", "nolan error", d.error);
|
|
1732
1810
|
});
|
|
1733
|
-
nolanSession =
|
|
1734
|
-
log("INFO", "app-sidecar", "
|
|
1811
|
+
nolanSession = nolanAgent;
|
|
1812
|
+
log("INFO", "app-sidecar", "nolan session ready", {
|
|
1735
1813
|
provider: target.provider,
|
|
1736
1814
|
model: target.model,
|
|
1737
1815
|
});
|
|
1738
|
-
return
|
|
1816
|
+
return nolanAgent;
|
|
1739
1817
|
}
|
|
1740
1818
|
// ── Autopilot Nolan (auto-reviewer) ──────────────────────────
|
|
1741
1819
|
// A THIRD read-only AgentSession, separate from chat Nolan. In autopilot mode
|
|
@@ -1758,13 +1836,13 @@ async function createSession(deps, opts) {
|
|
|
1758
1836
|
if (st.provider === provider && st.model === model)
|
|
1759
1837
|
return;
|
|
1760
1838
|
await nolanAutoSession.switchModel(provider, model);
|
|
1761
|
-
log("INFO", "app-sidecar", "
|
|
1839
|
+
log("INFO", "app-sidecar", "nolan autopilot session model synced", { provider, model });
|
|
1762
1840
|
}
|
|
1763
1841
|
async function ensureNolanAutoSession() {
|
|
1764
1842
|
if (nolanAutoSession)
|
|
1765
1843
|
return nolanAutoSession;
|
|
1766
1844
|
const target = nolanCurrentModel();
|
|
1767
|
-
const
|
|
1845
|
+
const nolanAgent = new AgentSession({
|
|
1768
1846
|
provider: target.provider,
|
|
1769
1847
|
model: target.model,
|
|
1770
1848
|
cwd,
|
|
@@ -1773,26 +1851,32 @@ async function createSession(deps, opts) {
|
|
|
1773
1851
|
allowedMcpServers: NOLAN_ALLOWED_MCP_SERVERS,
|
|
1774
1852
|
transient: true,
|
|
1775
1853
|
signal: nolanAutoAbort.signal,
|
|
1854
|
+
// Route this window's MCP elicitation prompts through the shared bridge.
|
|
1855
|
+
onMcpElicit: elicitations.onElicit,
|
|
1776
1856
|
// Autopilot review rounds routinely span the injected EZ Coder run
|
|
1777
1857
|
// (often >5 min) regardless of the user's global speedProfile pick.
|
|
1778
1858
|
forceLongCacheRetention: true,
|
|
1779
1859
|
});
|
|
1780
1860
|
// Nolan is already the independent autopilot reviewer; recursively running
|
|
1781
1861
|
// his own Ideal self-review adds latency and can corrupt the verdict shape.
|
|
1782
|
-
|
|
1783
|
-
await
|
|
1862
|
+
nolanAgent.setIdealReviewSuppressed(true);
|
|
1863
|
+
await nolanAgent.initialize();
|
|
1784
1864
|
// Deliberately no bus bridge: the review is silent. Errors surface via the
|
|
1785
1865
|
// runAutopilotReview try/catch as autopilot_error frames.
|
|
1786
|
-
nolanAutoSession =
|
|
1787
|
-
log("INFO", "app-sidecar", "
|
|
1866
|
+
nolanAutoSession = nolanAgent;
|
|
1867
|
+
log("INFO", "app-sidecar", "nolan autopilot session ready", {
|
|
1788
1868
|
provider: target.provider,
|
|
1789
1869
|
model: target.model,
|
|
1790
1870
|
});
|
|
1791
|
-
return
|
|
1871
|
+
return nolanAgent;
|
|
1792
1872
|
}
|
|
1793
1873
|
function abortOwnedWork() {
|
|
1794
1874
|
cancelGeneration++;
|
|
1795
1875
|
abort.abort();
|
|
1876
|
+
// An MCP tool call parked on user input is not cancelled by the signal —
|
|
1877
|
+
// the promise lives in the bridge. Release it, or the aborted turn's tool
|
|
1878
|
+
// call never returns.
|
|
1879
|
+
elicitations.cancelAll();
|
|
1796
1880
|
// Stop a run-all sweep and every async child through AgentSession's signal.
|
|
1797
1881
|
taskRunAll = false;
|
|
1798
1882
|
autopilotCancelled = true;
|
|
@@ -1804,9 +1888,9 @@ async function createSession(deps, opts) {
|
|
|
1804
1888
|
nolanAutoAbort = new AbortController();
|
|
1805
1889
|
nolanAutoSession?.setSignal(nolanAutoAbort.signal);
|
|
1806
1890
|
}
|
|
1807
|
-
function finishOwnedGeneration(generation, emitCancelledFallback) {
|
|
1891
|
+
function finishOwnedGeneration(generation, emitCancelledFallback, outcome = "completed") {
|
|
1808
1892
|
const cancelled = runLifecycle.isCancellationRequested(generation);
|
|
1809
|
-
const settlement = runLifecycle.settle(generation);
|
|
1893
|
+
const settlement = runLifecycle.settle(generation, outcome);
|
|
1810
1894
|
if (!settlement.settled)
|
|
1811
1895
|
return cancelled;
|
|
1812
1896
|
// A replacement signal is safe only after the provider-backed owner settled.
|
|
@@ -1884,8 +1968,9 @@ async function createSession(deps, opts) {
|
|
|
1884
1968
|
});
|
|
1885
1969
|
}
|
|
1886
1970
|
}
|
|
1887
|
-
if (ownsGeneration)
|
|
1888
|
-
finishOwnedGeneration(generation, false);
|
|
1971
|
+
if (ownsGeneration) {
|
|
1972
|
+
finishOwnedGeneration(generation, false, runSucceeded ? "completed" : "failed");
|
|
1973
|
+
}
|
|
1889
1974
|
// A cancelled injected run is still owned by the surrounding autopilot
|
|
1890
1975
|
// cycle; its outer finalizer emits the one terminal cancelled run_end.
|
|
1891
1976
|
if (!(cancelled && !ownsGeneration)) {
|
|
@@ -1904,7 +1989,10 @@ async function createSession(deps, opts) {
|
|
|
1904
1989
|
// completed ones so they drop out of the Tasks modal automatically (users
|
|
1905
1990
|
// never have to delete finished tasks by hand).
|
|
1906
1991
|
broadcast("tasks_list", { tasks: pruneDoneTasksSync(cwd) });
|
|
1907
|
-
broadcast("queued", {
|
|
1992
|
+
broadcast("queued", {
|
|
1993
|
+
count: session.getQueuedCount(),
|
|
1994
|
+
messages: session.listQueuedMessages(),
|
|
1995
|
+
});
|
|
1908
1996
|
broadcast("extras", footerExtras());
|
|
1909
1997
|
}
|
|
1910
1998
|
}
|
|
@@ -1918,7 +2006,7 @@ async function createSession(deps, opts) {
|
|
|
1918
2006
|
autopilotReviewing = true;
|
|
1919
2007
|
broadcast("autopilot_review_start", {});
|
|
1920
2008
|
try {
|
|
1921
|
-
const
|
|
2009
|
+
const nolanAgent = await ensureNolanAutoSession();
|
|
1922
2010
|
const digest = buildNolanAutopilotContext({
|
|
1923
2011
|
cwd,
|
|
1924
2012
|
gitBranch,
|
|
@@ -1927,8 +2015,8 @@ async function createSession(deps, opts) {
|
|
|
1927
2015
|
injectedPrompts: [...injectedAutopilotPrompts],
|
|
1928
2016
|
workflowCommands: await loadWorkflowCommandSpecs(),
|
|
1929
2017
|
});
|
|
1930
|
-
await
|
|
1931
|
-
return parseAutopilotVerdict(lastAssistantText(
|
|
2018
|
+
await nolanAgent.prompt(digest);
|
|
2019
|
+
return parseAutopilotVerdict(lastAssistantText(nolanAgent.getMessages()));
|
|
1932
2020
|
}
|
|
1933
2021
|
catch (err) {
|
|
1934
2022
|
if (!autopilotCancelled)
|
|
@@ -1958,7 +2046,7 @@ async function createSession(deps, opts) {
|
|
|
1958
2046
|
autopilotReviewing = true;
|
|
1959
2047
|
broadcast("autopilot_review_start", {});
|
|
1960
2048
|
try {
|
|
1961
|
-
const
|
|
2049
|
+
const nolanAgent = await ensureNolanAutoSession();
|
|
1962
2050
|
// Re-read the plan file (the run may have revised it in place); fall
|
|
1963
2051
|
// back to the content captured at exit_plan time.
|
|
1964
2052
|
const planContent = await fs.readFile(planPath, "utf-8").catch(() => pendingPlanContent);
|
|
@@ -1971,10 +2059,10 @@ async function createSession(deps, opts) {
|
|
|
1971
2059
|
workflowCommands: await loadWorkflowCommandSpecs(),
|
|
1972
2060
|
planContent,
|
|
1973
2061
|
});
|
|
1974
|
-
await
|
|
2062
|
+
await nolanAgent.prompt(digest);
|
|
1975
2063
|
if (autopilotCancelled || planGeneration !== genAtStart)
|
|
1976
2064
|
return null;
|
|
1977
|
-
return parseAutopilotVerdict(lastAssistantText(
|
|
2065
|
+
return parseAutopilotVerdict(lastAssistantText(nolanAgent.getMessages()));
|
|
1978
2066
|
}
|
|
1979
2067
|
catch (err) {
|
|
1980
2068
|
// User action mid-review (manual Accept aborts the nolanAuto run): drop
|
|
@@ -2000,7 +2088,7 @@ async function createSession(deps, opts) {
|
|
|
2000
2088
|
const IMPLEMENT_PLAN_PROMPT = "The plan has been approved. Implement it now, following each step in order.";
|
|
2001
2089
|
// Drive the review→prompt→review loop for one finished user turn. Only ever
|
|
2002
2090
|
// called after shouldStartAutopilotCycle approves the turn (POST /prompt or
|
|
2003
|
-
// the stranded-queue drain) — never from the task runner, resume, /
|
|
2091
|
+
// the stranded-queue drain) — never from the task runner, resume, /nolan, or
|
|
2004
2092
|
// error paths, so there's no recursion and no guard tangle. The loop's
|
|
2005
2093
|
// control flow lives in driveAutopilotCycle (core/autopilot-cycle.ts) so
|
|
2006
2094
|
// every exit path is unit-tested; this only wires the real dependencies.
|
|
@@ -2064,7 +2152,7 @@ async function createSession(deps, opts) {
|
|
|
2064
2152
|
// label stays the clean prompt.
|
|
2065
2153
|
const framed = frameAutopilotInjection(IMPLEMENT_PLAN_PROMPT);
|
|
2066
2154
|
injectedAutopilotPrompts.push(framed);
|
|
2067
|
-
return runAgent(IMPLEMENT_PLAN_PROMPT, () => session.prompt(framed));
|
|
2155
|
+
return runAgent(IMPLEMENT_PLAN_PROMPT, () => session.prompt(framed, AUTOMATION_PROVENANCE));
|
|
2068
2156
|
},
|
|
2069
2157
|
// Lean context per user turn: wipe prior review history so each new
|
|
2070
2158
|
// turn starts cheap, while within this cycle the few review messages
|
|
@@ -2093,19 +2181,21 @@ async function createSession(deps, opts) {
|
|
|
2093
2181
|
},
|
|
2094
2182
|
// Autopilot-injected run: EZ Coder receives the framed prompt (no human
|
|
2095
2183
|
// is watching this turn) while run_start keeps the clean label.
|
|
2096
|
-
runPrompt: (body) => runAgent(body, () => session.prompt(frameAutopilotInjection(body))),
|
|
2184
|
+
runPrompt: (body) => runAgent(body, () => session.prompt(frameAutopilotInjection(body), AUTOMATION_PROVENANCE)),
|
|
2097
2185
|
emit: (event) => {
|
|
2098
2186
|
// Persist the terminal verdict marker so a resumed session renders the
|
|
2099
2187
|
// same Nolan bubble the live run showed instead of dropping it or
|
|
2100
2188
|
// falling back to the raw verdict text (e.g. ALL_CLEAR).
|
|
2101
2189
|
if (event.type === "autopilot_done") {
|
|
2102
2190
|
// Broadcast the SAME copySeed the persisted marker will produce on
|
|
2103
|
-
// resume, so the live all-clear wording matches the resumed one
|
|
2104
|
-
//
|
|
2191
|
+
// resume, so the live all-clear wording matches the resumed one.
|
|
2192
|
+
// Must use the PERSISTED count — that's what persistAutopilotMarker
|
|
2193
|
+
// anchors against, and it trails the in-memory list after a run
|
|
2194
|
+
// whose messages never made it to disk.
|
|
2105
2195
|
const seed = autopilotMarkerCopySeed({
|
|
2106
2196
|
version: 1,
|
|
2107
2197
|
phase: "done",
|
|
2108
|
-
afterMessageCount: session.
|
|
2198
|
+
afterMessageCount: session.getPersistedTranscriptCount(),
|
|
2109
2199
|
});
|
|
2110
2200
|
broadcast(event.type, { ...event.data, copySeed: seed });
|
|
2111
2201
|
void session.persistAutopilotMarker("done");
|
|
@@ -2150,7 +2240,10 @@ async function createSession(deps, opts) {
|
|
|
2150
2240
|
const next = session.takeNextQueuedMessage();
|
|
2151
2241
|
if (!next)
|
|
2152
2242
|
return;
|
|
2153
|
-
broadcast("queued", {
|
|
2243
|
+
broadcast("queued", {
|
|
2244
|
+
count: session.getQueuedCount(),
|
|
2245
|
+
messages: session.listQueuedMessages(),
|
|
2246
|
+
});
|
|
2154
2247
|
if (!next.text.trim() && next.attachments.length === 0)
|
|
2155
2248
|
continue;
|
|
2156
2249
|
// A queued message draining as a fresh turn supersedes any pending
|
|
@@ -2236,7 +2329,7 @@ async function createSession(deps, opts) {
|
|
|
2236
2329
|
const shortId = task.id.slice(0, 8);
|
|
2237
2330
|
const completionHint = `\n\n---\nWhen you have fully completed this task, call the tasks tool to mark it done:\n` +
|
|
2238
2331
|
`tasks({ action: "done", id: "${shortId}" })`;
|
|
2239
|
-
await runAgent(task.title, () => session.prompt(task.prompt + completionHint));
|
|
2332
|
+
await runAgent(task.title, () => session.prompt(task.prompt + completionHint, AUTOMATION_PROVENANCE));
|
|
2240
2333
|
// The agent typically marks the task done via the tasks tool during the run;
|
|
2241
2334
|
// prune completed tasks and push the refreshed list so the modal drops them.
|
|
2242
2335
|
broadcast("tasks_list", { tasks: pruneDoneTasksSync(cwd) });
|
|
@@ -2469,6 +2562,45 @@ async function createSession(deps, opts) {
|
|
|
2469
2562
|
});
|
|
2470
2563
|
return;
|
|
2471
2564
|
}
|
|
2565
|
+
if (method === "GET" && url === "/plugins") {
|
|
2566
|
+
void listInstalledPlugins(paths.extensionsDir)
|
|
2567
|
+
.then((plugins) => json(res, 200, { plugins }))
|
|
2568
|
+
.catch((error) => {
|
|
2569
|
+
captureSidecarError(error, "app-sidecar.plugins.list");
|
|
2570
|
+
json(res, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
2571
|
+
});
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
if (method === "POST" && url === "/plugins/install") {
|
|
2575
|
+
void readBody(req, res).then(async (raw) => {
|
|
2576
|
+
if (raw === null)
|
|
2577
|
+
return;
|
|
2578
|
+
try {
|
|
2579
|
+
const bundlePath = JSON.parse(raw).bundlePath;
|
|
2580
|
+
if (typeof bundlePath !== "string" || !path.isAbsolute(bundlePath)) {
|
|
2581
|
+
json(res, 400, { error: "bundlePath must be an absolute path" });
|
|
2582
|
+
return;
|
|
2583
|
+
}
|
|
2584
|
+
const plugin = await installPlugin(bundlePath, paths.extensionsDir);
|
|
2585
|
+
json(res, 200, { plugin, restartRequired: true });
|
|
2586
|
+
}
|
|
2587
|
+
catch (error) {
|
|
2588
|
+
captureSidecarError(error, "app-sidecar.plugins.install");
|
|
2589
|
+
json(res, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
2590
|
+
}
|
|
2591
|
+
});
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2594
|
+
if (method === "DELETE" && url.startsWith("/plugins/")) {
|
|
2595
|
+
const pluginId = decodeURIComponent(url.slice("/plugins/".length));
|
|
2596
|
+
void removePlugin(pluginId, paths.extensionsDir)
|
|
2597
|
+
.then(() => json(res, 200, { removed: pluginId, restartRequired: true }))
|
|
2598
|
+
.catch((error) => {
|
|
2599
|
+
captureSidecarError(error, "app-sidecar.plugins.remove");
|
|
2600
|
+
json(res, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
2601
|
+
});
|
|
2602
|
+
return;
|
|
2603
|
+
}
|
|
2472
2604
|
if (method === "GET" && url === "/settings") {
|
|
2473
2605
|
// `configured` is true only when the user explicitly saved a projects root
|
|
2474
2606
|
// (the ezcoder-app.json file exists with a value) — not when we fall back to the
|
|
@@ -2555,9 +2687,53 @@ async function createSession(deps, opts) {
|
|
|
2555
2687
|
});
|
|
2556
2688
|
return;
|
|
2557
2689
|
}
|
|
2690
|
+
// Dismiss a project from the picker (or restore it with hidden:false). The
|
|
2691
|
+
// path is kept rather than the row: sessions in scratch dirs keep
|
|
2692
|
+
// re-surfacing, so the user's decision has to outlive any one scan.
|
|
2693
|
+
if (method === "POST" && url === "/projects/hidden") {
|
|
2694
|
+
void readBody(req, res).then(async (raw) => {
|
|
2695
|
+
if (raw === null)
|
|
2696
|
+
return;
|
|
2697
|
+
let body;
|
|
2698
|
+
try {
|
|
2699
|
+
body = JSON.parse(raw);
|
|
2700
|
+
}
|
|
2701
|
+
catch {
|
|
2702
|
+
json(res, 400, { error: "invalid JSON body" });
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
const target = body.path?.trim();
|
|
2706
|
+
if (!target) {
|
|
2707
|
+
json(res, 400, { error: "path is required" });
|
|
2708
|
+
return;
|
|
2709
|
+
}
|
|
2710
|
+
const key = path.resolve(target);
|
|
2711
|
+
try {
|
|
2712
|
+
const settings = await loadAppSettings();
|
|
2713
|
+
const current = new Set((settings.hiddenProjects ?? []).map((p) => path.resolve(p)));
|
|
2714
|
+
if (body.hidden === false)
|
|
2715
|
+
current.delete(key);
|
|
2716
|
+
else
|
|
2717
|
+
current.add(key);
|
|
2718
|
+
settings.hiddenProjects = current.size > 0 ? Array.from(current) : undefined;
|
|
2719
|
+
await saveAppSettings(settings);
|
|
2720
|
+
json(res, 200, { hidden: Array.from(current) });
|
|
2721
|
+
}
|
|
2722
|
+
catch (err) {
|
|
2723
|
+
captureSidecarError(err, "app-sidecar.projects.hidden");
|
|
2724
|
+
json(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
2725
|
+
}
|
|
2726
|
+
});
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2558
2729
|
if (method === "GET" && url === "/projects") {
|
|
2559
|
-
//
|
|
2560
|
-
void
|
|
2730
|
+
// Session stores plus configured project folders, including unopened projects.
|
|
2731
|
+
void loadAppSettings()
|
|
2732
|
+
.then(({ projectsRoot, projectRoots, hiddenProjects }) => discoverProjects({
|
|
2733
|
+
projectsRoot,
|
|
2734
|
+
extraRoots: projectRoots,
|
|
2735
|
+
hiddenPaths: hiddenProjects,
|
|
2736
|
+
}))
|
|
2561
2737
|
.then((projects) => json(res, 200, { projects }))
|
|
2562
2738
|
.catch((err) => {
|
|
2563
2739
|
captureSidecarError(err, "app-sidecar.projects.discover");
|
|
@@ -2667,7 +2843,7 @@ async function createSession(deps, opts) {
|
|
|
2667
2843
|
// Nolan (mentor) turns to interleave: group by the non-system message count
|
|
2668
2844
|
// they were recorded after, so each lands right after that message. A
|
|
2669
2845
|
// turn becomes two wire rows: the `@Nolan` question (user) + Nolan's reply
|
|
2670
|
-
// (assistant), both flagged `
|
|
2846
|
+
// (assistant), both flagged `nolan` so the webview tints them.
|
|
2671
2847
|
// Deduped; stale anchors are clamped to the last message (Nolan turns
|
|
2672
2848
|
// carry real conversation, so they render at the end instead of
|
|
2673
2849
|
// vanishing).
|
|
@@ -2683,16 +2859,16 @@ async function createSession(deps, opts) {
|
|
|
2683
2859
|
return;
|
|
2684
2860
|
nolanByCount.delete(count);
|
|
2685
2861
|
for (const turn of turns) {
|
|
2686
|
-
history.push({ role: "user", text: `@Nolan ${turn.question}`,
|
|
2687
|
-
history.push({ role: "assistant", text: turn.reply,
|
|
2862
|
+
history.push({ role: "user", text: `@Nolan ${turn.question}`, nolan: true });
|
|
2863
|
+
history.push({ role: "assistant", text: turn.reply, nolan: true });
|
|
2688
2864
|
}
|
|
2689
2865
|
};
|
|
2690
2866
|
// Autopilot verdict markers to interleave, same anchor scheme as Nolan
|
|
2691
2867
|
// turns — each becomes a single assistant row the webview renders
|
|
2692
2868
|
// exactly like the live `autopilot` item (never a raw verdict string).
|
|
2693
|
-
//
|
|
2694
|
-
//
|
|
2695
|
-
//
|
|
2869
|
+
// Normalization pulls anchors left over from an unrebased compaction
|
|
2870
|
+
// back to where the marker was actually written, so stale all-clear
|
|
2871
|
+
// bubbles no longer bunch at the bottom of a reopened session.
|
|
2696
2872
|
const restoredMessageCount = messages.filter((m) => m.role !== "system").length;
|
|
2697
2873
|
const autopilotByCount = new Map();
|
|
2698
2874
|
for (const marker of normalizeAutopilotMarkersForHistory(session.getAutopilotMarkers(), restoredMessageCount)) {
|
|
@@ -2776,6 +2952,21 @@ async function createSession(deps, opts) {
|
|
|
2776
2952
|
},
|
|
2777
2953
|
});
|
|
2778
2954
|
}
|
|
2955
|
+
else if (marker.kind === "interrupted_run") {
|
|
2956
|
+
// Rendered as an error row: the run's tools already changed the
|
|
2957
|
+
// repo, so the user needs to see it and decide what to do. We
|
|
2958
|
+
// never replay it — that would duplicate those changes.
|
|
2959
|
+
history.push({
|
|
2960
|
+
role: "assistant",
|
|
2961
|
+
text: "",
|
|
2962
|
+
error: {
|
|
2963
|
+
scope: "interrupted_run",
|
|
2964
|
+
headline: "A run was interrupted",
|
|
2965
|
+
message: "EZ Coder stopped mid-run, so this turn is incomplete. Any files its tools already changed are still on disk.",
|
|
2966
|
+
guidance: "Review the working tree, then re-send the request if you still want it.",
|
|
2967
|
+
},
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2779
2970
|
}
|
|
2780
2971
|
};
|
|
2781
2972
|
let nonSystemCount = 0;
|
|
@@ -2784,10 +2975,11 @@ async function createSession(deps, opts) {
|
|
|
2784
2975
|
flushNolan(0);
|
|
2785
2976
|
flushAutopilot(0);
|
|
2786
2977
|
flushAppMarkers(0);
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2978
|
+
await replayMessagesInOrder(messages, async (msg, count) => {
|
|
2979
|
+
nonSystemCount = count;
|
|
2980
|
+
const visibility = getHistoryMessageVisibility(msg);
|
|
2981
|
+
if (visibility === "hidden")
|
|
2982
|
+
return;
|
|
2791
2983
|
if (msg.role === "tool") {
|
|
2792
2984
|
// Tool result messages: check for ImageContent blocks (screenshots,
|
|
2793
2985
|
// generated images) and emit a toolImages entry.
|
|
@@ -2831,20 +3023,39 @@ async function createSession(deps, opts) {
|
|
|
2831
3023
|
});
|
|
2832
3024
|
}
|
|
2833
3025
|
}
|
|
2834
|
-
|
|
3026
|
+
return;
|
|
2835
3027
|
}
|
|
2836
3028
|
// User or assistant message — text/hook/command/compacted extraction,
|
|
2837
3029
|
// plus sub-agent group detection for assistant tool_calls.
|
|
2838
3030
|
if (msg.role === "user") {
|
|
2839
3031
|
// Rebuild the live bubble: strip the steering wrapper, drop
|
|
2840
3032
|
// attachment/file notes the model saw but the bubble never showed.
|
|
2841
|
-
const restored = restoreUserRow(msg.content);
|
|
3033
|
+
const restored = restoreUserRow(msg.content, msg.provenance);
|
|
2842
3034
|
const text = restored.text;
|
|
2843
|
-
const hook = detectHookKind(text);
|
|
2844
|
-
const compacted =
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
3035
|
+
const hook = msg.provenance ? null : detectHookKind(text);
|
|
3036
|
+
const compacted = visibility === "summary" ||
|
|
3037
|
+
(!msg.provenance && !hook && text.startsWith("[Previous conversation summary]"));
|
|
3038
|
+
const hint = userHintByCount.get(nonSystemCount);
|
|
3039
|
+
// The typed invocation persisted alongside the prompt is
|
|
3040
|
+
// authoritative. Reversing the expanded body only works while the
|
|
3041
|
+
// template is byte-identical, and templates drift (edited
|
|
3042
|
+
// `.ezcoder/commands/*.md`, reworded built-ins, app-vs-CLI phrasing) —
|
|
3043
|
+
// after which the resumed session dumped the raw multi-KB body
|
|
3044
|
+
// instead of the `/name` chip. Older sessions have no hint, so the
|
|
3045
|
+
// body match stays as the fallback.
|
|
3046
|
+
const command = !hook && !compacted
|
|
3047
|
+
? resolveRestoredCommand(typeof hint?.command === "string" ? hint.command : null, text, commandCandidates)
|
|
3048
|
+
: null;
|
|
3049
|
+
// Autopilot injected this turn — live showed only the Nolan-tinted
|
|
3050
|
+
// marker for it, never a user bubble. Emitting one here would print
|
|
3051
|
+
// the injected instruction a second time, unstyled.
|
|
3052
|
+
//
|
|
3053
|
+
// Pushed background-status updates are skipped for the same reason:
|
|
3054
|
+
// the live run rendered no bubble for them, so showing them here
|
|
3055
|
+
// would fill a reopened session with machine-facing status lines
|
|
3056
|
+
// the user never saw while working.
|
|
3057
|
+
if ((!msg.provenance || (!restored.autopilotInjected && !restored.notification)) &&
|
|
3058
|
+
(text.trim() || restored.images.length > 0)) {
|
|
2848
3059
|
history.push({
|
|
2849
3060
|
role: "user",
|
|
2850
3061
|
text: command ?? text,
|
|
@@ -2907,12 +3118,13 @@ async function createSession(deps, opts) {
|
|
|
2907
3118
|
});
|
|
2908
3119
|
}
|
|
2909
3120
|
}
|
|
2910
|
-
|
|
2911
|
-
// after
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
3121
|
+
}, (count) => {
|
|
3122
|
+
// Markers flush after every physical message, including tools and
|
|
3123
|
+
// provenance-hidden runtime context.
|
|
3124
|
+
flushNolan(count);
|
|
3125
|
+
flushAutopilot(count);
|
|
3126
|
+
flushAppMarkers(count);
|
|
3127
|
+
});
|
|
2916
3128
|
// Flush remaining Nolan turns whose anchor is at/after the message count so
|
|
2917
3129
|
// none are dropped. Autopilot/app markers beyond the restored message
|
|
2918
3130
|
// count were already filtered above; any remaining marker here is valid.
|
|
@@ -2973,7 +3185,12 @@ async function createSession(deps, opts) {
|
|
|
2973
3185
|
return;
|
|
2974
3186
|
}
|
|
2975
3187
|
if (method === "POST" && url === "/prompt") {
|
|
2976
|
-
|
|
3188
|
+
// Per-request: only the prompt that actually claimed the start may release
|
|
3189
|
+
// it. A bare release would let an early-returning request (bad JSON, or a
|
|
3190
|
+
// prompt that queued) clear a claim another request is still holding.
|
|
3191
|
+
let claimedStart = false;
|
|
3192
|
+
void readBody(req, res)
|
|
3193
|
+
.then(async (raw) => {
|
|
2977
3194
|
if (raw === null)
|
|
2978
3195
|
return;
|
|
2979
3196
|
let text;
|
|
@@ -2993,14 +3210,17 @@ async function createSession(deps, opts) {
|
|
|
2993
3210
|
json(res, 400, { error: "empty prompt" });
|
|
2994
3211
|
return;
|
|
2995
3212
|
}
|
|
2996
|
-
if (runLifecycle.running &&
|
|
3213
|
+
if (runLifecycle.running &&
|
|
3214
|
+
runLifecycle.isCancellationRequested(runLifecycle.generation)) {
|
|
2997
3215
|
json(res, 409, {
|
|
2998
3216
|
error: runLifecycle.state === "cancelling" ? "run_cancelling" : "cancel_failed",
|
|
2999
3217
|
runState: runLifecycle.state,
|
|
3000
3218
|
});
|
|
3001
3219
|
return;
|
|
3002
3220
|
}
|
|
3003
|
-
|
|
3221
|
+
// `runClaim` covers the gap before `runAgent` flips `running`: a
|
|
3222
|
+
// prompt arriving in that window must queue, not start a second run.
|
|
3223
|
+
if (running || runClaim.active || autopilotActive) {
|
|
3004
3224
|
// Queue prompts as mid-run steering (mirrors the CLI). Also queue while
|
|
3005
3225
|
// an autopilot cycle is active but between injected runs (build idle,
|
|
3006
3226
|
// Nolan reviewing) so the message never starts a run that collides with
|
|
@@ -3009,20 +3229,47 @@ async function createSession(deps, opts) {
|
|
|
3009
3229
|
// path as a non-queued attachment prompt when it drains.
|
|
3010
3230
|
const prepared = attachments.length > 0 ? await prepareAttachments(cwd, attachments) : [];
|
|
3011
3231
|
const count = session.queueMessage(text, prepared);
|
|
3012
|
-
broadcast("queued", { count });
|
|
3232
|
+
broadcast("queued", { count, messages: session.listQueuedMessages() });
|
|
3013
3233
|
json(res, 202, { queued: true, count });
|
|
3014
3234
|
return;
|
|
3015
3235
|
}
|
|
3236
|
+
// Claim the run NOW, synchronously. Everything below this line may
|
|
3237
|
+
// yield, and `running` does not flip until runAgent begins.
|
|
3238
|
+
claimedStart = runClaim.claim();
|
|
3016
3239
|
json(res, 202, { accepted: true });
|
|
3240
|
+
// Gate inputs captured around the run: whether this turn is a workflow
|
|
3241
|
+
// slash command (attachment prompts skip slash expansion entirely), and
|
|
3242
|
+
// how many assistant messages the run actually adds. Computed even when
|
|
3243
|
+
// autopilot is currently off — the toggle can flip ON mid-run, and the
|
|
3244
|
+
// gate reads the post-run value.
|
|
3245
|
+
const workflowCommand = attachments.length === 0 &&
|
|
3246
|
+
isWorkflowCommandText(text, await loadWorkflowCommandSpecs());
|
|
3247
|
+
// Does this input actually expand into a persisted user message? Asked
|
|
3248
|
+
// of the session itself, because only it knows whether the command
|
|
3249
|
+
// resolves here (name/alias casing, custom `.ezcoder/commands`, non-coder
|
|
3250
|
+
// agents that don't expand at all). A looser guess would anchor the
|
|
3251
|
+
// hint at +1 with no message to land on — decorating an unrelated
|
|
3252
|
+
// later bubble with the wrong `/name`.
|
|
3253
|
+
const expandsToTemplate = attachments.length === 0 && (await session.willExpandPromptTemplate(text));
|
|
3017
3254
|
// 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
|
-
|
|
3255
|
+
// label / enhancer highlight segments / the `/name` a command was typed
|
|
3256
|
+
// as). Anchored +1 so it attaches to the user message the prompt below
|
|
3257
|
+
// is about to push. Queued prompts skip this (their position in the run
|
|
3258
|
+
// is unpredictable).
|
|
3259
|
+
//
|
|
3260
|
+
// Recording the invocation matters because the agent persists the
|
|
3261
|
+
// EXPANDED template as the user message. Resume used to recover
|
|
3262
|
+
// `/name` by matching that body against the current templates, which
|
|
3263
|
+
// silently fails the moment a template is edited or reworded — the
|
|
3264
|
+
// reopened session then rendered the raw multi-KB prompt instead of
|
|
3265
|
+
// the command chip.
|
|
3266
|
+
if (expandsToTemplate ||
|
|
3267
|
+
(meta && (meta.nolanSent === true || Array.isArray(meta.enhancements)))) {
|
|
3022
3268
|
void session
|
|
3023
3269
|
.persistAppMarker("user_hint", {
|
|
3024
|
-
...(
|
|
3025
|
-
...(
|
|
3270
|
+
...(expandsToTemplate ? { command: text.trim() } : {}),
|
|
3271
|
+
...(meta?.nolanSent === true ? { nolanSent: true } : {}),
|
|
3272
|
+
...(Array.isArray(meta?.enhancements) ? { enhancements: meta.enhancements } : {}),
|
|
3026
3273
|
}, 1)
|
|
3027
3274
|
.catch(() => { });
|
|
3028
3275
|
}
|
|
@@ -3033,12 +3280,6 @@ async function createSession(deps, opts) {
|
|
|
3033
3280
|
// feedback, anything) supersedes the pending plan — the bump also
|
|
3034
3281
|
// invalidates any in-flight Nolan plan review.
|
|
3035
3282
|
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
3283
|
const assistantsBefore = countAssistantMessages(session.getMessages());
|
|
3043
3284
|
const messagesBefore = session.getMessages().length;
|
|
3044
3285
|
await runAgent(text, async () => {
|
|
@@ -3090,14 +3331,18 @@ async function createSession(deps, opts) {
|
|
|
3090
3331
|
// A prompt sent while Nolan was reviewing (build idle) queued but had no
|
|
3091
3332
|
// run to steer into — run it now as a fresh turn so it never strands.
|
|
3092
3333
|
await runStrandedQueue();
|
|
3334
|
+
})
|
|
3335
|
+
.finally(() => {
|
|
3336
|
+
if (claimedStart)
|
|
3337
|
+
runClaim.release();
|
|
3093
3338
|
});
|
|
3094
3339
|
return;
|
|
3095
3340
|
}
|
|
3096
3341
|
// Nolan Grout (mentor): an independent read-only advisory run on the nolanSession.
|
|
3097
|
-
// Runs concurrently with a build run — its events are
|
|
3342
|
+
// Runs concurrently with a build run — its events are nolan_-prefixed so the
|
|
3098
3343
|
// webview keeps the bubbles separate. The context digest is assembled fresh
|
|
3099
3344
|
// from the BUILD session's transcript each turn (one-way mirror).
|
|
3100
|
-
if (method === "POST" && url === "/
|
|
3345
|
+
if (method === "POST" && url === "/nolan/prompt") {
|
|
3101
3346
|
if (mode === "chat") {
|
|
3102
3347
|
json(res, 404, { error: "Nolan is not available in EZ Chat." });
|
|
3103
3348
|
return;
|
|
@@ -3125,18 +3370,18 @@ async function createSession(deps, opts) {
|
|
|
3125
3370
|
nolanRunning = true;
|
|
3126
3371
|
broadcast("nolan_run_start", { text });
|
|
3127
3372
|
try {
|
|
3128
|
-
const
|
|
3373
|
+
const nolanAgent = await ensureNolanSession();
|
|
3129
3374
|
const digest = await buildNolanContext(session, cwd, gitBranch, text, await loadWorkflowCommandSpecs(), injectedAutopilotPrompts);
|
|
3130
|
-
await
|
|
3375
|
+
await nolanAgent.prompt(digest);
|
|
3131
3376
|
// Record the turn against the BUILD session so it persists + survives
|
|
3132
3377
|
// resume (advisory custom entry, never an LLM message). Reply is Nolan's
|
|
3133
3378
|
// last assistant message; skip persistence if he produced nothing.
|
|
3134
|
-
const reply = lastAssistantText(
|
|
3379
|
+
const reply = lastAssistantText(nolanAgent.getMessages());
|
|
3135
3380
|
if (reply.trim())
|
|
3136
3381
|
await session.persistNolanTurn(text, reply);
|
|
3137
3382
|
}
|
|
3138
3383
|
catch (err) {
|
|
3139
|
-
broadcastError("nolan_error", "
|
|
3384
|
+
broadcastError("nolan_error", "nolan run failed", err);
|
|
3140
3385
|
}
|
|
3141
3386
|
finally {
|
|
3142
3387
|
nolanRunning = false;
|
|
@@ -3149,7 +3394,7 @@ async function createSession(deps, opts) {
|
|
|
3149
3394
|
});
|
|
3150
3395
|
return;
|
|
3151
3396
|
}
|
|
3152
|
-
if (method === "POST" && url === "/
|
|
3397
|
+
if (method === "POST" && url === "/nolan/cancel") {
|
|
3153
3398
|
nolanAbort.abort();
|
|
3154
3399
|
nolanAbort = new AbortController();
|
|
3155
3400
|
nolanSession?.setSignal(nolanAbort.signal);
|
|
@@ -3524,7 +3769,7 @@ async function createSession(deps, opts) {
|
|
|
3524
3769
|
// no-op event (keeps footer/event tests from treating an EZ Coder
|
|
3525
3770
|
// switch as a Nolan switch).
|
|
3526
3771
|
if (!nolanModelOverride)
|
|
3527
|
-
broadcast("
|
|
3772
|
+
broadcast("nolan_model_change", nolanStatePayload());
|
|
3528
3773
|
// The new model usually has a different context window — push extras so
|
|
3529
3774
|
// the footer's context meter rescales immediately.
|
|
3530
3775
|
broadcast("extras", footerExtras());
|
|
@@ -3536,7 +3781,7 @@ async function createSession(deps, opts) {
|
|
|
3536
3781
|
// { model: null } / "" to clear (Nolan resumes following EZ Coder). Applies
|
|
3537
3782
|
// to BOTH Nolan sessions (chat + autopilot reviewer); a switch landing while
|
|
3538
3783
|
// either is mid-run defers via the pending-model mechanics.
|
|
3539
|
-
if (method === "POST" && url === "/
|
|
3784
|
+
if (method === "POST" && url === "/nolan/model") {
|
|
3540
3785
|
void readBody(req, res).then(async (raw) => {
|
|
3541
3786
|
if (raw === null)
|
|
3542
3787
|
return;
|
|
@@ -3571,17 +3816,49 @@ async function createSession(deps, opts) {
|
|
|
3571
3816
|
await saveNolanModelPref(cwd, nolanModelOverride);
|
|
3572
3817
|
await syncNolanModel(target.provider, target.id);
|
|
3573
3818
|
await syncNolanAutoModel(target.provider, target.id);
|
|
3574
|
-
log("INFO", "app-sidecar", "
|
|
3819
|
+
log("INFO", "app-sidecar", "nolan model pinned", {
|
|
3575
3820
|
provider: target.provider,
|
|
3576
3821
|
model: target.id,
|
|
3577
3822
|
});
|
|
3578
3823
|
}
|
|
3579
3824
|
const payload = nolanStatePayload();
|
|
3580
|
-
broadcast("
|
|
3825
|
+
broadcast("nolan_model_change", payload);
|
|
3581
3826
|
json(res, 200, payload);
|
|
3582
3827
|
});
|
|
3583
3828
|
return;
|
|
3584
3829
|
}
|
|
3830
|
+
// Pending queued steering, for the composer's cancel affordance.
|
|
3831
|
+
if (method === "GET" && url === "/queued") {
|
|
3832
|
+
json(res, 200, { queued: session.listQueuedMessages() });
|
|
3833
|
+
return;
|
|
3834
|
+
}
|
|
3835
|
+
// Cancel one pending queued message by id.
|
|
3836
|
+
if (method === "POST" && url === "/queued/cancel") {
|
|
3837
|
+
void readBody(req, res).then((raw) => {
|
|
3838
|
+
if (raw === null)
|
|
3839
|
+
return;
|
|
3840
|
+
let id;
|
|
3841
|
+
try {
|
|
3842
|
+
id = JSON.parse(raw).id ?? "";
|
|
3843
|
+
}
|
|
3844
|
+
catch {
|
|
3845
|
+
json(res, 400, { error: "invalid JSON body" });
|
|
3846
|
+
return;
|
|
3847
|
+
}
|
|
3848
|
+
if (!id.trim()) {
|
|
3849
|
+
json(res, 400, { error: "missing queued message id" });
|
|
3850
|
+
return;
|
|
3851
|
+
}
|
|
3852
|
+
// `false` means it already drained into the run between render and
|
|
3853
|
+
// click. That is a race, not an error, so report it as a normal result
|
|
3854
|
+
// and let the client reconcile from the fresh list.
|
|
3855
|
+
const cancelled = session.cancelQueuedMessage(id);
|
|
3856
|
+
const queued = session.listQueuedMessages();
|
|
3857
|
+
broadcast("queued", { count: queued.length, messages: queued });
|
|
3858
|
+
json(res, 200, { cancelled, queued });
|
|
3859
|
+
});
|
|
3860
|
+
return;
|
|
3861
|
+
}
|
|
3585
3862
|
if (method === "POST" && url === "/kill") {
|
|
3586
3863
|
void readBody(req, res).then(async (raw) => {
|
|
3587
3864
|
if (raw === null)
|
|
@@ -3605,6 +3882,33 @@ async function createSession(deps, opts) {
|
|
|
3605
3882
|
});
|
|
3606
3883
|
return;
|
|
3607
3884
|
}
|
|
3885
|
+
// Import a Claude Code / Codex / Cursor transcript into a resumable GG
|
|
3886
|
+
// Coder session. The importer never throws — it returns a typed failure so
|
|
3887
|
+
// the app can show the reason verbatim.
|
|
3888
|
+
if (method === "POST" && url === "/import-transcript") {
|
|
3889
|
+
void readBody(req, res).then(async (raw) => {
|
|
3890
|
+
if (raw === null)
|
|
3891
|
+
return;
|
|
3892
|
+
let body;
|
|
3893
|
+
try {
|
|
3894
|
+
body = JSON.parse(raw);
|
|
3895
|
+
}
|
|
3896
|
+
catch {
|
|
3897
|
+
json(res, 400, { error: "invalid JSON body" });
|
|
3898
|
+
return;
|
|
3899
|
+
}
|
|
3900
|
+
const filePath = body.path?.trim();
|
|
3901
|
+
if (!filePath) {
|
|
3902
|
+
json(res, 400, { error: "missing transcript path" });
|
|
3903
|
+
return;
|
|
3904
|
+
}
|
|
3905
|
+
const result = await session.importForeignTranscript(filePath, {
|
|
3906
|
+
...(body.cwd ? { cwd: body.cwd } : {}),
|
|
3907
|
+
});
|
|
3908
|
+
json(res, result.ok ? 200 : 400, result);
|
|
3909
|
+
});
|
|
3910
|
+
return;
|
|
3911
|
+
}
|
|
3608
3912
|
if (method === "POST" && url === "/thinking") {
|
|
3609
3913
|
const st = session.getState();
|
|
3610
3914
|
const next = getNextThinkingLevel(st.provider, st.model, session.getThinkingLevel());
|
|
@@ -3639,7 +3943,7 @@ async function createSession(deps, opts) {
|
|
|
3639
3943
|
const generation = runLifecycle.generation;
|
|
3640
3944
|
if (!pendingCancelDrain || pendingCancelDrain.generation !== generation) {
|
|
3641
3945
|
pendingCancelDrain = { generation, text: session.drainQueue() };
|
|
3642
|
-
broadcast("queued", { count: 0 });
|
|
3946
|
+
broadcast("queued", { count: 0, messages: [] });
|
|
3643
3947
|
}
|
|
3644
3948
|
const result = await runLifecycle.cancel(CANCEL_TIMEOUT_MS);
|
|
3645
3949
|
const drained = pendingCancelDrain.text;
|
|
@@ -3799,7 +4103,17 @@ async function createSession(deps, opts) {
|
|
|
3799
4103
|
...(baseUrl ? { baseUrl } : {}),
|
|
3800
4104
|
};
|
|
3801
4105
|
await auth.setCredentials(storageKey, creds);
|
|
3802
|
-
|
|
4106
|
+
// auth.json is shared by every window, so this is a global change:
|
|
4107
|
+
// close their login modals and refresh their provider lists too.
|
|
4108
|
+
broadcastAll("auth_done", { provider });
|
|
4109
|
+
// `auth_done` means "a login succeeded" (modals close on it).
|
|
4110
|
+
// `auth_change` means "auth.json changed" — which a DISCONNECT also is,
|
|
4111
|
+
// so connection state has one signal that covers both directions.
|
|
4112
|
+
broadcastAll("auth_change", { provider });
|
|
4113
|
+
// A newly connected provider unlocks its models. `/models` filters on
|
|
4114
|
+
// who is logged in, so every window's picker is now stale — without
|
|
4115
|
+
// this the new models don't appear until the session is reopened.
|
|
4116
|
+
broadcastAll("models_change", {});
|
|
3803
4117
|
json(res, 200, { ok: true });
|
|
3804
4118
|
});
|
|
3805
4119
|
return;
|
|
@@ -3825,7 +4139,18 @@ async function createSession(deps, opts) {
|
|
|
3825
4139
|
json(res, 409, { error: "a login is already in progress" });
|
|
3826
4140
|
return;
|
|
3827
4141
|
}
|
|
4142
|
+
// A login writes the shared ~/.ezcoder/auth.json, so two windows racing the
|
|
4143
|
+
// same provider means two browser tabs and two token exchanges whose
|
|
4144
|
+
// writes clobber each other. The per-session flag above cannot see
|
|
4145
|
+
// that — guard the provider daemon-wide as well.
|
|
4146
|
+
if (oauthInFlightProviders.has(provider)) {
|
|
4147
|
+
json(res, 409, {
|
|
4148
|
+
error: `a ${meta.label} login is already in progress in another window`,
|
|
4149
|
+
});
|
|
4150
|
+
return;
|
|
4151
|
+
}
|
|
3828
4152
|
oauthInFlight = true;
|
|
4153
|
+
oauthInFlightProviders.add(provider);
|
|
3829
4154
|
json(res, 202, { accepted: true });
|
|
3830
4155
|
void (async () => {
|
|
3831
4156
|
const cb = authCallbacks();
|
|
@@ -3846,10 +4171,19 @@ async function createSession(deps, opts) {
|
|
|
3846
4171
|
throw new Error(`OAuth not implemented for ${provider}`);
|
|
3847
4172
|
}
|
|
3848
4173
|
await auth.setCredentials(storageKey, creds);
|
|
3849
|
-
|
|
4174
|
+
// Terminal outcome of a GLOBAL change: every window's login modal
|
|
4175
|
+
// should close and its provider list refresh, not just the one
|
|
4176
|
+
// that started the flow.
|
|
4177
|
+
broadcastAll("auth_done", { provider });
|
|
4178
|
+
broadcastAll("auth_change", { provider });
|
|
4179
|
+
// The OAuth provider's models just became selectable everywhere.
|
|
4180
|
+
broadcastAll("models_change", {});
|
|
3850
4181
|
}
|
|
3851
4182
|
catch (err) {
|
|
3852
4183
|
captureSidecarError(err, "app-sidecar.auth.oauth", { provider });
|
|
4184
|
+
// Deliberately session-scoped: this is the outcome of ONE window's
|
|
4185
|
+
// attempt. Another window that never pressed Connect has nothing to
|
|
4186
|
+
// show an error about, and its modal correctly still offers login.
|
|
3853
4187
|
broadcast("auth_error", {
|
|
3854
4188
|
provider,
|
|
3855
4189
|
message: err instanceof Error ? err.message : String(err),
|
|
@@ -3857,6 +4191,7 @@ async function createSession(deps, opts) {
|
|
|
3857
4191
|
}
|
|
3858
4192
|
finally {
|
|
3859
4193
|
oauthInFlight = false;
|
|
4194
|
+
oauthInFlightProviders.delete(provider);
|
|
3860
4195
|
pendingCode = null;
|
|
3861
4196
|
}
|
|
3862
4197
|
})();
|
|
@@ -3885,6 +4220,39 @@ async function createSession(deps, opts) {
|
|
|
3885
4220
|
});
|
|
3886
4221
|
return;
|
|
3887
4222
|
}
|
|
4223
|
+
if (method === "POST" && url.startsWith("/mcp/elicit/")) {
|
|
4224
|
+
const id = decodeURIComponent(url.slice("/mcp/elicit/".length));
|
|
4225
|
+
void readBody(req, res).then((raw) => {
|
|
4226
|
+
if (raw === null)
|
|
4227
|
+
return;
|
|
4228
|
+
let result;
|
|
4229
|
+
try {
|
|
4230
|
+
const parsed = JSON.parse(raw);
|
|
4231
|
+
if (parsed.action !== "accept" &&
|
|
4232
|
+
parsed.action !== "decline" &&
|
|
4233
|
+
parsed.action !== "cancel") {
|
|
4234
|
+
json(res, 400, { error: "action must be accept, decline, or cancel" });
|
|
4235
|
+
return;
|
|
4236
|
+
}
|
|
4237
|
+
result =
|
|
4238
|
+
parsed.action === "accept"
|
|
4239
|
+
? { action: "accept", content: parsed.content ?? {} }
|
|
4240
|
+
: { action: parsed.action };
|
|
4241
|
+
}
|
|
4242
|
+
catch {
|
|
4243
|
+
json(res, 400, { error: "invalid JSON body" });
|
|
4244
|
+
return;
|
|
4245
|
+
}
|
|
4246
|
+
// Unknown id means it already timed out or was cancelled by an abort —
|
|
4247
|
+
// the tool call has moved on, so the answer has nowhere to go.
|
|
4248
|
+
if (!elicitations.settle(id, result)) {
|
|
4249
|
+
json(res, 409, { error: "no elicitation is awaiting a response" });
|
|
4250
|
+
return;
|
|
4251
|
+
}
|
|
4252
|
+
json(res, 200, { ok: true });
|
|
4253
|
+
});
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
3888
4256
|
if (method === "POST" && url === "/auth/logout") {
|
|
3889
4257
|
void readBody(req, res).then(async (raw) => {
|
|
3890
4258
|
if (raw === null)
|
|
@@ -4270,6 +4638,7 @@ async function createSession(deps, opts) {
|
|
|
4270
4638
|
json(res, 404, { error: "not found" });
|
|
4271
4639
|
}
|
|
4272
4640
|
async function dispose() {
|
|
4641
|
+
elicitations.cancelAll();
|
|
4273
4642
|
tasksPollStopped = true;
|
|
4274
4643
|
if (tasksPoll)
|
|
4275
4644
|
clearTimeout(tasksPoll);
|