cli-jaw 2.2.16 → 2.2.17
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 +10 -1
- package/dist/bin/commands/_shared/dashboard-client.js +42 -0
- package/dist/bin/commands/_shared/dashboard-client.js.map +1 -0
- package/dist/bin/commands/_shared/search-format.js +40 -0
- package/dist/bin/commands/_shared/search-format.js.map +1 -0
- package/dist/bin/commands/chat-search.js +5 -3
- package/dist/bin/commands/chat-search.js.map +1 -1
- package/dist/bin/commands/dashboard-chat.js +14 -31
- package/dist/bin/commands/dashboard-chat.js.map +1 -1
- package/dist/bin/commands/dashboard-memory.js +12 -49
- package/dist/bin/commands/dashboard-memory.js.map +1 -1
- package/dist/bin/commands/doctor.js +33 -10
- package/dist/bin/commands/doctor.js.map +1 -1
- package/dist/bin/commands/init.js +13 -1
- package/dist/bin/commands/init.js.map +1 -1
- package/dist/bin/commands/memory.js +5 -1
- package/dist/bin/commands/memory.js.map +1 -1
- package/dist/bin/commands/slack.js +57 -3
- package/dist/bin/commands/slack.js.map +1 -1
- package/dist/bin/commands/tui/input-handler.js +1 -1
- package/dist/bin/commands/tui/input-handler.js.map +1 -1
- package/dist/bin/commands/tui/slash-command-runner.js +4 -5
- package/dist/bin/commands/tui/slash-command-runner.js.map +1 -1
- package/dist/bin/postinstall.js +16 -18
- package/dist/bin/postinstall.js.map +1 -1
- package/dist/lib/media-kind.js +28 -0
- package/dist/lib/media-kind.js.map +1 -0
- package/dist/lib/upload.js +27 -10
- package/dist/lib/upload.js.map +1 -1
- package/dist/server.js +35 -51
- package/dist/server.js.map +1 -1
- package/dist/src/agent/args.js +34 -0
- package/dist/src/agent/args.js.map +1 -1
- package/dist/src/agent/codex-app-client.js +1016 -135
- package/dist/src/agent/codex-app-client.js.map +1 -1
- package/dist/src/agent/codex-app-events.js +72 -1
- package/dist/src/agent/codex-app-events.js.map +1 -1
- package/dist/src/agent/codex-host-pool.js +617 -0
- package/dist/src/agent/codex-host-pool.js.map +1 -0
- package/dist/src/agent/jwc-runtime.js +12 -4
- package/dist/src/agent/jwc-runtime.js.map +1 -1
- package/dist/src/agent/kiro-auth.js +10 -4
- package/dist/src/agent/kiro-auth.js.map +1 -1
- package/dist/src/agent/kiro-runtime.js +45 -8
- package/dist/src/agent/kiro-runtime.js.map +1 -1
- package/dist/src/agent/lifecycle-handler.js +75 -52
- package/dist/src/agent/lifecycle-handler.js.map +1 -1
- package/dist/src/agent/memory-flush-controller.js +241 -33
- package/dist/src/agent/memory-flush-controller.js.map +1 -1
- package/dist/src/agent/prompt-context.js +4 -0
- package/dist/src/agent/prompt-context.js.map +1 -1
- package/dist/src/agent/runtime-pool.js +64 -34
- package/dist/src/agent/runtime-pool.js.map +1 -1
- package/dist/src/agent/session-persistence.js +71 -15
- package/dist/src/agent/session-persistence.js.map +1 -1
- package/dist/src/agent/spawn/queue.js +319 -162
- package/dist/src/agent/spawn/queue.js.map +1 -1
- package/dist/src/agent/spawn.js +581 -268
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/bgtask/types.js +1 -1
- package/dist/src/bgtask/types.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/safety.js +25 -0
- package/dist/src/browser/adaptive-fetch/safety.js.map +1 -1
- package/dist/src/browser/connection.js +2 -8
- package/dist/src/browser/connection.js.map +1 -1
- package/dist/src/cli/capability-probe-worker.js +172 -0
- package/dist/src/cli/capability-probe-worker.js.map +1 -0
- package/dist/src/cli/capability-probe.js +83 -0
- package/dist/src/cli/capability-probe.js.map +1 -0
- package/dist/src/cli/claude-models.js +2 -2
- package/dist/src/cli/claude-models.js.map +1 -1
- package/dist/src/cli/cli-status-worker.js +296 -0
- package/dist/src/cli/cli-status-worker.js.map +1 -0
- package/dist/src/cli/cli-status.js +81 -0
- package/dist/src/cli/cli-status.js.map +1 -0
- package/dist/src/cli/command-context.js +17 -5
- package/dist/src/cli/command-context.js.map +1 -1
- package/dist/src/cli/compact.js +56 -19
- package/dist/src/cli/compact.js.map +1 -1
- package/dist/src/cli/handlers-runtime.js +6 -4
- package/dist/src/cli/handlers-runtime.js.map +1 -1
- package/dist/src/cli/handlers.js +7 -2
- package/dist/src/cli/handlers.js.map +1 -1
- package/dist/src/cli/opencodex-models.js +5 -32
- package/dist/src/cli/opencodex-models.js.map +1 -1
- package/dist/src/cli/opencodex-runtime.js +82 -0
- package/dist/src/cli/opencodex-runtime.js.map +1 -0
- package/dist/src/cli/readiness.js +35 -18
- package/dist/src/cli/readiness.js.map +1 -1
- package/dist/src/cli/registry-live.js +8 -2
- package/dist/src/cli/registry-live.js.map +1 -1
- package/dist/src/cli/registry.js +1 -1
- package/dist/src/cli/registry.js.map +1 -1
- package/dist/src/core/browser-open-default.js +6 -4
- package/dist/src/core/browser-open-default.js.map +1 -1
- package/dist/src/core/browser-open.js +11 -15
- package/dist/src/core/browser-open.js.map +1 -1
- package/dist/src/core/bus.js +7 -1
- package/dist/src/core/bus.js.map +1 -1
- package/dist/src/core/chat-sessions.js +121 -12
- package/dist/src/core/chat-sessions.js.map +1 -1
- package/dist/src/core/cli-detect.js +148 -4
- package/dist/src/core/cli-detect.js.map +1 -1
- package/dist/src/core/codex-config.js +21 -0
- package/dist/src/core/codex-config.js.map +1 -1
- package/dist/src/core/compact.js +73 -21
- package/dist/src/core/compact.js.map +1 -1
- package/dist/src/core/config.js +449 -20
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/db.js +225 -0
- package/dist/src/core/db.js.map +1 -1
- package/dist/src/core/event-bus.js +49 -4
- package/dist/src/core/event-bus.js.map +1 -1
- package/dist/src/core/event-scope.js +100 -0
- package/dist/src/core/event-scope.js.map +1 -0
- package/dist/src/core/main-session.js +64 -21
- package/dist/src/core/main-session.js.map +1 -1
- package/dist/src/core/message-summary.js +12 -2
- package/dist/src/core/message-summary.js.map +1 -1
- package/dist/src/core/platform-kind.js +139 -0
- package/dist/src/core/platform-kind.js.map +1 -0
- package/dist/src/core/rate-limit.js +156 -0
- package/dist/src/core/rate-limit.js.map +1 -0
- package/dist/src/core/runtime-settings.js +165 -17
- package/dist/src/core/runtime-settings.js.map +1 -1
- package/dist/src/core/session-context.js +7 -0
- package/dist/src/core/session-context.js.map +1 -0
- package/dist/src/core/session-ops.js +53 -9
- package/dist/src/core/session-ops.js.map +1 -1
- package/dist/src/core/settings-merge.js +105 -3
- package/dist/src/core/settings-merge.js.map +1 -1
- package/dist/src/core/settings-watch.js +29 -5
- package/dist/src/core/settings-watch.js.map +1 -1
- package/dist/src/core/widget-watcher.js +15 -2
- package/dist/src/core/widget-watcher.js.map +1 -1
- package/dist/src/discord/bot.js +5 -2
- package/dist/src/discord/bot.js.map +1 -1
- package/dist/src/discord/commands.js +4 -4
- package/dist/src/discord/commands.js.map +1 -1
- package/dist/src/goal/heartbeat.js +1 -1
- package/dist/src/goal/heartbeat.js.map +1 -1
- package/dist/src/goal/runtime.js +1 -1
- package/dist/src/goal/runtime.js.map +1 -1
- package/dist/src/goal-run/controller.js +2 -2
- package/dist/src/goal-run/controller.js.map +1 -1
- package/dist/src/manager/internal-fetch.js +15 -0
- package/dist/src/manager/internal-fetch.js.map +1 -0
- package/dist/src/manager/logs.js +2 -1
- package/dist/src/manager/logs.js.map +1 -1
- package/dist/src/manager/memory/chat-federation.js +104 -11
- package/dist/src/manager/memory/chat-federation.js.map +1 -1
- package/dist/src/manager/memory/federation.js +100 -0
- package/dist/src/manager/memory/federation.js.map +1 -1
- package/dist/src/manager/metadata.js +4 -1
- package/dist/src/manager/metadata.js.map +1 -1
- package/dist/src/manager/notes/assets.js +1 -1
- package/dist/src/manager/notes/assets.js.map +1 -1
- package/dist/src/manager/notes/frontmatter.js +5 -49
- package/dist/src/manager/notes/frontmatter.js.map +1 -1
- package/dist/src/manager/notes/link-resolver.js +2 -2
- package/dist/src/manager/notes/link-resolver.js.map +1 -1
- package/dist/src/manager/notes/remote-assets.js +1 -1
- package/dist/src/manager/notes/remote-assets.js.map +1 -1
- package/dist/src/manager/notes/routes.js +2 -2
- package/dist/src/manager/notes/routes.js.map +1 -1
- package/dist/src/manager/notes/store.js +2 -2
- package/dist/src/manager/notes/store.js.map +1 -1
- package/dist/src/manager/notes/trash.js +1 -1
- package/dist/src/manager/notes/trash.js.map +1 -1
- package/dist/src/manager/notes/vault-index.js +2 -2
- package/dist/src/manager/notes/vault-index.js.map +1 -1
- package/dist/src/manager/notes/wiki-links.js +1 -1
- package/dist/src/manager/notes/wiki-links.js.map +1 -1
- package/dist/src/manager/notes/wiki-routes.js +84 -0
- package/dist/src/manager/notes/wiki-routes.js.map +1 -0
- package/dist/src/manager/proxy.js +1 -0
- package/dist/src/manager/proxy.js.map +1 -1
- package/dist/src/manager/routes/dashboard-memory.js +24 -1
- package/dist/src/manager/routes/dashboard-memory.js.map +1 -1
- package/dist/src/manager/routes/runtime-monitor.js +1 -1
- package/dist/src/manager/routes/runtime-monitor.js.map +1 -1
- package/dist/src/manager/scan.js +5 -3
- package/dist/src/manager/scan.js.map +1 -1
- package/dist/src/manager/server.js +20 -6
- package/dist/src/manager/server.js.map +1 -1
- package/dist/src/manager/telegram-hub/hub-bot.js +4 -3
- package/dist/src/manager/telegram-hub/hub-bot.js.map +1 -1
- package/dist/src/manager/worker-events.js +2 -1
- package/dist/src/manager/worker-events.js.map +1 -1
- package/dist/src/memory/heartbeat.js +6 -5
- package/dist/src/memory/heartbeat.js.map +1 -1
- package/dist/src/memory/indexing.js +162 -45
- package/dist/src/memory/indexing.js.map +1 -1
- package/dist/src/messaging/channel-validate.js +14 -1
- package/dist/src/messaging/channel-validate.js.map +1 -1
- package/dist/src/messaging/redact.js +2 -1
- package/dist/src/messaging/redact.js.map +1 -1
- package/dist/src/messaging/session-key.js +21 -2
- package/dist/src/messaging/session-key.js.map +1 -1
- package/dist/src/notes/constants.js.map +1 -0
- package/dist/src/notes/frontmatter.js +59 -0
- package/dist/src/notes/frontmatter.js.map +1 -0
- package/dist/src/notes/path-guards.js.map +1 -0
- package/dist/src/notes/search.js.map +1 -0
- package/dist/src/orchestrator/attestation.js +1 -1
- package/dist/src/orchestrator/attestation.js.map +1 -1
- package/dist/src/orchestrator/gateway.js +135 -31
- package/dist/src/orchestrator/gateway.js.map +1 -1
- package/dist/src/orchestrator/pipeline.js +53 -21
- package/dist/src/orchestrator/pipeline.js.map +1 -1
- package/dist/src/orchestrator/scope.js +36 -2
- package/dist/src/orchestrator/scope.js.map +1 -1
- package/dist/src/orchestrator/session-lanes.js +87 -0
- package/dist/src/orchestrator/session-lanes.js.map +1 -0
- package/dist/src/orchestrator/session-work.js +25 -0
- package/dist/src/orchestrator/session-work.js.map +1 -0
- package/dist/src/orchestrator/state-machine.js +1 -5
- package/dist/src/orchestrator/state-machine.js.map +1 -1
- package/dist/src/orchestrator/worker-registry.js +20 -10
- package/dist/src/orchestrator/worker-registry.js.map +1 -1
- package/dist/src/prompt/builder.js +40 -2
- package/dist/src/prompt/builder.js.map +1 -1
- package/dist/src/prompt/templates/a1-system.md +8 -1
- package/dist/src/routes/agent-control.js +44 -8
- package/dist/src/routes/agent-control.js.map +1 -1
- package/dist/src/routes/chat-sessions.js +34 -6
- package/dist/src/routes/chat-sessions.js.map +1 -1
- package/dist/src/routes/command.js +63 -8
- package/dist/src/routes/command.js.map +1 -1
- package/dist/src/routes/events.js +11 -2
- package/dist/src/routes/events.js.map +1 -1
- package/dist/src/routes/link-preview.js +1 -27
- package/dist/src/routes/link-preview.js.map +1 -1
- package/dist/src/routes/messages.js +6 -5
- package/dist/src/routes/messages.js.map +1 -1
- package/dist/src/routes/messaging.js +45 -1
- package/dist/src/routes/messaging.js.map +1 -1
- package/dist/src/routes/orchestrate.js +53 -33
- package/dist/src/routes/orchestrate.js.map +1 -1
- package/dist/src/routes/search.js +84 -0
- package/dist/src/routes/search.js.map +1 -0
- package/dist/src/routes/session-request.js +29 -0
- package/dist/src/routes/session-request.js.map +1 -0
- package/dist/src/routes/settings.js +110 -3
- package/dist/src/routes/settings.js.map +1 -1
- package/dist/src/routes/static.js +30 -8
- package/dist/src/routes/static.js.map +1 -1
- package/dist/src/routes/system.js +1 -1
- package/dist/src/routes/system.js.map +1 -1
- package/dist/src/routes/wiki.js +122 -0
- package/dist/src/routes/wiki.js.map +1 -0
- package/dist/src/search/contract.js +2 -0
- package/dist/src/search/contract.js.map +1 -0
- package/dist/src/search/coordinator.js +153 -0
- package/dist/src/search/coordinator.js.map +1 -0
- package/dist/src/search/provider.js +40 -0
- package/dist/src/search/provider.js.map +1 -0
- package/dist/src/search/providers/chat.js +115 -0
- package/dist/src/search/providers/chat.js.map +1 -0
- package/dist/src/search/providers/memory.js +53 -0
- package/dist/src/search/providers/memory.js.map +1 -0
- package/dist/src/search/providers/wiki.js +80 -0
- package/dist/src/search/providers/wiki.js.map +1 -0
- package/dist/src/slack/api.js +10 -1
- package/dist/src/slack/api.js.map +1 -1
- package/dist/src/slack/attachment-recovery.js +42 -0
- package/dist/src/slack/attachment-recovery.js.map +1 -0
- package/dist/src/slack/bot.js +120 -41
- package/dist/src/slack/bot.js.map +1 -1
- package/dist/src/slack/commands.js +24 -8
- package/dist/src/slack/commands.js.map +1 -1
- package/dist/src/slack/events.js +12 -1
- package/dist/src/slack/events.js.map +1 -1
- package/dist/src/slack/history.js +108 -0
- package/dist/src/slack/history.js.map +1 -0
- package/dist/src/slack/inbound-file.js +242 -0
- package/dist/src/slack/inbound-file.js.map +1 -0
- package/dist/src/slack/inbound-url.js +39 -0
- package/dist/src/slack/inbound-url.js.map +1 -0
- package/dist/src/slack/ingress.js +140 -0
- package/dist/src/slack/ingress.js.map +1 -0
- package/dist/src/slack/manifest.js +3 -0
- package/dist/src/slack/manifest.js.map +1 -1
- package/dist/src/slack/socket.js +1 -1
- package/dist/src/slack/socket.js.map +1 -1
- package/dist/src/slack/thread-tracker.js +86 -0
- package/dist/src/slack/thread-tracker.js.map +1 -0
- package/dist/src/telegram/bot.js +21 -11
- package/dist/src/telegram/bot.js.map +1 -1
- package/dist/src/wiki/config.js +193 -0
- package/dist/src/wiki/config.js.map +1 -0
- package/dist/src/wiki/entities.js +61 -0
- package/dist/src/wiki/entities.js.map +1 -0
- package/dist/src/wiki/ontology.js +72 -0
- package/dist/src/wiki/ontology.js.map +1 -0
- package/dist/src/wiki/prompt.js +172 -0
- package/dist/src/wiki/prompt.js.map +1 -0
- package/dist/src/wiki/safe-traversal.js +198 -0
- package/dist/src/wiki/safe-traversal.js.map +1 -0
- package/dist/src/wiki/scaffold.js +77 -0
- package/dist/src/wiki/scaffold.js.map +1 -0
- package/package.json +1 -1
- package/public/css/session-hub.css +266 -0
- package/public/dist/assets/AdvancedExport-DYGcCAUq.js +1 -0
- package/public/dist/assets/Agent-CNNQ7KkJ.js +1 -0
- package/public/dist/assets/{DesignPanel-BGMPhZSx.js → DesignPanel-DcL369nB.js} +1 -1
- package/public/dist/assets/{DocPanel-Dz-J_l5M.js → DocPanel-K8A2pm_1.js} +1 -1
- package/public/dist/assets/{FolderPanel-DbPhzzvi.js → FolderPanel-CV52EzwG.js} +1 -1
- package/public/dist/assets/{MilkdownWysiwygEditor-Em7iC6y9.js → MilkdownWysiwygEditor-BKOCDI4Z.js} +1 -1
- package/public/dist/assets/api-B25zNNFJ.js +1 -0
- package/public/dist/assets/{app-DX4e6isX.css → app-DjgGeXhJ.css} +1 -1
- package/public/dist/assets/app-Dsgp4xpU.js +55 -0
- package/public/dist/assets/bgtask-badge-C6h5C-o4.js +1 -0
- package/public/dist/assets/{bgtask-badge-qMkKOZx0.js → bgtask-badge-CpavV0Jc.js} +4 -4
- package/public/dist/assets/{employees-BnobWM_2.js → employees-BJyEgim7.js} +4 -4
- package/public/dist/assets/idb-cache-B5PX63p7.js +1 -0
- package/public/dist/assets/{idb-cache-BzzftUET.js → idb-cache-lOntwU3S.js} +1 -1
- package/public/dist/assets/{iframe-renderer-C5vRbbl9.js → iframe-renderer-B8k3AvTv.js} +12 -12
- package/public/dist/assets/iframe-renderer-BO_GgCTR.js +1 -0
- package/public/dist/assets/manager-LkGs6qEJ.css +1 -0
- package/public/dist/assets/manager-kXYQyMhR.js +13 -0
- package/public/dist/assets/{memory-DF4kPwwC.js → memory-D2RbBVO-.js} +11 -11
- package/public/dist/assets/memory-amsvg4Ey.js +1 -0
- package/public/dist/assets/message-history-BGHZxvgV.js +1 -0
- package/public/dist/assets/message-history-Bw2E_b8L.js +1 -0
- package/public/dist/assets/pi-settings-5HVg1sXJ.js +1 -0
- package/public/dist/assets/render-BkGcpH7z.js +157 -0
- package/public/dist/assets/settings-B1FPORO2.js +56 -0
- package/public/dist/assets/settings-CMN-zN4Q.js +1 -0
- package/public/dist/assets/settings-core-B_OjGYVN.js +1 -0
- package/public/dist/assets/settings-core-CxRwqx86.js +144 -0
- package/public/dist/assets/sidebar-DW78DANx.js +49 -0
- package/public/dist/assets/skills-Dz-9Yrxr.js +1 -0
- package/public/dist/assets/{skills-IGquen_j.js → skills-lWi0ZFiV.js} +6 -6
- package/public/dist/assets/{slash-commands-ZRDOL99E.js → slash-commands-DYOI6nKj.js} +1 -1
- package/public/dist/assets/slash-commands-xJFCdTTM.js +1 -0
- package/public/dist/assets/{trace-drawer-D8To8fYt.js → trace-drawer-DBX9xzmt.js} +1 -1
- package/public/dist/assets/{ui-Hx_mNoDf.js → ui-Bx7eqnXt.js} +1 -1
- package/public/dist/assets/ui-JOcrYeG-.js +1 -0
- package/public/dist/assets/{wiki-link-suggestions-DZQc78gj.js → wiki-link-suggestions-Dp6kBQVu.js} +1 -1
- package/public/dist/index.html +4 -4
- package/public/dist/manager/index.html +2 -2
- package/public/index.html +3 -2
- package/public/js/event-channel.ts +25 -0
- package/public/js/features/channel-onboarding-flow.ts +16 -1
- package/public/js/features/channel-onboarding.ts +20 -2
- package/public/js/features/chat-messages.ts +3 -1
- package/public/js/features/chat-search.ts +2 -1
- package/public/js/features/chat.ts +56 -18
- package/public/js/features/elicitation.ts +15 -0
- package/public/js/features/idb-cache.ts +34 -0
- package/public/js/features/message-history.ts +2 -1
- package/public/js/features/session-hub.ts +334 -0
- package/public/js/features/settings-cli-status-render.ts +1 -1
- package/public/js/features/settings-cli-status.ts +25 -11
- package/public/js/features/settings-core.ts +72 -3
- package/public/js/features/settings-types.ts +32 -0
- package/public/js/features/ui-status.ts +2 -1
- package/public/js/main.ts +20 -0
- package/public/js/render/compose-block.ts +7 -0
- package/public/js/ws.ts +22 -2
- package/public/locales/en.json +46 -4
- package/public/locales/ja.json +46 -4
- package/public/locales/ko.json +46 -4
- package/public/locales/zh.json +46 -4
- package/public/manager/src/App.tsx +1 -1
- package/public/manager/src/InstancePreview.tsx +5 -1
- package/public/manager/src/api.ts +2 -1
- package/public/manager/src/components/InstanceGroups.tsx +10 -0
- package/public/manager/src/components/InstanceListContent.tsx +24 -1
- package/public/manager/src/components/InstanceRow.tsx +33 -3
- package/public/manager/src/components/InstanceSessionList.tsx +132 -0
- package/public/manager/src/hooks/useActiveSessionDisclosure.ts +45 -0
- package/public/manager/src/lib/resilient-fetch.ts +93 -0
- package/public/manager/src/lib/session-store.ts +216 -0
- package/public/manager/src/manager-components.css +97 -0
- package/public/manager/src/settings/pages/AdvancedExport.tsx +12 -2
- package/public/manager/src/settings/pages/Agent.tsx +113 -1
- package/public/manager/src/settings/pages/components/agent/agent-meta.ts +1 -1
- package/scripts/burst-scenario.mjs +72 -0
- package/scripts/check-redaction-sinks.mjs +1 -0
- package/scripts/check-strict-baseline.mjs +1 -1
- package/dist/src/manager/notes/constants.js.map +0 -1
- package/dist/src/manager/notes/path-guards.js.map +0 -1
- package/dist/src/manager/notes/search.js.map +0 -1
- package/public/dist/assets/AdvancedExport-DsDpqXoH.js +0 -1
- package/public/dist/assets/Agent-BjbZVZ9p.js +0 -1
- package/public/dist/assets/api-C9dOxby7.js +0 -1
- package/public/dist/assets/app-BzD_ZLvt.js +0 -55
- package/public/dist/assets/bgtask-badge-Dqbp-Y4e.js +0 -1
- package/public/dist/assets/idb-cache-ZH6lOebk.js +0 -1
- package/public/dist/assets/iframe-renderer-Df4KIVyx.js +0 -1
- package/public/dist/assets/manager-C4eNDNFt.css +0 -1
- package/public/dist/assets/manager-DaN201mN.js +0 -13
- package/public/dist/assets/memory-D-bULTHl.js +0 -1
- package/public/dist/assets/message-history-C4WOisGx.js +0 -1
- package/public/dist/assets/message-history-ZBoza6ew.js +0 -1
- package/public/dist/assets/pi-settings-CBUGDAuT.js +0 -1
- package/public/dist/assets/render-DYX6GRQ0.js +0 -122
- package/public/dist/assets/settings-DbJpGoaX.js +0 -55
- package/public/dist/assets/settings-SreihUje.js +0 -1
- package/public/dist/assets/settings-core-B1Y5Nb4Z.js +0 -1
- package/public/dist/assets/settings-core-DobZWIZ7.js +0 -140
- package/public/dist/assets/sidebar-Cp22g0Gv.js +0 -49
- package/public/dist/assets/skills-DwPIP08e.js +0 -1
- package/public/dist/assets/slash-commands-BBVfwcnW.js +0 -1
- package/public/dist/assets/ui-CAicpUZc.js +0 -1
- /package/dist/src/{manager/notes → notes}/constants.js +0 -0
- /package/dist/src/{manager/notes → notes}/path-guards.js +0 -0
- /package/dist/src/{manager/notes → notes}/search.js +0 -0
- /package/public/dist/assets/{pi-settings-Dh5lgb87.js → pi-settings-DEuKgvIj.js} +0 -0
package/dist/src/agent/spawn.js
CHANGED
|
@@ -10,10 +10,11 @@ import { publish as ssePublish } from '../core/event-bus.js';
|
|
|
10
10
|
import { settings, UPLOADS_DIR, detectCli, getProjectDirs } from '../core/config.js';
|
|
11
11
|
import { migrateLegacyClaudeValue } from '../cli/claude-models.js';
|
|
12
12
|
import { stripUndefined } from '../core/strip-undefined.js';
|
|
13
|
-
import { clearEmployeeSession, getSession, insertMessage, insertMessageWithTraceRun, getRecentMessages, listQueuedMessages, insertQueuedMessage, deleteQueuedMessage, getSessionBucket, clearSessionBucket, setSessionBucketSnapshot, } from '../core/db.js';
|
|
13
|
+
import { clearEmployeeSession, getSession, insertMessage, insertMessageWithTraceRun, getRecentMessages, listQueuedMessages, insertQueuedMessage, deleteQueuedMessage, migrateQueuedMessagesV1ToV2, getSessionBucket, clearSessionBucket, setSessionBucketSnapshot, } from '../core/db.js';
|
|
14
14
|
import { sanitizeToolLogForDurableStorage } from '../shared/tool-log-sanitize.js';
|
|
15
15
|
import { buildTaskSnapshot } from '../memory/runtime.js';
|
|
16
16
|
import { getActiveChatSession } from '../core/chat-sessions.js';
|
|
17
|
+
import { currentSessionScope } from '../core/session-context.js';
|
|
17
18
|
import { getSystemPrompt, regenerateB } from '../prompt/builder.js';
|
|
18
19
|
import { extractSessionId, extractFromEvent, extractFromAcpUpdate, extractOutputChunk, logEventSummary, flushClaudeBuffers, flushOpenCodeBuffers } from './events.js';
|
|
19
20
|
import { detectSmokeResponse } from './smoke-detector.js';
|
|
@@ -22,35 +23,33 @@ import { resolveMainCli, consumePendingBootstrapPrompt, peekPendingBootstrapProm
|
|
|
22
23
|
import { getSessionOwnershipGeneration, persistMainSession, } from './session-persistence.js';
|
|
23
24
|
import { isCompactMarkerRow } from '../core/compact.js';
|
|
24
25
|
import { isRuntimeSettingsMutationInFlight, waitForRuntimeSettingsIdle } from '../core/runtime-settings-gate.js';
|
|
25
|
-
import { hasBlockingWorkers, hasPendingWorkerReplays, getActiveWorkers, clearAllWorkers } from '../orchestrator/worker-registry.js';
|
|
26
|
+
import { hasBlockingWorkers, hasPendingWorkerReplays, getActiveWorkers, clearAllWorkers, clearWorkersForScope } from '../orchestrator/worker-registry.js';
|
|
26
27
|
import { sanitizeWorkerProgressTools } from '../orchestrator/worker-progress.js';
|
|
27
28
|
import { handleAgentExit, setSpawnAgent, setMainMetaHandler } from './lifecycle-handler.js';
|
|
28
29
|
import { buildServicePath } from '../core/runtime-path.js';
|
|
29
|
-
import { resolveOrcScope } from '../orchestrator/scope.js';
|
|
30
|
+
import { LOCAL_SESSION_SCOPE_ACTIVATION, resolveOrcScope } from '../orchestrator/scope.js';
|
|
30
31
|
import { stripInterviewTracker } from '../orchestrator/sanitize.js';
|
|
31
32
|
import { beginLiveRun, appendLiveRunText, setLiveRunTraceId, clearLiveRun, replaceLiveRunTools, appendLiveRunTool, getLiveRun } from './live-run-state.js';
|
|
32
33
|
import { setSpawnRef as setMemorySpawnRef, } from './memory-flush-controller.js';
|
|
33
34
|
import { applyCliEnvDefaults, buildSessionResumeKey, ensureOpencodeAlwaysAllowPermissions } from './spawn-env.js';
|
|
34
|
-
import { buildPromptForArgs, withHistoryPrompt } from './prompt-context.js';
|
|
35
|
+
import { buildPromptForArgs, shouldBuildHistoryBlock, withHistoryPrompt } from './prompt-context.js';
|
|
35
36
|
import { attachWatchdog, DEFAULT_WATCHDOG_ABSOLUTE_HARD_CAP_MS } from './watchdog.js';
|
|
36
37
|
import { buildOpencodeRuntimeSnapshot, buildOpencodeSpawnAudit, pushOpencodeRawEvent, resolveOpencodeBinary, } from './opencode-diagnostics.js';
|
|
37
38
|
import { asCliEventRecord, discriminate, fieldString } from '../types/cli-events.js';
|
|
38
39
|
import { isJawRuntimeEvent, handleJawRuntimeEvent } from './claude-e-runtime.js';
|
|
39
|
-
import {
|
|
40
|
+
import { jawRuntimesByScope, runtimeForScope } from './jwc-runtime.js';
|
|
40
41
|
import { applyOutputPolicy, runBeforeSpawnChecks } from '../core/policy-hooks.js';
|
|
41
|
-
import { appendTraceEvent, stampTraceTool, startTraceRun } from '../trace/store.js';
|
|
42
|
+
import { appendTraceEvent, finalizeTraceRun, stampTraceTool, startTraceRun } from '../trace/store.js';
|
|
42
43
|
import { AGY_COMPLETE_KILL_REASON, appendAgyFullText, classifyAgyTranscriptMode, describeAgyFinalSource, extractAgyConversationId, finalizeAgyFallbackText, AGY_PLANNER_ONLY_NOTICE, isAgyIntermediatePlannerText, formatAgyWatchdogContext, resolveAgyEmptyCloseError, formatAgyTimeoutMessage, getAgyQuietCompletionDelayMs, isAgyStaleSessionOutput, normalizeAgyCloseText, shouldFreezeAgyLiveDisplay, stripAgyPromptEchoPrefix, stripAgyResumeReplayPrefix, stripAgyResumeReplayPrefixes, } from './agy-runtime.js';
|
|
43
44
|
import { detectAgyCapabilities } from './agy-capabilities.js';
|
|
44
45
|
import { buildAgyBootstrapEnvelope, resolveAgyPromptOrder, } from './agy-bootstrap.js';
|
|
45
46
|
import { startAgyTranscriptWatcher } from './agy-transcript-watcher.js';
|
|
46
|
-
import { appendAssistantTextSegment, emitAgentTool, normalizeAssistantDisplayText, pushTrace } from './events/helpers.js';
|
|
47
|
-
import {
|
|
48
|
-
import { captureKiroSessionIdAfterExit, finalizeKiroFullText, flushKiroStdoutContext, isKiroPlainTextCli, isKiroStaleSessionOutput, parseAiESessionIdFromStderr, processKiroStdoutChunk, } from './kiro-runtime.js';
|
|
47
|
+
import { appendAssistantRawText, appendAssistantTextSegment, emitAgentTool, normalizeAssistantDisplayText, pushTrace } from './events/helpers.js';
|
|
48
|
+
import { captureKiroSessionIdAfterExit, finalizeKiroFullText, flushKiroStdoutContext, isKiroPlainTextCli, isKiroStaleSessionOutput, parseAiESessionIdFromStderr, processKiroStdoutChunk, spawnWithKiroSnapshot, } from './kiro-runtime.js';
|
|
49
49
|
import { resolveCursorModelVariant } from './cursor-runtime.js';
|
|
50
50
|
import { normalizePiSettings, spawnPiRpc } from './pi-runtime.js';
|
|
51
51
|
import { getEmployeeMcpServers } from './mcp-passthrough.js';
|
|
52
52
|
// ─── State ───────────────────────────────────────────
|
|
53
|
-
export let activeProcess = null;
|
|
54
53
|
export const activeProcesses = new Map(); // agentId → child process
|
|
55
54
|
/** Kill reason recorded when a duplicate registration reaps the previous child. */
|
|
56
55
|
const DUP_REGISTRATION_KILL_REASON = 'dup-registration';
|
|
@@ -98,12 +97,40 @@ function registerActiveProcess(agentLabel, child) {
|
|
|
98
97
|
}
|
|
99
98
|
activeProcesses.set(agentLabel, child);
|
|
100
99
|
}
|
|
101
|
-
|
|
102
|
-
export function getCurrentMainMeta() {
|
|
103
|
-
|
|
100
|
+
export const activeMainProcesses = new Map();
|
|
101
|
+
export function getCurrentMainMeta(scopeKey) {
|
|
102
|
+
const scope = scopeKey ?? currentSessionScope()?.scope ?? 'default';
|
|
103
|
+
return activeMainProcesses.get(scope)?.meta ?? null;
|
|
104
104
|
}
|
|
105
|
-
export function setCurrentMainMeta(
|
|
106
|
-
|
|
105
|
+
export function setCurrentMainMeta(scopeKeyOrMeta, nextMeta) {
|
|
106
|
+
const scopeKey = typeof scopeKeyOrMeta === 'string'
|
|
107
|
+
? scopeKeyOrMeta
|
|
108
|
+
: currentSessionScope()?.scope ?? 'default';
|
|
109
|
+
const meta = typeof scopeKeyOrMeta === 'string' ? nextMeta ?? null : scopeKeyOrMeta;
|
|
110
|
+
const run = activeMainProcesses.get(scopeKey);
|
|
111
|
+
if (!meta) {
|
|
112
|
+
activeMainProcesses.delete(scopeKey);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (run) {
|
|
116
|
+
run.meta = meta;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
activeMainProcesses.set(scopeKey, {
|
|
120
|
+
process: null,
|
|
121
|
+
starting: false,
|
|
122
|
+
steering: false,
|
|
123
|
+
ownerGeneration: 0,
|
|
124
|
+
meta,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
export function releaseMainRun(scopeKey, child, ownerGeneration) {
|
|
129
|
+
const run = activeMainProcesses.get(scopeKey);
|
|
130
|
+
if (!run || run.process !== child || run.ownerGeneration !== ownerGeneration)
|
|
131
|
+
return false;
|
|
132
|
+
activeMainProcesses.delete(scopeKey);
|
|
133
|
+
return true;
|
|
107
134
|
}
|
|
108
135
|
export function buildAiERuntimeStatusMeta(cli, provider, model) {
|
|
109
136
|
if (cli !== 'ai-e')
|
|
@@ -230,7 +257,7 @@ export function killAgentById(agentId) {
|
|
|
230
257
|
}
|
|
231
258
|
export { memoryFlushCounter, flushCycleCount } from './memory-flush-controller.js';
|
|
232
259
|
const queueCtrl = createQueueController({
|
|
233
|
-
isSpawnBusy: () =>
|
|
260
|
+
isSpawnBusy: (scopeKey) => isAgentBusy(scopeKey),
|
|
234
261
|
hasBlockingWorkers,
|
|
235
262
|
hasPendingWorkerReplays,
|
|
236
263
|
insertMessage,
|
|
@@ -238,30 +265,41 @@ const queueCtrl = createQueueController({
|
|
|
238
265
|
insertQueuedMessage,
|
|
239
266
|
deleteQueuedMessage,
|
|
240
267
|
listQueuedMessages: listQueuedMessages,
|
|
268
|
+
migrateQueuedMessagesV1ToV2,
|
|
241
269
|
broadcast,
|
|
242
270
|
importPipeline: () => import('../orchestrator/pipeline.js'),
|
|
243
271
|
getWorkingDir: () => settings["workingDir"] || null,
|
|
272
|
+
isMultiSessionEnabled: () => settings["multiSession"]?.enabled === true,
|
|
273
|
+
isLocalSessionScopeEnabled: () => LOCAL_SESSION_SCOPE_ACTIVATION,
|
|
244
274
|
});
|
|
245
|
-
export const { messageQueue, enqueueMessage, removeQueuedMessage, processQueue, setQueueHold, clearQueueHold, getQueueHoldId,
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
let cancelActiveCodexAppTurn = null;
|
|
250
|
-
let cancelActivePiTurn = null;
|
|
275
|
+
export const { messageQueue, enqueueMessage, removeQueuedMessage, processQueue, setQueueHold, clearQueueHold, getQueueHoldId, isScopedQueue, isRetryPending, isQueueBusy, clearRetryTimer,
|
|
276
|
+
// Exposed so DELETE's 409 paths can be driven end-to-end against the
|
|
277
|
+
// production controller instead of an isolated instance.
|
|
278
|
+
retryStateForScope, resetFallbackState, getFallbackState, getQueuedMessageSnapshotForScope, purgeQueueOnStop, } = queueCtrl;
|
|
251
279
|
const piProfileFingerprintKey = crypto.randomBytes(32);
|
|
252
|
-
export function setSteerInProgress(
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
280
|
+
export function setSteerInProgress(scopeKeyOrValue, nextValue) {
|
|
281
|
+
const scopeKey = typeof scopeKeyOrValue === 'string' ? scopeKeyOrValue : 'default';
|
|
282
|
+
const value = typeof scopeKeyOrValue === 'boolean' ? scopeKeyOrValue : nextValue === true;
|
|
283
|
+
const run = activeMainProcesses.get(scopeKey);
|
|
284
|
+
if (!run)
|
|
285
|
+
return;
|
|
286
|
+
const was = run.steering;
|
|
287
|
+
run.steering = value;
|
|
288
|
+
if (was && !value)
|
|
289
|
+
queueMicrotask(() => { void processQueue(scopeKey); });
|
|
257
290
|
}
|
|
258
|
-
export function isSteerInProgress() {
|
|
259
|
-
return
|
|
291
|
+
export function isSteerInProgress(scopeKey = 'default') {
|
|
292
|
+
return activeMainProcesses.get(scopeKey)?.steering === true;
|
|
260
293
|
}
|
|
261
|
-
export function isAgentBusy() {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
294
|
+
export function isAgentBusy(scopeKey = 'default') {
|
|
295
|
+
if (scopeKey === null) {
|
|
296
|
+
return activeMainProcesses.size > 0
|
|
297
|
+
|| [...jawRuntimesByScope.values()].some(runtime => runtime.busy)
|
|
298
|
+
|| queueCtrl.isRetryPending(null);
|
|
299
|
+
}
|
|
300
|
+
return activeMainProcesses.has(scopeKey)
|
|
301
|
+
|| runtimeForScope(scopeKey).busy
|
|
302
|
+
|| queueCtrl.isRetryPending(scopeKey);
|
|
265
303
|
}
|
|
266
304
|
// ─── Kill / Steer ────────────────────────────────────
|
|
267
305
|
// [I2] Per-process kill reason map (replaces global variable to avoid cross-process confusion)
|
|
@@ -272,25 +310,28 @@ const CLAUDE_E_STEER_WAIT_MS = 30_000;
|
|
|
272
310
|
const CLAUDE_E_STEER_KILL_ESCALATION_MS = 8_000;
|
|
273
311
|
const DEFAULT_CODEX_APP_TURN_IDLE_MS = 300_000;
|
|
274
312
|
const DEFAULT_CODEX_APP_TURN_ABS_MS = 2 * 60 * 60_000;
|
|
313
|
+
const DEFAULT_CODEX_APP_ACQUIRE_WAIT_MS = 60_000;
|
|
314
|
+
const CODEX_APP_ACQUIRE_RETRY_BACKOFF_MAX_MS = 250;
|
|
275
315
|
function configuredPositiveMs(value, fallback) {
|
|
276
316
|
const parsed = Number(value);
|
|
277
317
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
278
318
|
}
|
|
279
|
-
function getActiveMainCli() {
|
|
280
|
-
|
|
319
|
+
function getActiveMainCli(scopeKey) {
|
|
320
|
+
const cli = activeMainProcesses.get(scopeKey)?.meta.cli;
|
|
321
|
+
return typeof cli === 'string' ? cli : null;
|
|
281
322
|
}
|
|
282
|
-
function isActiveAiEPtyRuntime() {
|
|
283
|
-
const cli = getActiveMainCli();
|
|
323
|
+
function isActiveAiEPtyRuntime(scopeKey) {
|
|
324
|
+
const cli = getActiveMainCli(scopeKey);
|
|
284
325
|
return cli === 'claude-e' || cli === 'ai-e';
|
|
285
326
|
}
|
|
286
|
-
function getKillPolicy(reason) {
|
|
287
|
-
if (reason === 'steer' && isActiveAiEPtyRuntime()) {
|
|
327
|
+
function getKillPolicy(scopeKey, reason) {
|
|
328
|
+
if (reason === 'steer' && isActiveAiEPtyRuntime(scopeKey)) {
|
|
288
329
|
return { signal: 'SIGINT', escalationMs: CLAUDE_E_STEER_KILL_ESCALATION_MS };
|
|
289
330
|
}
|
|
290
331
|
return { signal: 'SIGTERM', escalationMs: DEFAULT_KILL_ESCALATION_MS };
|
|
291
332
|
}
|
|
292
|
-
export function getSteerWaitMsForActiveAgent() {
|
|
293
|
-
return isActiveAiEPtyRuntime() ? CLAUDE_E_STEER_WAIT_MS : DEFAULT_STEER_WAIT_MS;
|
|
333
|
+
export function getSteerWaitMsForActiveAgent(scopeKey = 'default') {
|
|
334
|
+
return isActiveAiEPtyRuntime(scopeKey) ? CLAUDE_E_STEER_WAIT_MS : DEFAULT_STEER_WAIT_MS;
|
|
294
335
|
}
|
|
295
336
|
/** Get kill reason for a process (by PID), consuming it */
|
|
296
337
|
function consumeKillReason(pid) {
|
|
@@ -303,81 +344,78 @@ function consumeKillReason(pid) {
|
|
|
303
344
|
}
|
|
304
345
|
/**
|
|
305
346
|
* Fix A: 사용자 stop은 메모리 큐 + DB persisted_queue + frontend pending row를
|
|
306
|
-
* 모두 폐기한다. exit handler의
|
|
347
|
+
* 모두 폐기한다. exit handler의 scoped queue 자동 드레인이 stop 직후 잔존
|
|
307
348
|
* 메시지를 "스스로 steer" 처럼 실행하던 회귀를 차단.
|
|
308
349
|
*/
|
|
309
350
|
/**
|
|
310
351
|
* Fix C2: 사용자 stop 시 worker-registry 도 비운다.
|
|
311
|
-
* gateway.submitMessage가
|
|
352
|
+
* gateway.submitMessage가 scoped main/worker/replay 상태를 모두 검사하므로,
|
|
312
353
|
* 도 검사하므로, 이걸 비우지 않으면 stop 직후 새 메시지가 busy 분기 → 큐로 떨어지고
|
|
313
354
|
* 프론트는 (1) 낙관 bubble + (2) applyQueuedOverlay 가 만든 queued bubble = 2개를 보여준다.
|
|
314
355
|
*/
|
|
315
|
-
function clearWorkerSlotsOnStop(reason) {
|
|
316
|
-
const active = getActiveWorkers().length;
|
|
317
|
-
if (active === 0 && !hasPendingWorkerReplays())
|
|
356
|
+
function clearWorkerSlotsOnStop(scopeKey, reason) {
|
|
357
|
+
const active = getActiveWorkers(scopeKey).length;
|
|
358
|
+
if (active === 0 && !hasPendingWorkerReplays(scopeKey))
|
|
318
359
|
return;
|
|
319
|
-
|
|
320
|
-
console.log(`[jaw:stop] cleared worker registry (active=${active}, reason=${reason})`);
|
|
360
|
+
clearWorkersForScope(scopeKey);
|
|
361
|
+
console.log(`[jaw:stop] cleared worker registry (active=${active}, scope=${scopeKey}, reason=${reason})`);
|
|
321
362
|
}
|
|
322
|
-
function clearMainLiveRunOnStop(reason) {
|
|
323
|
-
if (reason !== 'api' && reason !== 'user' && reason !== 'steer')
|
|
363
|
+
function clearMainLiveRunOnStop(scopeKey, reason) {
|
|
364
|
+
if (reason !== 'api' && reason !== 'user' && reason !== 'steer' && reason !== 'interrupt')
|
|
324
365
|
return;
|
|
325
|
-
|
|
326
|
-
origin: currentMainMeta?.origin || 'web',
|
|
327
|
-
chatId: currentMainMeta?.chatId,
|
|
328
|
-
workingDir: settings["workingDir"] || null,
|
|
329
|
-
}));
|
|
330
|
-
clearLiveRun(scope);
|
|
366
|
+
clearLiveRun(scopeKey);
|
|
331
367
|
}
|
|
332
368
|
/**
|
|
333
369
|
* jwc turns run in-process (no ChildProcess), so the SIGTERM/SIGKILL paths
|
|
334
370
|
* below never touch them — abort the resident runtime session explicitly or
|
|
335
371
|
* /api/stop is a no-op while jwc streams (devlog 260703 tui_steer_esc_rca).
|
|
336
372
|
*/
|
|
337
|
-
function abortInProcessRuntimeOnStop(reason) {
|
|
338
|
-
if (reason !== 'api' && reason !== 'user' && reason !== 'steer')
|
|
373
|
+
function abortInProcessRuntimeOnStop(scopeKey, reason) {
|
|
374
|
+
if (reason !== 'api' && reason !== 'user' && reason !== 'steer' && reason !== 'interrupt')
|
|
339
375
|
return false;
|
|
340
|
-
if (
|
|
376
|
+
if (getActiveMainCli(scopeKey) !== 'jwc')
|
|
341
377
|
return false;
|
|
342
|
-
|
|
378
|
+
const runtime = runtimeForScope(scopeKey);
|
|
379
|
+
if (!runtime.busy)
|
|
380
|
+
return false;
|
|
381
|
+
runtime.abort().catch((err) => {
|
|
343
382
|
console.warn('[jaw:stop] jwc abort failed', err?.message || err);
|
|
344
383
|
});
|
|
345
384
|
return true;
|
|
346
385
|
}
|
|
347
|
-
export function killActiveAgent(
|
|
348
|
-
const
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const
|
|
386
|
+
export function killActiveAgent(scopeKeyOrReason = 'user', scopedReason) {
|
|
387
|
+
const scopeKey = scopedReason === undefined ? 'default' : scopeKeyOrReason;
|
|
388
|
+
const reason = scopedReason ?? scopeKeyOrReason;
|
|
389
|
+
const run = activeMainProcesses.get(scopeKey);
|
|
390
|
+
const hadTimer = queueCtrl.isRetryPending(scopeKey);
|
|
391
|
+
const cancelledPendingMain = run?.cancelPending ? (run.cancelPending(reason), true) : false;
|
|
392
|
+
clearRetryTimer(scopeKey, false);
|
|
393
|
+
if (!cancelledPendingMain)
|
|
394
|
+
clearMainLiveRunOnStop(scopeKey, reason);
|
|
395
|
+
const abortedInProcess = abortInProcessRuntimeOnStop(scopeKey, reason);
|
|
353
396
|
// Fix A: 사용자 stop은 큐도 폐기. steer/internal kill은 큐 보존.
|
|
354
397
|
// Fix C2: worker registry 도 비워서 hasBlockingWorkers/hasPendingWorkerReplays가 즉시 false.
|
|
355
398
|
if (reason === 'api' || reason === 'user') {
|
|
356
|
-
queueCtrl.purgeQueueOnStop(reason);
|
|
357
|
-
clearWorkerSlotsOnStop(reason);
|
|
399
|
+
queueCtrl.purgeQueueOnStop(scopeKey, reason);
|
|
400
|
+
clearWorkerSlotsOnStop(scopeKey, reason);
|
|
358
401
|
}
|
|
359
|
-
if (
|
|
360
|
-
if (
|
|
361
|
-
killReasons.set(
|
|
362
|
-
console.log(`[jaw:kill] reason=${reason} cli
|
|
363
|
-
|
|
364
|
-
if (reason === 'api' || reason === 'user' || reason === 'steer')
|
|
365
|
-
|
|
402
|
+
if (run?.cancelTurn && (getActiveMainCli(scopeKey) === 'codex-app' || getActiveMainCli(scopeKey) === 'pi')) {
|
|
403
|
+
if (run.process?.pid)
|
|
404
|
+
killReasons.set(run.process.pid, reason);
|
|
405
|
+
console.log(`[jaw:kill] reason=${reason} scope=${scopeKey} cli=${getActiveMainCli(scopeKey)} action=lease.cancel`);
|
|
406
|
+
run.cancelTurn(reason);
|
|
407
|
+
if (reason === 'api' || reason === 'user' || reason === 'steer' || reason === 'interrupt')
|
|
408
|
+
activeMainProcesses.delete(scopeKey);
|
|
366
409
|
return true;
|
|
367
410
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (reason === 'api' || reason === 'user' || reason === 'steer')
|
|
374
|
-
activeProcess = null;
|
|
375
|
-
return true;
|
|
411
|
+
const activeProcess = run?.process ?? null;
|
|
412
|
+
if (!activeProcess) {
|
|
413
|
+
if (reason === 'api' || reason === 'user' || reason === 'steer' || reason === 'interrupt')
|
|
414
|
+
activeMainProcesses.delete(scopeKey);
|
|
415
|
+
return hadTimer || cancelledPendingMain || abortedInProcess;
|
|
376
416
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const policy = getKillPolicy(reason);
|
|
380
|
-
console.log(`[jaw:kill] reason=${reason} cli=${getActiveMainCli() || 'unknown'} signal=${policy.signal} escalationMs=${policy.escalationMs}`);
|
|
417
|
+
const policy = getKillPolicy(scopeKey, reason);
|
|
418
|
+
console.log(`[jaw:kill] reason=${reason} scope=${scopeKey} cli=${getActiveMainCli(scopeKey) || 'unknown'} signal=${policy.signal} escalationMs=${policy.escalationMs}`);
|
|
381
419
|
if (activeProcess.pid)
|
|
382
420
|
killReasons.set(activeProcess.pid, reason);
|
|
383
421
|
try {
|
|
@@ -411,25 +449,23 @@ export function killActiveAgent(reason = 'user') {
|
|
|
411
449
|
proc.stdout?.destroy();
|
|
412
450
|
proc.stderr?.destroy();
|
|
413
451
|
}, policy.escalationMs);
|
|
414
|
-
// Fix C1: 사용자 stop/steer 시
|
|
452
|
+
// Fix C1: 사용자 stop/steer 시 해당 scope busy가 즉시 false가 되도록 참조를 동기 해제.
|
|
415
453
|
// 실제 child 종료는 위 setTimeout SIGKILL이 백그라운드에서 마무리.
|
|
416
454
|
// exit handler의 setActiveProcess(null) / activeProcesses.delete 는 idempotent.
|
|
417
|
-
if (reason === 'api' || reason === 'user' || reason === 'steer') {
|
|
418
|
-
|
|
455
|
+
if (reason === 'api' || reason === 'user' || reason === 'steer' || reason === 'interrupt') {
|
|
456
|
+
activeMainProcesses.delete(scopeKey);
|
|
419
457
|
}
|
|
420
458
|
return true;
|
|
421
459
|
}
|
|
422
460
|
export function killAllAgents(reason = 'user') {
|
|
423
|
-
const hadTimer = queueCtrl.isRetryPending();
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
// Fix A: 사용자 stop은 큐도 폐기. Fix C2: worker 슬롯도 비움.
|
|
429
|
-
if (reason === 'api' || reason === 'user') {
|
|
430
|
-
queueCtrl.purgeQueueOnStop(reason);
|
|
431
|
-
clearWorkerSlotsOnStop(reason);
|
|
461
|
+
const hadTimer = queueCtrl.isRetryPending(null);
|
|
462
|
+
const mainScopes = [...activeMainProcesses.keys()];
|
|
463
|
+
let killedMain = false;
|
|
464
|
+
for (const scopeKey of mainScopes) {
|
|
465
|
+
killedMain = killActiveAgent(scopeKey, reason) || killedMain;
|
|
432
466
|
}
|
|
467
|
+
if (reason === 'api' || reason === 'user')
|
|
468
|
+
queueCtrl.purgeQueueOnStop(null, reason);
|
|
433
469
|
let killed = 0;
|
|
434
470
|
for (const [id, proc] of activeProcesses) {
|
|
435
471
|
console.log(`[jaw:killAll] killing ${id}, reason=${reason}`);
|
|
@@ -465,23 +501,21 @@ export function killAllAgents(reason = 'user') {
|
|
|
465
501
|
ref.stderr?.destroy();
|
|
466
502
|
}, 2000);
|
|
467
503
|
}
|
|
468
|
-
// Also kill main activeProcess if not in map
|
|
469
|
-
if (activeProcess && !activeProcesses.has('main')) {
|
|
470
|
-
killActiveAgent(reason);
|
|
471
|
-
}
|
|
472
|
-
// Fix C1: 사용자 stop 시 isAgentBusy() 즉시 false. 실제 종료는 백그라운드 SIGKILL.
|
|
473
504
|
if (reason === 'api' || reason === 'user') {
|
|
474
|
-
activeProcess = null;
|
|
475
505
|
activeProcesses.clear();
|
|
506
|
+
activeMainProcesses.clear();
|
|
507
|
+
clearAllWorkers();
|
|
476
508
|
}
|
|
477
|
-
return killed > 0 ||
|
|
509
|
+
return killed > 0 || killedMain || hadTimer;
|
|
478
510
|
}
|
|
479
|
-
export function waitForProcessEnd(
|
|
480
|
-
|
|
511
|
+
export function waitForProcessEnd(scopeKeyOrTimeout = 'default', scopedTimeout = 3000) {
|
|
512
|
+
const scopeKey = typeof scopeKeyOrTimeout === 'string' ? scopeKeyOrTimeout : 'default';
|
|
513
|
+
const timeoutMs = typeof scopeKeyOrTimeout === 'number' ? scopeKeyOrTimeout : scopedTimeout;
|
|
514
|
+
if (!activeMainProcesses.has(scopeKey))
|
|
481
515
|
return Promise.resolve();
|
|
482
516
|
return new Promise(resolve => {
|
|
483
517
|
const check = setInterval(() => {
|
|
484
|
-
if (!
|
|
518
|
+
if (!activeMainProcesses.has(scopeKey)) {
|
|
485
519
|
clearInterval(check);
|
|
486
520
|
resolve();
|
|
487
521
|
}
|
|
@@ -489,21 +523,35 @@ export function waitForProcessEnd(timeoutMs = 3000) {
|
|
|
489
523
|
setTimeout(() => { clearInterval(check); resolve(); }, timeoutMs);
|
|
490
524
|
});
|
|
491
525
|
}
|
|
492
|
-
export
|
|
493
|
-
const
|
|
494
|
-
|
|
526
|
+
export function canSteerAgent(scopeKey) {
|
|
527
|
+
const run = activeMainProcesses.get(scopeKey);
|
|
528
|
+
return run?.meta.cli === 'jwc' && jawRuntimesByScope.get(scopeKey)?.busy === true;
|
|
529
|
+
}
|
|
530
|
+
export async function steerAgent(scopeKey, newPrompt, source, meta) {
|
|
531
|
+
const run = activeMainProcesses.get(scopeKey);
|
|
532
|
+
const runtime = runtimeForScope(scopeKey);
|
|
533
|
+
const chatSessionId = meta?.chatSessionId || run?.meta.chatSessionId || getActiveChatSession();
|
|
534
|
+
if (run?.meta.cli === 'jwc' && runtime.busy) {
|
|
535
|
+
insertMessage.run('user', newPrompt, source, '', settings["workingDir"] || null, chatSessionId);
|
|
536
|
+
broadcast('new_message', { role: 'user', content: newPrompt, source, scope: scopeKey, sessionId: chatSessionId });
|
|
537
|
+
broadcast('steer_started', stripUndefined({ prompt: newPrompt, origin: source || 'web', scope: scopeKey, sessionId: chatSessionId, target: meta?.target, chatId: meta?.chatId, requestId: meta?.requestId, remoteKey: meta?.remoteKey, replyViaTarget: meta?.replyViaTarget }));
|
|
538
|
+
await runtime.steer(settings["workingDir"] || process.cwd(), newPrompt);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const steerWaitMs = getSteerWaitMsForActiveAgent(scopeKey);
|
|
542
|
+
const wasRunning = killActiveAgent(scopeKey, 'steer');
|
|
495
543
|
if (wasRunning)
|
|
496
|
-
await waitForProcessEnd(steerWaitMs);
|
|
497
|
-
insertMessage.run('user', newPrompt, source, '', settings["workingDir"] || null,
|
|
498
|
-
broadcast('new_message', { role: 'user', content: newPrompt, source });
|
|
499
|
-
broadcast('steer_started', { prompt: newPrompt, origin: source || 'web' });
|
|
544
|
+
await waitForProcessEnd(scopeKey, steerWaitMs);
|
|
545
|
+
insertMessage.run('user', newPrompt, source, '', settings["workingDir"] || null, chatSessionId);
|
|
546
|
+
broadcast('new_message', { role: 'user', content: newPrompt, source, scope: scopeKey, sessionId: chatSessionId });
|
|
547
|
+
broadcast('steer_started', { prompt: newPrompt, origin: source || 'web', scope: scopeKey });
|
|
500
548
|
const { orchestrate, orchestrateContinue, orchestrateReset, isContinueIntent, isResetIntent } = await import('../orchestrator/pipeline.js');
|
|
501
549
|
const origin = source || 'web';
|
|
502
550
|
const task = isResetIntent(newPrompt)
|
|
503
|
-
? orchestrateReset({ origin, _skipInsert: true })
|
|
551
|
+
? orchestrateReset({ origin, scope: scopeKey, chatSessionId, _skipInsert: true })
|
|
504
552
|
: isContinueIntent(newPrompt)
|
|
505
|
-
? orchestrateContinue({ origin, _skipInsert: true })
|
|
506
|
-
: orchestrate(newPrompt, { origin, _skipInsert: true });
|
|
553
|
+
? orchestrateContinue({ origin, scope: scopeKey, chatSessionId, _skipInsert: true })
|
|
554
|
+
: orchestrate(newPrompt, { origin, scope: scopeKey, chatSessionId, _skipInsert: true });
|
|
507
555
|
task.catch((err) => {
|
|
508
556
|
console.error('[steer:orchestrate]', err.message);
|
|
509
557
|
broadcast('orchestrate_done', { text: `[error] ${err.message}`, error: true, origin });
|
|
@@ -539,8 +587,8 @@ function formatCliUnavailableMessage(cli, detected) {
|
|
|
539
587
|
}
|
|
540
588
|
return `CLI '${cli}' not found in PATH. Run \`jaw doctor --json\`.`;
|
|
541
589
|
}
|
|
542
|
-
function buildHistoryBlock(currentPrompt, workingDir, maxSessions = 10, maxTotalChars = 8000) {
|
|
543
|
-
const recent = getRecentMessages.all(workingDir || null,
|
|
590
|
+
function buildHistoryBlock(currentPrompt, workingDir, chatSessionId, maxSessions = 10, maxTotalChars = 8000) {
|
|
591
|
+
const recent = getRecentMessages.all(workingDir || null, chatSessionId, Math.max(1, maxSessions * 2));
|
|
544
592
|
if (!recent.length)
|
|
545
593
|
return '';
|
|
546
594
|
const promptText = String(currentPrompt || '').trim();
|
|
@@ -605,18 +653,21 @@ function isStaleWorklogHistoryArtifact(text) {
|
|
|
605
653
|
'正在从上一个 worklog 继续。',
|
|
606
654
|
].some(marker => value.includes(marker));
|
|
607
655
|
}
|
|
608
|
-
|
|
609
|
-
|
|
656
|
+
// The session is passed in rather than read globally: the replay is prepended to THIS
|
|
657
|
+
// run's prompt, so it has to come from this run's conversation and not from whichever
|
|
658
|
+
// one happens to be active (073 §2.5a).
|
|
659
|
+
function getLatestAssistantContentForAgyResume(workingDir, chatSessionId) {
|
|
660
|
+
const rows = getRecentMessages.all(workingDir || null, chatSessionId, 12);
|
|
610
661
|
const row = rows.find((msg) => msg.role === 'assistant' && typeof msg.content === 'string' && msg.content.trim().length > 0);
|
|
611
662
|
return row?.content || null;
|
|
612
663
|
}
|
|
613
|
-
function getRecentAssistantContentsForAgyResume(workingDir) {
|
|
614
|
-
const rows = getRecentMessages.all(workingDir || null,
|
|
664
|
+
function getRecentAssistantContentsForAgyResume(workingDir, chatSessionId) {
|
|
665
|
+
const rows = getRecentMessages.all(workingDir || null, chatSessionId, 20);
|
|
615
666
|
return rows
|
|
616
667
|
.filter((msg) => msg.role === 'assistant' && typeof msg.content === 'string' && msg.content.trim().length > 0)
|
|
617
668
|
.map((msg) => String(msg.content || '').trim());
|
|
618
669
|
}
|
|
619
|
-
import { buildArgs, buildResumeArgs, formatAgyPrintTimeout, resolveAiEProvider, resolveSessionBucket } from './args.js';
|
|
670
|
+
import { buildArgs, buildResumeArgs, formatAgyPrintTimeout, resolveAiEProvider, resolveScopedSessionBucket, resolveSessionBucket } from './args.js';
|
|
620
671
|
export { buildArgs, buildResumeArgs, resolveAiEProvider, resolveSessionBucket };
|
|
621
672
|
const warnedAgyCapabilityFallbacks = new Set();
|
|
622
673
|
// ─── Upload wrapper ──────────────────────────────────
|
|
@@ -625,9 +676,10 @@ export { buildMediaPrompt, buildMediaPromptMany };
|
|
|
625
676
|
// ─── Spawn Agent ─────────────────────────────────────
|
|
626
677
|
import { AcpClient } from '../cli/acp-client.js';
|
|
627
678
|
import { CodexAppClient, isRecoverableResumeError } from './codex-app-client.js';
|
|
628
|
-
import { acquireCodexAppRuntime, acquirePiRuntime } from './runtime-pool.js';
|
|
679
|
+
import { acquireCodexAppRuntime, acquirePiRuntime, } from './runtime-pool.js';
|
|
680
|
+
import { acquireCodexAppLane, CodexHostGenerationStaleError, prepareCodexAppHost, } from './codex-host-pool.js';
|
|
629
681
|
import { loadCatalogEfforts, resolveCatalogPath, validateModelEffort } from './codex-app-catalog.js';
|
|
630
|
-
import {
|
|
682
|
+
import { listenCodexAppTurnAdapter, } from './codex-app-events.js';
|
|
631
683
|
import { canGuardedAgyResume, resolveAgyNativeResume, shouldEmitHeartbeat, shouldResumeBucketSession } from './spawn/resume.js';
|
|
632
684
|
export { canGuardedAgyResume, resolveAgyNativeResume, shouldEmitHeartbeat, shouldResumeBucketSession };
|
|
633
685
|
import { createQueueController, FALLBACK_MAX_RETRIES } from './spawn/queue.js';
|
|
@@ -649,19 +701,57 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
649
701
|
const gateEligibleMain = mainManaged && !opts.agentId && !opts.internal && !opts._isFallback && !opts._isSmokeContinuation && !opts._isGoalContinuation;
|
|
650
702
|
const isEmployee = !mainManaged;
|
|
651
703
|
const empTag = isEmployee ? { isEmployee: true } : {};
|
|
704
|
+
const multiSessionEnabled = settings["multiSession"]?.enabled === true;
|
|
705
|
+
const capturedScope = currentSessionScope();
|
|
706
|
+
const scopeKey = multiSessionEnabled
|
|
707
|
+
? (opts.scopeKey || capturedScope?.scope || resolveOrcScope(stripUndefined({
|
|
708
|
+
origin,
|
|
709
|
+
target: opts.target,
|
|
710
|
+
chatId: opts.chatId,
|
|
711
|
+
workingDir: settings["workingDir"] || null,
|
|
712
|
+
persistedScopeId: opts.remoteKey,
|
|
713
|
+
multiSessionEnabled,
|
|
714
|
+
})))
|
|
715
|
+
: 'default';
|
|
716
|
+
const chatSessionId = multiSessionEnabled
|
|
717
|
+
? (opts.chatSessionId || capturedScope?.chatSessionId || getActiveChatSession())
|
|
718
|
+
: getActiveChatSession();
|
|
719
|
+
if (multiSessionEnabled) {
|
|
720
|
+
opts = stripUndefined({
|
|
721
|
+
...opts,
|
|
722
|
+
scopeKey,
|
|
723
|
+
chatSessionId,
|
|
724
|
+
...(opts.remoteKey ? { remoteKey: opts.remoteKey } : {}),
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
let mainRun = mainManaged ? activeMainProcesses.get(scopeKey) : undefined;
|
|
728
|
+
if (mainManaged && mainRun && !opts._settingsGateWaited) {
|
|
729
|
+
console.log(`[jaw] Agent already running for scope=${scopeKey}, skipping`);
|
|
730
|
+
return { child: null, promise: Promise.resolve({ text: '', code: -1 }) };
|
|
731
|
+
}
|
|
732
|
+
if (mainManaged && !mainRun) {
|
|
733
|
+
mainRun = {
|
|
734
|
+
process: null,
|
|
735
|
+
starting: false,
|
|
736
|
+
steering: false,
|
|
737
|
+
ownerGeneration: 0,
|
|
738
|
+
meta: { origin, scopeId: scopeKey, chatSessionId, ...(opts.remoteKey ? { remoteKey: opts.remoteKey } : {}) },
|
|
739
|
+
};
|
|
740
|
+
activeMainProcesses.set(scopeKey, mainRun);
|
|
741
|
+
}
|
|
652
742
|
if (gateEligibleMain && !opts._settingsGateWaited && isRuntimeSettingsMutationInFlight()) {
|
|
653
|
-
if (
|
|
743
|
+
if (queueCtrl.isRetryPending(scopeKey) || mainRun?.starting) {
|
|
654
744
|
console.log('[jaw] Agent already running, skipping');
|
|
655
745
|
return { child: null, promise: Promise.resolve({ text: '', code: -1 }) };
|
|
656
746
|
}
|
|
657
|
-
|
|
747
|
+
mainRun.starting = true;
|
|
658
748
|
let cancelled = false;
|
|
659
749
|
let cancelReason = 'user';
|
|
660
750
|
const cancelThisSpawn = (reason) => {
|
|
661
751
|
cancelled = true;
|
|
662
752
|
cancelReason = reason;
|
|
663
753
|
};
|
|
664
|
-
|
|
754
|
+
mainRun.cancelPending = cancelThisSpawn;
|
|
665
755
|
const promise = (async () => {
|
|
666
756
|
try {
|
|
667
757
|
await waitForRuntimeSettingsIdle();
|
|
@@ -672,10 +762,12 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
672
762
|
return await next.promise;
|
|
673
763
|
}
|
|
674
764
|
finally {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
765
|
+
const latest = activeMainProcesses.get(scopeKey);
|
|
766
|
+
if (latest?.cancelPending === cancelThisSpawn)
|
|
767
|
+
delete latest.cancelPending;
|
|
768
|
+
if (latest)
|
|
769
|
+
latest.starting = false;
|
|
770
|
+
void processQueue(scopeKey);
|
|
679
771
|
}
|
|
680
772
|
})();
|
|
681
773
|
return { child: null, promise };
|
|
@@ -684,39 +776,46 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
684
776
|
// Skip for employee spawns — distribute.ts manages AGENTS.md isolation
|
|
685
777
|
if (!opts.internal && !opts._isFallback && !opts.agentId)
|
|
686
778
|
regenerateB();
|
|
687
|
-
const liveScope =
|
|
779
|
+
const liveScope = scopeKey;
|
|
688
780
|
// Employee must not pollute boss's liveRun (see devlog 260423_employee_liverun_contamination)
|
|
689
781
|
const effectiveLiveScope = mainManaged ? liveScope : null;
|
|
690
|
-
// INVARIANT: 모든 외부 호출은 gateway.ts
|
|
691
|
-
// 직접 spawnAgent 호출 시
|
|
692
|
-
if (
|
|
782
|
+
// INVARIANT: 모든 외부 호출은 gateway.ts의 scoped busy admission을 거침.
|
|
783
|
+
// 직접 spawnAgent 호출 시 scope별 retry state도 확인할 것.
|
|
784
|
+
if (mainManaged && mainRun?.starting && gateEligibleMain && !opts._settingsGateWaited) {
|
|
693
785
|
console.log('[jaw] Agent already running, skipping');
|
|
694
786
|
return { child: null, promise: Promise.resolve({ text: '', code: -1 }) };
|
|
695
787
|
}
|
|
696
788
|
// Capture Boss main session channel so disconnected worker results can be
|
|
697
789
|
// replayed to the correct origin/chatId later. Cleared in lifecycle-handler.
|
|
698
790
|
if (mainManaged) {
|
|
699
|
-
setCurrentMainMeta(stripUndefined({
|
|
791
|
+
setCurrentMainMeta(scopeKey, stripUndefined({
|
|
700
792
|
origin,
|
|
701
793
|
target: opts.target,
|
|
702
794
|
chatId: opts.chatId,
|
|
703
795
|
requestId: opts.requestId,
|
|
704
796
|
replyViaTarget: opts.replyViaTarget,
|
|
705
797
|
scopeId: liveScope,
|
|
798
|
+
chatSessionId,
|
|
799
|
+
...(opts.remoteKey ? { remoteKey: opts.remoteKey } : {}),
|
|
706
800
|
}));
|
|
707
801
|
}
|
|
708
802
|
let resolve;
|
|
709
803
|
const resultPromise = new Promise(r => { resolve = r; });
|
|
710
804
|
const session = getSession() ?? {};
|
|
711
|
-
const
|
|
805
|
+
const persistenceOwner = getSessionOwnershipGeneration(scopeKey);
|
|
806
|
+
const ownerGeneration = persistenceOwner.global;
|
|
807
|
+
if (mainRun)
|
|
808
|
+
mainRun.ownerGeneration = ownerGeneration;
|
|
712
809
|
let cli = resolveMainCli(opts.cli, settings, session);
|
|
810
|
+
if (mainRun)
|
|
811
|
+
mainRun.meta.cli = cli;
|
|
713
812
|
// Phase 52: Bootstrap consumption is moved BELOW the bucket-aware `isResume`
|
|
714
813
|
// computation so we can use the authoritative per-bucket resume decision
|
|
715
814
|
// instead of the legacy `isResumeGuess` heuristic. See comment near line 762.
|
|
716
815
|
// ─── Fallback retry: skip to fallback if retries exhausted ───
|
|
717
816
|
if (!opts._isFallback && !opts.internal) {
|
|
718
|
-
const st = queueCtrl.
|
|
719
|
-
if (st && st.retriesLeft <= 0) {
|
|
817
|
+
const st = queueCtrl.fallbackStateForScope(scopeKey).get(cli);
|
|
818
|
+
if (st?.fallbackCli && st.retriesLeft <= 0) {
|
|
720
819
|
const fbAvail = detectCli(st.fallbackCli)?.available;
|
|
721
820
|
if (fbAvail) {
|
|
722
821
|
console.log(`[jaw:fallback] ${cli} retries exhausted → direct ${st.fallbackCli}`);
|
|
@@ -730,8 +829,9 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
730
829
|
// ─── jwc in-process branch (110.3 §B) ───────────────────────────────
|
|
731
830
|
// Resident engine, no ChildProcess. Mirrors the main-managed lifecycle
|
|
732
831
|
// (insertMessage → beginLiveRun → run → persist → clearLiveRun → processQueue)
|
|
733
|
-
// so
|
|
832
|
+
// so scoped busy/queue/SSE behave identically. Employees fall through.
|
|
734
833
|
if (cli === 'jwc' && mainManaged && !opts.internal) {
|
|
834
|
+
const jawRuntime = runtimeForScope(scopeKey);
|
|
735
835
|
const jwcLabel = 'main';
|
|
736
836
|
const jwcOverrides = settings["activeOverrides"]?.['jwc'];
|
|
737
837
|
const jwcPerCli = settings["perCli"]?.['jwc'];
|
|
@@ -739,9 +839,9 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
739
839
|
const jwcProvider = jwcOverrides?.['provider'] || jwcPerCli?.['provider'] || 'anthropic';
|
|
740
840
|
const jwcCwd = settings["workingDir"] || process.cwd();
|
|
741
841
|
if (!opts._skipInsert) {
|
|
742
|
-
insertMessage.run('user', prompt, 'jwc', jwcModel, settings["workingDir"] || null,
|
|
842
|
+
insertMessage.run('user', prompt, 'jwc', jwcModel, settings["workingDir"] || null, chatSessionId);
|
|
743
843
|
}
|
|
744
|
-
|
|
844
|
+
mainRun.starting = true;
|
|
745
845
|
beginLiveRun(liveScope, 'jwc');
|
|
746
846
|
broadcast('agent_status', { running: true, agentId: jwcLabel, cli: 'jwc' });
|
|
747
847
|
const jwcEffort = jwcOverrides?.['effort'] || jwcPerCli?.['effort'] || '';
|
|
@@ -753,9 +853,9 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
753
853
|
const rawFinalText = result.code === 0 ? live.text : result.text;
|
|
754
854
|
const finalText = applyOutputPolicy(rawFinalText, { scope: 'main' }).text;
|
|
755
855
|
// Persist may throw (better-sqlite3 is sync: DB lock / schema). Cleanup MUST
|
|
756
|
-
// still run or
|
|
856
|
+
// still run or this scope's starting flag stays true and its queue deadlocks.
|
|
757
857
|
try {
|
|
758
|
-
insertMessageWithTraceRun.run('assistant', finalText, 'jwc', jwcModel, null, JSON.stringify(sanitizeToolLogForDurableStorage(live.toolLog)), settings["workingDir"] || null, live.traceRunId || null,
|
|
858
|
+
insertMessageWithTraceRun.run('assistant', finalText, 'jwc', jwcModel, null, JSON.stringify(sanitizeToolLogForDurableStorage(live.toolLog)), settings["workingDir"] || null, live.traceRunId || null, chatSessionId);
|
|
759
859
|
broadcast('agent_done', { text: finalText, origin, ...(result.code === 0 ? {} : { error: true }) });
|
|
760
860
|
}
|
|
761
861
|
catch (err) {
|
|
@@ -765,10 +865,11 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
765
865
|
finally {
|
|
766
866
|
clearLiveRun(liveScope);
|
|
767
867
|
broadcast('agent_status', { running: false, agentId: jwcLabel, cli: 'jwc' });
|
|
768
|
-
|
|
868
|
+
mainRun.starting = false;
|
|
769
869
|
jawRuntime.setLiveScope(undefined);
|
|
870
|
+
releaseMainRun(scopeKey, null, ownerGeneration);
|
|
770
871
|
resolve({ text: finalText, code: result.code });
|
|
771
|
-
processQueue();
|
|
872
|
+
void processQueue(scopeKey);
|
|
772
873
|
}
|
|
773
874
|
};
|
|
774
875
|
// jawRuntime.prompt is designed never to reject, but guard defensively so a
|
|
@@ -798,14 +899,18 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
798
899
|
? migrateLegacyClaudeValue(requestedModel)
|
|
799
900
|
: requestedModel;
|
|
800
901
|
const runtimeModel = cli === 'cursor' ? resolveCursorModelVariant(model, effort) : model;
|
|
902
|
+
const codexMultiplexMain = cli === 'codex-app' && mainManaged && !opts.agentId
|
|
903
|
+
&& settings["runtime"]?.codexApp?.multiplex === true;
|
|
801
904
|
if (mainManaged) {
|
|
802
|
-
setCurrentMainMeta(stripUndefined({
|
|
905
|
+
setCurrentMainMeta(scopeKey, stripUndefined({
|
|
803
906
|
origin,
|
|
804
907
|
target: opts.target,
|
|
805
908
|
chatId: opts.chatId,
|
|
806
909
|
requestId: opts.requestId,
|
|
807
910
|
replyViaTarget: opts.replyViaTarget,
|
|
808
911
|
scopeId: liveScope,
|
|
912
|
+
chatSessionId,
|
|
913
|
+
...(opts.remoteKey ? { remoteKey: opts.remoteKey } : {}),
|
|
809
914
|
cli,
|
|
810
915
|
model: runtimeModel,
|
|
811
916
|
effectiveProvider,
|
|
@@ -821,7 +926,12 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
821
926
|
// Bucket-aware resume: codex-spark is kept in its own session bucket so
|
|
822
927
|
// cross-model resume (gpt-5.4 ↔ gpt-5.3-codex-spark) doesn't send a
|
|
823
928
|
// mismatched session_id to the server.
|
|
824
|
-
|
|
929
|
+
// Every runtime now keys its bucket by scope (073 §2.1), which replaces the guard 072
|
|
930
|
+
// put here. That guard gave a non-default scope no bucket at all — no resume, no
|
|
931
|
+
// snapshot, no stale clear — because sharing one was worse. Having its own is better
|
|
932
|
+
// than either. The default scope keeps the bare bucket name, so a session that existed
|
|
933
|
+
// before this change continues the conversation it was already in.
|
|
934
|
+
const currentBucket = resolveScopedSessionBucket(cli, runtimeModel, effectiveProvider, scopeKey, effort, 'fallback', codexMultiplexMain);
|
|
825
935
|
const envDefaultsCli = cli === 'ai-e' ? effectiveProvider : cli;
|
|
826
936
|
const cliEnv = applyCliEnvDefaults(envDefaultsCli, opts.env);
|
|
827
937
|
const spawnEnv = makeCleanEnv(cliEnv);
|
|
@@ -839,7 +949,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
839
949
|
sessionId: bucketSessionId, bucketUpdatedAt, requestedModel: runtimeModel, bucketModel,
|
|
840
950
|
cwd: settings['workingDir'] || '', lastRunCwd: bucketRow?.last_run_cwd,
|
|
841
951
|
lastRunClean: bucketRow?.last_run_clean, lastRunMeta: bucketRow?.last_run_meta,
|
|
842
|
-
freshBootstrap: forceNew || opts._skipResume === true || Boolean(peekPendingBootstrapPrompt()),
|
|
952
|
+
freshBootstrap: forceNew || opts._skipResume === true || Boolean(peekPendingBootstrapPrompt(scopeKey)),
|
|
843
953
|
});
|
|
844
954
|
if (cli === 'agy')
|
|
845
955
|
console.log(`[agy-resume] ${agyResumeDecision.ok ? 'resume' : 'fresh'} reason=${agyResumeDecision.reason}`);
|
|
@@ -859,16 +969,20 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
859
969
|
// Using `isResume` (bucket-aware) instead of legacy `isResumeGuess` so cross-model
|
|
860
970
|
// toggles (e.g. gpt-5.4 ↔ gpt-5.3-codex-spark) get the bootstrap they need.
|
|
861
971
|
if (!opts.agentId && !opts.internal && !isResume) {
|
|
862
|
-
const pending = consumePendingBootstrapPrompt();
|
|
972
|
+
const pending = consumePendingBootstrapPrompt(scopeKey);
|
|
863
973
|
if (pending) {
|
|
864
974
|
console.log(`[jaw:compact] injecting bootstrap (${pending.length} chars)`);
|
|
865
975
|
prompt = `${pending}\n\n---\n\n${prompt}`;
|
|
866
976
|
}
|
|
867
977
|
}
|
|
868
978
|
if (!empSid && !forceNew && bucketSessionId && !canResumeBucketSession) {
|
|
869
|
-
if (!peekPendingBootstrapPrompt()) {
|
|
979
|
+
if (!peekPendingBootstrapPrompt(scopeKey)) {
|
|
870
980
|
import('../core/compact.js')
|
|
871
|
-
.then(({ autoCompactRefresh }) => autoCompactRefresh({
|
|
981
|
+
.then(({ autoCompactRefresh }) => autoCompactRefresh({
|
|
982
|
+
workDir: settings["workingDir"] || null, instructions: '', cli, model: runtimeModel, scopeKey,
|
|
983
|
+
chatSessionId,
|
|
984
|
+
...(currentBucket ? { sessionBucket: currentBucket } : {}),
|
|
985
|
+
}))
|
|
872
986
|
.catch(() => { });
|
|
873
987
|
}
|
|
874
988
|
try {
|
|
@@ -934,9 +1048,14 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
934
1048
|
prompt = `${ts}\n${projLine}${prompt}${memoryNudge}`;
|
|
935
1049
|
}
|
|
936
1050
|
const resumeSessionId = empSid || (isResume ? bucketSessionId : null);
|
|
937
|
-
const needsHistory =
|
|
1051
|
+
const needsHistory = shouldBuildHistoryBlock({
|
|
1052
|
+
skipHistory: opts._skipHistory === true,
|
|
1053
|
+
isResume,
|
|
1054
|
+
cli,
|
|
1055
|
+
codexMultiplexMain,
|
|
1056
|
+
});
|
|
938
1057
|
const historyBlock = needsHistory
|
|
939
|
-
? buildHistoryBlock(prompt, settings["workingDir"], 10, 8000)
|
|
1058
|
+
? buildHistoryBlock(prompt, settings["workingDir"], chatSessionId, 10, 8000)
|
|
940
1059
|
: '';
|
|
941
1060
|
let agyBootstrap = null;
|
|
942
1061
|
let promptForArgs = buildPromptForArgs({
|
|
@@ -948,10 +1067,10 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
948
1067
|
isResume,
|
|
949
1068
|
});
|
|
950
1069
|
const agyResumeReplayPrefix = cli === 'agy' && isResume
|
|
951
|
-
? getLatestAssistantContentForAgyResume(settings["workingDir"])
|
|
1070
|
+
? getLatestAssistantContentForAgyResume(settings["workingDir"], chatSessionId)
|
|
952
1071
|
: null;
|
|
953
1072
|
const agyResumeReplayPrefixes = cli === 'agy' && isResume
|
|
954
|
-
? getRecentAssistantContentsForAgyResume(settings["workingDir"])
|
|
1073
|
+
? getRecentAssistantContentsForAgyResume(settings["workingDir"], chatSessionId)
|
|
955
1074
|
: [];
|
|
956
1075
|
const claudeBin = (cli === 'claude-e' || (cli === 'ai-e' && effectiveProvider === 'claude'))
|
|
957
1076
|
? detectCli('claude').path
|
|
@@ -1045,8 +1164,8 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1045
1164
|
promptChars: promptForArgs.length + (sysPrompt?.length || 0),
|
|
1046
1165
|
prompt: `${sysPrompt || ''}\n${promptForArgs}`,
|
|
1047
1166
|
});
|
|
1048
|
-
if (policyVerdicts.length &&
|
|
1049
|
-
|
|
1167
|
+
if (policyVerdicts.length && mainRun)
|
|
1168
|
+
mainRun.meta.policyVerdicts = policyVerdicts;
|
|
1050
1169
|
// ─── DIFF-A: Preflight — verify CLI binary exists before spawn ───
|
|
1051
1170
|
const detected = detectCli(cli);
|
|
1052
1171
|
const resolvedOpencodeBinary = cli === 'opencode'
|
|
@@ -1062,8 +1181,10 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1062
1181
|
clearLiveRun(liveScope);
|
|
1063
1182
|
broadcast('agent_done', { text: `❌ ${msg}`, error: true, origin, ...empTag }, isEmployee ? 'internal' : 'public');
|
|
1064
1183
|
resolve({ text: '', code: 127 });
|
|
1065
|
-
if (mainManaged)
|
|
1066
|
-
|
|
1184
|
+
if (mainManaged) {
|
|
1185
|
+
releaseMainRun(scopeKey, null, ownerGeneration);
|
|
1186
|
+
void processQueue(scopeKey);
|
|
1187
|
+
}
|
|
1067
1188
|
cleanupEmployeeTmpDir(spawnCwd, settings["workingDir"], agentLabel);
|
|
1068
1189
|
return { child: null, promise: resultPromise };
|
|
1069
1190
|
}
|
|
@@ -1113,8 +1234,9 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1113
1234
|
throw new Error('Copilot ACP process was not created');
|
|
1114
1235
|
}
|
|
1115
1236
|
if (mainManaged)
|
|
1116
|
-
|
|
1117
|
-
|
|
1237
|
+
mainRun.process = child;
|
|
1238
|
+
else
|
|
1239
|
+
registerActiveProcess(agentLabel, child);
|
|
1118
1240
|
if (!opts.internal)
|
|
1119
1241
|
broadcast('agent_status', { running: true, agentId: agentLabel, cli, ...empTag });
|
|
1120
1242
|
if (mainManaged && !opts.internal)
|
|
@@ -1129,19 +1251,21 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1129
1251
|
opts.lifecycle?.onExit?.(null);
|
|
1130
1252
|
const msg = `Copilot ACP spawn failed: ${err.message}`;
|
|
1131
1253
|
console.error(`[acp:error] ${msg}`);
|
|
1132
|
-
activeProcesses.delete(agentLabel);
|
|
1133
1254
|
if (mainManaged) {
|
|
1134
|
-
|
|
1255
|
+
releaseMainRun(scopeKey, child, ownerGeneration);
|
|
1135
1256
|
clearLiveRun(liveScope);
|
|
1136
1257
|
broadcast('agent_status', { running: false, agentId: agentLabel });
|
|
1137
1258
|
}
|
|
1259
|
+
else {
|
|
1260
|
+
activeProcesses.delete(agentLabel);
|
|
1261
|
+
}
|
|
1138
1262
|
broadcast('agent_done', { text: `❌ ${msg}`, error: true, origin, ...empTag }, isEmployee ? 'internal' : 'public');
|
|
1139
1263
|
resolve({ text: '', code: 1 });
|
|
1140
1264
|
if (mainManaged)
|
|
1141
|
-
processQueue();
|
|
1265
|
+
void processQueue(scopeKey);
|
|
1142
1266
|
});
|
|
1143
1267
|
if (mainManaged && !opts.internal && !opts._skipInsert) {
|
|
1144
|
-
insertMessage.run('user', prompt, cli, model, settings["workingDir"] || null,
|
|
1268
|
+
insertMessage.run('user', prompt, cli, model, settings["workingDir"] || null, chatSessionId);
|
|
1145
1269
|
}
|
|
1146
1270
|
if (!opts.internal)
|
|
1147
1271
|
broadcast('agent_status', { status: 'running', cli, agentId: agentLabel, ...empTag }, traceAudience);
|
|
@@ -1317,7 +1441,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1317
1441
|
}
|
|
1318
1442
|
// If loadSession failed (or not resuming), inject history into prompt
|
|
1319
1443
|
const needsHistoryFallback = isResume && !loadSessionOk;
|
|
1320
|
-
const fallbackHistory = needsHistoryFallback && !opts._skipHistory ? buildHistoryBlock(prompt, settings["workingDir"]) : '';
|
|
1444
|
+
const fallbackHistory = needsHistoryFallback && !opts._skipHistory ? buildHistoryBlock(prompt, settings["workingDir"], chatSessionId) : '';
|
|
1321
1445
|
const acpPrompt = needsHistoryFallback
|
|
1322
1446
|
? withHistoryPrompt(prompt, fallbackHistory)
|
|
1323
1447
|
: (isResume ? prompt : withHistoryPrompt(prompt, historyBlock));
|
|
@@ -1330,7 +1454,8 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1330
1454
|
// which skips the exit handler's code===0 gate, losing session continuity.
|
|
1331
1455
|
const persistedAcpSessionId = ctx.sessionId;
|
|
1332
1456
|
if (persistedAcpSessionId && persistMainSession(stripUndefined({
|
|
1333
|
-
|
|
1457
|
+
persistenceOwner,
|
|
1458
|
+
scopeKey,
|
|
1334
1459
|
forceNew,
|
|
1335
1460
|
employeeSessionId: empSid,
|
|
1336
1461
|
sessionId: persistedAcpSessionId,
|
|
@@ -1340,6 +1465,11 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1340
1465
|
resumeKey,
|
|
1341
1466
|
effort: cfg.effort || '',
|
|
1342
1467
|
skipSessionPersist: opts._skipSessionPersist === true,
|
|
1468
|
+
// Without this the save falls back to the bare, unscoped bucket, and a
|
|
1469
|
+
// successful turn in another session writes its vendor id into the row
|
|
1470
|
+
// the default session resumes from. The exit handler below passes the
|
|
1471
|
+
// same value; this path runs first and must not disagree with it.
|
|
1472
|
+
scopedBucket: currentBucket,
|
|
1343
1473
|
}))) {
|
|
1344
1474
|
console.log(`[jaw:session] saved ${cli} session=${persistedAcpSessionId.slice(0, 12)}... (pre-shutdown)`);
|
|
1345
1475
|
}
|
|
@@ -1376,14 +1506,18 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1376
1506
|
handleAgentExit({
|
|
1377
1507
|
ctx, code: acpCode, cli, model, agentLabel, mainManaged, origin,
|
|
1378
1508
|
resumeKey,
|
|
1379
|
-
prompt, opts, cfg, ownerGeneration, forceNew, empSid,
|
|
1509
|
+
prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
|
|
1380
1510
|
isResume, wasKilled, wasSteer, smokeResult,
|
|
1381
1511
|
effortDefault: '', costLine: '',
|
|
1382
1512
|
resolve: resolve,
|
|
1383
1513
|
activeProcesses,
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1514
|
+
scopeKey,
|
|
1515
|
+
scopedBucket: currentBucket,
|
|
1516
|
+
chatSessionId,
|
|
1517
|
+
childProcess: child,
|
|
1518
|
+
releaseMainRun,
|
|
1519
|
+
retryState: queueCtrl.retryStateForScope(scopeKey),
|
|
1520
|
+
fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
|
|
1387
1521
|
fallbackMaxRetries: FALLBACK_MAX_RETRIES,
|
|
1388
1522
|
processQueue,
|
|
1389
1523
|
}).catch((err) => {
|
|
@@ -1515,8 +1649,8 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1515
1649
|
return leaseCancel;
|
|
1516
1650
|
};
|
|
1517
1651
|
const cancelHook = (_reason) => { void requestCancel(); };
|
|
1518
|
-
if (lease)
|
|
1519
|
-
|
|
1652
|
+
if (lease && mainRun)
|
|
1653
|
+
mainRun.cancelTurn = cancelHook;
|
|
1520
1654
|
const piWatchdog = attachWatchdog(child, agentLabel, (reason) => {
|
|
1521
1655
|
console.log(`[jaw:watchdog] cancelling ${agentLabel} (pi) — ${reason}`);
|
|
1522
1656
|
ctx.stallReason = reason;
|
|
@@ -1524,8 +1658,9 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1524
1658
|
});
|
|
1525
1659
|
ctx.stallWatchdog = piWatchdog;
|
|
1526
1660
|
if (mainManaged)
|
|
1527
|
-
|
|
1528
|
-
|
|
1661
|
+
mainRun.process = child;
|
|
1662
|
+
else
|
|
1663
|
+
registerActiveProcess(agentLabel, child);
|
|
1529
1664
|
if (!opts.internal)
|
|
1530
1665
|
broadcast('agent_status', { running: true, agentId: agentLabel, cli, provider: profile.id, ...empTag });
|
|
1531
1666
|
if (mainManaged && !opts.internal) {
|
|
@@ -1533,15 +1668,15 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1533
1668
|
setLiveRunTraceId(liveScope, traceRunId);
|
|
1534
1669
|
}
|
|
1535
1670
|
if (mainManaged && !opts.internal && !opts._skipInsert) {
|
|
1536
|
-
insertMessage.run('user', prompt, cli, runtimeModel, settings["workingDir"] || null,
|
|
1671
|
+
insertMessage.run('user', prompt, cli, runtimeModel, settings["workingDir"] || null, chatSessionId);
|
|
1537
1672
|
}
|
|
1538
1673
|
if (!opts.internal)
|
|
1539
1674
|
broadcast('agent_status', { status: 'running', cli, agentId: agentLabel, provider: profile.id, ...empTag }, traceAudience);
|
|
1540
1675
|
const releaseLease = async () => {
|
|
1541
1676
|
if (leaseCancel)
|
|
1542
1677
|
await leaseCancel;
|
|
1543
|
-
if (
|
|
1544
|
-
|
|
1678
|
+
if (mainRun?.cancelTurn === cancelHook)
|
|
1679
|
+
delete mainRun.cancelTurn;
|
|
1545
1680
|
if (lease)
|
|
1546
1681
|
lease.release();
|
|
1547
1682
|
else
|
|
@@ -1568,14 +1703,18 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1568
1703
|
return handleAgentExit({
|
|
1569
1704
|
ctx, code: result.code, cli, model: runtimeModel, effectiveProvider: profile.id, agentLabel, mainManaged, origin,
|
|
1570
1705
|
resumeKey,
|
|
1571
|
-
prompt, opts, cfg, ownerGeneration, forceNew, empSid,
|
|
1706
|
+
prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
|
|
1572
1707
|
isResume: false, wasKilled, wasSteer, smokeResult,
|
|
1573
1708
|
effortDefault: 'medium', costLine: '',
|
|
1574
1709
|
resolve: resolve,
|
|
1575
1710
|
activeProcesses,
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1711
|
+
scopeKey,
|
|
1712
|
+
scopedBucket: currentBucket,
|
|
1713
|
+
chatSessionId,
|
|
1714
|
+
childProcess: child,
|
|
1715
|
+
releaseMainRun,
|
|
1716
|
+
retryState: queueCtrl.retryStateForScope(scopeKey),
|
|
1717
|
+
fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
|
|
1579
1718
|
fallbackMaxRetries: FALLBACK_MAX_RETRIES,
|
|
1580
1719
|
processQueue,
|
|
1581
1720
|
});
|
|
@@ -1588,14 +1727,18 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1588
1727
|
handleAgentExit({
|
|
1589
1728
|
ctx, code: 1, cli, model: runtimeModel, effectiveProvider: profile.id, agentLabel, mainManaged, origin,
|
|
1590
1729
|
resumeKey,
|
|
1591
|
-
prompt, opts, cfg, ownerGeneration, forceNew, empSid,
|
|
1730
|
+
prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
|
|
1592
1731
|
isResume: false, wasKilled: false, wasSteer: false, smokeResult: detectSmokeResponse('', [], 1, cli),
|
|
1593
1732
|
effortDefault: 'medium', costLine: '',
|
|
1594
1733
|
resolve: resolve,
|
|
1595
1734
|
activeProcesses,
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1735
|
+
scopeKey,
|
|
1736
|
+
scopedBucket: currentBucket,
|
|
1737
|
+
chatSessionId,
|
|
1738
|
+
childProcess: child,
|
|
1739
|
+
releaseMainRun,
|
|
1740
|
+
retryState: queueCtrl.retryStateForScope(scopeKey),
|
|
1741
|
+
fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
|
|
1599
1742
|
fallbackMaxRetries: FALLBACK_MAX_RETRIES,
|
|
1600
1743
|
processQueue,
|
|
1601
1744
|
}).catch((handleErr) => {
|
|
@@ -1620,10 +1763,10 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1620
1763
|
.update(profile.apiKey || '')
|
|
1621
1764
|
.digest('hex')
|
|
1622
1765
|
.slice(0, 12);
|
|
1623
|
-
|
|
1766
|
+
mainRun.starting = true;
|
|
1624
1767
|
void acquirePiRuntime({
|
|
1625
1768
|
key: {
|
|
1626
|
-
scopeKey
|
|
1769
|
+
scopeKey,
|
|
1627
1770
|
cwd: spawnCwd,
|
|
1628
1771
|
profileId: profile.id,
|
|
1629
1772
|
fullEndpoint: profile.endpoint,
|
|
@@ -1637,20 +1780,21 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1637
1780
|
instructions: piSysPrompt,
|
|
1638
1781
|
forceNew,
|
|
1639
1782
|
}).then((lease) => {
|
|
1640
|
-
|
|
1783
|
+
mainRun.starting = false;
|
|
1641
1784
|
ctx.sessionId = lease.session.sessionId;
|
|
1642
1785
|
console.log(`[jaw:pi:pool] reused=${lease.reused} sessionId=${lease.session.sessionId || 'new'}`);
|
|
1643
1786
|
const done = lease.session.sendPrompt(piPrompt, { effort, onEvent: onPiEvent })
|
|
1644
1787
|
.then((result) => ({ ...result, code: 0, sessionId: lease.session.sessionId }));
|
|
1645
1788
|
runPiTurn(lease.session.child, done, lease);
|
|
1646
1789
|
}).catch((err) => {
|
|
1647
|
-
|
|
1790
|
+
mainRun.starting = false;
|
|
1648
1791
|
console.error(`[jaw:pi:pool] acquire failed: ${err.message}`);
|
|
1649
1792
|
clearLiveRun(liveScope);
|
|
1650
1793
|
broadcast('agent_status', { running: false, agentId: agentLabel });
|
|
1651
1794
|
broadcast('agent_done', { text: `❌ Pi RPC acquire failed: ${err.message}`, error: true, origin }, 'public');
|
|
1795
|
+
releaseMainRun(scopeKey, null, ownerGeneration);
|
|
1652
1796
|
resolve({ text: '', code: 1 });
|
|
1653
|
-
processQueue();
|
|
1797
|
+
void processQueue(scopeKey);
|
|
1654
1798
|
});
|
|
1655
1799
|
return { child: null, promise: resultPromise };
|
|
1656
1800
|
}
|
|
@@ -1664,7 +1808,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1664
1808
|
}
|
|
1665
1809
|
}
|
|
1666
1810
|
if (mainManaged && !opts.internal && !opts._skipInsert) {
|
|
1667
|
-
insertMessage.run('user', prompt, cli, model, settings["workingDir"] || null,
|
|
1811
|
+
insertMessage.run('user', prompt, cli, model, settings["workingDir"] || null, chatSessionId);
|
|
1668
1812
|
}
|
|
1669
1813
|
if (!opts.internal)
|
|
1670
1814
|
broadcast('agent_status', { status: 'running', cli, agentId: agentLabel, ...empTag }, traceAudience);
|
|
@@ -1711,13 +1855,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1711
1855
|
settleTurn = resolveTurn;
|
|
1712
1856
|
rejectTurn = rejectTurnPromise;
|
|
1713
1857
|
});
|
|
1714
|
-
const
|
|
1715
|
-
markCodexProgress();
|
|
1716
|
-
if (method === 'turn/completed' || method === 'turn/started' || method === 'error') {
|
|
1717
|
-
console.log(`[codex-app:notify] ${method}`);
|
|
1718
|
-
}
|
|
1719
|
-
appendTraceEvent({ runId: ctx.traceRunId, source: 'codex_app_raw', eventType: method, raw: params });
|
|
1720
|
-
const parsed = extractFromCodexAppEvent(method, params, ctx);
|
|
1858
|
+
const consumeCodexAppEvent = (method, parsed) => {
|
|
1721
1859
|
if (!parsed) {
|
|
1722
1860
|
if (method === 'turn/completed')
|
|
1723
1861
|
settleTurn();
|
|
@@ -1748,7 +1886,11 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1748
1886
|
}
|
|
1749
1887
|
if (parsed.text) {
|
|
1750
1888
|
flushCodexAppThinking();
|
|
1751
|
-
|
|
1889
|
+
// codex-app streams item/agentMessage/delta at TOKEN granularity;
|
|
1890
|
+
// the segment formatter would inject "\n- " between unjoined
|
|
1891
|
+
// tokens ("이"+"지만" → "이\n- 지만"). Raw-append like the plain
|
|
1892
|
+
// `claude` text_delta path (events/index.ts) instead.
|
|
1893
|
+
const segment = appendAssistantRawText(ctx, parsed.text);
|
|
1752
1894
|
if (segment) {
|
|
1753
1895
|
broadcastAgentOutput(ctx, agentLabel, cli, segment, empTag, traceAudience);
|
|
1754
1896
|
lastVisibleBroadcastTs = Date.now();
|
|
@@ -1786,13 +1928,14 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1786
1928
|
}
|
|
1787
1929
|
};
|
|
1788
1930
|
const effectiveFastMode = cfg.fastMode ?? settings["perCli"]?.["codex"]?.fastMode ?? false;
|
|
1789
|
-
const runCodexAppTurn = async (appClient, lease) => {
|
|
1931
|
+
const runCodexAppTurn = async (appClient, lease, laneScope) => {
|
|
1790
1932
|
const child = appClient.proc;
|
|
1791
1933
|
if (!child)
|
|
1792
1934
|
throw new Error('Codex AppServer process was not created');
|
|
1793
1935
|
if (mainManaged)
|
|
1794
|
-
|
|
1795
|
-
|
|
1936
|
+
mainRun.process = child;
|
|
1937
|
+
else
|
|
1938
|
+
registerActiveProcess(agentLabel, child);
|
|
1796
1939
|
if (!opts.internal)
|
|
1797
1940
|
broadcast('agent_status', { running: true, agentId: agentLabel, cli, ...empTag });
|
|
1798
1941
|
const processExit = { value: null };
|
|
@@ -1813,8 +1956,8 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1813
1956
|
return leaseCancel;
|
|
1814
1957
|
};
|
|
1815
1958
|
const cancelHook = (_reason) => { void requestLeaseCancel(); };
|
|
1816
|
-
if (lease)
|
|
1817
|
-
|
|
1959
|
+
if (lease && mainRun)
|
|
1960
|
+
mainRun.cancelTurn = cancelHook;
|
|
1818
1961
|
const watchdogTimeout = (kind) => {
|
|
1819
1962
|
if (watchdogCancel)
|
|
1820
1963
|
return;
|
|
@@ -1829,8 +1972,23 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1829
1972
|
idleTimer = setTimeout(() => { watchdogTimeout('idle'); }, idleMs);
|
|
1830
1973
|
absoluteTimer = setTimeout(() => { watchdogTimeout('absolute'); }, absoluteMs);
|
|
1831
1974
|
markCodexProgress = resetIdleTimer;
|
|
1832
|
-
const
|
|
1833
|
-
|
|
1975
|
+
const listener = listenCodexAppTurnAdapter(appClient, lease, laneScope, ctx, {
|
|
1976
|
+
onProgress: () => { markCodexProgress(); },
|
|
1977
|
+
onRawNotification: (method, params) => {
|
|
1978
|
+
if (method === 'turn/completed' || method === 'turn/started' || method === 'error') {
|
|
1979
|
+
console.log(`[codex-app:notify] ${method}`);
|
|
1980
|
+
}
|
|
1981
|
+
appendTraceEvent({ runId: ctx.traceRunId, source: 'codex_app_raw', eventType: method, raw: params });
|
|
1982
|
+
},
|
|
1983
|
+
onDiagnosticNotification: (entry) => {
|
|
1984
|
+
appendTraceEvent({
|
|
1985
|
+
runId: ctx.traceRunId,
|
|
1986
|
+
source: 'codex_app_raw',
|
|
1987
|
+
eventType: 'unrouted-notification',
|
|
1988
|
+
raw: entry,
|
|
1989
|
+
});
|
|
1990
|
+
},
|
|
1991
|
+
onEvent: consumeCodexAppEvent,
|
|
1834
1992
|
onStderr: handleStderr,
|
|
1835
1993
|
onExit: (code, signal) => {
|
|
1836
1994
|
processExit.value = { code, signal };
|
|
@@ -1850,9 +2008,16 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1850
2008
|
const initResult = await appClient.initialize();
|
|
1851
2009
|
if (process.env["DEBUG"])
|
|
1852
2010
|
console.log('[codex-app:init]', JSON.stringify(initResult).slice(0, 200));
|
|
2011
|
+
const threadOptions = {
|
|
2012
|
+
model,
|
|
2013
|
+
effort,
|
|
2014
|
+
cwd: spawnCwd,
|
|
2015
|
+
fastMode: effectiveFastMode,
|
|
2016
|
+
instructions: sysPrompt,
|
|
2017
|
+
};
|
|
1853
2018
|
if (isResume && resumeSessionId) {
|
|
1854
2019
|
try {
|
|
1855
|
-
await appClient.resumeThread(resumeSessionId,
|
|
2020
|
+
await appClient.resumeThread(laneScope, resumeSessionId, threadOptions);
|
|
1856
2021
|
console.log(`[codex-app:session] resumeThread OK: ${resumeSessionId.slice(0, 12)}...`);
|
|
1857
2022
|
}
|
|
1858
2023
|
catch (resumeErr) {
|
|
@@ -1862,27 +2027,29 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1862
2027
|
console.warn(`[codex-app:session] resumeThread FAILED (recoverable): ${message} — starting new thread`);
|
|
1863
2028
|
if (empSid && opts.agentId)
|
|
1864
2029
|
clearEmployeeSession.run(opts.agentId);
|
|
1865
|
-
await appClient.startThread(
|
|
2030
|
+
await appClient.startThread(laneScope, threadOptions);
|
|
1866
2031
|
}
|
|
1867
2032
|
}
|
|
1868
2033
|
else {
|
|
1869
|
-
await appClient.startThread(
|
|
2034
|
+
await appClient.startThread(laneScope, threadOptions);
|
|
1870
2035
|
}
|
|
1871
|
-
ctx.sessionId = appClient.
|
|
2036
|
+
ctx.sessionId = appClient.getThreadId(laneScope) ?? '';
|
|
1872
2037
|
}
|
|
1873
2038
|
const shouldPrependHistory = lease
|
|
1874
|
-
? !lease.resumedThread
|
|
2039
|
+
? !(lease.resumedThread || lease.reused)
|
|
1875
2040
|
: !(isResume && Boolean(resumeSessionId));
|
|
1876
2041
|
const codexAppPrompt = (shouldPrependHistory && historyBlock)
|
|
1877
2042
|
? `${historyBlock}\n\n[User Message]\n${prompt}`
|
|
1878
2043
|
: prompt;
|
|
1879
|
-
|
|
2044
|
+
const startTurn = appClient.startTurn(laneScope, codexAppPrompt);
|
|
2045
|
+
await Promise.race([startTurn, turnDone]);
|
|
1880
2046
|
await turnDone;
|
|
1881
2047
|
turnCompleted = !turnReportedFailure;
|
|
1882
2048
|
flushCodexAppThinking();
|
|
1883
|
-
const persistedThreadId = lease?.threadId ?? appClient.
|
|
2049
|
+
const persistedThreadId = lease?.threadId ?? appClient.getThreadId(laneScope);
|
|
1884
2050
|
if (persistedThreadId && persistMainSession(stripUndefined({
|
|
1885
|
-
|
|
2051
|
+
persistenceOwner,
|
|
2052
|
+
scopeKey,
|
|
1886
2053
|
forceNew,
|
|
1887
2054
|
employeeSessionId: empSid,
|
|
1888
2055
|
sessionId: persistedThreadId,
|
|
@@ -1892,6 +2059,11 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1892
2059
|
resumeKey,
|
|
1893
2060
|
effort: cfg.effort || '',
|
|
1894
2061
|
skipSessionPersist: opts._skipSessionPersist === true,
|
|
2062
|
+
...(lease?.bucketKey ? { codexAppBucket: lease.bucketKey } : {}),
|
|
2063
|
+
// With multiplex off there is no lease bucket key, and without this the
|
|
2064
|
+
// save lands on the bare `codex-app` row that belongs to the default
|
|
2065
|
+
// session. codex-app is the default runtime, so that is the common case.
|
|
2066
|
+
scopedBucket: currentBucket,
|
|
1895
2067
|
}))) {
|
|
1896
2068
|
console.log(`[jaw:session] saved ${cli} session=${persistedThreadId.slice(0, 12)}... (pre-shutdown)`);
|
|
1897
2069
|
}
|
|
@@ -1909,9 +2081,9 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1909
2081
|
markCodexProgress = () => { };
|
|
1910
2082
|
if (watchdogCancel)
|
|
1911
2083
|
await watchdogCancel;
|
|
1912
|
-
if (
|
|
1913
|
-
|
|
1914
|
-
|
|
2084
|
+
if (mainRun?.cancelTurn === cancelHook)
|
|
2085
|
+
delete mainRun.cancelTurn;
|
|
2086
|
+
listener.dispose();
|
|
1915
2087
|
if (lease)
|
|
1916
2088
|
lease.release();
|
|
1917
2089
|
else {
|
|
@@ -1920,7 +2092,11 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1920
2092
|
cleanupEmployeeTmpDir(spawnCwd, settings["workingDir"], agentLabel);
|
|
1921
2093
|
}
|
|
1922
2094
|
}
|
|
1923
|
-
|
|
2095
|
+
// A turn that never completed is a failure even when the process it
|
|
2096
|
+
// was running on exited cleanly. Trusting the child's status here
|
|
2097
|
+
// reports success for a turn that produced nothing, which is exactly
|
|
2098
|
+
// what happens when a shared host is closed mid-turn.
|
|
2099
|
+
const exitCode = turnCompleted ? 0 : (processExit.value?.code || 1);
|
|
1924
2100
|
opts.lifecycle?.onExit?.(exitCode);
|
|
1925
2101
|
const killReason = consumeKillReason(child.pid);
|
|
1926
2102
|
if (processExit.value && processExit.value.code !== 0 && !killReason) {
|
|
@@ -1934,14 +2110,19 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1934
2110
|
await handleAgentExit({
|
|
1935
2111
|
ctx, code: exitCode, cli, model, agentLabel, mainManaged, origin,
|
|
1936
2112
|
resumeKey,
|
|
1937
|
-
prompt, opts, cfg, ownerGeneration, forceNew, empSid,
|
|
2113
|
+
prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
|
|
1938
2114
|
isResume, wasKilled, wasSteer, smokeResult,
|
|
1939
2115
|
effortDefault: '', costLine: '',
|
|
1940
2116
|
resolve: resolve,
|
|
1941
2117
|
activeProcesses,
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
2118
|
+
scopeKey,
|
|
2119
|
+
scopedBucket: currentBucket,
|
|
2120
|
+
chatSessionId,
|
|
2121
|
+
...(lease?.bucketKey ? { codexAppBucket: lease.bucketKey } : {}),
|
|
2122
|
+
childProcess: child,
|
|
2123
|
+
releaseMainRun,
|
|
2124
|
+
retryState: queueCtrl.retryStateForScope(scopeKey),
|
|
2125
|
+
fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
|
|
1945
2126
|
fallbackMaxRetries: FALLBACK_MAX_RETRIES,
|
|
1946
2127
|
processQueue,
|
|
1947
2128
|
}).catch((err) => {
|
|
@@ -1949,38 +2130,160 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1949
2130
|
});
|
|
1950
2131
|
};
|
|
1951
2132
|
if (opts.agentId) {
|
|
2133
|
+
const employeeLaneScope = `employee:${opts.agentId}`;
|
|
1952
2134
|
const appClient = new CodexAppClient({
|
|
1953
2135
|
binary: detected.path || 'codex', workDir: spawnCwd, env: spawnEnv,
|
|
1954
|
-
model, effort, fastMode: effectiveFastMode,
|
|
1955
2136
|
});
|
|
1956
2137
|
appClient.spawn();
|
|
1957
2138
|
const child = appClient.proc;
|
|
1958
2139
|
if (!child)
|
|
1959
2140
|
throw new Error('Codex AppServer process was not created');
|
|
1960
|
-
void runCodexAppTurn(appClient, null);
|
|
2141
|
+
void runCodexAppTurn(appClient, null, employeeLaneScope);
|
|
1961
2142
|
return { child, promise: resultPromise };
|
|
1962
2143
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
2144
|
+
mainRun.starting = true;
|
|
2145
|
+
const acquireCodexAppForTurn = async () => {
|
|
2146
|
+
let cancelled = false;
|
|
2147
|
+
let cancelReason = 'user';
|
|
2148
|
+
const cancelThisAcquire = (reason) => {
|
|
2149
|
+
cancelled = true;
|
|
2150
|
+
cancelReason = reason;
|
|
2151
|
+
};
|
|
2152
|
+
const acquireWasCancelled = () => cancelled || activeMainProcesses.get(scopeKey) !== mainRun;
|
|
2153
|
+
try {
|
|
2154
|
+
if (!codexMultiplexMain) {
|
|
2155
|
+
const lease = await acquireCodexAppRuntime({
|
|
2156
|
+
binary: detected.path || 'codex', env: spawnEnv,
|
|
2157
|
+
route: 'legacy',
|
|
2158
|
+
key: {
|
|
2159
|
+
scopeKey,
|
|
2160
|
+
cwd: spawnCwd, model, effort, fastMode: effectiveFastMode,
|
|
2161
|
+
},
|
|
2162
|
+
storedThreadId: resumeSessionId || null,
|
|
2163
|
+
instructions: sysPrompt,
|
|
2164
|
+
forceNew,
|
|
2165
|
+
});
|
|
2166
|
+
return { kind: 'lease', lease };
|
|
2167
|
+
}
|
|
2168
|
+
mainRun.cancelPending = cancelThisAcquire;
|
|
2169
|
+
const waitMs = configuredPositiveMs(process.env["CODEX_APP_ACQUIRE_WAIT_MS"], DEFAULT_CODEX_APP_ACQUIRE_WAIT_MS);
|
|
2170
|
+
const deadlineAt = Date.now() + waitMs;
|
|
2171
|
+
let lastStaleError = null;
|
|
2172
|
+
let staleAttempts = 0;
|
|
2173
|
+
const deadlineError = (stage) => lastStaleError
|
|
2174
|
+
?? new Error(`Codex App ${stage} timed out after ${waitMs}ms`);
|
|
2175
|
+
const awaitWithinDeadline = async (stage, pending, onLateValue) => {
|
|
2176
|
+
let deadlineWon = false;
|
|
2177
|
+
let timeout;
|
|
2178
|
+
void pending.then((value) => {
|
|
2179
|
+
if (deadlineWon)
|
|
2180
|
+
onLateValue?.(value);
|
|
2181
|
+
}, () => { });
|
|
2182
|
+
const remainingMs = deadlineAt - Date.now();
|
|
2183
|
+
if (remainingMs <= 0) {
|
|
2184
|
+
deadlineWon = true;
|
|
2185
|
+
throw deadlineError(stage);
|
|
2186
|
+
}
|
|
2187
|
+
const deadline = new Promise((_resolveDeadline, rejectDeadline) => {
|
|
2188
|
+
timeout = setTimeout(() => {
|
|
2189
|
+
deadlineWon = true;
|
|
2190
|
+
rejectDeadline(deadlineError(stage));
|
|
2191
|
+
}, remainingMs);
|
|
2192
|
+
});
|
|
2193
|
+
try {
|
|
2194
|
+
return await Promise.race([pending, deadline]);
|
|
2195
|
+
}
|
|
2196
|
+
finally {
|
|
2197
|
+
if (timeout)
|
|
2198
|
+
clearTimeout(timeout);
|
|
2199
|
+
}
|
|
2200
|
+
};
|
|
2201
|
+
for (;;) {
|
|
2202
|
+
if (acquireWasCancelled())
|
|
2203
|
+
return { kind: 'cancelled', reason: cancelReason };
|
|
2204
|
+
// Check the budget before spawning more work. awaitWithinDeadline()
|
|
2205
|
+
// only measures what remains once the promise already exists, so a
|
|
2206
|
+
// backoff that consumed the last of the budget would still get to
|
|
2207
|
+
// start one more prepare.
|
|
2208
|
+
if (deadlineAt - Date.now() <= 0)
|
|
2209
|
+
throw lastStaleError ?? deadlineError('prepare');
|
|
2210
|
+
try {
|
|
2211
|
+
const prepared = await awaitWithinDeadline('prepare', prepareCodexAppHost({
|
|
2212
|
+
binary: detected.path || 'codex', cwd: spawnCwd,
|
|
2213
|
+
fastMode: effectiveFastMode, env: spawnEnv, model, effort,
|
|
2214
|
+
}));
|
|
2215
|
+
if (acquireWasCancelled())
|
|
2216
|
+
return { kind: 'cancelled', reason: cancelReason };
|
|
2217
|
+
const lease = await awaitWithinDeadline('acquire', acquireCodexAppLane(prepared, {
|
|
2218
|
+
scopeKey,
|
|
2219
|
+
bucketKey: currentBucket,
|
|
2220
|
+
storedThreadId: resumeSessionId || null,
|
|
2221
|
+
instructions: sysPrompt,
|
|
2222
|
+
forceNew,
|
|
2223
|
+
waitMs: deadlineAt - Date.now(),
|
|
2224
|
+
}), (lateLease) => { lateLease.release(); });
|
|
2225
|
+
if (acquireWasCancelled()) {
|
|
2226
|
+
lease.release();
|
|
2227
|
+
return { kind: 'cancelled', reason: cancelReason };
|
|
2228
|
+
}
|
|
2229
|
+
return { kind: 'lease', lease };
|
|
2230
|
+
}
|
|
2231
|
+
catch (err) {
|
|
2232
|
+
if (!(err instanceof CodexHostGenerationStaleError))
|
|
2233
|
+
throw err;
|
|
2234
|
+
lastStaleError = err;
|
|
2235
|
+
if (acquireWasCancelled())
|
|
2236
|
+
return { kind: 'cancelled', reason: cancelReason };
|
|
2237
|
+
const remainingMs = deadlineAt - Date.now();
|
|
2238
|
+
if (remainingMs <= 0)
|
|
2239
|
+
throw lastStaleError;
|
|
2240
|
+
staleAttempts += 1;
|
|
2241
|
+
const backoffMs = Math.min(remainingMs, CODEX_APP_ACQUIRE_RETRY_BACKOFF_MAX_MS, 25 * staleAttempts);
|
|
2242
|
+
await new Promise((done) => { setTimeout(done, backoffMs); });
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
finally {
|
|
2247
|
+
const latest = activeMainProcesses.get(scopeKey);
|
|
2248
|
+
if (latest?.cancelPending === cancelThisAcquire)
|
|
2249
|
+
delete latest.cancelPending;
|
|
2250
|
+
mainRun.starting = false;
|
|
2251
|
+
}
|
|
2252
|
+
};
|
|
2253
|
+
void acquireCodexAppForTurn().then(async (outcome) => {
|
|
2254
|
+
// A run that never started a turn owns nothing but its own map slot.
|
|
2255
|
+
// releaseMainRun() matches on (process, ownerGeneration), and a pending
|
|
2256
|
+
// run has process=null while sharing the global generation with whatever
|
|
2257
|
+
// replaced it, so calling it here would delete the replacement's entry.
|
|
2258
|
+
// Compare the captured object instead and only drop our own slot.
|
|
2259
|
+
const abandonTurn = (lease) => {
|
|
2260
|
+
lease?.release();
|
|
2261
|
+
finalizeTraceRun(traceRunId, 'interrupted');
|
|
2262
|
+
clearLiveRun(liveScope);
|
|
2263
|
+
broadcast('agent_status', { running: false, agentId: agentLabel });
|
|
2264
|
+
resolve({ text: '', code: -1 });
|
|
2265
|
+
if (activeMainProcesses.get(scopeKey) === mainRun)
|
|
2266
|
+
activeMainProcesses.delete(scopeKey);
|
|
2267
|
+
void processQueue(scopeKey);
|
|
2268
|
+
};
|
|
2269
|
+
if (outcome.kind === 'cancelled') {
|
|
2270
|
+
abandonTurn(null);
|
|
2271
|
+
return;
|
|
2272
|
+
}
|
|
2273
|
+
const lease = outcome.lease;
|
|
2274
|
+
if (activeMainProcesses.get(scopeKey) !== mainRun) {
|
|
2275
|
+
abandonTurn(lease);
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
await runCodexAppTurn(lease.client, lease, lease.laneScope);
|
|
1976
2279
|
}).catch((err) => {
|
|
1977
|
-
mainSpawnStarting = false;
|
|
1978
2280
|
console.error(`[codex-app:pool] acquire failed: ${err.message}`);
|
|
1979
2281
|
clearLiveRun(liveScope);
|
|
1980
2282
|
broadcast('agent_status', { running: false, agentId: agentLabel });
|
|
1981
2283
|
broadcast('agent_done', { text: `❌ Codex AppServer acquire failed: ${err.message}`, error: true, origin }, 'public');
|
|
2284
|
+
releaseMainRun(scopeKey, null, ownerGeneration);
|
|
1982
2285
|
resolve({ text: '', code: 1 });
|
|
1983
|
-
processQueue();
|
|
2286
|
+
void processQueue(scopeKey);
|
|
1984
2287
|
});
|
|
1985
2288
|
return { child: null, promise: resultPromise };
|
|
1986
2289
|
}
|
|
@@ -1997,15 +2300,23 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1997
2300
|
if (opencodeSpawnAudit) {
|
|
1998
2301
|
console.log(`[jaw:opencode:audit] ${JSON.stringify(opencodeSpawnAudit)}`);
|
|
1999
2302
|
}
|
|
2000
|
-
|
|
2303
|
+
// The snapshot has to predate the child; the helper owns that ordering (073 §2.4).
|
|
2304
|
+
const kiroPlainText = isKiroPlainTextCli(cli, effectiveProvider);
|
|
2305
|
+
const { child, kiroConversationIdsBefore, kiroSpawnStartedAt } = spawnWithKiroSnapshot({
|
|
2306
|
+
kiroPlainText,
|
|
2307
|
+
isFreshMainRun: !isResume && !empSid,
|
|
2001
2308
|
cwd: spawnCwd,
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2309
|
+
spawn: () => spawn(spawnCommand, args, {
|
|
2310
|
+
cwd: spawnCwd,
|
|
2311
|
+
env: spawnEnv,
|
|
2312
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
2313
|
+
...(windowsSpawnUsesShell ? { shell: true } : {}),
|
|
2314
|
+
}),
|
|
2005
2315
|
});
|
|
2006
2316
|
if (mainManaged)
|
|
2007
|
-
|
|
2008
|
-
|
|
2317
|
+
mainRun.process = child;
|
|
2318
|
+
else
|
|
2319
|
+
registerActiveProcess(agentLabel, child);
|
|
2009
2320
|
if (!opts.internal)
|
|
2010
2321
|
broadcast('agent_status', { running: true, agentId: agentLabel, cli, ...runtimeStatusMeta, ...empTag });
|
|
2011
2322
|
if (mainManaged && !opts.internal)
|
|
@@ -2050,19 +2361,21 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
2050
2361
|
? `CLI '${cli}' 실행 실패 (ENOEXEC). PATH의 실행 파일이 바이너리 또는 shebang 스크립트가 아닙니다. \`jaw doctor --json\`으로 깨진 shim을 확인하세요.`
|
|
2051
2362
|
: `CLI '${cli}' 실행 실패: ${err.message}`;
|
|
2052
2363
|
console.error(`[jaw:${agentLabel}:error] ${msg}`);
|
|
2053
|
-
activeProcesses.delete(agentLabel);
|
|
2054
2364
|
if (mainManaged) {
|
|
2055
|
-
|
|
2365
|
+
releaseMainRun(scopeKey, child, ownerGeneration);
|
|
2056
2366
|
clearLiveRun(liveScope);
|
|
2057
2367
|
broadcast('agent_status', { running: false, agentId: agentLabel });
|
|
2058
2368
|
}
|
|
2369
|
+
else {
|
|
2370
|
+
activeProcesses.delete(agentLabel);
|
|
2371
|
+
}
|
|
2059
2372
|
broadcast('agent_done', { text: `❌ ${msg}`, error: true, origin, ...empTag }, isEmployee ? 'internal' : 'public');
|
|
2060
2373
|
resolve({ text: '', code: 127 });
|
|
2061
2374
|
if (mainManaged)
|
|
2062
|
-
processQueue();
|
|
2375
|
+
void processQueue(scopeKey);
|
|
2063
2376
|
});
|
|
2064
2377
|
if (mainManaged && !opts.internal && !opts._skipInsert) {
|
|
2065
|
-
insertMessage.run('user', prompt, cli, runtimeModel, settings["workingDir"] || null,
|
|
2378
|
+
insertMessage.run('user', prompt, cli, runtimeModel, settings["workingDir"] || null, chatSessionId);
|
|
2066
2379
|
}
|
|
2067
2380
|
if (cli === 'claude') {
|
|
2068
2381
|
child.stdin.write(isResume ? prompt : withHistoryPrompt(prompt, historyBlock));
|
|
@@ -2082,11 +2395,6 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
2082
2395
|
const traceRunId = startTraceRun({ cli, model: runtimeModel, workingDir: settings["workingDir"] || null, agentLabel, audience: traceAudience });
|
|
2083
2396
|
if (mainManaged && !opts.internal)
|
|
2084
2397
|
setLiveRunTraceId(liveScope, traceRunId);
|
|
2085
|
-
const kiroPlainText = isKiroPlainTextCli(cli, effectiveProvider);
|
|
2086
|
-
const kiroSpawnStartedAt = kiroPlainText ? Date.now() - 1000 : 0;
|
|
2087
|
-
const kiroConversationIdsBefore = (kiroPlainText && !isResume && !empSid)
|
|
2088
|
-
? listKiroConversationIdsForCwd(spawnCwd)
|
|
2089
|
-
: null;
|
|
2090
2398
|
// Native `agy --conversation ... -p` may emit only the current answer.
|
|
2091
2399
|
// Length-based replay trimming can therefore swallow the whole new answer.
|
|
2092
2400
|
const agyResumeOffset = 0;
|
|
@@ -2468,7 +2776,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
2468
2776
|
if (cli === 'agy' && isResume && (agyGuardedStaleDetected || isAgyStaleSessionOutput(ctx.fullText))) {
|
|
2469
2777
|
console.log(`[jaw:agy] stale session detected (Warning: conversation not found) — clearing bucket`);
|
|
2470
2778
|
try {
|
|
2471
|
-
const bucket =
|
|
2779
|
+
const bucket = currentBucket;
|
|
2472
2780
|
clearSessionBucket.run(bucket);
|
|
2473
2781
|
}
|
|
2474
2782
|
catch (e) {
|
|
@@ -2476,9 +2784,10 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
2476
2784
|
}
|
|
2477
2785
|
ctx.sessionId = null;
|
|
2478
2786
|
if (agyResumeDecision.ok && !opts._agyStaleFreshRetry) {
|
|
2479
|
-
activeProcesses.delete(agentLabel);
|
|
2480
2787
|
if (mainManaged)
|
|
2481
|
-
|
|
2788
|
+
releaseMainRun(scopeKey, child, ownerGeneration);
|
|
2789
|
+
else
|
|
2790
|
+
activeProcesses.delete(agentLabel);
|
|
2482
2791
|
const { promise: freshPromise } = spawnAgent(prompt, {
|
|
2483
2792
|
...opts, _agyStaleFreshRetry: true, _skipResume: true, _skipInsert: true,
|
|
2484
2793
|
});
|
|
@@ -2506,7 +2815,7 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
2506
2815
|
if (isResume && isKiroStaleSessionOutput(ctx.fullText)) {
|
|
2507
2816
|
console.log('[jaw:kiro] stale session detected in output — clearing bucket');
|
|
2508
2817
|
try {
|
|
2509
|
-
const bucket =
|
|
2818
|
+
const bucket = currentBucket;
|
|
2510
2819
|
clearSessionBucket.run(bucket);
|
|
2511
2820
|
}
|
|
2512
2821
|
catch (e) {
|
|
@@ -2651,14 +2960,18 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
2651
2960
|
handleAgentExit({
|
|
2652
2961
|
ctx, code: effectiveExitCode, cli, model: runtimeModel, effectiveProvider, agentLabel, mainManaged, origin,
|
|
2653
2962
|
resumeKey,
|
|
2654
|
-
prompt, opts, cfg, ownerGeneration, forceNew, empSid,
|
|
2963
|
+
prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
|
|
2655
2964
|
isResume, wasKilled, wasSteer, smokeResult,
|
|
2656
2965
|
effortDefault: cli === 'grok' ? '' : 'medium', costLine,
|
|
2657
2966
|
resolve: resolve,
|
|
2658
2967
|
activeProcesses,
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2968
|
+
scopeKey,
|
|
2969
|
+
scopedBucket: currentBucket,
|
|
2970
|
+
chatSessionId,
|
|
2971
|
+
childProcess: child,
|
|
2972
|
+
releaseMainRun,
|
|
2973
|
+
retryState: queueCtrl.retryStateForScope(scopeKey),
|
|
2974
|
+
fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
|
|
2662
2975
|
fallbackMaxRetries: FALLBACK_MAX_RETRIES,
|
|
2663
2976
|
processQueue,
|
|
2664
2977
|
...(agyTotalOutputLen > 0 ? { outputLen: agyTotalOutputLen } : {}),
|