cli-jaw 2.17.62 → 2.17.63
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.ja.md +17 -17
- package/README.ko.md +17 -17
- package/README.md +17 -17
- package/README.zh-CN.md +17 -17
- package/dist/bin/commands/tui/input-handler.js +19 -0
- package/dist/bin/commands/tui/input-handler.js.map +1 -1
- package/dist/bin/commands/tui/overlays.js +8 -4
- package/dist/bin/commands/tui/overlays.js.map +1 -1
- package/dist/bin/star-prompt.js +1 -1
- package/dist/bin/star-prompt.js.map +1 -1
- package/dist/server.js +14 -1
- package/dist/server.js.map +1 -1
- package/dist/src/agent/claude-runtime-pool.js +2 -1
- package/dist/src/agent/claude-runtime-pool.js.map +1 -1
- package/dist/src/agent/claude-runtime-run.js +28 -7
- package/dist/src/agent/claude-runtime-run.js.map +1 -1
- package/dist/src/agent/lifecycle-handler.js +34 -6
- package/dist/src/agent/lifecycle-handler.js.map +1 -1
- package/dist/src/agent/runtime/claude-sdk-events.js +136 -9
- package/dist/src/agent/runtime/claude-sdk-events.js.map +1 -1
- package/dist/src/agent/runtime/claude-sdk-history-loader.js +24 -0
- package/dist/src/agent/runtime/claude-sdk-history-loader.js.map +1 -0
- package/dist/src/agent/runtime/claude-sdk-hooks.js +10 -4
- package/dist/src/agent/runtime/claude-sdk-hooks.js.map +1 -1
- package/dist/src/agent/runtime/claude-sdk-messages.js +25 -2
- package/dist/src/agent/runtime/claude-sdk-messages.js.map +1 -1
- package/dist/src/agent/runtime/claude-sdk-options.js +36 -3
- package/dist/src/agent/runtime/claude-sdk-options.js.map +1 -1
- package/dist/src/agent/runtime/claude-sdk-permissions.js +32 -9
- package/dist/src/agent/runtime/claude-sdk-permissions.js.map +1 -1
- package/dist/src/agent/runtime/claude-sdk-session.js +165 -17
- package/dist/src/agent/runtime/claude-sdk-session.js.map +1 -1
- package/dist/src/agent/runtime/claude-sdk-version.js +8 -0
- package/dist/src/agent/runtime/claude-sdk-version.js.map +1 -0
- package/dist/src/agent/spawn/backend-codex-app.js +619 -0
- package/dist/src/agent/spawn/backend-codex-app.js.map +1 -0
- package/dist/src/agent/spawn/backend-context.js +2 -0
- package/dist/src/agent/spawn/backend-context.js.map +1 -0
- package/dist/src/agent/spawn/backend-copilot.js +385 -0
- package/dist/src/agent/spawn/backend-copilot.js.map +1 -0
- package/dist/src/agent/spawn/backend-native-acp.js +379 -0
- package/dist/src/agent/spawn/backend-native-acp.js.map +1 -0
- package/dist/src/agent/spawn/backend-pi.js +565 -0
- package/dist/src/agent/spawn/backend-pi.js.map +1 -0
- package/dist/src/agent/spawn/types.js +2 -0
- package/dist/src/agent/spawn/types.js.map +1 -0
- package/dist/src/agent/spawn.js +44 -1858
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/cli/claude-default-model-boot.js +36 -0
- package/dist/src/cli/claude-default-model-boot.js.map +1 -0
- package/dist/src/cli/claude-model-discovery.js +13 -1
- package/dist/src/cli/claude-model-discovery.js.map +1 -1
- package/dist/src/cli/claude-models.js +7 -1
- package/dist/src/cli/claude-models.js.map +1 -1
- package/dist/src/cli/commands.js +5 -15
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/tui/composer.js +3 -3
- package/dist/src/cli/tui/composer.js.map +1 -1
- package/dist/src/cli/tui/skill-mention.js +15 -0
- package/dist/src/cli/tui/skill-mention.js.map +1 -0
- package/dist/src/code-mode/manager.js +174 -9
- package/dist/src/code-mode/manager.js.map +1 -1
- package/dist/src/code-mode/providers/catalog.js +5 -3
- package/dist/src/code-mode/providers/catalog.js.map +1 -1
- package/dist/src/code-mode/providers/claude-history.js +132 -0
- package/dist/src/code-mode/providers/claude-history.js.map +1 -0
- package/dist/src/code-mode/providers/claude.js +74 -7
- package/dist/src/code-mode/providers/claude.js.map +1 -1
- package/dist/src/code-mode/providers/codex-app.js +7 -1
- package/dist/src/code-mode/providers/codex-app.js.map +1 -1
- package/dist/src/code-mode/session.js +158 -11
- package/dist/src/code-mode/session.js.map +1 -1
- package/dist/src/code-mode/store.js +416 -28
- package/dist/src/code-mode/store.js.map +1 -1
- package/dist/src/command-contract/catalog.js +3 -22
- package/dist/src/command-contract/catalog.js.map +1 -1
- package/dist/src/core/codex-config.js +3 -62
- package/dist/src/core/codex-config.js.map +1 -1
- package/dist/src/core/config.js +116 -14
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/runtime-settings.js +20 -10
- package/dist/src/core/runtime-settings.js.map +1 -1
- package/dist/src/core/settings-watch.js +1 -0
- package/dist/src/core/settings-watch.js.map +1 -1
- package/dist/src/core/skill-mentions.js +83 -0
- package/dist/src/core/skill-mentions.js.map +1 -0
- package/dist/src/manager/registry.js +5 -1
- package/dist/src/manager/registry.js.map +1 -1
- package/dist/src/manager/routes/desktop-status.js +1 -1
- package/dist/src/manager/routes/desktop-status.js.map +1 -1
- package/dist/src/orchestrator/pipeline.js +11 -1
- package/dist/src/orchestrator/pipeline.js.map +1 -1
- package/dist/src/prompt/builder.js +10 -4
- package/dist/src/prompt/builder.js.map +1 -1
- package/dist/src/prompt/templates/a1-system.md +1 -1
- package/dist/src/routes/code-native.js +51 -3
- package/dist/src/routes/code-native.js.map +1 -1
- package/dist/src/routes/settings.js +1 -4
- package/dist/src/routes/settings.js.map +1 -1
- package/dist/src/routes/skills.js +7 -0
- package/dist/src/routes/skills.js.map +1 -1
- package/dist/src/shared/skill-mention.js +61 -0
- package/dist/src/shared/skill-mention.js.map +1 -0
- package/dist/src/shared/tool-input.js +137 -0
- package/dist/src/shared/tool-input.js.map +1 -0
- package/package.json +7 -5
- package/public/css/activity.css +83 -20
- package/public/css/layout.css +2 -2
- package/public/css/sidebar.css +18 -0
- package/public/css/variables.css +23 -2
- package/public/dist/.vite/manifest.json +302 -334
- package/public/dist/assets/Activity-DPLlOJ46.js +1 -0
- package/public/dist/assets/AdvancedExport-BgOgGvc5.js +1 -0
- package/public/dist/assets/Agent-O_U8ny90.js +1 -0
- package/public/dist/assets/Browser-m6VynQXV.js +1 -0
- package/public/dist/assets/BrowserPanel-Bb-Bge6B.js +3 -0
- package/public/dist/assets/ChannelEnablementControl-BSuKQhTW.js +85 -0
- package/public/dist/assets/ChannelSetupEntry-DHLoZEzJ.js +1 -0
- package/public/dist/assets/ChannelsDiscord-B-G8vsYy.js +1 -0
- package/public/dist/assets/ChannelsSlack-oPuh922z.js +1 -0
- package/public/dist/assets/ChannelsTelegram-PmTv43KQ.js +1 -0
- package/public/dist/assets/CodeCanvas-CVbdp0S-.js +3 -0
- package/public/dist/assets/DashboardMeta-BqEiJrvr.js +1 -0
- package/public/dist/assets/DesignPanel-BOb-HMbl.js +2 -0
- package/public/dist/assets/{Developer-BdKPcw8Z.js → Developer-CbySB7_Z.js} +1 -1
- package/public/dist/assets/{DiffPanel-DtJuvU5y.js → DiffPanel-D9Cch5FG.js} +1 -1
- package/public/dist/assets/Display-C0Zdamcb.js +1 -0
- package/public/dist/assets/Display-DU4_76cS.js +1 -0
- package/public/dist/assets/DocPanel-DvtFw8AP.js +1 -0
- package/public/dist/assets/{Embedding-DvdTj1BF.js → Embedding-CCFMOB_2.js} +1 -1
- package/public/dist/assets/Employees-BanvF58d.js +1 -0
- package/public/dist/assets/FolderPanel-CJvwRwIV.js +3 -0
- package/public/dist/assets/Heartbeat-uyi_MpUd.js +1 -0
- package/public/dist/assets/InlineWarn-Cq4z28F3.js +1 -0
- package/public/dist/assets/MarkdownRenderer-Cc3DeHvm.js +1 -0
- package/public/dist/assets/{MarkdownRenderer-DhonsAPC.js → MarkdownRenderer-DzN66XVb.js} +1 -1
- package/public/dist/assets/Mcp-rjPoL_Zo.js +4 -0
- package/public/dist/assets/Memory-C9I33pIi.js +1 -0
- package/public/dist/assets/{MilkdownWysiwygEditor-D6JaLDE4.js → MilkdownWysiwygEditor-xObhviw7.js} +1 -1
- package/public/dist/assets/ModelProvider-fiUktMaO.js +1 -0
- package/public/dist/assets/Network-CuqFrOvR.js +1 -0
- package/public/dist/assets/NotesGraphWorkspace-DO9LtVGF.js +1 -0
- package/public/dist/assets/PanelLayoutProvider-DRZ4QkBC.js +1 -0
- package/public/dist/assets/Permissions-BltXiZnz.js +1 -0
- package/public/dist/assets/{Permissions-BwiMEoTV.js → Permissions-DcCbFMIq.js} +1 -1
- package/public/dist/assets/Profile-DUL7-RE_.js +1 -0
- package/public/dist/assets/Prompts-C9vY8ivQ.js +1 -0
- package/public/dist/assets/SpeechKeys-BJ8V7U5E.js +1 -0
- package/public/dist/assets/TelegramHub-DtyjLGFR.js +1 -0
- package/public/dist/assets/{TerminalPanel-Bu9i-P2f.js → TerminalPanel-c1xhiy53.js} +1 -1
- package/public/dist/assets/app-BjKzqXCv.css +1 -0
- package/public/dist/assets/app-CZwx0V6K.js +59 -0
- package/public/dist/assets/bgtask-badge-CnVkcp3c.js +1 -0
- package/public/dist/assets/{bgtask-badge-DfiHuzxT.js → bgtask-badge-Cp26S8Uq.js} +1 -1
- package/public/dist/assets/client-platform-WO8m6kPQ.js +1 -0
- package/public/dist/assets/{employees-BQaJNp-u.js → employees-CsvI0_5V.js} +1 -1
- package/public/dist/assets/error-normalize-clOi-AAA.js +1 -0
- package/public/dist/assets/fields-CicOWY-X.js +1 -0
- package/public/dist/assets/iframe-renderer-DWx9v2Iz.js +1 -0
- package/public/dist/assets/iframe-renderer-DmHYOI1A.js +142 -0
- package/public/dist/assets/{react-C_pPTFsM.js → jsx-runtime-CkEUZM6i.js} +1 -1
- package/public/dist/assets/manager-CCXm5Yfe.js +5 -0
- package/public/dist/assets/manager-D9rhKFKc.css +1 -0
- package/public/dist/assets/manager-shortcuts-Cqmr0mEq.js +1 -0
- package/public/dist/assets/{memory-EG1B51up.js → memory-C6qtAMdJ.js} +1 -1
- package/public/dist/assets/memory-PPofQPeB.js +1 -0
- package/public/dist/assets/message-history-DcjBuubA.js +4 -0
- package/public/dist/assets/message-history-bw4qF01Z.js +1 -0
- package/public/dist/assets/page-shell-DfinU3mb.js +1 -0
- package/public/dist/assets/react-dom-URBLzV3w.js +1 -0
- package/public/dist/assets/{render-Dp07lZAJ.js → render-DfYro-fG.js} +1 -1
- package/public/dist/assets/{settings-jlI4X-zt.js → settings-CvsLWlJn.js} +38 -30
- package/public/dist/assets/settings-DMiQe469.js +1 -0
- package/public/dist/assets/settings-DgAlohx9.js +1 -0
- package/public/dist/assets/settings-core-BH7J_Rbm.js +1 -0
- package/public/dist/assets/{settings-core-B5Uf_lWY.js → settings-core-pnpw-eZv.js} +1 -1
- package/public/dist/assets/{settings-embedding-BfWzWKMM.js → settings-embedding-BRW0WZvh.js} +3 -3
- package/public/dist/assets/{settings-embedding-DhEdT86W.css → settings-embedding-CUQ8Is3y.css} +1 -1
- package/public/dist/assets/settings-types-CYKy0xz4.js +1 -0
- package/public/dist/assets/shared-D1begqvi.js +1 -0
- package/public/dist/assets/{sidebar-BUemG2ZN.js → sidebar-C_80w4iY.js} +3 -3
- package/public/dist/assets/{skills-CKUcEN9m.js → skills-D_2adqge.js} +1 -1
- package/public/dist/assets/skills-DeNVAVgt.js +1 -0
- package/public/dist/assets/{slash-commands-D34GgHFX.js → slash-commands-B_Hq5RkC.js} +2 -2
- package/public/dist/assets/slash-commands-Cj5qoRWc.js +1 -0
- package/public/dist/assets/tool-input-DI7uu8Ah.js +2 -0
- package/public/dist/assets/{trace-drawer-DBmK-5F_.js → trace-drawer-B6XuPodh.js} +1 -1
- package/public/dist/assets/ui-BbR_fFiK.js +1 -0
- package/public/dist/assets/{ui-vm2iuLcS.js → ui-D8xzm7DN.js} +1 -1
- package/public/dist/index.html +11 -3
- package/public/dist/manager/index.html +3 -3
- package/public/dist/settings/index.html +2 -2
- package/public/index.html +9 -1
- package/public/js/features/activity-history.ts +50 -21
- package/public/js/features/activity-rows.ts +158 -10
- package/public/js/features/activity-view.ts +5 -5
- package/public/js/features/chat.ts +2 -0
- package/public/js/features/employees.ts +1 -0
- package/public/js/features/resize-guard.ts +39 -0
- package/public/js/features/settings-cli-status.ts +49 -17
- package/public/js/features/settings-types.ts +39 -1
- package/public/js/features/skill-mentions.ts +143 -0
- package/public/js/main.ts +15 -0
- package/public/js/virtual-scroll.ts +67 -1
- package/public/locales/en.json +2 -0
- package/public/locales/ja.json +2 -0
- package/public/locales/ko.json +2 -0
- package/public/locales/zh.json +2 -0
- package/public/manager/src/AppChrome.tsx +2 -0
- package/public/manager/src/InstancePreview.tsx +1 -1
- package/public/manager/src/SidebarRailRouter.tsx +3 -1
- package/public/manager/src/browser-panel/BrowserPanel.tsx +47 -12
- package/public/manager/src/browser-panel/browser-address-state.ts +11 -0
- package/public/manager/src/client-platform.ts +18 -0
- package/public/manager/src/code/CodeCanvas.tsx +4 -2
- package/public/manager/src/code/CodeComposer.tsx +22 -10
- package/public/manager/src/code/CodeDraftEmptyState.tsx +64 -0
- package/public/manager/src/code/CodeSessionList.tsx +154 -40
- package/public/manager/src/code/CodeTranscript.tsx +104 -16
- package/public/manager/src/code/CodeWorkbench.tsx +21 -11
- package/public/manager/src/code/CodeWorkspaceHeader.tsx +5 -10
- package/public/manager/src/code/ComposerFooter.tsx +9 -1
- package/public/manager/src/code/code-composer.css +1 -1
- package/public/manager/src/code/code-controller-draft-storage.ts +16 -8
- package/public/manager/src/code/code-controller-drafts.ts +33 -6
- package/public/manager/src/code/code-controller-runtime.ts +189 -17
- package/public/manager/src/code/code-controller-types.ts +19 -1
- package/public/manager/src/code/code-recent-workspaces.ts +25 -0
- package/public/manager/src/code/code-session-client.ts +19 -1
- package/public/manager/src/code/code-session-sidebar.css +38 -0
- package/public/manager/src/code/code-session-state.ts +2 -0
- package/public/manager/src/code/code-types.ts +47 -0
- package/public/manager/src/code/code-workbench.css +36 -1
- package/public/manager/src/code/code.css +171 -7
- package/public/manager/src/code/session-order.ts +80 -0
- package/public/manager/src/code/tool-summary.ts +45 -11
- package/public/manager/src/folder-panel/folder-shortcuts.ts +5 -9
- package/public/manager/src/hooks/useCommandPalette.ts +3 -7
- package/public/manager/src/main.tsx +1 -0
- package/public/manager/src/manager-components.css +25 -23
- package/public/manager/src/manager-dashboard-reminders.css +2 -2
- package/public/manager/src/manager-layout.css +1 -1
- package/public/manager/src/manager-shortcut-runner.ts +9 -2
- package/public/manager/src/manager-shortcuts.ts +83 -21
- package/public/manager/src/panels/desktop-bridge.ts +4 -1
- package/public/manager/src/panels/panels.css +9 -0
- package/public/manager/src/preview.ts +19 -4
- package/public/manager/src/settings/pages/AdvancedExport.tsx +28 -30
- package/public/manager/src/settings/pages/Browser.tsx +45 -47
- package/public/manager/src/settings/pages/ChannelsDiscord.tsx +51 -26
- package/public/manager/src/settings/pages/ChannelsSlack.tsx +59 -17
- package/public/manager/src/settings/pages/ChannelsTelegram.tsx +51 -26
- package/public/manager/src/settings/pages/Display.tsx +2 -1
- package/public/manager/src/settings/pages/Employees.tsx +3 -3
- package/public/manager/src/settings/pages/Mcp.tsx +220 -200
- package/public/manager/src/settings/pages/Memory.tsx +35 -41
- package/public/manager/src/settings/pages/ModelProvider.tsx +23 -98
- package/public/manager/src/settings/pages/Network.tsx +5 -4
- package/public/manager/src/settings/pages/Permissions.tsx +38 -16
- package/public/manager/src/settings/pages/Prompts.tsx +12 -9
- package/public/manager/src/settings/pages/SpeechKeys.tsx +132 -139
- package/public/manager/src/settings/pages/TelegramHub.tsx +67 -20
- package/public/manager/src/settings/pages/components/AvatarCard.tsx +9 -8
- package/public/manager/src/settings/pages/components/ChannelSetupEntry.tsx +2 -2
- package/public/manager/src/settings/pages/components/EmployeeRow.tsx +6 -12
- package/public/manager/src/settings/pages/components/HealthBadge.tsx +11 -6
- package/public/manager/src/settings/pages/components/HeartbeatJobRow.tsx +35 -16
- package/public/manager/src/settings/pages/components/HeartbeatJobsSection.tsx +11 -11
- package/public/manager/src/settings/pages/components/HeartbeatMarkdownSection.tsx +7 -7
- package/public/manager/src/settings/pages/components/InlineWarn.tsx +4 -9
- package/public/manager/src/settings/pages/components/McpServerCard.tsx +39 -27
- package/public/manager/src/settings/pages/components/MemoryRow.tsx +21 -16
- package/public/manager/src/settings/pages/components/PerCliRow.tsx +122 -121
- package/public/manager/src/settings/pages/components/PiProfileDialog.tsx +9 -6
- package/public/manager/src/settings/pages/components/SlackSetup.tsx +8 -8
- package/public/manager/src/settings/pages/components/TransportStatusChips.tsx +14 -10
- package/public/manager/src/settings/pages/components/agent/AgentEmployeesSection.tsx +26 -26
- package/public/manager/src/settings/pages/components/agent/PermissionQuickSection.tsx +5 -5
- package/public/manager/src/settings/pages/components/agent/RuntimeEmployeeRow.tsx +14 -11
- package/public/manager/src/settings/pages/components/agent/RuntimeHeader.tsx +1 -3
- package/public/manager/src/settings/pages/components/agent/agent-meta.ts +0 -2
- package/public/manager/src/settings/pages/components/runtime-transport-field.tsx +2 -2
- package/public/manager/src/settings/pages/manager/Activity.tsx +6 -2
- package/public/manager/src/settings/pages/manager/Display.tsx +3 -8
- package/public/manager/src/settings/pages/manager/shared.tsx +44 -33
- package/public/manager/src/settings/pages/page-shell.tsx +66 -0
- package/public/manager/src/settings-agent.css +185 -36
- package/public/manager/src/settings-standalone.tsx +1 -0
- package/public/manager/src/settings-surface.css +96 -0
- package/public/manager/src/types.ts +1 -0
- package/scripts/ci/install-ripgrep.sh +35 -0
- package/scripts/collect-fresh-install-evidence.sh +2 -2
- package/scripts/install-risk-gate.mjs +2 -3
- package/scripts/install-wsl.sh +1 -1
- package/scripts/install.ps1 +1 -1
- package/scripts/install.sh +1 -1
- package/scripts/release-1.6.0.sh +1 -1
- package/scripts/release-gates.mjs +48 -13
- package/scripts/release-preview.sh +2 -2
- package/scripts/restack-pr.sh +88 -0
- package/scripts/verify-electron-update-metadata.mjs +1 -1
- package/scripts/windows-support-contract.json +1 -1
- package/public/dist/assets/Activity-BUrp73Re.js +0 -1
- package/public/dist/assets/AdvancedExport-B7h_ZvBu.js +0 -1
- package/public/dist/assets/Agent-DnbZJP7b.js +0 -1
- package/public/dist/assets/Browser-CiBoK5Qx.js +0 -1
- package/public/dist/assets/BrowserPanel-SZAUc9T2.js +0 -3
- package/public/dist/assets/ChannelEnablementControl-Dl0uYx38.js +0 -85
- package/public/dist/assets/ChannelSetupEntry-DxtOrN01.js +0 -1
- package/public/dist/assets/ChannelsDiscord-BEGGwd_3.js +0 -1
- package/public/dist/assets/ChannelsSlack-BryC2Ahr.js +0 -1
- package/public/dist/assets/ChannelsTelegram-CGSpaImJ.js +0 -1
- package/public/dist/assets/CodeCanvas-DpkFi5Au.js +0 -2
- package/public/dist/assets/DashboardMeta-Dh-tasbY.js +0 -1
- package/public/dist/assets/DesignPanel-NLNK9wCo.js +0 -2
- package/public/dist/assets/Display-BHGFX3O0.js +0 -1
- package/public/dist/assets/Display-BW-_pKRf.js +0 -1
- package/public/dist/assets/DocPanel-BM41cYpS.js +0 -1
- package/public/dist/assets/Employees-BuFw6-fT.js +0 -1
- package/public/dist/assets/FolderPanel-C-eQT3Yq.js +0 -3
- package/public/dist/assets/Heartbeat-CIVtKWNc.js +0 -1
- package/public/dist/assets/InlineWarn-DIMbcbzk.js +0 -1
- package/public/dist/assets/MarkdownRenderer-DyVx6DxM.js +0 -1
- package/public/dist/assets/Mcp-uKjor7vh.js +0 -4
- package/public/dist/assets/Memory-BSDIXT0B.js +0 -1
- package/public/dist/assets/ModelProvider-DMh2tXUx.js +0 -1
- package/public/dist/assets/Network-4Pa6JkhK.js +0 -1
- package/public/dist/assets/NotesGraphWorkspace-CgdC-fM6.js +0 -1
- package/public/dist/assets/PanelLayoutProvider-mn8sI0h-.js +0 -1
- package/public/dist/assets/Permissions-CodIW-Yd.js +0 -1
- package/public/dist/assets/Profile-DPqrYA44.js +0 -1
- package/public/dist/assets/Prompts-CvAx7QqR.js +0 -1
- package/public/dist/assets/SpeechKeys-CVBCqoBn.js +0 -1
- package/public/dist/assets/TelegramHub-4zKwpk0P.js +0 -1
- package/public/dist/assets/app-ByMfeXp-.js +0 -55
- package/public/dist/assets/app-DAJWqs33.css +0 -1
- package/public/dist/assets/bgtask-badge-Ch88fnhf.js +0 -1
- package/public/dist/assets/error-normalize-CL69Bl7z.js +0 -1
- package/public/dist/assets/fields-mlGpofqQ.js +0 -1
- package/public/dist/assets/iframe-renderer-Bj1JolO8.js +0 -142
- package/public/dist/assets/iframe-renderer-DWnvUSrx.js +0 -1
- package/public/dist/assets/jsx-runtime-NZYk81nU.js +0 -1
- package/public/dist/assets/manager-Bi_xw7TW.js +0 -5
- package/public/dist/assets/manager-BnQHAKk6.css +0 -1
- package/public/dist/assets/manager-shortcuts-BDOzd8zm.js +0 -1
- package/public/dist/assets/memory-Bk_cb3sw.js +0 -1
- package/public/dist/assets/message-history-D2vniu04.js +0 -4
- package/public/dist/assets/message-history-GAtF_wbZ.js +0 -1
- package/public/dist/assets/page-shell-BeGD0rRQ.js +0 -1
- package/public/dist/assets/react-dom-DyUOYpVL.js +0 -1
- package/public/dist/assets/settings-W4KG9ivu.js +0 -1
- package/public/dist/assets/settings-WVGhtneN.js +0 -1
- package/public/dist/assets/settings-client-CmwRsVd1.js +0 -1
- package/public/dist/assets/settings-core-CgKGnMA5.js +0 -1
- package/public/dist/assets/settings-types-D3cd6SWy.js +0 -1
- package/public/dist/assets/shared-CiNP20HB.js +0 -1
- package/public/dist/assets/skills-Co7Mr-qP.js +0 -1
- package/public/dist/assets/slash-commands-BEr-vmIr.js +0 -1
- package/public/dist/assets/ui-TUyJsMUf.js +0 -1
- package/public/manager/src/settings/pages/components/EngineSubsection.tsx +0 -25
package/dist/src/agent/spawn.js
CHANGED
|
@@ -7,7 +7,6 @@ import crypto from 'node:crypto';
|
|
|
7
7
|
import { join } from 'path';
|
|
8
8
|
import { spawn } from 'child_process';
|
|
9
9
|
import { createTextStreamReader, sliceWithoutSplittingSurrogate } from './stream-text.js';
|
|
10
|
-
import { userErrorText } from '../messaging/redact.js';
|
|
11
10
|
import { resolveWindowsLaunchSpec, launchArgv } from '../core/windows-launch-spec.js';
|
|
12
11
|
import { decideShellFallback } from '../core/windows-shell-fallback.js';
|
|
13
12
|
/** Cap on retained stderr text used for error classification. */
|
|
@@ -20,17 +19,17 @@ import { publish as ssePublish } from '../core/event-bus.js';
|
|
|
20
19
|
import { settleOnce } from '../orchestrator/request-registry.js';
|
|
21
20
|
import { settings, UPLOADS_DIR, detectCli, getProjectDirs } from '../core/config.js';
|
|
22
21
|
import { stripUndefined } from '../core/strip-undefined.js';
|
|
23
|
-
import {
|
|
22
|
+
import { getSession, insertMessage, getRecentMessages, listQueuedMessages, insertQueuedMessage, deleteQueuedMessage, migrateQueuedMessagesV1ToV2, getSessionBucket, clearSessionBucket, setSessionBucketSnapshot, getMaxMessageId, getSteerSalvageAfter, } from '../core/db.js';
|
|
24
23
|
import { buildTaskSnapshot } from '../memory/runtime.js';
|
|
25
24
|
import { getActiveChatSession, getRemoteBoundSessionId } from '../core/chat-sessions.js';
|
|
26
25
|
import { currentSessionScope } from '../core/session-context.js';
|
|
27
26
|
import { getSystemPrompt, regenerateB } from '../prompt/builder.js';
|
|
28
27
|
import { prependRemoteConversationContext } from '../prompt/conversation-context.js';
|
|
29
|
-
import { extractSessionId, extractFromEvent,
|
|
28
|
+
import { extractSessionId, extractFromEvent, extractOutputChunk, logEventSummary, flushClaudeBuffers, flushOpenCodeBuffers } from './events.js';
|
|
30
29
|
import { detectSmokeResponse } from './smoke-detector.js';
|
|
31
30
|
import { saveUpload as _saveUpload, buildMediaPrompt, buildMediaPromptMany } from '../../lib/upload.js';
|
|
32
31
|
import { resolveMainCli, consumePendingBootstrapPrompt, peekPendingBootstrapPrompt } from '../core/main-session.js';
|
|
33
|
-
import { getSessionOwnershipGeneration, isCurrentSessionOwner,
|
|
32
|
+
import { getSessionOwnershipGeneration, isCurrentSessionOwner, } from './session-persistence.js';
|
|
34
33
|
import { isCompactMarkerRow } from '../core/compact.js';
|
|
35
34
|
import { isRuntimeSettingsMutationInFlight, waitForRuntimeSettingsIdle } from '../core/runtime-settings-gate.js';
|
|
36
35
|
import { hasBlockingWorkers, hasPendingWorkerReplays, getActiveWorkers, clearAllWorkers, clearWorkersForScope, cancelWorker } from '../orchestrator/worker-registry.js';
|
|
@@ -38,50 +37,40 @@ import { sanitizeWorkerProgressTools } from '../orchestrator/worker-progress.js'
|
|
|
38
37
|
import { handleAgentExit, setSpawnAgent, setMainMetaHandler } from './lifecycle-handler.js';
|
|
39
38
|
import { buildServicePath } from '../core/runtime-path.js';
|
|
40
39
|
import { formatCliUnavailableMessage, detectCliBinary } from '../core/cli-detect.js';
|
|
40
|
+
import { claudeModelGateMessage } from '../cli/claude-default-model-boot.js';
|
|
41
41
|
import { LOCAL_SESSION_SCOPE_ACTIVATION, resolveExecutionBinding } from '../orchestrator/scope.js';
|
|
42
42
|
import { stripInterviewTracker } from '../orchestrator/sanitize.js';
|
|
43
|
-
import { beginLiveRun, appendLiveRunText, setLiveRunTraceId, clearLiveRun, replaceLiveRunTools, appendLiveRunTool
|
|
43
|
+
import { beginLiveRun, appendLiveRunText, setLiveRunTraceId, clearLiveRun, replaceLiveRunTools, appendLiveRunTool } from './live-run-state.js';
|
|
44
44
|
import { setSpawnRef as setMemorySpawnRef, } from './memory-flush-controller.js';
|
|
45
45
|
import { applyCliEnvDefaults, buildSessionResumeKey, ensureOpencodeAlwaysAllowPermissions, mergeEnvWindowsSafe } from './spawn-env.js';
|
|
46
|
-
import { buildPromptForArgs, shouldBuildHistoryBlock, withHistoryPrompt, withSteerContext, PROMPT_HISTORY_MAX_ROWS, PROMPT_HISTORY_MAX_CHARS,
|
|
46
|
+
import { buildPromptForArgs, shouldBuildHistoryBlock, withHistoryPrompt, withSteerContext, PROMPT_HISTORY_MAX_ROWS, PROMPT_HISTORY_MAX_CHARS, } from './prompt-context.js';
|
|
47
47
|
import { attachWatchdog, DEFAULT_WATCHDOG_ABSOLUTE_HARD_CAP_MS } from './watchdog.js';
|
|
48
48
|
import { buildOpencodeRuntimeSnapshot, buildOpencodeSpawnAudit, pushOpencodeRawEvent, resolveOpencodeBinary, } from './opencode-diagnostics.js';
|
|
49
|
-
import {
|
|
50
|
-
import { AcpRuntimeSession } from './runtime/acp/runtime-session.js';
|
|
51
|
-
import { MainReplacementOwnerMismatchError, replaceAcpMainTurn } from './runtime/replace-turn.js';
|
|
52
|
-
import { AcpReplacement } from './runtime/acp/replacement.js';
|
|
49
|
+
import { MainReplacementOwnerMismatchError } from './runtime/replace-turn.js';
|
|
53
50
|
import { beginSteerInput, cancelSteerInputs, cancelAllSteerInputs } from './steer-input-guard.js';
|
|
54
|
-
import { runNativeRuntime, NativeRunFailure } from './native-runtime-run.js';
|
|
55
51
|
import { startClaudeNativeRun } from './claude-runtime-run.js';
|
|
56
52
|
import { hasClaudeRuns, hasClaudeMainRuns, hasClaudeWorker, cancelClaudeWorker, cancelClaudeScope, cancelAllClaudeRuns } from './runtime/claude-run-controls.js';
|
|
57
|
-
import { syncLiveTools } from './events/helpers.js';
|
|
58
|
-
import { RuntimeProjection } from './runtime/projection.js';
|
|
59
|
-
import { recordRuntimeEvent } from './runtime/events.js';
|
|
60
|
-
import { CodexProjection } from './runtime/codex-projection.js';
|
|
61
|
-
import { PiProjection } from './runtime/pi-projection.js';
|
|
62
|
-
import { PiRawTrace } from './runtime/pi-raw-trace.js';
|
|
63
53
|
import { createPrintActivity, finishPrintActivity } from './runtime/print-activity.js';
|
|
64
54
|
import { isNativeAdapterImplemented, isNativeWorkerImplemented, isSwitchableNativeCli, resolveRuntimeTransport, runtimeSessionBucket } from './runtime/selection.js';
|
|
65
|
-
import { clearNativeStartFailure, nativeStartFailure, recordNativeStartFailure } from './runtime/start-failure.js';
|
|
66
55
|
import { asCliEventRecord, discriminate, fieldString } from '../types/cli-events.js';
|
|
67
56
|
import { isRemoteTarget } from '../messaging/types.js';
|
|
68
57
|
import { buildRemoteBindingKey } from '../messaging/session-key.js';
|
|
69
58
|
import { runPinFields, sameRunConversation } from '../messaging/run-pin.js';
|
|
70
59
|
import { isRetiredCliSelection, retiredRuntimeDiagnostic } from '../types/cli-engine.js';
|
|
71
60
|
import { runBeforeSpawnChecks } from '../core/policy-hooks.js';
|
|
72
|
-
import { appendTraceEvent,
|
|
61
|
+
import { appendTraceEvent, stampTraceTool, startTraceRun } from '../trace/store.js';
|
|
73
62
|
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';
|
|
74
63
|
import { detectAgyCapabilities } from './agy-capabilities.js';
|
|
75
64
|
import { buildAgyBootstrapEnvelope, resolveAgyPromptOrder, } from './agy-bootstrap.js';
|
|
76
65
|
import { startAgyTranscriptWatcher } from './agy-transcript-watcher.js';
|
|
77
|
-
import {
|
|
66
|
+
import { emitAgentTool, normalizeAssistantDisplayText, pushTrace, streamJsonMarksProgress } from './events/helpers.js';
|
|
78
67
|
import { captureKiroSessionIdAfterExit, finalizeKiroFullText, flushKiroStdoutContext, isKiroPlainTextCli, isKiroStaleSessionOutput, processKiroStdoutChunk, spawnWithKiroSnapshot, } from './kiro-runtime.js';
|
|
79
68
|
import { resolveCursorModelVariant } from './cursor-runtime.js';
|
|
80
|
-
import { bindPiExecutionCancel, normalizePiSettings, openPiRpc } from './pi-runtime.js';
|
|
81
69
|
import * as piExecutionControls from './pi-runtime.js';
|
|
82
|
-
import {
|
|
83
|
-
import {
|
|
84
|
-
import {
|
|
70
|
+
import { runNativeAcpBackend } from './spawn/backend-native-acp.js';
|
|
71
|
+
import { runCopilotBackend } from './spawn/backend-copilot.js';
|
|
72
|
+
import { runPiBackend } from './spawn/backend-pi.js';
|
|
73
|
+
import { runCodexAppBackend } from './spawn/backend-codex-app.js';
|
|
85
74
|
// ─── State ───────────────────────────────────────────
|
|
86
75
|
export const activeProcesses = new Map(); // agentId → child process
|
|
87
76
|
export function hasActiveAgent(agentId) {
|
|
@@ -181,7 +170,6 @@ import { buildSteerStartedEvent } from './spawn/steer-event.js';
|
|
|
181
170
|
import { armExitSettle, captureExitSettler, settleCapturedExit, settleExit, waitForExitSettled, } from './spawn/exit-settle.js';
|
|
182
171
|
import { releaseChildOutputAfterExit } from './spawn/exit-drain.js';
|
|
183
172
|
import { createNdjsonFramer, MAX_PENDING_LINE_CHARS } from './spawn/line-buffer.js';
|
|
184
|
-
import { appendBoundedFullText } from './events/fulltext-bound.js';
|
|
185
173
|
/** No provider turn was dispatched; preserve the captured cancellation receipt. */
|
|
186
174
|
function stoppedBeforeStart(reason, traceRunId) {
|
|
187
175
|
const stopCause = stopCauseFromKillReason(reason);
|
|
@@ -951,17 +939,10 @@ const warnedAgyCapabilityFallbacks = new Set();
|
|
|
951
939
|
// ─── Upload wrapper ──────────────────────────────────
|
|
952
940
|
export const saveUpload = (buffer, originalName, options) => _saveUpload(UPLOADS_DIR, Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer), originalName, options);
|
|
953
941
|
export { buildMediaPrompt, buildMediaPromptMany };
|
|
954
|
-
// ─── Spawn Agent ─────────────────────────────────────
|
|
955
|
-
import { AcpClient } from '../cli/acp-client.js';
|
|
956
|
-
import { CodexAppClient, CodexSteerError, isRecoverableResumeError } from './codex-app-client.js';
|
|
957
|
-
import { acquireCodexAppRuntime, acquirePiRuntime, acquireCursorRuntime, acquireGrokRuntime, } from './runtime-pool.js';
|
|
958
|
-
import { grokMainOptions } from './runtime/grok-main.js';
|
|
959
|
-
import { acquireCodexAppLane, CodexHostGenerationStaleError, prepareCodexAppHost, } from './codex-host-pool.js';
|
|
960
|
-
import { loadCatalogEfforts, resolveCatalogPath, validateModelEffort } from './codex-app-catalog.js';
|
|
961
|
-
import { listenCodexAppTurnAdapter, applyCodexAppTextEvent, } from './codex-app-events.js';
|
|
962
942
|
import { canGuardedAgyResume, resolveAgyNativeResume, shouldEmitHeartbeat, shouldResumeBucketSession } from './spawn/resume.js';
|
|
963
943
|
export { canGuardedAgyResume, resolveAgyNativeResume, shouldEmitHeartbeat, shouldResumeBucketSession };
|
|
964
944
|
import { createQueueController, FALLBACK_MAX_RETRIES } from './spawn/queue.js';
|
|
945
|
+
import { stripSkillMentionBlock } from '../core/skill-mentions.js';
|
|
965
946
|
function cleanupEmployeeTmpDir(cwd, workingDir, label) {
|
|
966
947
|
if (cwd !== workingDir) {
|
|
967
948
|
try {
|
|
@@ -1499,6 +1480,22 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1499
1480
|
cleanupEmployeeTmpDir(spawnCwd, settings["workingDir"], agentLabel);
|
|
1500
1481
|
return { child: null, promise: resultPromise };
|
|
1501
1482
|
}
|
|
1483
|
+
// ─── Claude model preflight: a model the installed CLI is known not to accept fails
|
|
1484
|
+
// here, for print and native, fresh and resume, instead of as a raw API 400. ───
|
|
1485
|
+
const claudeModelRefusal = cli === 'claude' ? claudeModelGateMessage(detected.path, runtimeModel) : null;
|
|
1486
|
+
if (claudeModelRefusal) {
|
|
1487
|
+
console.error(`[jaw:${agentLabel}] ${claudeModelRefusal}`);
|
|
1488
|
+
if (mainManaged)
|
|
1489
|
+
clearLiveRun(liveScope);
|
|
1490
|
+
broadcast('agent_done', { ...runPin, text: `❌ ${claudeModelRefusal}`, error: true, origin, ...empTag }, isEmployee ? 'internal' : 'public');
|
|
1491
|
+
resolve({ text: '', code: 1 });
|
|
1492
|
+
if (mainManaged) {
|
|
1493
|
+
releaseMainRun(scopeKey, null, ownerGeneration);
|
|
1494
|
+
void processQueue(scopeKey);
|
|
1495
|
+
}
|
|
1496
|
+
cleanupEmployeeTmpDir(spawnCwd, settings["workingDir"], agentLabel);
|
|
1497
|
+
return { child: null, promise: resultPromise };
|
|
1498
|
+
}
|
|
1502
1499
|
if (cli === 'copilot') {
|
|
1503
1500
|
console.log(`[jaw:${agentLabel}] Spawning: copilot --acp --model ${model} [${permissions}]`);
|
|
1504
1501
|
}
|
|
@@ -1600,1833 +1597,21 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
1600
1597
|
}
|
|
1601
1598
|
}
|
|
1602
1599
|
// ─── Native ACP main: protocol ownership is separate from application settlement. ───
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
try {
|
|
1610
|
-
traceRunId = startTraceRun({ cli, model: runtimeModel, workingDir: nativeCwd, agentLabel, audience: traceAudience, sessionId: chatSessionId, scopeKey });
|
|
1611
|
-
}
|
|
1612
|
-
catch {
|
|
1613
|
-
traceRunId = createTraceId();
|
|
1614
|
-
console.warn(`[runtime:${cli}] trace creation unavailable`);
|
|
1615
|
-
}
|
|
1616
|
-
const identity = Object.freeze({ runId: traceRunId, sessionId: chatSessionId, scope: scopeKey,
|
|
1617
|
-
turnId: traceRunId, audience: traceAudience,
|
|
1618
|
-
...(opts.runtimeParentItemId ? { parentItemId: opts.runtimeParentItemId } : {}) });
|
|
1619
|
-
const ctx = { fullText: '', traceLog: [], toolLog: [], seenToolKeys: new Set(),
|
|
1620
|
-
hasClaudeStreamEvents: false, sessionId: null, cost: null, turns: null, duration: null, tokens: null,
|
|
1621
|
-
stderrBuf: '', runStartedAt: Date.now(), origin,
|
|
1622
|
-
...(opts.requestId ? { requestId: opts.requestId } : {}),
|
|
1623
|
-
liveScope: effectiveLiveScope, parentLiveScope: parentLiveScopeForChild, traceRunId, traceAudience };
|
|
1624
|
-
const toolMirror = new Map();
|
|
1625
|
-
const mirrorLimit = 160;
|
|
1626
|
-
let facade = null, ownedLease = null;
|
|
1627
|
-
let failedStart = null;
|
|
1628
|
-
let nativeStarted = false, runtimeEnded = false, finalizeFailed = false, finalized = false;
|
|
1629
|
-
let stopReason = null, queueRequested = false;
|
|
1630
|
-
let capturedExit;
|
|
1631
|
-
let selectedResult;
|
|
1632
|
-
const ownsRun = () => !finalized && activeMainProcesses.get(scopeKey) === capturedRun
|
|
1633
|
-
&& isCurrentSessionOwner(persistenceOwner, scopeKey);
|
|
1634
|
-
const cursorTarget = opts.target ? { ...opts.target } : undefined;
|
|
1635
|
-
let acceptedContext = { messages: [], omitted: false };
|
|
1636
|
-
const prepareReplacement = (instruction, partialText) => {
|
|
1637
|
-
if (!ownsRun())
|
|
1638
|
-
throw new Error('cursor_acp_owner_lost');
|
|
1639
|
-
return { text: buildCursorReplacementPrompt({
|
|
1640
|
-
instruction: prependRemoteConversationContext(instruction, cursorTarget),
|
|
1641
|
-
originalRequest: promptForSnapshot, accepted: acceptedContext, partialText, sysPrompt,
|
|
1642
|
-
}) };
|
|
1643
|
-
};
|
|
1644
|
-
const cursorReplaceHook = (instruction, commitInput) => replaceAcpMainTurn(facade, instruction, () => {
|
|
1645
|
-
if (!ownsRun())
|
|
1646
|
-
throw new Error('cursor_acp_owner_lost');
|
|
1647
|
-
const next = appendCursorAcceptedInstruction(acceptedContext, instruction);
|
|
1648
|
-
const result = commitInput();
|
|
1649
|
-
if (result !== null && (typeof result === 'object' || typeof result === 'function')
|
|
1650
|
-
&& typeof result.then === 'function') {
|
|
1651
|
-
void Promise.resolve(result).catch(() => undefined);
|
|
1652
|
-
throw new Error('cursor_acp_async_input_commit');
|
|
1653
|
-
}
|
|
1654
|
-
acceptedContext = next;
|
|
1655
|
-
});
|
|
1656
|
-
const resultFor = (outcome) => ({
|
|
1657
|
-
text: outcome.finalText?.trim() ?? '', code: outcome.status === 'done' ? 0 : outcome.status === 'stopped' ? 130 : 1,
|
|
1658
|
-
runtimeOutcome: outcome, traceRunId,
|
|
1659
|
-
});
|
|
1660
|
-
// A runtime that never acquired a lease has no facade to ask, so the
|
|
1661
|
-
// substring heuristic below could only ever produce the generic sentence
|
|
1662
|
-
// for exactly the failures that need naming. The recorded start code is
|
|
1663
|
-
// provider-independent by construction, so it decides the wording and is
|
|
1664
|
-
// shown alongside it (#658).
|
|
1665
|
-
const diagnostic = () => {
|
|
1666
|
-
const code = nativeStarted ? undefined : nativeStartFailure(cli)?.code;
|
|
1667
|
-
const named = code ? ` (${code})` : '';
|
|
1668
|
-
if (grok)
|
|
1669
|
-
return `Grok native runtime failed.${named} Check the native model, effort and existing CLI login.`;
|
|
1670
|
-
const configFault = code ? code.startsWith('acp_config_') : facade?.lastError?.includes('config');
|
|
1671
|
-
return configFault
|
|
1672
|
-
? `Cursor native model or effort is unsupported.${named} Choose an advertised model/effort; Composer models may require an unset effort.`
|
|
1673
|
-
: `Cursor native runtime failed.${named} Check the native model, effort and existing CLI login.`;
|
|
1674
|
-
};
|
|
1675
|
-
const startFailedRuntime = () => {
|
|
1676
|
-
if (!failedStart) {
|
|
1677
|
-
failedStart = new RuntimeProjection(identity);
|
|
1678
|
-
failedStart.start(cli);
|
|
1679
|
-
}
|
|
1680
|
-
return failedStart;
|
|
1681
|
-
};
|
|
1682
|
-
const closeFailedTrace = (outcome) => {
|
|
1683
|
-
try {
|
|
1684
|
-
finalizeTraceRun(traceRunId, outcome.status === 'stopped' ? 'interrupted' : outcome.status, outcome.status === 'error' ? diagnostic() : null, { onlyIfRunning: true });
|
|
1685
|
-
}
|
|
1686
|
-
catch {
|
|
1687
|
-
console.warn(`[runtime:${cli}] failure trace finalization unavailable`);
|
|
1688
|
-
}
|
|
1689
|
-
};
|
|
1690
|
-
const endRuntime = (end) => {
|
|
1691
|
-
if (runtimeEnded)
|
|
1692
|
-
return;
|
|
1693
|
-
runtimeEnded = true;
|
|
1694
|
-
if (facade?.claimTurnOutcome(traceRunId)) {
|
|
1695
|
-
if (!facade.finalizeTurn(traceRunId, end))
|
|
1696
|
-
finalizeFailed = true;
|
|
1697
|
-
}
|
|
1698
|
-
else if (!nativeStarted) {
|
|
1699
|
-
startFailedRuntime().close(end);
|
|
1700
|
-
}
|
|
1701
|
-
else {
|
|
1702
|
-
finalizeFailed = true;
|
|
1703
|
-
console.warn(`[runtime:${cli}] missing owned finalizer`);
|
|
1704
|
-
}
|
|
1705
|
-
};
|
|
1706
|
-
const failRuntime = (outcome) => {
|
|
1707
|
-
ctx.stallWatchdog?.stop();
|
|
1708
|
-
const selected = ctx.runtimeTerminalAttempted && ctx.runtimeOutcome ? ctx.runtimeOutcome : {
|
|
1709
|
-
status: stopReason || ctx.stallReason ? 'stopped' : 'error',
|
|
1710
|
-
finalText: null, partialText: outcome.partialText,
|
|
1711
|
-
};
|
|
1712
|
-
const stopCause = selected.status === 'stopped' ? stopCauseFromKillReason(stopReason) : undefined;
|
|
1713
|
-
handoffRuntimeOutcome(ctx, selected);
|
|
1714
|
-
try {
|
|
1715
|
-
// Admit the captured run before compatibility consumers retire it.
|
|
1716
|
-
if (!nativeStarted && !runtimeEnded)
|
|
1717
|
-
startFailedRuntime();
|
|
1718
|
-
if (!ctx.runtimeTerminalAttempted) {
|
|
1719
|
-
ctx.runtimeTerminalAttempted = true;
|
|
1720
|
-
broadcast('agent_done', { ...runPin, traceRunId, cli,
|
|
1721
|
-
text: selected.status === 'stopped' ? '' : `❌ ${diagnostic()}`, error: true,
|
|
1722
|
-
runtimeStatus: selected.status, runtimeFinality: selected.finalText === null ? 'absent' : 'present',
|
|
1723
|
-
...(stopCause ? { stopCause } : {}),
|
|
1724
|
-
}, traceAudience);
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
|
-
finally {
|
|
1728
|
-
try {
|
|
1729
|
-
endRuntime({ kind: 'turn-end', status: selected.status, finalText: selected.finalText,
|
|
1730
|
-
...(selected.status === 'error' ? { error: diagnostic() } : {}) });
|
|
1731
|
-
}
|
|
1732
|
-
finally {
|
|
1733
|
-
closeFailedTrace(selected);
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
return selectedResult ?? { ...resultFor(selected), ...(stopCause ? { stopCause } : {}) };
|
|
1737
|
-
};
|
|
1738
|
-
let nativeRun;
|
|
1739
|
-
const cancelHook = (reason) => {
|
|
1740
|
-
stopReason ??= reason;
|
|
1741
|
-
if (isLifecycleExitSettleReason(reason)) {
|
|
1742
|
-
armExitSettle(scopeKey);
|
|
1743
|
-
capturedExit ??= captureExitSettler(scopeKey);
|
|
1744
|
-
}
|
|
1745
|
-
nativeRun.cancel(reason);
|
|
1746
|
-
};
|
|
1747
|
-
const grokReplaceHook = async (text, commitInput) => {
|
|
1748
|
-
if (!ownsRun())
|
|
1749
|
-
return { kind: 'race', reason: 'native-owner-lost' };
|
|
1750
|
-
const result = await replaceAcpMainTurn(facade, text, commitInput);
|
|
1751
|
-
return result.kind === 'unavailable' && !ownsRun() ? { kind: 'race', reason: 'native-owner-lost' } : result;
|
|
1752
|
-
};
|
|
1753
|
-
const replaceHook = grok ? grokReplaceHook : cursorReplaceHook;
|
|
1754
|
-
if (grok)
|
|
1755
|
-
capturedRun.replaceTurn = replaceHook;
|
|
1756
|
-
capturedRun.starting = true;
|
|
1757
|
-
nativeRun = runNativeRuntime({
|
|
1758
|
-
turnId: traceRunId, prompt: { text: promptForArgs }, isCurrent: ownsRun,
|
|
1759
|
-
acquire: async (signal) => {
|
|
1760
|
-
const lease = await acquireRuntime({
|
|
1761
|
-
key: { scopeKey, cwd: nativeCwd, model: runtimeModel === 'default' ? '' : runtimeModel, effort, permissions },
|
|
1762
|
-
binary: detected.path || (grok ? 'grok' : 'cursor-agent'), env: spawnEnv, promptTimeoutMs: resolvedAgyPrintTimeoutMs,
|
|
1763
|
-
persistenceOwner, isCurrentOwner: token => isCurrentSessionOwner(token, scopeKey), canAcquire: ownsRun,
|
|
1764
|
-
storedSessionId: resumeSessionId,
|
|
1765
|
-
forceNew: forceNew || Boolean(slackToolGrant),
|
|
1766
|
-
...(slackToolGrant ? { lifetime: 'request' } : {}),
|
|
1767
|
-
signal,
|
|
1768
|
-
});
|
|
1769
|
-
facade = new AcpRuntimeSession(lease.session, { provider: cli, deferTurnEnd: true,
|
|
1770
|
-
...(grok ? grokMainOptions : { createReplacement: io => new AcpReplacement(io), prepareReplacement }),
|
|
1771
|
-
getTurnContext: () => ({ ...identity, isCurrent: ownsRun }),
|
|
1772
|
-
capabilities: { transport: 'native', steer: 'cancel-reprompt', resume: lease.session.agentCapabilities['loadSession'] === true,
|
|
1773
|
-
tools: true, toolOutput: true, approvals: true, questions: false, images: false, subagents: false },
|
|
1774
|
-
record: (context, body) => {
|
|
1775
|
-
if (body.kind === 'turn-start')
|
|
1776
|
-
nativeStarted = true;
|
|
1777
|
-
return recordRuntimeEvent(context, body);
|
|
1778
|
-
},
|
|
1779
|
-
});
|
|
1780
|
-
ownedLease = { child: lease.session.child, session: facade,
|
|
1781
|
-
release: () => lease.release(), retire: reason => lease.retire(reason) };
|
|
1782
|
-
return ownedLease;
|
|
1783
|
-
},
|
|
1784
|
-
ready: lease => {
|
|
1785
|
-
if (!ownsRun())
|
|
1786
|
-
throw new Error('native_run_owner_lost');
|
|
1787
|
-
capturedRun.process = lease.child;
|
|
1788
|
-
// This start reached its lease, so the previous start failure is spent.
|
|
1789
|
-
clearNativeStartFailure(cli);
|
|
1790
|
-
if (!opts._skipInsert)
|
|
1791
|
-
insertMessage.run('user', prompt, cli, runtimeModel, nativeCwd, chatSessionId);
|
|
1792
|
-
capturedRun.starting = false;
|
|
1793
|
-
ctx.sessionId = lease.session.nativeSessionId || null;
|
|
1794
|
-
if (capturedRun.cancelPending === cancelHook)
|
|
1795
|
-
delete capturedRun.cancelPending;
|
|
1796
|
-
capturedRun.cancelTurn = cancelHook;
|
|
1797
|
-
capturedRun.replaceTurn = replaceHook;
|
|
1798
|
-
beginLiveRun(liveScope, cli);
|
|
1799
|
-
setLiveRunTraceId(liveScope, traceRunId);
|
|
1800
|
-
const activityIdentity = { runId: traceRunId, sessionId: chatSessionId,
|
|
1801
|
-
scope: scopeKey, origin, ...(opts.requestId ? { requestId: opts.requestId } : {}) };
|
|
1802
|
-
const onIo = () => {
|
|
1803
|
-
if (!ownsRun())
|
|
1804
|
-
return;
|
|
1805
|
-
try {
|
|
1806
|
-
opts.lifecycle?.onActivity?.('native-runtime', activityIdentity);
|
|
1807
|
-
}
|
|
1808
|
-
catch {
|
|
1809
|
-
console.warn(`[runtime:${cli}] liveness observer failed`);
|
|
1810
|
-
}
|
|
1811
|
-
};
|
|
1812
|
-
const dispose = () => {
|
|
1813
|
-
ctx.stallWatchdog?.stop();
|
|
1814
|
-
if (capturedRun.replaceTurn === replaceHook)
|
|
1815
|
-
delete capturedRun.replaceTurn;
|
|
1816
|
-
lease.child.stdout?.off('data', onIo);
|
|
1817
|
-
lease.child.stderr?.off('data', onIo);
|
|
1818
|
-
};
|
|
1819
|
-
try {
|
|
1820
|
-
lease.child.stdout?.on('data', onIo);
|
|
1821
|
-
lease.child.stderr?.on('data', onIo);
|
|
1822
|
-
ctx.stallWatchdog = attachWatchdog(lease.child, agentLabel, reason => {
|
|
1823
|
-
ctx.stallReason = reason;
|
|
1824
|
-
nativeRun.cancel(reason);
|
|
1825
|
-
});
|
|
1826
|
-
broadcast('agent_status', { running: true, status: 'running', agentId: agentLabel, cli,
|
|
1827
|
-
scope: scopeKey, sessionId: chatSessionId, traceRunId,
|
|
1828
|
-
...(opts.requestId ? { requestId: opts.requestId } : {}) }, traceAudience);
|
|
1829
|
-
return dispose;
|
|
1830
|
-
}
|
|
1831
|
-
catch (error) {
|
|
1832
|
-
dispose();
|
|
1833
|
-
throw error;
|
|
1834
|
-
}
|
|
1835
|
-
},
|
|
1836
|
-
event: (event) => {
|
|
1837
|
-
if (event.kind === 'usage') {
|
|
1838
|
-
ctx.tokens = { ...(event.inputTokens === undefined ? {} : { input_tokens: event.inputTokens }),
|
|
1839
|
-
...(event.outputTokens === undefined ? {} : { output_tokens: event.outputTokens }),
|
|
1840
|
-
...(event.cachedTokens === undefined ? {} : { cached_input_tokens: event.cachedTokens }) };
|
|
1841
|
-
}
|
|
1842
|
-
if (event.kind !== 'tool')
|
|
1843
|
-
return;
|
|
1844
|
-
let tool = toolMirror.get(event.itemId);
|
|
1845
|
-
if (!tool && toolMirror.size >= mirrorLimit)
|
|
1846
|
-
return;
|
|
1847
|
-
const detail = [event.input, event.output, event.detail].filter(value => value !== undefined && value !== '').join('\n');
|
|
1848
|
-
if (!tool) {
|
|
1849
|
-
tool = { icon: '🔧', label: event.name, toolType: 'tool', status: event.status, detail,
|
|
1850
|
-
stepRef: `runtime:${traceRunId}:${event.itemId}` };
|
|
1851
|
-
stampTraceTool(tool, ctx, 'tool');
|
|
1852
|
-
toolMirror.set(event.itemId, tool);
|
|
1853
|
-
}
|
|
1854
|
-
else {
|
|
1855
|
-
Object.assign(tool, { label: event.name, status: event.status, detail });
|
|
1856
|
-
updateTraceToolRow(tool);
|
|
1857
|
-
}
|
|
1858
|
-
ctx.toolLog = [...toolMirror.values()];
|
|
1859
|
-
syncLiveTools(ctx);
|
|
1860
|
-
},
|
|
1861
|
-
settle: async (lease, outcome, problem) => {
|
|
1862
|
-
// Immediate Stop can settle without entering acquire/ready/send.
|
|
1863
|
-
if (!nativeStarted && !runtimeEnded)
|
|
1864
|
-
startFailedRuntime();
|
|
1865
|
-
ctx.stallWatchdog?.stop();
|
|
1866
|
-
ctx.fullText = outcome.partialText;
|
|
1867
|
-
if (problem)
|
|
1868
|
-
ctx.stderrBuf = problem;
|
|
1869
|
-
if (lease)
|
|
1870
|
-
ctx.sessionId = lease.session.nativeSessionId || null;
|
|
1871
|
-
const recordedReason = consumeKillReason(lease?.child.pid);
|
|
1872
|
-
const killReason = stopReason || recordedReason;
|
|
1873
|
-
const wasKilled = Boolean(killReason), wasSteer = isLifecycleSteerReason(killReason);
|
|
1874
|
-
const code = outcome.status === 'done' ? 0 : outcome.status === 'stopped' ? 130 : 1;
|
|
1875
|
-
handoffRuntimeOutcome(ctx, outcome);
|
|
1876
|
-
try {
|
|
1877
|
-
opts.lifecycle?.onExit?.(code);
|
|
1878
|
-
}
|
|
1879
|
-
catch {
|
|
1880
|
-
console.warn(`[runtime:${cli}] exit observer failed`);
|
|
1881
|
-
}
|
|
1882
|
-
await handleAgentExit({ onRuntimeEnd: endRuntime,
|
|
1883
|
-
ctx, code, cli, model: runtimeModel, effectiveProvider, agentLabel, mainManaged, origin,
|
|
1884
|
-
killReason,
|
|
1885
|
-
resumeKey, prompt, opts, cfg: { ...cfg, effort }, ownerGeneration, persistenceOwner, forceNew, empSid,
|
|
1886
|
-
isResume, wasKilled, wasSteer, smokeResult: detectSmokeResponse(outcome.finalText ?? '', ctx.toolLog, code, cli),
|
|
1887
|
-
effortDefault: effort, costLine: '', resolve: value => { selectedResult ??= value; },
|
|
1888
|
-
activeProcesses, scopeKey, runtimeTransport, scopedBucket: currentBucket, chatSessionId,
|
|
1889
|
-
childProcess: lease?.child ?? null, releaseMainRun,
|
|
1890
|
-
retryState: queueCtrl.retryStateForScope(scopeKey), fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
|
|
1891
|
-
fallbackMaxRetries: FALLBACK_MAX_RETRIES, processQueue: () => { queueRequested = true; },
|
|
1892
|
-
});
|
|
1893
|
-
if (finalizeFailed && lease)
|
|
1894
|
-
await lease.retire(new Error('native_runtime_finalization_failed'));
|
|
1895
|
-
return selectedResult ?? resultFor(ctx.runtimeOutcome ?? outcome);
|
|
1896
|
-
},
|
|
1897
|
-
failed: (error, lease, outcome) => {
|
|
1898
|
-
// A null lease means acquire never returned: the runtime failed to
|
|
1899
|
-
// start, which is the only class this diagnostic answers (#658).
|
|
1900
|
-
// Teardown faults after a claimed answer are a different failure.
|
|
1901
|
-
if (!lease)
|
|
1902
|
-
recordNativeStartFailure(cli, error);
|
|
1903
|
-
return failRuntime(outcome);
|
|
1904
|
-
},
|
|
1905
|
-
finalized: () => {
|
|
1906
|
-
finalized = true;
|
|
1907
|
-
try {
|
|
1908
|
-
if (capturedRun.cancelPending === cancelHook)
|
|
1909
|
-
delete capturedRun.cancelPending;
|
|
1910
|
-
if (capturedRun.cancelTurn === cancelHook)
|
|
1911
|
-
delete capturedRun.cancelTurn;
|
|
1912
|
-
if (capturedRun.replaceTurn === replaceHook)
|
|
1913
|
-
delete capturedRun.replaceTurn;
|
|
1914
|
-
if (activeMainProcesses.get(scopeKey) === capturedRun) {
|
|
1915
|
-
const child = ownedLease?.child ?? null;
|
|
1916
|
-
if (releaseMainRun(scopeKey, child, ownerGeneration)) {
|
|
1917
|
-
if (getLiveRun(liveScope).traceRunId === traceRunId)
|
|
1918
|
-
clearLiveRun(liveScope);
|
|
1919
|
-
broadcast('agent_status', { running: false, agentId: agentLabel, cli, scope: scopeKey,
|
|
1920
|
-
sessionId: chatSessionId, traceRunId }, traceAudience);
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
finally {
|
|
1925
|
-
// Projection/listener/cleanup failure must not leave our durable
|
|
1926
|
-
// row running or rewrite a lifecycle that already selected a result.
|
|
1927
|
-
if (ctx.runtimeOutcome)
|
|
1928
|
-
closeFailedTrace(ctx.runtimeOutcome);
|
|
1929
|
-
settleCapturedExit(scopeKey, capturedExit);
|
|
1930
|
-
// Exceptional settlement can release the slot before lifecycle requests its normal wake.
|
|
1931
|
-
if (queueRequested || !activeMainProcesses.has(scopeKey))
|
|
1932
|
-
void processQueue(scopeKey);
|
|
1933
|
-
}
|
|
1934
|
-
},
|
|
1935
|
-
});
|
|
1936
|
-
capturedRun.cancelPending = cancelHook;
|
|
1937
|
-
let resolved = false;
|
|
1938
|
-
const resolveOnce = (value) => { if (!resolved) {
|
|
1939
|
-
resolved = true;
|
|
1940
|
-
resolve(value);
|
|
1941
|
-
} };
|
|
1942
|
-
nativeRun.done.then(resolveOnce, error => {
|
|
1943
|
-
const prior = ctx.runtimeTerminalAttempted && ctx.runtimeOutcome ? ctx.runtimeOutcome
|
|
1944
|
-
: error instanceof NativeRunFailure ? { ...error.outcome,
|
|
1945
|
-
status: stopReason || ctx.stallReason || error.outcome.status === 'stopped' ? 'stopped' : 'error', finalText: null }
|
|
1946
|
-
: { status: 'error', finalText: null, partialText: ctx.fullText };
|
|
1947
|
-
resolveOnce(selectedResult ?? resultFor(prior));
|
|
1948
|
-
});
|
|
1949
|
-
return { child: null, promise: resultPromise };
|
|
1950
|
-
}
|
|
1600
|
+
// Backend branches live in ./spawn/backend-*.ts; they read these values, captured here,
|
|
1601
|
+
// and never write back (no spawnAgent binding they read is reassigned after this point).
|
|
1602
|
+
const backendLocals = { agentLabel, bucketSessionId, cfg, chatSessionId, cleanupPiEmployee, cli, codexMultiplexMain, currentBucket, detected, effectiveLiveScope, effectiveProvider, effort, empSid, empTag, forceNew, historyBlock, isEmployee, isResume, liveScope, mainManaged, mainRun, model, opts, origin, ownerGeneration, parentLiveScopeForChild, permissions, persistenceOwner, prompt, promptForArgs, promptForSnapshot, resolve: (value) => resolve(value), resolvedAgyPrintTimeoutMs, resultPromise, resumeKey, resumeSessionId, runPin, runtimeModel, runtimeTransport, scopeKey, slackToolGrant, spawnCwd, spawnEnv, sysPrompt, traceAudience };
|
|
1603
|
+
const backendHost = { CODEX_APP_ACQUIRE_RETRY_BACKOFF_MAX_MS, DEFAULT_CODEX_APP_ACQUIRE_WAIT_MS, DEFAULT_CODEX_APP_TURN_ABS_MS, DEFAULT_CODEX_APP_TURN_IDLE_MS, STDERR_BUF_CAP, activeMainProcesses, activeProcesses, appendParentLiveRunTool, broadcastAgentOutput, buildHistoryBlock, cancelOwnedPiProcess, cleanupEmployeeTmpDir, clearMainLiveRunOnStop, configuredPositiveMs, consumeKillReason, piProfileFingerprintKey, processQueue, queueCtrl, registerActiveProcess, releaseMainRun, stoppedBeforeStart };
|
|
1604
|
+
if (runtimeTransport === 'native' && (cli === 'cursor' || cli === 'grok') && mainManaged)
|
|
1605
|
+
return runNativeAcpBackend(backendLocals, backendHost);
|
|
1951
1606
|
// ─── Copilot ACP branch ──────────────────────
|
|
1952
|
-
if (cli === 'copilot')
|
|
1953
|
-
|
|
1954
|
-
try {
|
|
1955
|
-
const cfgPath = join(os.homedir(), '.copilot', 'config.json');
|
|
1956
|
-
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
1957
|
-
let changed = false;
|
|
1958
|
-
// Sync model
|
|
1959
|
-
if (model && model !== 'default') {
|
|
1960
|
-
if (cfg.model !== model) {
|
|
1961
|
-
cfg.model = model;
|
|
1962
|
-
changed = true;
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
// Sync effort
|
|
1966
|
-
if (effort) {
|
|
1967
|
-
if (cfg.reasoning_effort !== effort) {
|
|
1968
|
-
cfg.reasoning_effort = effort;
|
|
1969
|
-
changed = true;
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
else if (cfg.reasoning_effort) {
|
|
1973
|
-
delete cfg.reasoning_effort;
|
|
1974
|
-
changed = true;
|
|
1975
|
-
}
|
|
1976
|
-
if (changed)
|
|
1977
|
-
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n');
|
|
1978
|
-
}
|
|
1979
|
-
catch (e) {
|
|
1980
|
-
console.warn('[jaw:copilot] config.json sync failed:', e.message);
|
|
1981
|
-
}
|
|
1982
|
-
const acp = new AcpClient({ model, workDir: spawnCwd, permissions, env: spawnEnv });
|
|
1983
|
-
acp.spawn();
|
|
1984
|
-
const child = acp.proc;
|
|
1985
|
-
if (!child) {
|
|
1986
|
-
throw new Error('Copilot ACP process was not created');
|
|
1987
|
-
}
|
|
1988
|
-
if (mainManaged)
|
|
1989
|
-
mainRun.process = child;
|
|
1990
|
-
else
|
|
1991
|
-
registerActiveProcess(agentLabel, child);
|
|
1992
|
-
if (!opts.internal)
|
|
1993
|
-
broadcast('agent_status', { running: true, agentId: agentLabel, cli, ...empTag });
|
|
1994
|
-
if (mainManaged && !opts.internal)
|
|
1995
|
-
beginLiveRun(liveScope, cli);
|
|
1996
|
-
// ─── DIFF-C: ACP error guard — prevent uncaught EventEmitter crash ───
|
|
1997
|
-
let acpSettled = false; // guard: error→exit can fire sequentially
|
|
1998
|
-
acp.on('error', (err) => {
|
|
1999
|
-
if (acpSettled)
|
|
2000
|
-
return;
|
|
2001
|
-
acpSettled = true;
|
|
2002
|
-
cleanupEmployeeTmpDir(spawnCwd, settings["workingDir"], agentLabel);
|
|
2003
|
-
opts.lifecycle?.onExit?.(null);
|
|
2004
|
-
const msg = `Copilot ACP spawn failed: ${err.message}`;
|
|
2005
|
-
console.error(`[acp:error] ${msg}`);
|
|
2006
|
-
if (mainManaged) {
|
|
2007
|
-
releaseMainRun(scopeKey, child, ownerGeneration);
|
|
2008
|
-
clearLiveRun(liveScope);
|
|
2009
|
-
broadcast('agent_status', { running: false, agentId: agentLabel });
|
|
2010
|
-
}
|
|
2011
|
-
else {
|
|
2012
|
-
activeProcesses.delete(agentLabel);
|
|
2013
|
-
}
|
|
2014
|
-
broadcast('agent_done', { ...runPin, text: `❌ ${msg}`, error: true, origin, ...empTag }, isEmployee ? 'internal' : 'public');
|
|
2015
|
-
finishPrintActivity(ctx, { kind: 'turn-end', status: 'error', finalText: null, error: msg });
|
|
2016
|
-
resolve({ text: '', code: 1 });
|
|
2017
|
-
if (mainManaged)
|
|
2018
|
-
void processQueue(scopeKey);
|
|
2019
|
-
});
|
|
2020
|
-
if (mainManaged && !opts.internal && !opts._skipInsert) {
|
|
2021
|
-
insertMessage.run('user', prompt, cli, model, settings["workingDir"] || null, chatSessionId);
|
|
2022
|
-
}
|
|
2023
|
-
if (!opts.internal)
|
|
2024
|
-
broadcast('agent_status', { status: 'running', cli, agentId: agentLabel, ...empTag }, traceAudience);
|
|
2025
|
-
if (mainManaged && !opts.internal)
|
|
2026
|
-
beginLiveRun(liveScope, cli);
|
|
2027
|
-
const traceRunId = startTraceRun({ cli, model, workingDir: settings["workingDir"] || null, agentLabel, audience: traceAudience, sessionId: chatSessionId, scopeKey });
|
|
2028
|
-
if (mainManaged && !opts.internal)
|
|
2029
|
-
setLiveRunTraceId(liveScope, traceRunId);
|
|
2030
|
-
const ctx = {
|
|
2031
|
-
fullText: '', traceLog: [], toolLog: [], seenToolKeys: new Set(),
|
|
2032
|
-
hasClaudeStreamEvents: false, sessionId: null, cost: null,
|
|
2033
|
-
turns: null, duration: null, tokens: null, stderrBuf: '',
|
|
2034
|
-
thinkingBuf: '',
|
|
2035
|
-
runStartedAt: Date.now(),
|
|
2036
|
-
...(opts.requestId ? { requestId: opts.requestId } : {}),
|
|
2037
|
-
...(origin ? { origin } : {}),
|
|
2038
|
-
liveScope: effectiveLiveScope,
|
|
2039
|
-
parentLiveScope: parentLiveScopeForChild,
|
|
2040
|
-
traceRunId,
|
|
2041
|
-
traceAudience,
|
|
2042
|
-
activityIdentity: { sessionId: chatSessionId, scope: scopeKey },
|
|
2043
|
-
};
|
|
2044
|
-
ctx.printActivity = createPrintActivity({ runId: traceRunId, sessionId: chatSessionId,
|
|
2045
|
-
scope: scopeKey, turnId: traceRunId, audience: traceAudience }, cli);
|
|
2046
|
-
// Flush accumulated 💭 thinking buffer as a single merged event
|
|
2047
|
-
function flushThinking() {
|
|
2048
|
-
if (!ctx.thinkingBuf)
|
|
2049
|
-
return;
|
|
2050
|
-
const merged = ctx.thinkingBuf.trim();
|
|
2051
|
-
if (merged) {
|
|
2052
|
-
const singleLine = merged.replace(/\s+/g, ' ').trim();
|
|
2053
|
-
const label = singleLine.length > 120 ? `${singleLine.slice(0, 119)}…` : singleLine;
|
|
2054
|
-
console.log(` 💭 ${label}`);
|
|
2055
|
-
const tool = { icon: '💭', label, toolType: 'thinking', detail: merged };
|
|
2056
|
-
stampTraceTool(tool, ctx, 'thinking');
|
|
2057
|
-
ctx.toolLog.push(tool);
|
|
2058
|
-
if (ctx.liveScope)
|
|
2059
|
-
replaceLiveRunTools(ctx.liveScope, ctx.toolLog);
|
|
2060
|
-
appendParentLiveRunTool(ctx, tool);
|
|
2061
|
-
emitAgentTool(ctx, agentLabel, tool, empTag);
|
|
2062
|
-
}
|
|
2063
|
-
ctx.thinkingBuf = '';
|
|
2064
|
-
}
|
|
2065
|
-
// session/update → broadcast mapping
|
|
2066
|
-
let replayMode = false; // Phase 17.2: suppress events during loadSession replay
|
|
2067
|
-
let lastVisibleBroadcastTs = Date.now();
|
|
2068
|
-
let heartbeatSent = false;
|
|
2069
|
-
acp.on('session/update', (params) => {
|
|
2070
|
-
if (replayMode)
|
|
2071
|
-
return; // 리플레이 중 모든 이벤트 무시
|
|
2072
|
-
const update = asCliEventRecord(asCliEventRecord(params)["update"]);
|
|
2073
|
-
appendTraceEvent({ runId: ctx.traceRunId, source: 'acp_raw', eventType: fieldString(update.sessionUpdate, 'session/update'), raw: params });
|
|
2074
|
-
const parsed = extractFromAcpUpdate(params, ctx);
|
|
2075
|
-
if (!parsed)
|
|
2076
|
-
return;
|
|
2077
|
-
if (parsed.tool) {
|
|
2078
|
-
const parsedTool = parsed.tool;
|
|
2079
|
-
// Buffer 💭 thought chunks → flush when different event arrives
|
|
2080
|
-
if (parsedTool.icon === '💭') {
|
|
2081
|
-
ctx.printActivity?.reasoning(parsedTool.detail || parsedTool.label, 'append');
|
|
2082
|
-
ctx.thinkingBuf += parsedTool.detail || parsedTool.label;
|
|
2083
|
-
return;
|
|
2084
|
-
}
|
|
2085
|
-
// Non-💭 tool → flush any pending thinking first
|
|
2086
|
-
flushThinking();
|
|
2087
|
-
// [I3] Include stepRef + status in dedupe key to allow repeated same-name tool calls
|
|
2088
|
-
const key = `${parsedTool.icon}:${parsedTool.label}:${parsedTool.stepRef || ''}:${parsedTool.status || ''}`;
|
|
2089
|
-
if (!ctx.seenToolKeys.has(key)) {
|
|
2090
|
-
ctx.seenToolKeys.add(key);
|
|
2091
|
-
stampTraceTool(parsedTool, ctx, parsedTool.toolType || 'tool');
|
|
2092
|
-
ctx.toolLog.push(parsedTool);
|
|
2093
|
-
if (ctx.liveScope)
|
|
2094
|
-
replaceLiveRunTools(ctx.liveScope, ctx.toolLog);
|
|
2095
|
-
appendParentLiveRunTool(ctx, parsedTool);
|
|
2096
|
-
emitAgentTool(ctx, agentLabel, parsedTool, empTag);
|
|
2097
|
-
// Reset heartbeat gate on actually visible broadcast (not 💭)
|
|
2098
|
-
lastVisibleBroadcastTs = Date.now();
|
|
2099
|
-
heartbeatSent = false;
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
if (parsed.text) {
|
|
2103
|
-
flushThinking();
|
|
2104
|
-
// NARRATION-BOUNDARY-01: a changed ACP messageId means a NEW
|
|
2105
|
-
// assistant message, so what accumulated was progress narration
|
|
2106
|
-
// rather than part of this answer. External channels deliver text
|
|
2107
|
-
// derived from ctx.fullText; the live UI keeps the narration via
|
|
2108
|
-
// the agent_output broadcast below. Chunks without a messageId
|
|
2109
|
-
// carry no boundary signal and simply accumulate.
|
|
2110
|
-
if (parsed.messageId && ctx.acpAssistantMessageId !== undefined
|
|
2111
|
-
&& ctx.acpAssistantMessageId !== parsed.messageId) {
|
|
2112
|
-
ctx.printActivity?.nextMessage();
|
|
2113
|
-
ctx.fullText = '';
|
|
2114
|
-
ctx.outputTextStarted = false;
|
|
2115
|
-
}
|
|
2116
|
-
if (parsed.messageId)
|
|
2117
|
-
ctx.acpAssistantMessageId = parsed.messageId;
|
|
2118
|
-
ctx.printActivity?.message(parsed.text, 'append', 'unknown');
|
|
2119
|
-
const segment = appendAssistantTextSegment(ctx, parsed.text);
|
|
2120
|
-
if (segment) {
|
|
2121
|
-
broadcastAgentOutput(ctx, agentLabel, cli, segment, empTag, traceAudience);
|
|
2122
|
-
lastVisibleBroadcastTs = Date.now();
|
|
2123
|
-
heartbeatSent = false;
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
opts.lifecycle?.onActivity?.('acp');
|
|
2127
|
-
});
|
|
2128
|
-
// [P2-3.14] session/cancelled → route through extractFromAcpUpdate for UI notification
|
|
2129
|
-
acp.on('session/cancelled', (params) => {
|
|
2130
|
-
appendTraceEvent({ runId: ctx.traceRunId, source: 'acp_raw', eventType: 'session/cancelled', raw: params });
|
|
2131
|
-
const parsed = extractFromAcpUpdate({
|
|
2132
|
-
update: { sessionUpdate: 'session_cancelled', ...(params || {}) },
|
|
2133
|
-
});
|
|
2134
|
-
if (parsed?.tool) {
|
|
2135
|
-
stampTraceTool(parsed.tool, ctx, parsed.tool.toolType || 'tool');
|
|
2136
|
-
ctx.toolLog.push(parsed.tool);
|
|
2137
|
-
if (ctx.liveScope)
|
|
2138
|
-
replaceLiveRunTools(ctx.liveScope, ctx.toolLog);
|
|
2139
|
-
appendParentLiveRunTool(ctx, parsed.tool);
|
|
2140
|
-
emitAgentTool(ctx, agentLabel, parsed.tool, empTag);
|
|
2141
|
-
}
|
|
2142
|
-
});
|
|
2143
|
-
// [P2-3.15] session/request_permission → audit record in toolLog
|
|
2144
|
-
acp.on('session/request_permission', (params) => {
|
|
2145
|
-
appendTraceEvent({ runId: ctx.traceRunId, source: 'acp_raw', eventType: 'session/request_permission', raw: params });
|
|
2146
|
-
const parsed = extractFromAcpUpdate({
|
|
2147
|
-
update: { sessionUpdate: 'request_permission', ...(params || {}) },
|
|
2148
|
-
});
|
|
2149
|
-
if (parsed?.tool) {
|
|
2150
|
-
stampTraceTool(parsed.tool, ctx, parsed.tool.toolType || 'tool');
|
|
2151
|
-
ctx.toolLog.push(parsed.tool);
|
|
2152
|
-
if (ctx.liveScope)
|
|
2153
|
-
replaceLiveRunTools(ctx.liveScope, ctx.toolLog);
|
|
2154
|
-
appendParentLiveRunTool(ctx, parsed.tool);
|
|
2155
|
-
emitAgentTool(ctx, agentLabel, parsed.tool, empTag);
|
|
2156
|
-
}
|
|
2157
|
-
});
|
|
2158
|
-
// stderr_activity → stderrBuf accumulation + conditional heartbeat
|
|
2159
|
-
acp.on('stderr_activity', (text) => {
|
|
2160
|
-
appendTraceEvent({ runId: ctx.traceRunId, source: 'stderr', eventType: 'stderr_activity', raw: text });
|
|
2161
|
-
// Accumulate stderr for diagnostics (capped)
|
|
2162
|
-
if (ctx.stderrBuf.length < 4000) {
|
|
2163
|
-
ctx.stderrBuf += text + '\n';
|
|
2164
|
-
}
|
|
2165
|
-
opts.lifecycle?.onActivity?.('stderr');
|
|
2166
|
-
// Conditional heartbeat: visible progress absent for N seconds
|
|
2167
|
-
if (shouldEmitHeartbeat(lastVisibleBroadcastTs, heartbeatSent)) {
|
|
2168
|
-
heartbeatSent = true;
|
|
2169
|
-
const elapsed = Math.round((Date.now() - lastVisibleBroadcastTs) / 1000);
|
|
2170
|
-
console.log(` ⏳ agent active (no visible event for ${elapsed}s)`);
|
|
2171
|
-
emitAgentTool(ctx, agentLabel, {
|
|
2172
|
-
icon: '⏳',
|
|
2173
|
-
label: 'working... (no visible progress)',
|
|
2174
|
-
}, empTag);
|
|
2175
|
-
}
|
|
2176
|
-
});
|
|
2177
|
-
// Run ACP flow
|
|
2178
|
-
let promptCompleted = false;
|
|
2179
|
-
(async () => {
|
|
2180
|
-
try {
|
|
2181
|
-
const initResult = await acp.initialize();
|
|
2182
|
-
if (process.env["DEBUG"])
|
|
2183
|
-
console.log('[acp:init]', JSON.stringify(initResult).slice(0, 200));
|
|
2184
|
-
replayMode = true; // Phase 17.2: mute during session load
|
|
2185
|
-
let loadSessionOk = false;
|
|
2186
|
-
if (isResume && resumeSessionId) {
|
|
2187
|
-
try {
|
|
2188
|
-
await acp.loadSession(resumeSessionId);
|
|
2189
|
-
loadSessionOk = true;
|
|
2190
|
-
console.log(`[acp:session] loadSession OK: ${resumeSessionId.slice(0, 12)}...`);
|
|
2191
|
-
}
|
|
2192
|
-
catch (loadErr) {
|
|
2193
|
-
console.warn(`[acp:session] loadSession FAILED: ${loadErr.message} — falling back to createSession`);
|
|
2194
|
-
if (empSid && opts.agentId) {
|
|
2195
|
-
clearEmployeeSession.run(opts.agentId);
|
|
2196
|
-
console.warn(`[acp:session] cleared stale employee resume for ${opts.agentId}`);
|
|
2197
|
-
}
|
|
2198
|
-
await acp.createSession(spawnCwd, getEmployeeMcpServers());
|
|
2199
|
-
}
|
|
2200
|
-
}
|
|
2201
|
-
else {
|
|
2202
|
-
await acp.createSession(spawnCwd, getEmployeeMcpServers());
|
|
2203
|
-
}
|
|
2204
|
-
replayMode = false; // Phase 17.2: unmute after session load
|
|
2205
|
-
ctx.sessionId = acp.sessionId;
|
|
2206
|
-
// Reset accumulated text from loadSession replay (ACP replays full history)
|
|
2207
|
-
ctx.fullText = '';
|
|
2208
|
-
ctx.toolLog = [];
|
|
2209
|
-
ctx.seenToolKeys.clear();
|
|
2210
|
-
ctx.thinkingBuf = ''; // Phase 17.2: clear replay thinking too
|
|
2211
|
-
if (mainManaged && !opts.internal) {
|
|
2212
|
-
beginLiveRun(liveScope, cli);
|
|
2213
|
-
if (ctx.traceRunId)
|
|
2214
|
-
setLiveRunTraceId(liveScope, ctx.traceRunId);
|
|
2215
|
-
}
|
|
2216
|
-
// If loadSession failed (or not resuming), inject history into prompt
|
|
2217
|
-
const needsHistoryFallback = isResume && !loadSessionOk;
|
|
2218
|
-
const fallbackHistory = needsHistoryFallback && !opts._skipHistory ? buildHistoryBlock(prompt, settings["workingDir"], chatSessionId) : '';
|
|
2219
|
-
const acpPrompt = needsHistoryFallback
|
|
2220
|
-
? withHistoryPrompt(prompt, fallbackHistory)
|
|
2221
|
-
: (isResume ? prompt : withHistoryPrompt(prompt, historyBlock));
|
|
2222
|
-
const acpPromptWithSteer = withSteerContext(acpPrompt, opts.steerContext);
|
|
2223
|
-
const { promise: promptPromise } = acp.prompt(acpPromptWithSteer);
|
|
2224
|
-
const promptResult = await promptPromise;
|
|
2225
|
-
promptCompleted = true;
|
|
2226
|
-
if (process.env["DEBUG"])
|
|
2227
|
-
console.log('[acp:prompt:result]', JSON.stringify(promptResult).slice(0, 200));
|
|
2228
|
-
// Save session BEFORE shutdown — acp.shutdown() causes SIGTERM (code=null),
|
|
2229
|
-
// which skips the exit handler's code===0 gate, losing session continuity.
|
|
2230
|
-
const persistedAcpSessionId = ctx.sessionId;
|
|
2231
|
-
if (persistedAcpSessionId && persistMainSession(stripUndefined({
|
|
2232
|
-
persistenceOwner,
|
|
2233
|
-
scopeKey,
|
|
2234
|
-
forceNew,
|
|
2235
|
-
employeeSessionId: empSid,
|
|
2236
|
-
sessionId: persistedAcpSessionId,
|
|
2237
|
-
isFallback: opts._isFallback,
|
|
2238
|
-
cli,
|
|
2239
|
-
model,
|
|
2240
|
-
resumeKey,
|
|
2241
|
-
effort: cfg.effort || '',
|
|
2242
|
-
skipSessionPersist: opts._skipSessionPersist === true,
|
|
2243
|
-
// Without this the save falls back to the bare, unscoped bucket, and a
|
|
2244
|
-
// successful turn in another session writes its vendor id into the row
|
|
2245
|
-
// the default session resumes from. The exit handler below passes the
|
|
2246
|
-
// same value; this path runs first and must not disagree with it.
|
|
2247
|
-
runtimeTransport,
|
|
2248
|
-
scopedBucket: currentBucket,
|
|
2249
|
-
}))) {
|
|
2250
|
-
console.log(`[jaw:session] saved ${cli} session=${persistedAcpSessionId.slice(0, 12)}... (pre-shutdown)`);
|
|
2251
|
-
}
|
|
2252
|
-
await acp.shutdown();
|
|
2253
|
-
}
|
|
2254
|
-
catch (err) {
|
|
2255
|
-
console.error(`[acp:error] ${err.message}`);
|
|
2256
|
-
if (ctx.stderrBuf.length < 4000)
|
|
2257
|
-
ctx.stderrBuf += err.message;
|
|
2258
|
-
acp.kill();
|
|
2259
|
-
}
|
|
2260
|
-
})();
|
|
2261
|
-
acp.on('exit', ({ code, signal }) => {
|
|
2262
|
-
if (acpSettled)
|
|
2263
|
-
return; // error handler already resolved
|
|
2264
|
-
acpSettled = true;
|
|
2265
|
-
cleanupEmployeeTmpDir(spawnCwd, settings["workingDir"], agentLabel);
|
|
2266
|
-
opts.lifecycle?.onExit?.(code ?? null);
|
|
2267
|
-
// [I2] Consume per-process kill reason
|
|
2268
|
-
const acpKillReason = consumeKillReason(acp.proc?.pid);
|
|
2269
|
-
if (code !== 0 && !acpKillReason) {
|
|
2270
|
-
console.warn(`[acp:unexpected-exit] code=${code} signal=${signal} sessionId=${ctx.sessionId || 'none'}`);
|
|
2271
|
-
}
|
|
2272
|
-
const wasKilled = !!acpKillReason;
|
|
2273
|
-
const wasSteer = isLifecycleSteerReason(acpKillReason);
|
|
2274
|
-
flushThinking(); // Flush any remaining thinking buffer
|
|
2275
|
-
const smokeResult = detectSmokeResponse(ctx.fullText, ctx.toolLog, code, cli);
|
|
2276
|
-
const acpCode = promptCompleted ? 0 : (code ?? 1);
|
|
2277
|
-
// Delegated to lifecycle-handler.ts → handleAgentExit:
|
|
2278
|
-
// - smoke continuation (guarded by !wasSteer)
|
|
2279
|
-
// - output: ⏹️ [interrupted] prefix (wasSteer && mainManaged && !opts.internal)
|
|
2280
|
-
// - error: code !== 0 && !wasKilled → classifyExitError
|
|
2281
|
-
// - trace: if (traceText) traceText = `⏹️ [interrupted]…`
|
|
2282
|
-
handleAgentExit({
|
|
2283
|
-
ctx, code: acpCode, childExitCode: code, cli, model, agentLabel, mainManaged, origin,
|
|
2284
|
-
killReason: acpKillReason,
|
|
2285
|
-
onRuntimeEnd: end => ctx.printActivity?.finish(end),
|
|
2286
|
-
resumeKey,
|
|
2287
|
-
prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
|
|
2288
|
-
isResume, wasKilled, wasSteer, smokeResult,
|
|
2289
|
-
effortDefault: '', costLine: '',
|
|
2290
|
-
resolve: resolve,
|
|
2291
|
-
activeProcesses,
|
|
2292
|
-
scopeKey,
|
|
2293
|
-
runtimeTransport,
|
|
2294
|
-
scopedBucket: currentBucket,
|
|
2295
|
-
chatSessionId,
|
|
2296
|
-
childProcess: child,
|
|
2297
|
-
releaseMainRun,
|
|
2298
|
-
retryState: queueCtrl.retryStateForScope(scopeKey),
|
|
2299
|
-
fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
|
|
2300
|
-
fallbackMaxRetries: FALLBACK_MAX_RETRIES,
|
|
2301
|
-
processQueue,
|
|
2302
|
-
}).catch((err) => {
|
|
2303
|
-
console.error('[jaw:lifecycle] handleAgentExit failed (ACP):', err.message);
|
|
2304
|
-
}).finally(() => settleExit(scopeKey));
|
|
2305
|
-
});
|
|
2306
|
-
return { child, promise: resultPromise };
|
|
2307
|
-
}
|
|
1607
|
+
if (cli === 'copilot')
|
|
1608
|
+
return runCopilotBackend(backendLocals, backendHost);
|
|
2308
1609
|
// ─── Pi RPC branch ─────────────────────────────
|
|
2309
|
-
if (cli === 'pi')
|
|
2310
|
-
|
|
2311
|
-
const settlePiExit = () => { settleCapturedExit(scopeKey, piExit); };
|
|
2312
|
-
const pi = normalizePiSettings(settings["pi"]);
|
|
2313
|
-
const profileId = cfg.provider || pi.defaultProfileId;
|
|
2314
|
-
const profile = pi.profiles.find((entry) => entry.id === profileId) || pi.profiles[0];
|
|
2315
|
-
if (!profile) {
|
|
2316
|
-
throw new Error('Pi profile is not configured');
|
|
2317
|
-
}
|
|
2318
|
-
const piSessionId = isResume && bucketSessionId ? bucketSessionId : '';
|
|
2319
|
-
console.log(`[jaw:pi] isResume=${isResume}, bucketSessionId=${bucketSessionId || 'none'}, piSessionId=${piSessionId || 'new'}`);
|
|
2320
|
-
const piPrompt = withSteerContext(piSessionId ? prompt : withHistoryPrompt(prompt, historyBlock), opts.steerContext);
|
|
2321
|
-
const traceRunId = startTraceRun({ cli, model: runtimeModel, workingDir: settings["workingDir"] || null, agentLabel, audience: traceAudience, sessionId: chatSessionId, scopeKey });
|
|
2322
|
-
const ctx = {
|
|
2323
|
-
fullText: '',
|
|
2324
|
-
traceLog: [],
|
|
2325
|
-
toolLog: [],
|
|
2326
|
-
seenToolKeys: new Set(),
|
|
2327
|
-
hasClaudeStreamEvents: false,
|
|
2328
|
-
runStartedAt: Date.now(),
|
|
2329
|
-
...(opts.requestId ? { requestId: opts.requestId } : {}),
|
|
2330
|
-
...(origin ? { origin } : {}),
|
|
2331
|
-
sessionId: null,
|
|
2332
|
-
cost: null,
|
|
2333
|
-
turns: null,
|
|
2334
|
-
duration: null,
|
|
2335
|
-
tokens: null,
|
|
2336
|
-
stderrBuf: '',
|
|
2337
|
-
hasActiveSubAgent: false,
|
|
2338
|
-
showReasoning: settings["showReasoning"] === true,
|
|
2339
|
-
outputTextStarted: false,
|
|
2340
|
-
effectiveProvider: profile.id,
|
|
2341
|
-
thinkingBuf: '',
|
|
2342
|
-
liveOutputText: '',
|
|
2343
|
-
liveScope: effectiveLiveScope,
|
|
2344
|
-
parentLiveScope: parentLiveScopeForChild,
|
|
2345
|
-
traceRunId,
|
|
2346
|
-
traceAudience,
|
|
2347
|
-
activityIdentity: { sessionId: chatSessionId, scope: scopeKey },
|
|
2348
|
-
};
|
|
2349
|
-
const activity = new RuntimeProjection({
|
|
2350
|
-
runId: traceRunId, sessionId: chatSessionId, scope: scopeKey,
|
|
2351
|
-
turnId: traceRunId, audience: traceAudience,
|
|
2352
|
-
...(opts.runtimeParentItemId ? { parentItemId: opts.runtimeParentItemId } : {}),
|
|
2353
|
-
});
|
|
2354
|
-
const piProjection = new PiProjection(activity);
|
|
2355
|
-
const rawTrace = new PiRawTrace(traceRunId, appendTraceEvent, () => activity.report('persistence'));
|
|
2356
|
-
activity.start('pi');
|
|
2357
|
-
const onPiRawRecord = (raw) => {
|
|
2358
|
-
rawTrace.record(raw);
|
|
2359
|
-
piProjection.observeRecord(raw);
|
|
2360
|
-
};
|
|
2361
|
-
function flushPiThinking() {
|
|
2362
|
-
if (!ctx.thinkingBuf)
|
|
2363
|
-
return;
|
|
2364
|
-
const merged = ctx.thinkingBuf.trim();
|
|
2365
|
-
if (merged) {
|
|
2366
|
-
const singleLine = merged.replace(/\s+/g, ' ').trim();
|
|
2367
|
-
const label = singleLine.length > 120 ? `${singleLine.slice(0, 119)}…` : singleLine;
|
|
2368
|
-
const tool = stripUndefined({ icon: '💭', label, toolType: 'thinking', detail: merged });
|
|
2369
|
-
stampTraceTool(tool, ctx, 'thinking');
|
|
2370
|
-
ctx.toolLog.push(tool);
|
|
2371
|
-
if (ctx.liveScope)
|
|
2372
|
-
replaceLiveRunTools(ctx.liveScope, ctx.toolLog);
|
|
2373
|
-
appendParentLiveRunTool(ctx, tool);
|
|
2374
|
-
emitAgentTool(ctx, agentLabel, tool, empTag);
|
|
2375
|
-
}
|
|
2376
|
-
ctx.thinkingBuf = '';
|
|
2377
|
-
}
|
|
2378
|
-
const piToolDiscipline = [
|
|
2379
|
-
'[Pi Tool Discipline]',
|
|
2380
|
-
'Your available tools are strictly lowercase: read, bash, edit, write, grep, find, ls.',
|
|
2381
|
-
'Capitalized variants (Read, Bash, Edit, Write, Grep, Find, Ls) do NOT exist and will fail.',
|
|
2382
|
-
].join('\n');
|
|
2383
|
-
const piSysPrompt = sysPrompt ? `${sysPrompt}\n\n${piToolDiscipline}` : piToolDiscipline;
|
|
2384
|
-
const onPiEvent = (event) => {
|
|
2385
|
-
piProjection.observe(event);
|
|
2386
|
-
try {
|
|
2387
|
-
opts.lifecycle?.onActivity?.('pi-rpc');
|
|
2388
|
-
}
|
|
2389
|
-
catch {
|
|
2390
|
-
console.warn('[jaw:pi] activity observer failed');
|
|
2391
|
-
}
|
|
2392
|
-
if (event.kind === 'thinking') {
|
|
2393
|
-
ctx.thinkingBuf = (ctx.thinkingBuf || '') + event.text;
|
|
2394
|
-
return;
|
|
2395
|
-
}
|
|
2396
|
-
if (event.kind === 'text') {
|
|
2397
|
-
flushPiThinking();
|
|
2398
|
-
const delta = String(event.text || '');
|
|
2399
|
-
if (!delta)
|
|
2400
|
-
return;
|
|
2401
|
-
{
|
|
2402
|
-
// D3: bound fullText — see events/fulltext-bound.ts.
|
|
2403
|
-
const bounded = appendBoundedFullText(ctx.fullText, delta);
|
|
2404
|
-
ctx.fullText = bounded.text;
|
|
2405
|
-
if (bounded.truncated)
|
|
2406
|
-
ctx.fullTextTruncated = true;
|
|
2407
|
-
}
|
|
2408
|
-
const displayDelta = normalizeAssistantDisplayText(delta);
|
|
2409
|
-
if (ctx.liveOutputText !== undefined) {
|
|
2410
|
-
// Bound this too: it is promoted into fullText at close.
|
|
2411
|
-
const live = appendBoundedFullText(ctx.liveOutputText, displayDelta);
|
|
2412
|
-
ctx.liveOutputText = live.text;
|
|
2413
|
-
if (live.truncated)
|
|
2414
|
-
ctx.fullTextTruncated = true;
|
|
2415
|
-
}
|
|
2416
|
-
if (!ctx.outputTextStarted)
|
|
2417
|
-
ctx.outputTextStarted = true;
|
|
2418
|
-
broadcastAgentOutput(ctx, agentLabel, cli, displayDelta, empTag, traceAudience);
|
|
2419
|
-
return;
|
|
2420
|
-
}
|
|
2421
|
-
if (event.kind === 'tool') {
|
|
2422
|
-
flushPiThinking();
|
|
2423
|
-
const tool = stripUndefined({ icon: '🔧', label: event.label, status: event.status, detail: event.detail, toolType: 'tool' });
|
|
2424
|
-
stampTraceTool(tool, ctx, 'tool');
|
|
2425
|
-
ctx.toolLog.push(tool);
|
|
2426
|
-
if (ctx.liveScope)
|
|
2427
|
-
replaceLiveRunTools(ctx.liveScope, ctx.toolLog);
|
|
2428
|
-
appendParentLiveRunTool(ctx, tool);
|
|
2429
|
-
emitAgentTool(ctx, agentLabel, tool, empTag);
|
|
2430
|
-
return;
|
|
2431
|
-
}
|
|
2432
|
-
if (event.kind === 'session')
|
|
2433
|
-
ctx.sessionId = event.sessionId;
|
|
2434
|
-
};
|
|
2435
|
-
let piFacade = null;
|
|
2436
|
-
let piTurnChild = null;
|
|
2437
|
-
let piSendStarted = false;
|
|
2438
|
-
const piTurnContext = () => ({
|
|
2439
|
-
runId: traceRunId, sessionId: chatSessionId, scope: scopeKey,
|
|
2440
|
-
turnId: traceRunId, audience: traceAudience,
|
|
2441
|
-
isCurrent: () => isCurrentSessionOwner(persistenceOwner, scopeKey)
|
|
2442
|
-
&& (mainManaged
|
|
2443
|
-
? activeMainProcesses.get(scopeKey) === mainRun
|
|
2444
|
-
: piTurnChild !== null && activeProcesses.get(agentLabel) === piTurnChild),
|
|
2445
|
-
});
|
|
2446
|
-
const appendPiStderr = (stderr) => {
|
|
2447
|
-
if (!stderr || ctx.stderrBuf.length >= STDERR_BUF_CAP)
|
|
2448
|
-
return;
|
|
2449
|
-
ctx.stderrBuf = sliceWithoutSplittingSurrogate(ctx.stderrBuf + stderr, STDERR_BUF_CAP);
|
|
2450
|
-
};
|
|
2451
|
-
const onPiFailure = (error, outcome) => {
|
|
2452
|
-
appendPiStderr(error.message);
|
|
2453
|
-
if (outcome.status !== 'error')
|
|
2454
|
-
return;
|
|
2455
|
-
const diagnostic = sliceWithoutSplittingSurrogate(userErrorText(error), STDERR_BUF_CAP);
|
|
2456
|
-
if (diagnostic.trim() && !ctx.runtimeDiagnostic?.trim())
|
|
2457
|
-
ctx.runtimeDiagnostic = diagnostic;
|
|
2458
|
-
};
|
|
2459
|
-
const onPiStderr = (stderr) => appendPiStderr(stderr);
|
|
2460
|
-
const mapPiSend = (child, send) => send.then((outcome) => ({
|
|
2461
|
-
text: outcome.finalText ?? outcome.partialText ?? '',
|
|
2462
|
-
code: typeof child.exitCode === 'number' ? child.exitCode
|
|
2463
|
-
: outcome.status === 'error' ? 1 : 0,
|
|
2464
|
-
sessionId: ctx.sessionId ?? piFacade?.nativeSessionId ?? null,
|
|
2465
|
-
runtimeOutcome: outcome,
|
|
2466
|
-
}));
|
|
2467
|
-
const endPiRuntime = (end) => {
|
|
2468
|
-
if (piFacade?.claimTurnOutcome(traceRunId)) {
|
|
2469
|
-
if (!piFacade.finalizeTurn(traceRunId, end)) {
|
|
2470
|
-
console.warn('[jaw:pi] owned finalizer rejected the terminal');
|
|
2471
|
-
activity.close(end);
|
|
2472
|
-
}
|
|
2473
|
-
return;
|
|
2474
|
-
}
|
|
2475
|
-
if (piSendStarted)
|
|
2476
|
-
console.warn('[jaw:pi] missing owned finalizer');
|
|
2477
|
-
activity.close(end);
|
|
2478
|
-
};
|
|
2479
|
-
const runPiTurn = (child, lease, directCleanup, start) => {
|
|
2480
|
-
let leaseCancel = null;
|
|
2481
|
-
let cleanupDone = false, queueRequested = false;
|
|
2482
|
-
let selectedResult;
|
|
2483
|
-
const recordResult = (result) => {
|
|
2484
|
-
if (selectedResult !== undefined)
|
|
2485
|
-
return;
|
|
2486
|
-
selectedResult = result;
|
|
2487
|
-
if (cleanupDone)
|
|
2488
|
-
resolve(result);
|
|
2489
|
-
};
|
|
2490
|
-
const requestQueue = () => {
|
|
2491
|
-
if (cleanupDone)
|
|
2492
|
-
void processQueue(scopeKey);
|
|
2493
|
-
else
|
|
2494
|
-
queueRequested = true;
|
|
2495
|
-
};
|
|
2496
|
-
const requestCancel = () => {
|
|
2497
|
-
if (cleanupDone)
|
|
2498
|
-
return Promise.resolve();
|
|
2499
|
-
if (!lease) {
|
|
2500
|
-
if (cancelOwnedPiProcess(child))
|
|
2501
|
-
return Promise.resolve();
|
|
2502
|
-
ownProcess(child).terminate('cancel');
|
|
2503
|
-
return Promise.resolve();
|
|
2504
|
-
}
|
|
2505
|
-
leaseCancel ??= lease.cancel();
|
|
2506
|
-
return leaseCancel;
|
|
2507
|
-
};
|
|
2508
|
-
const cancelHook = (reason) => {
|
|
2509
|
-
if (cleanupDone)
|
|
2510
|
-
return;
|
|
2511
|
-
if (isLifecycleExitSettleReason(reason))
|
|
2512
|
-
piExit = captureExitSettler(scopeKey);
|
|
2513
|
-
void requestCancel();
|
|
2514
|
-
};
|
|
2515
|
-
if (lease && mainRun)
|
|
2516
|
-
mainRun.cancelTurn = cancelHook;
|
|
2517
|
-
let piWatchdog;
|
|
2518
|
-
let watchdogStopped = false;
|
|
2519
|
-
const stopWatchdog = () => { if (!watchdogStopped) {
|
|
2520
|
-
watchdogStopped = true;
|
|
2521
|
-
piWatchdog?.stop();
|
|
2522
|
-
} };
|
|
2523
|
-
let setupError;
|
|
2524
|
-
try {
|
|
2525
|
-
piWatchdog = attachWatchdog(child, agentLabel, (reason) => {
|
|
2526
|
-
console.log(`[jaw:watchdog] cancelling ${agentLabel} (pi) — ${reason}`);
|
|
2527
|
-
ctx.stallReason = reason;
|
|
2528
|
-
void requestCancel();
|
|
2529
|
-
});
|
|
2530
|
-
ctx.stallWatchdog = piWatchdog;
|
|
2531
|
-
if (mainManaged)
|
|
2532
|
-
mainRun.process = child;
|
|
2533
|
-
else
|
|
2534
|
-
registerActiveProcess(agentLabel, child);
|
|
2535
|
-
if (!opts.internal)
|
|
2536
|
-
broadcast('agent_status', { running: true, agentId: agentLabel, cli, provider: profile.id, ...empTag });
|
|
2537
|
-
if (mainManaged && !opts.internal) {
|
|
2538
|
-
beginLiveRun(liveScope, cli);
|
|
2539
|
-
setLiveRunTraceId(liveScope, traceRunId);
|
|
2540
|
-
}
|
|
2541
|
-
if (mainManaged && !opts.internal && !opts._skipInsert) {
|
|
2542
|
-
insertMessage.run('user', prompt, cli, runtimeModel, settings["workingDir"] || null, chatSessionId);
|
|
2543
|
-
}
|
|
2544
|
-
if (!opts.internal)
|
|
2545
|
-
broadcast('agent_status', { status: 'running', cli, agentId: agentLabel, provider: profile.id, ...empTag }, traceAudience);
|
|
2546
|
-
}
|
|
2547
|
-
catch (error) {
|
|
2548
|
-
setupError = error;
|
|
2549
|
-
try {
|
|
2550
|
-
void requestCancel().catch(() => { console.warn('[jaw:pi] setup cancellation failed'); });
|
|
2551
|
-
}
|
|
2552
|
-
catch {
|
|
2553
|
-
console.warn('[jaw:pi] setup cancellation failed');
|
|
2554
|
-
}
|
|
2555
|
-
}
|
|
2556
|
-
const releaseLease = async () => {
|
|
2557
|
-
try {
|
|
2558
|
-
if (leaseCancel)
|
|
2559
|
-
await leaseCancel;
|
|
2560
|
-
}
|
|
2561
|
-
finally {
|
|
2562
|
-
if (mainRun?.cancelTurn === cancelHook)
|
|
2563
|
-
delete mainRun.cancelTurn;
|
|
2564
|
-
consumeKillReason(child.pid);
|
|
2565
|
-
if (lease)
|
|
2566
|
-
lease.release();
|
|
2567
|
-
else {
|
|
2568
|
-
let receipt;
|
|
2569
|
-
try {
|
|
2570
|
-
receipt = await directCleanup ?? undefined;
|
|
2571
|
-
}
|
|
2572
|
-
catch { /* Missing/rejected evidence never authorizes deletion. */ }
|
|
2573
|
-
if (receipt?.cwdDisposition === 'removable')
|
|
2574
|
-
cleanupPiEmployee();
|
|
2575
|
-
else
|
|
2576
|
-
console.warn('[jaw:pi] retaining employee cwd: physical cleanup unconfirmed', spawnCwd);
|
|
2577
|
-
}
|
|
2578
|
-
}
|
|
2579
|
-
};
|
|
2580
|
-
const done = start();
|
|
2581
|
-
done.then(async (result) => {
|
|
2582
|
-
if (setupError !== undefined)
|
|
2583
|
-
throw setupError;
|
|
2584
|
-
if (result.runtimeOutcome !== undefined)
|
|
2585
|
-
handoffRuntimeOutcome(ctx, result.runtimeOutcome);
|
|
2586
|
-
const killReason = consumeKillReason(child.pid);
|
|
2587
|
-
stopWatchdog();
|
|
2588
|
-
flushPiThinking();
|
|
2589
|
-
if (result.sessionId)
|
|
2590
|
-
ctx.sessionId = result.sessionId;
|
|
2591
|
-
if (!ctx.fullText && result.text)
|
|
2592
|
-
ctx.fullText = result.text;
|
|
2593
|
-
try {
|
|
2594
|
-
opts.lifecycle?.onExit?.(result.code);
|
|
2595
|
-
}
|
|
2596
|
-
catch {
|
|
2597
|
-
console.warn('[jaw:pi] exit observer failed');
|
|
2598
|
-
}
|
|
2599
|
-
const wasKilled = !!killReason;
|
|
2600
|
-
const wasSteer = isLifecycleSteerReason(killReason);
|
|
2601
|
-
const smokeResult = detectSmokeResponse(ctx.fullText, ctx.toolLog, result.code, cli);
|
|
2602
|
-
return handleAgentExit({
|
|
2603
|
-
onRuntimeEnd: endPiRuntime,
|
|
2604
|
-
ctx, code: result.code, cli, model: runtimeModel, effectiveProvider: profile.id, agentLabel, mainManaged, origin,
|
|
2605
|
-
killReason,
|
|
2606
|
-
resumeKey,
|
|
2607
|
-
prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
|
|
2608
|
-
isResume: false, wasKilled, wasSteer, smokeResult,
|
|
2609
|
-
effortDefault: 'medium', costLine: '',
|
|
2610
|
-
resolve: recordResult,
|
|
2611
|
-
activeProcesses,
|
|
2612
|
-
scopeKey,
|
|
2613
|
-
runtimeTransport,
|
|
2614
|
-
scopedBucket: currentBucket,
|
|
2615
|
-
chatSessionId,
|
|
2616
|
-
childProcess: child,
|
|
2617
|
-
releaseMainRun: (scope, process, generation) => activeMainProcesses.get(scope) === mainRun
|
|
2618
|
-
&& releaseMainRun(scope, process, generation),
|
|
2619
|
-
retryState: queueCtrl.retryStateForScope(scopeKey),
|
|
2620
|
-
fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
|
|
2621
|
-
fallbackMaxRetries: FALLBACK_MAX_RETRIES,
|
|
2622
|
-
processQueue: requestQueue,
|
|
2623
|
-
});
|
|
2624
|
-
}, async (err) => {
|
|
2625
|
-
if (setupError !== undefined)
|
|
2626
|
-
throw setupError;
|
|
2627
|
-
const failedOutcome = piFailureOutcome(err);
|
|
2628
|
-
if (failedOutcome !== undefined)
|
|
2629
|
-
handoffRuntimeOutcome(ctx, failedOutcome);
|
|
2630
|
-
const killReason = consumeKillReason(child.pid);
|
|
2631
|
-
const wasKilled = !!killReason;
|
|
2632
|
-
const wasSteer = isLifecycleSteerReason(killReason);
|
|
2633
|
-
stopWatchdog();
|
|
2634
|
-
appendPiStderr(err.message);
|
|
2635
|
-
console.error('[jaw:pi] runtime failed:', userErrorText(err));
|
|
2636
|
-
return handleAgentExit({
|
|
2637
|
-
onRuntimeEnd: endPiRuntime,
|
|
2638
|
-
ctx, code: 1, cli, model: runtimeModel, effectiveProvider: profile.id, agentLabel, mainManaged, origin,
|
|
2639
|
-
killReason,
|
|
2640
|
-
resumeKey,
|
|
2641
|
-
prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
|
|
2642
|
-
isResume: false, wasKilled, wasSteer, smokeResult: detectSmokeResponse('', [], 1, cli),
|
|
2643
|
-
effortDefault: 'medium', costLine: '',
|
|
2644
|
-
resolve: recordResult,
|
|
2645
|
-
activeProcesses,
|
|
2646
|
-
scopeKey,
|
|
2647
|
-
runtimeTransport,
|
|
2648
|
-
scopedBucket: currentBucket,
|
|
2649
|
-
chatSessionId,
|
|
2650
|
-
childProcess: child,
|
|
2651
|
-
releaseMainRun: (scope, process, generation) => activeMainProcesses.get(scope) === mainRun
|
|
2652
|
-
&& releaseMainRun(scope, process, generation),
|
|
2653
|
-
retryState: queueCtrl.retryStateForScope(scopeKey),
|
|
2654
|
-
fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
|
|
2655
|
-
fallbackMaxRetries: FALLBACK_MAX_RETRIES,
|
|
2656
|
-
processQueue: requestQueue,
|
|
2657
|
-
});
|
|
2658
|
-
}).catch((handleErr) => {
|
|
2659
|
-
endPiRuntime({ kind: 'turn-end', status: 'error', finalText: null, error: 'Lifecycle failed' });
|
|
2660
|
-
console.error('[jaw:lifecycle] handleAgentExit failed (Pi):', handleErr.message);
|
|
2661
|
-
try {
|
|
2662
|
-
finalizeTraceRun(traceRunId, 'error', 'Lifecycle failed', { onlyIfRunning: true });
|
|
2663
|
-
}
|
|
2664
|
-
catch {
|
|
2665
|
-
console.warn('[runtime] Pi lifecycle trace finalization failed');
|
|
2666
|
-
}
|
|
2667
|
-
if (mainManaged && activeMainProcesses.get(scopeKey) === mainRun) {
|
|
2668
|
-
if (releaseMainRun(scopeKey, mainRun.process, ownerGeneration) && !opts.internal)
|
|
2669
|
-
requestQueue();
|
|
2670
|
-
if (getLiveRun(liveScope).traceRunId === traceRunId)
|
|
2671
|
-
clearLiveRun(liveScope);
|
|
2672
|
-
}
|
|
2673
|
-
else if (!mainManaged && activeProcesses.get(agentLabel) === child)
|
|
2674
|
-
activeProcesses.delete(agentLabel);
|
|
2675
|
-
recordResult({ text: ctx.runtimeOutcome?.finalText ?? '', code: 1,
|
|
2676
|
-
...(ctx.runtimeOutcome === undefined ? {} : { runtimeOutcome: { ...ctx.runtimeOutcome } }) });
|
|
2677
|
-
}).finally(async () => {
|
|
2678
|
-
try {
|
|
2679
|
-
stopWatchdog();
|
|
2680
|
-
await releaseLease();
|
|
2681
|
-
}
|
|
2682
|
-
catch {
|
|
2683
|
-
console.warn('[jaw:pi] runtime cleanup failed');
|
|
2684
|
-
recordResult({ text: '', code: 1 });
|
|
2685
|
-
}
|
|
2686
|
-
finally {
|
|
2687
|
-
cleanupDone = true;
|
|
2688
|
-
settlePiExit();
|
|
2689
|
-
if (queueRequested)
|
|
2690
|
-
void processQueue(scopeKey);
|
|
2691
|
-
if (selectedResult !== undefined)
|
|
2692
|
-
resolve(selectedResult);
|
|
2693
|
-
}
|
|
2694
|
-
});
|
|
2695
|
-
};
|
|
2696
|
-
if (opts.agentId) {
|
|
2697
|
-
let opened;
|
|
2698
|
-
try {
|
|
2699
|
-
opened = openPiRpc(profile, pi, {
|
|
2700
|
-
model: runtimeModel,
|
|
2701
|
-
...(piSessionId ? { sessionId: piSessionId } : {}),
|
|
2702
|
-
effort, cwd: spawnCwd, env: spawnEnv,
|
|
2703
|
-
onEvent: onPiEvent, onRawRecord: onPiRawRecord,
|
|
2704
|
-
});
|
|
2705
|
-
}
|
|
2706
|
-
catch (error) {
|
|
2707
|
-
activity.close({ kind: 'turn-end', status: 'error', finalText: null, error: 'Pi process creation failed' });
|
|
2708
|
-
finalizeTraceRun(traceRunId, 'error', 'Pi process creation failed');
|
|
2709
|
-
cleanupPiEmployee();
|
|
2710
|
-
throw error;
|
|
2711
|
-
}
|
|
2712
|
-
piTurnChild = opened.child;
|
|
2713
|
-
piFacade = new PiRuntimeSession(opened, {
|
|
2714
|
-
lifetime: 'oneshot',
|
|
2715
|
-
provider: 'pi',
|
|
2716
|
-
deferTurnEnd: true,
|
|
2717
|
-
projection: activity,
|
|
2718
|
-
getTurnContext: piTurnContext,
|
|
2719
|
-
...(effort ? { effort } : {}),
|
|
2720
|
-
onPiEvent,
|
|
2721
|
-
onRawRecord: onPiRawRecord,
|
|
2722
|
-
onFailure: onPiFailure,
|
|
2723
|
-
onStderr: onPiStderr,
|
|
2724
|
-
});
|
|
2725
|
-
bindPiExecutionCancel(opened.child, () => { void piFacade.cancel(); });
|
|
2726
|
-
runPiTurn(opened.child, null, opened.cleanup, () => {
|
|
2727
|
-
piSendStarted = true;
|
|
2728
|
-
return mapPiSend(opened.child, piFacade.send({ text: `${piSysPrompt}\n\n${piPrompt}` }, () => { }));
|
|
2729
|
-
});
|
|
2730
|
-
return { child: opened.child, promise: resultPromise };
|
|
2731
|
-
}
|
|
2732
|
-
const profileFp = crypto.createHmac('sha256', piProfileFingerprintKey)
|
|
2733
|
-
.update(profile.apiKey || '')
|
|
2734
|
-
.digest('hex')
|
|
2735
|
-
.slice(0, 12);
|
|
2736
|
-
mainRun.starting = true;
|
|
2737
|
-
let piAcquireStopReason;
|
|
2738
|
-
const cancelPiAcquire = (reason) => {
|
|
2739
|
-
piAcquireStopReason ??= reason;
|
|
2740
|
-
clearMainLiveRunOnStop(scopeKey, reason);
|
|
2741
|
-
};
|
|
2742
|
-
const finishPiAcquire = () => {
|
|
2743
|
-
mainRun.starting = false;
|
|
2744
|
-
if (mainRun.cancelPending === cancelPiAcquire)
|
|
2745
|
-
delete mainRun.cancelPending;
|
|
2746
|
-
};
|
|
2747
|
-
const abandonPiAcquire = () => {
|
|
2748
|
-
activity.close({ kind: 'turn-end', status: 'stopped', finalText: null });
|
|
2749
|
-
try {
|
|
2750
|
-
finalizeTraceRun(traceRunId, 'interrupted');
|
|
2751
|
-
}
|
|
2752
|
-
catch {
|
|
2753
|
-
console.warn('[runtime] Pi cancelled acquisition trace finalization failed');
|
|
2754
|
-
}
|
|
2755
|
-
if (activeMainProcesses.get(scopeKey) === mainRun) {
|
|
2756
|
-
if (getLiveRun(liveScope).traceRunId === traceRunId)
|
|
2757
|
-
clearLiveRun(liveScope);
|
|
2758
|
-
releaseMainRun(scopeKey, null, ownerGeneration);
|
|
2759
|
-
}
|
|
2760
|
-
settlePiExit();
|
|
2761
|
-
resolve(stoppedBeforeStart(piAcquireStopReason, traceRunId));
|
|
2762
|
-
};
|
|
2763
|
-
mainRun.cancelPending = cancelPiAcquire;
|
|
2764
|
-
void acquirePiRuntime({
|
|
2765
|
-
key: {
|
|
2766
|
-
scopeKey,
|
|
2767
|
-
cwd: spawnCwd,
|
|
2768
|
-
profileId: profile.id,
|
|
2769
|
-
fullEndpoint: profile.endpoint,
|
|
2770
|
-
apiKind: profile.apiKind,
|
|
2771
|
-
model: runtimeModel,
|
|
2772
|
-
effort,
|
|
2773
|
-
profileFp,
|
|
2774
|
-
},
|
|
2775
|
-
piSettings: pi,
|
|
2776
|
-
env: spawnEnv,
|
|
2777
|
-
storedSessionId: piSessionId || null,
|
|
2778
|
-
instructions: piSysPrompt,
|
|
2779
|
-
forceNew: forceNew || Boolean(slackToolGrant),
|
|
2780
|
-
}).then((lease) => {
|
|
2781
|
-
finishPiAcquire();
|
|
2782
|
-
if (piAcquireStopReason !== undefined || activeMainProcesses.get(scopeKey) !== mainRun || !isCurrentSessionOwner(persistenceOwner, scopeKey)) {
|
|
2783
|
-
lease.release();
|
|
2784
|
-
abandonPiAcquire();
|
|
2785
|
-
return;
|
|
2786
|
-
}
|
|
2787
|
-
ctx.sessionId = lease.session.sessionId;
|
|
2788
|
-
console.log(`[jaw:pi:pool] reused=${lease.reused} sessionId=${lease.session.sessionId || 'new'}`);
|
|
2789
|
-
piTurnChild = lease.session.child;
|
|
2790
|
-
piFacade = new PiRuntimeSession(lease.session, {
|
|
2791
|
-
lifetime: 'pooled',
|
|
2792
|
-
provider: 'pi',
|
|
2793
|
-
deferTurnEnd: true,
|
|
2794
|
-
projection: activity,
|
|
2795
|
-
getTurnContext: piTurnContext,
|
|
2796
|
-
...(effort ? { effort } : {}),
|
|
2797
|
-
onPiEvent,
|
|
2798
|
-
onRawRecord: onPiRawRecord,
|
|
2799
|
-
onFailure: onPiFailure,
|
|
2800
|
-
onStderr: onPiStderr,
|
|
2801
|
-
});
|
|
2802
|
-
runPiTurn(lease.session.child, lease, null, () => {
|
|
2803
|
-
piSendStarted = true;
|
|
2804
|
-
return mapPiSend(lease.session.child, piFacade.send({ text: piPrompt }, () => { }));
|
|
2805
|
-
});
|
|
2806
|
-
}).catch((err) => {
|
|
2807
|
-
finishPiAcquire();
|
|
2808
|
-
if (piAcquireStopReason !== undefined) {
|
|
2809
|
-
abandonPiAcquire();
|
|
2810
|
-
return;
|
|
2811
|
-
}
|
|
2812
|
-
console.error(`[jaw:pi:pool] acquire failed: ${err.message}`);
|
|
2813
|
-
activity.close({ kind: 'turn-end', status: 'error', finalText: null, error: 'Pi acquisition failed' });
|
|
2814
|
-
try {
|
|
2815
|
-
finalizeTraceRun(traceRunId, 'error', 'Pi acquisition failed');
|
|
2816
|
-
}
|
|
2817
|
-
catch {
|
|
2818
|
-
console.warn('[runtime] Pi acquisition trace finalization failed');
|
|
2819
|
-
}
|
|
2820
|
-
const ownsRun = activeMainProcesses.get(scopeKey) === mainRun;
|
|
2821
|
-
if (ownsRun) {
|
|
2822
|
-
clearLiveRun(liveScope);
|
|
2823
|
-
try {
|
|
2824
|
-
broadcast('agent_status', { running: false, agentId: agentLabel });
|
|
2825
|
-
broadcast('agent_done', { ...runPin, text: `❌ Pi RPC acquire failed: ${err.message}`, error: true, origin }, 'public');
|
|
2826
|
-
}
|
|
2827
|
-
catch {
|
|
2828
|
-
console.warn('[jaw:pi] acquisition diagnostic delivery failed');
|
|
2829
|
-
}
|
|
2830
|
-
releaseMainRun(scopeKey, null, ownerGeneration);
|
|
2831
|
-
}
|
|
2832
|
-
resolve({ text: '', code: 1 });
|
|
2833
|
-
if (ownsRun) {
|
|
2834
|
-
settlePiExit();
|
|
2835
|
-
void processQueue(scopeKey);
|
|
2836
|
-
}
|
|
2837
|
-
});
|
|
2838
|
-
return { child: null, promise: resultPromise };
|
|
2839
|
-
}
|
|
1610
|
+
if (cli === 'pi')
|
|
1611
|
+
return runPiBackend(backendLocals, backendHost);
|
|
2840
1612
|
// ─── Codex AppServer branch ────────────────────
|
|
2841
|
-
if (cli === 'codex-app')
|
|
2842
|
-
|
|
2843
|
-
if (catalogPath) {
|
|
2844
|
-
const verdict = validateModelEffort(model, effort, loadCatalogEfforts(catalogPath));
|
|
2845
|
-
if (!verdict.ok) {
|
|
2846
|
-
throw new Error(`[codex-app] ${verdict.error}`);
|
|
2847
|
-
}
|
|
2848
|
-
}
|
|
2849
|
-
if (mainManaged && !opts.internal && !opts._skipInsert) {
|
|
2850
|
-
insertMessage.run('user', prompt, cli, model, settings["workingDir"] || null, chatSessionId);
|
|
2851
|
-
}
|
|
2852
|
-
if (!opts.internal)
|
|
2853
|
-
broadcast('agent_status', { status: 'running', cli, agentId: agentLabel, ...empTag }, traceAudience);
|
|
2854
|
-
const traceRunId = startTraceRun({ cli, model, workingDir: settings["workingDir"] || null, agentLabel, audience: traceAudience, sessionId: chatSessionId, scopeKey });
|
|
2855
|
-
if (mainManaged && !opts.internal)
|
|
2856
|
-
setLiveRunTraceId(liveScope, traceRunId);
|
|
2857
|
-
const ctx = {
|
|
2858
|
-
fullText: '', traceLog: [], toolLog: [], seenToolKeys: new Set(),
|
|
2859
|
-
hasClaudeStreamEvents: false, sessionId: null, cost: null,
|
|
2860
|
-
turns: null, duration: null, tokens: null, stderrBuf: '',
|
|
2861
|
-
thinkingBuf: '',
|
|
2862
|
-
runStartedAt: Date.now(),
|
|
2863
|
-
...(opts.requestId ? { requestId: opts.requestId } : {}),
|
|
2864
|
-
...(origin ? { origin } : {}),
|
|
2865
|
-
liveScope: effectiveLiveScope,
|
|
2866
|
-
parentLiveScope: parentLiveScopeForChild,
|
|
2867
|
-
traceRunId,
|
|
2868
|
-
traceAudience,
|
|
2869
|
-
activityIdentity: { sessionId: chatSessionId, scope: scopeKey },
|
|
2870
|
-
};
|
|
2871
|
-
const activity = new RuntimeProjection({
|
|
2872
|
-
runId: traceRunId, sessionId: chatSessionId, scope: scopeKey,
|
|
2873
|
-
turnId: traceRunId, audience: traceAudience,
|
|
2874
|
-
...(opts.runtimeParentItemId ? { parentItemId: opts.runtimeParentItemId } : {}),
|
|
2875
|
-
});
|
|
2876
|
-
const codexProjection = new CodexProjection(activity);
|
|
2877
|
-
activity.start('codex-app');
|
|
2878
|
-
function flushCodexAppThinking() {
|
|
2879
|
-
if (!ctx.thinkingBuf)
|
|
2880
|
-
return;
|
|
2881
|
-
const merged = ctx.thinkingBuf.trim();
|
|
2882
|
-
if (merged) {
|
|
2883
|
-
const singleLine = merged.replace(/\s+/g, ' ').trim();
|
|
2884
|
-
const label = singleLine.length > 120 ? `${singleLine.slice(0, 119)}…` : singleLine;
|
|
2885
|
-
console.log(` 💭 ${label}`);
|
|
2886
|
-
const tool = { icon: '💭', label, toolType: 'thinking', detail: merged };
|
|
2887
|
-
stampTraceTool(tool, ctx, 'thinking');
|
|
2888
|
-
ctx.toolLog.push(tool);
|
|
2889
|
-
if (ctx.liveScope)
|
|
2890
|
-
replaceLiveRunTools(ctx.liveScope, ctx.toolLog);
|
|
2891
|
-
appendParentLiveRunTool(ctx, tool);
|
|
2892
|
-
emitAgentTool(ctx, agentLabel, tool, empTag);
|
|
2893
|
-
}
|
|
2894
|
-
ctx.thinkingBuf = '';
|
|
2895
|
-
}
|
|
2896
|
-
let lastVisibleBroadcastTs = Date.now();
|
|
2897
|
-
let heartbeatSent = false;
|
|
2898
|
-
let turnCompleted = false;
|
|
2899
|
-
let turnReportedFailure = false;
|
|
2900
|
-
let markCodexProgress = () => { };
|
|
2901
|
-
let settleTurn;
|
|
2902
|
-
let rejectTurn;
|
|
2903
|
-
const turnDone = new Promise((resolveTurn, rejectTurnPromise) => {
|
|
2904
|
-
settleTurn = resolveTurn;
|
|
2905
|
-
rejectTurn = rejectTurnPromise;
|
|
2906
|
-
});
|
|
2907
|
-
const consumeCodexAppEvent = (method, parsed) => {
|
|
2908
|
-
if (!parsed) {
|
|
2909
|
-
if (method === 'turn/completed')
|
|
2910
|
-
settleTurn();
|
|
2911
|
-
return;
|
|
2912
|
-
}
|
|
2913
|
-
if (parsed.flushThinking) {
|
|
2914
|
-
flushCodexAppThinking();
|
|
2915
|
-
}
|
|
2916
|
-
if (parsed.tool) {
|
|
2917
|
-
const parsedTool = parsed.tool;
|
|
2918
|
-
if (parsedTool.icon === '💭') {
|
|
2919
|
-
ctx.thinkingBuf += parsedTool.detail || parsedTool.label;
|
|
2920
|
-
return;
|
|
2921
|
-
}
|
|
2922
|
-
flushCodexAppThinking();
|
|
2923
|
-
const key = `${parsedTool.icon}:${parsedTool.label}:${parsedTool.stepRef || ''}:${parsedTool.status || ''}`;
|
|
2924
|
-
if (!ctx.seenToolKeys.has(key)) {
|
|
2925
|
-
ctx.seenToolKeys.add(key);
|
|
2926
|
-
stampTraceTool(parsedTool, ctx, parsedTool.toolType || 'tool');
|
|
2927
|
-
ctx.toolLog.push(parsedTool);
|
|
2928
|
-
if (ctx.liveScope)
|
|
2929
|
-
replaceLiveRunTools(ctx.liveScope, ctx.toolLog);
|
|
2930
|
-
appendParentLiveRunTool(ctx, parsedTool);
|
|
2931
|
-
emitAgentTool(ctx, agentLabel, parsedTool, empTag);
|
|
2932
|
-
lastVisibleBroadcastTs = Date.now();
|
|
2933
|
-
heartbeatSent = false;
|
|
2934
|
-
}
|
|
2935
|
-
}
|
|
2936
|
-
// Sticky channel/item bookkeeping and the durable-vs-live decision live
|
|
2937
|
-
// in applyCodexAppTextEvent so they can be tested without a live runtime.
|
|
2938
|
-
if (parsed.text)
|
|
2939
|
-
flushCodexAppThinking();
|
|
2940
|
-
const textDecision = applyCodexAppTextEvent(ctx, parsed);
|
|
2941
|
-
if (textDecision.durable) {
|
|
2942
|
-
// codex-app streams item/agentMessage/delta at TOKEN granularity;
|
|
2943
|
-
// the segment formatter would inject "\n- " between unjoined
|
|
2944
|
-
// tokens ("이"+"지만" → "이\n- 지만"). Raw-append like the plain
|
|
2945
|
-
// `claude` text_delta path (events/index.ts) instead.
|
|
2946
|
-
const segment = appendAssistantRawText(ctx, textDecision.durable);
|
|
2947
|
-
if (segment) {
|
|
2948
|
-
broadcastAgentOutput(ctx, agentLabel, cli, segment, empTag, traceAudience);
|
|
2949
|
-
lastVisibleBroadcastTs = Date.now();
|
|
2950
|
-
heartbeatSent = false;
|
|
2951
|
-
}
|
|
2952
|
-
}
|
|
2953
|
-
else if (textDecision.live) {
|
|
2954
|
-
// Commentary is a transient progress update, not part of the durable
|
|
2955
|
-
// response. Broadcast it for live UI preview but keep it out of
|
|
2956
|
-
// fullText — that way agent_done (and therefore Slack/Telegram/
|
|
2957
|
-
// Discord delivery) contains only the final answer.
|
|
2958
|
-
broadcastAgentOutput(ctx, agentLabel, cli, textDecision.live, empTag, traceAudience);
|
|
2959
|
-
}
|
|
2960
|
-
if (parsed.sessionId && !ctx.sessionId) {
|
|
2961
|
-
ctx.sessionId = parsed.sessionId;
|
|
2962
|
-
}
|
|
2963
|
-
if (parsed.tokens) {
|
|
2964
|
-
ctx.tokens = parsed.tokens;
|
|
2965
|
-
}
|
|
2966
|
-
if (parsed.turnStatus && parsed.turnStatus !== 'completed') {
|
|
2967
|
-
console.warn(`[codex-app:turn] final status: ${parsed.turnStatus}`);
|
|
2968
|
-
turnReportedFailure = true;
|
|
2969
|
-
}
|
|
2970
|
-
opts.lifecycle?.onActivity?.('codex-app');
|
|
2971
|
-
if (method === 'turn/completed')
|
|
2972
|
-
settleTurn();
|
|
2973
|
-
};
|
|
2974
|
-
const handleStderr = (text) => {
|
|
2975
|
-
appendTraceEvent({ runId: ctx.traceRunId, source: 'stderr', eventType: 'stderr', raw: text });
|
|
2976
|
-
if (ctx.stderrBuf.length < 4000) {
|
|
2977
|
-
ctx.stderrBuf += text + '\n';
|
|
2978
|
-
}
|
|
2979
|
-
opts.lifecycle?.onActivity?.('stderr');
|
|
2980
|
-
if (shouldEmitHeartbeat(lastVisibleBroadcastTs, heartbeatSent)) {
|
|
2981
|
-
heartbeatSent = true;
|
|
2982
|
-
const elapsed = Math.round((Date.now() - lastVisibleBroadcastTs) / 1000);
|
|
2983
|
-
console.log(` ⏳ agent active (no visible event for ${elapsed}s)`);
|
|
2984
|
-
emitAgentTool(ctx, agentLabel, {
|
|
2985
|
-
icon: '⏳',
|
|
2986
|
-
label: 'working... (no visible progress)',
|
|
2987
|
-
}, empTag);
|
|
2988
|
-
}
|
|
2989
|
-
};
|
|
2990
|
-
const effectiveFastMode = cfg.fastMode ?? settings["perCli"]?.["codex"]?.fastMode ?? false;
|
|
2991
|
-
const runCodexAppTurn = async (appClient, lease, laneScope) => {
|
|
2992
|
-
const child = appClient.proc;
|
|
2993
|
-
if (!child)
|
|
2994
|
-
throw new Error('Codex AppServer process was not created');
|
|
2995
|
-
if (mainManaged)
|
|
2996
|
-
mainRun.process = child;
|
|
2997
|
-
else
|
|
2998
|
-
registerActiveProcess(agentLabel, child);
|
|
2999
|
-
if (!opts.internal)
|
|
3000
|
-
broadcast('agent_status', { running: true, agentId: agentLabel, cli, ...empTag });
|
|
3001
|
-
const processExit = { value: null };
|
|
3002
|
-
// The one documented cross-runtime exception. codex-app keeps a 300s idle
|
|
3003
|
-
// bound where the shared watchdog defaults to 90s, because an appserver turn
|
|
3004
|
-
// reports sparse STRUCTURED progress instead of a continuous stream and
|
|
3005
|
-
// nothing here counts stdout as liveness. settings.agentTimeout deliberately
|
|
3006
|
-
// does not narrow it either: a global idleMs written for print CLIs would
|
|
3007
|
-
// cut the default runtime's bound by two thirds. The env overrides remain
|
|
3008
|
-
// the supported knob and are mapped into the watchdog config below.
|
|
3009
|
-
const idleMs = configuredPositiveMs(process.env["CODEX_APP_TURN_IDLE_MS"], DEFAULT_CODEX_APP_TURN_IDLE_MS);
|
|
3010
|
-
const absoluteMs = configuredPositiveMs(process.env["CODEX_APP_TURN_ABS_MS"], DEFAULT_CODEX_APP_TURN_ABS_MS);
|
|
3011
|
-
let watchdogCancel = null;
|
|
3012
|
-
let leaseCancel = null;
|
|
3013
|
-
const requestLeaseCancel = () => {
|
|
3014
|
-
if (!lease) {
|
|
3015
|
-
appClient.kill();
|
|
3016
|
-
return Promise.resolve();
|
|
3017
|
-
}
|
|
3018
|
-
leaseCancel ??= lease.cancel().catch((err) => {
|
|
3019
|
-
console.warn('[codex-app:turn] cancel failed:', err.message);
|
|
3020
|
-
});
|
|
3021
|
-
return leaseCancel;
|
|
3022
|
-
};
|
|
3023
|
-
const cancelHook = (_reason) => { void requestLeaseCancel(); };
|
|
3024
|
-
if (lease && mainRun)
|
|
3025
|
-
mainRun.cancelTurn = cancelHook;
|
|
3026
|
-
// Same-turn steer (turn/steer) for the duration of THIS turn only:
|
|
3027
|
-
// installed and torn down with cancelHook so capability reads never
|
|
3028
|
-
// outlive the steerable window.
|
|
3029
|
-
const steerHook = async (text) => {
|
|
3030
|
-
try {
|
|
3031
|
-
await appClient.steerTurn(laneScope, text, { clientUserMessageId: crypto.randomUUID() });
|
|
3032
|
-
return 'steered';
|
|
3033
|
-
}
|
|
3034
|
-
catch (err) {
|
|
3035
|
-
if (err instanceof CodexSteerError) {
|
|
3036
|
-
// review/compact turns reject steer; a mismatched/finished
|
|
3037
|
-
// turn raced us. Both are queue-fallback territory.
|
|
3038
|
-
return err.code === 'not-steerable' ? 'rejected' : 'unavailable';
|
|
3039
|
-
}
|
|
3040
|
-
throw err;
|
|
3041
|
-
}
|
|
3042
|
-
};
|
|
3043
|
-
if (mainRun)
|
|
3044
|
-
mainRun.steerTurnInBand = steerHook;
|
|
3045
|
-
// The same watchdog every other runtime uses. The bespoke timer pair this
|
|
3046
|
-
// replaces never set `ctx.stallReason`, and that field is the ONLY trigger
|
|
3047
|
-
// for the #405 truncation notice (`error-classifier.ts:149-159`). So on the
|
|
3048
|
-
// DEFAULT runtime a timeout surfaced as a generic error and the #405 fix
|
|
3049
|
-
// never applied to the path most turns actually take.
|
|
3050
|
-
const turnWatchdog = attachWatchdog(child, agentLabel, reason => {
|
|
3051
|
-
if (watchdogCancel)
|
|
3052
|
-
return;
|
|
3053
|
-
console.warn(`[codex-app:turn] watchdog stall (${reason}, idleMs=${idleMs}, absoluteMs=${absoluteMs})`);
|
|
3054
|
-
ctx.stallReason = reason;
|
|
3055
|
-
watchdogCancel = requestLeaseCancel();
|
|
3056
|
-
rejectTurn(new Error(`Codex AppServer turn watchdog timeout: ${reason}`));
|
|
3057
|
-
}, {
|
|
3058
|
-
// This child's stdout IS the JSON-RPC stream and readline already owns
|
|
3059
|
-
// it, so counting raw traffic as liveness would keep a wedged turn
|
|
3060
|
-
// looking alive forever. Progress comes from the turn adapter instead.
|
|
3061
|
-
observeStdio: false,
|
|
3062
|
-
// The two bespoke timers map onto the watchdog's two real gates:
|
|
3063
|
-
// markProgress slides absoluteDeadline to now+absoluteMs, giving
|
|
3064
|
-
// "no progress for idleMs", capped at startedAt+absoluteHardCapMs,
|
|
3065
|
-
// giving "total run beyond absoluteMs". firstProgressMs bounds a turn
|
|
3066
|
-
// that never reports at all.
|
|
3067
|
-
firstProgressMs: idleMs, idleMs, absoluteMs: idleMs, absoluteHardCapMs: absoluteMs,
|
|
3068
|
-
});
|
|
3069
|
-
ctx.stallWatchdog = turnWatchdog;
|
|
3070
|
-
markCodexProgress = () => { turnWatchdog.markProgress(); };
|
|
3071
|
-
const listener = listenCodexAppTurnAdapter(appClient, lease, laneScope, ctx, {
|
|
3072
|
-
onProgress: () => { markCodexProgress(); },
|
|
3073
|
-
onRawNotification: (method, params) => {
|
|
3074
|
-
if (method === 'turn/completed' || method === 'turn/started' || method === 'error') {
|
|
3075
|
-
console.log(`[codex-app:notify] ${method}`);
|
|
3076
|
-
}
|
|
3077
|
-
appendTraceEvent({ runId: ctx.traceRunId, source: 'codex_app_raw', eventType: method, raw: params });
|
|
3078
|
-
},
|
|
3079
|
-
onDiagnosticNotification: (entry) => {
|
|
3080
|
-
appendTraceEvent({
|
|
3081
|
-
runId: ctx.traceRunId,
|
|
3082
|
-
source: 'codex_app_raw',
|
|
3083
|
-
eventType: 'unrouted-notification',
|
|
3084
|
-
raw: entry,
|
|
3085
|
-
});
|
|
3086
|
-
},
|
|
3087
|
-
onEvent: consumeCodexAppEvent,
|
|
3088
|
-
onProjectionNotification: (method, params, parsed) => {
|
|
3089
|
-
codexProjection.observe(method, params, parsed, ctx.codexAppActiveChannel || '');
|
|
3090
|
-
},
|
|
3091
|
-
onStderr: handleStderr,
|
|
3092
|
-
onExit: (code, signal) => {
|
|
3093
|
-
processExit.value = { code, signal };
|
|
3094
|
-
rejectTurn(new Error(`Codex AppServer exited (code=${code}, signal=${signal})`));
|
|
3095
|
-
},
|
|
3096
|
-
onError: rejectTurn,
|
|
3097
|
-
onInterruptFailed: (err) => {
|
|
3098
|
-
console.warn(`[codex-app:interrupt] ${err.message}`);
|
|
3099
|
-
},
|
|
3100
|
-
});
|
|
3101
|
-
try {
|
|
3102
|
-
if (lease) {
|
|
3103
|
-
ctx.sessionId = lease.threadId;
|
|
3104
|
-
console.log(`[codex-app:pool] thread=${lease.threadId.slice(0, 12)}... reused=${lease.reused} resumed=${lease.resumedThread}`);
|
|
3105
|
-
}
|
|
3106
|
-
else {
|
|
3107
|
-
const initResult = await appClient.initialize();
|
|
3108
|
-
if (process.env["DEBUG"])
|
|
3109
|
-
console.log('[codex-app:init]', JSON.stringify(initResult).slice(0, 200));
|
|
3110
|
-
const threadOptions = {
|
|
3111
|
-
model,
|
|
3112
|
-
effort,
|
|
3113
|
-
cwd: spawnCwd,
|
|
3114
|
-
fastMode: effectiveFastMode,
|
|
3115
|
-
instructions: sysPrompt,
|
|
3116
|
-
};
|
|
3117
|
-
if (isResume && resumeSessionId) {
|
|
3118
|
-
try {
|
|
3119
|
-
await appClient.resumeThread(laneScope, resumeSessionId, threadOptions);
|
|
3120
|
-
console.log(`[codex-app:session] resumeThread OK: ${resumeSessionId.slice(0, 12)}...`);
|
|
3121
|
-
}
|
|
3122
|
-
catch (resumeErr) {
|
|
3123
|
-
const message = resumeErr.message || '';
|
|
3124
|
-
if (!isRecoverableResumeError(message))
|
|
3125
|
-
throw resumeErr;
|
|
3126
|
-
console.warn(`[codex-app:session] resumeThread FAILED (recoverable): ${message} — starting new thread`);
|
|
3127
|
-
if (empSid && opts.agentId)
|
|
3128
|
-
clearEmployeeSession.run(opts.agentId);
|
|
3129
|
-
await appClient.startThread(laneScope, threadOptions);
|
|
3130
|
-
}
|
|
3131
|
-
}
|
|
3132
|
-
else {
|
|
3133
|
-
await appClient.startThread(laneScope, threadOptions);
|
|
3134
|
-
}
|
|
3135
|
-
ctx.sessionId = appClient.getThreadId(laneScope) ?? '';
|
|
3136
|
-
}
|
|
3137
|
-
const shouldPrependHistory = lease
|
|
3138
|
-
? !(lease.resumedThread || lease.reused)
|
|
3139
|
-
: !(isResume && Boolean(resumeSessionId));
|
|
3140
|
-
const codexAppPrompt = (shouldPrependHistory && historyBlock)
|
|
3141
|
-
? `${historyBlock}\n\n[User Message]\n${prompt}`
|
|
3142
|
-
: prompt;
|
|
3143
|
-
const codexAppPromptWithSteer = withSteerContext(codexAppPrompt, opts.steerContext);
|
|
3144
|
-
const startTurn = appClient.startTurn(laneScope, codexAppPromptWithSteer);
|
|
3145
|
-
await Promise.race([startTurn, turnDone]);
|
|
3146
|
-
await turnDone;
|
|
3147
|
-
turnCompleted = !turnReportedFailure;
|
|
3148
|
-
flushCodexAppThinking();
|
|
3149
|
-
const persistedThreadId = lease?.threadId ?? appClient.getThreadId(laneScope);
|
|
3150
|
-
if (persistedThreadId && persistMainSession(stripUndefined({
|
|
3151
|
-
persistenceOwner,
|
|
3152
|
-
scopeKey,
|
|
3153
|
-
forceNew,
|
|
3154
|
-
employeeSessionId: empSid,
|
|
3155
|
-
sessionId: persistedThreadId,
|
|
3156
|
-
isFallback: opts._isFallback,
|
|
3157
|
-
cli,
|
|
3158
|
-
model,
|
|
3159
|
-
resumeKey,
|
|
3160
|
-
effort: cfg.effort || '',
|
|
3161
|
-
skipSessionPersist: opts._skipSessionPersist === true,
|
|
3162
|
-
...(lease?.bucketKey ? { codexAppBucket: lease.bucketKey } : {}),
|
|
3163
|
-
// With multiplex off there is no lease bucket key, and without this the
|
|
3164
|
-
// save lands on the bare `codex-app` row that belongs to the default
|
|
3165
|
-
// session. codex-app is the default runtime, so that is the common case.
|
|
3166
|
-
runtimeTransport,
|
|
3167
|
-
scopedBucket: currentBucket,
|
|
3168
|
-
}))) {
|
|
3169
|
-
console.log(`[jaw:session] saved ${cli} session=${persistedThreadId.slice(0, 12)}... (pre-shutdown)`);
|
|
3170
|
-
}
|
|
3171
|
-
}
|
|
3172
|
-
catch (err) {
|
|
3173
|
-
console.error(`[codex-app:error] ${err.message}`);
|
|
3174
|
-
if (ctx.stderrBuf.length < 4000)
|
|
3175
|
-
ctx.stderrBuf += err.message;
|
|
3176
|
-
if (!lease)
|
|
3177
|
-
appClient.kill();
|
|
3178
|
-
}
|
|
3179
|
-
finally {
|
|
3180
|
-
turnWatchdog.stop();
|
|
3181
|
-
markCodexProgress = () => { };
|
|
3182
|
-
if (watchdogCancel)
|
|
3183
|
-
await watchdogCancel;
|
|
3184
|
-
if (mainRun?.cancelTurn === cancelHook)
|
|
3185
|
-
delete mainRun.cancelTurn;
|
|
3186
|
-
if (mainRun?.steerTurnInBand === steerHook)
|
|
3187
|
-
delete mainRun.steerTurnInBand;
|
|
3188
|
-
listener.dispose();
|
|
3189
|
-
if (lease)
|
|
3190
|
-
lease.release();
|
|
3191
|
-
else {
|
|
3192
|
-
await appClient.closeGracefully();
|
|
3193
|
-
appClient.cleanup();
|
|
3194
|
-
cleanupEmployeeTmpDir(spawnCwd, settings["workingDir"], agentLabel);
|
|
3195
|
-
}
|
|
3196
|
-
}
|
|
3197
|
-
// A turn that never completed is a failure even when the process it
|
|
3198
|
-
// was running on exited cleanly. Trusting the child's status here
|
|
3199
|
-
// reports success for a turn that produced nothing, which is exactly
|
|
3200
|
-
// what happens when a shared host is closed mid-turn.
|
|
3201
|
-
const exitCode = turnCompleted ? 0 : (processExit.value?.code || 1);
|
|
3202
|
-
opts.lifecycle?.onExit?.(exitCode);
|
|
3203
|
-
const killReason = consumeKillReason(child.pid);
|
|
3204
|
-
if (processExit.value && processExit.value.code !== 0 && !killReason) {
|
|
3205
|
-
console.warn(`[codex-app:unexpected-exit] code=${processExit.value.code} signal=${processExit.value.signal} threadId=${ctx.sessionId || 'none'}`);
|
|
3206
|
-
}
|
|
3207
|
-
const wasKilled = !!killReason;
|
|
3208
|
-
const wasSteer = isLifecycleSteerReason(killReason);
|
|
3209
|
-
flushCodexAppThinking();
|
|
3210
|
-
const smokeResult = detectSmokeResponse(ctx.fullText, ctx.toolLog, exitCode, cli);
|
|
3211
|
-
await handleAgentExit({
|
|
3212
|
-
onRuntimeEnd: (end) => { activity.close(end); },
|
|
3213
|
-
ctx, code: exitCode, cli, model, agentLabel, mainManaged, origin,
|
|
3214
|
-
killReason,
|
|
3215
|
-
resumeKey,
|
|
3216
|
-
prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
|
|
3217
|
-
isResume, wasKilled, wasSteer, smokeResult,
|
|
3218
|
-
effortDefault: '', costLine: '',
|
|
3219
|
-
resolve: resolve,
|
|
3220
|
-
activeProcesses,
|
|
3221
|
-
scopeKey,
|
|
3222
|
-
runtimeTransport,
|
|
3223
|
-
scopedBucket: currentBucket,
|
|
3224
|
-
chatSessionId,
|
|
3225
|
-
...(lease?.bucketKey ? { codexAppBucket: lease.bucketKey } : {}),
|
|
3226
|
-
childProcess: child,
|
|
3227
|
-
releaseMainRun,
|
|
3228
|
-
retryState: queueCtrl.retryStateForScope(scopeKey),
|
|
3229
|
-
fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
|
|
3230
|
-
fallbackMaxRetries: FALLBACK_MAX_RETRIES,
|
|
3231
|
-
processQueue,
|
|
3232
|
-
}).catch((err) => {
|
|
3233
|
-
activity.close({ kind: 'turn-end', status: 'error', finalText: null, error: 'Lifecycle failed' });
|
|
3234
|
-
console.error('[jaw:lifecycle] handleAgentExit failed (codex-app):', err.message);
|
|
3235
|
-
}).finally(() => settleExit(scopeKey));
|
|
3236
|
-
};
|
|
3237
|
-
if (opts.agentId) {
|
|
3238
|
-
const employeeLaneScope = `employee:${opts.agentId}`;
|
|
3239
|
-
const appClient = new CodexAppClient({
|
|
3240
|
-
binary: detected.path || 'codex', workDir: spawnCwd, env: spawnEnv,
|
|
3241
|
-
});
|
|
3242
|
-
let child;
|
|
3243
|
-
try {
|
|
3244
|
-
appClient.spawn();
|
|
3245
|
-
if (!appClient.proc)
|
|
3246
|
-
throw new Error('Codex AppServer process was not created');
|
|
3247
|
-
child = appClient.proc;
|
|
3248
|
-
}
|
|
3249
|
-
catch (error) {
|
|
3250
|
-
activity.close({ kind: 'turn-end', status: 'error', finalText: null, error: 'Codex process creation failed' });
|
|
3251
|
-
finalizeTraceRun(traceRunId, 'error', 'Codex process creation failed');
|
|
3252
|
-
cleanupEmployeeTmpDir(spawnCwd, settings["workingDir"], agentLabel);
|
|
3253
|
-
throw error;
|
|
3254
|
-
}
|
|
3255
|
-
void runCodexAppTurn(appClient, null, employeeLaneScope);
|
|
3256
|
-
return { child, promise: resultPromise };
|
|
3257
|
-
}
|
|
3258
|
-
mainRun.starting = true;
|
|
3259
|
-
let codexAcquireStopReason;
|
|
3260
|
-
const cancelCodexAcquire = (reason) => { codexAcquireStopReason ??= reason; };
|
|
3261
|
-
const releaseCodexAcquireHook = () => {
|
|
3262
|
-
if (mainRun.cancelPending === cancelCodexAcquire)
|
|
3263
|
-
delete mainRun.cancelPending;
|
|
3264
|
-
};
|
|
3265
|
-
mainRun.cancelPending = cancelCodexAcquire;
|
|
3266
|
-
const acquireCodexAppForTurn = async () => {
|
|
3267
|
-
const acquireWasCancelled = () => codexAcquireStopReason !== undefined || activeMainProcesses.get(scopeKey) !== mainRun;
|
|
3268
|
-
try {
|
|
3269
|
-
if (!codexMultiplexMain) {
|
|
3270
|
-
const lease = await acquireCodexAppRuntime({
|
|
3271
|
-
binary: detected.path || 'codex', env: spawnEnv,
|
|
3272
|
-
route: 'legacy',
|
|
3273
|
-
key: {
|
|
3274
|
-
scopeKey,
|
|
3275
|
-
cwd: spawnCwd, model, effort, fastMode: effectiveFastMode,
|
|
3276
|
-
},
|
|
3277
|
-
storedThreadId: resumeSessionId || null,
|
|
3278
|
-
instructions: sysPrompt,
|
|
3279
|
-
forceNew: forceNew || Boolean(slackToolGrant),
|
|
3280
|
-
});
|
|
3281
|
-
return { kind: 'lease', lease };
|
|
3282
|
-
}
|
|
3283
|
-
const waitMs = configuredPositiveMs(process.env["CODEX_APP_ACQUIRE_WAIT_MS"], DEFAULT_CODEX_APP_ACQUIRE_WAIT_MS);
|
|
3284
|
-
const deadlineAt = Date.now() + waitMs;
|
|
3285
|
-
let lastStaleError = null;
|
|
3286
|
-
let staleAttempts = 0;
|
|
3287
|
-
const deadlineError = (stage) => lastStaleError
|
|
3288
|
-
?? new Error(`Codex App ${stage} timed out after ${waitMs}ms`);
|
|
3289
|
-
const awaitWithinDeadline = async (stage, pending, onLateValue) => {
|
|
3290
|
-
let deadlineWon = false;
|
|
3291
|
-
let timeout;
|
|
3292
|
-
void pending.then((value) => {
|
|
3293
|
-
if (deadlineWon)
|
|
3294
|
-
onLateValue?.(value);
|
|
3295
|
-
}, () => { });
|
|
3296
|
-
const remainingMs = deadlineAt - Date.now();
|
|
3297
|
-
if (remainingMs <= 0) {
|
|
3298
|
-
deadlineWon = true;
|
|
3299
|
-
throw deadlineError(stage);
|
|
3300
|
-
}
|
|
3301
|
-
const deadline = new Promise((_resolveDeadline, rejectDeadline) => {
|
|
3302
|
-
timeout = setTimeout(() => {
|
|
3303
|
-
deadlineWon = true;
|
|
3304
|
-
rejectDeadline(deadlineError(stage));
|
|
3305
|
-
}, remainingMs);
|
|
3306
|
-
});
|
|
3307
|
-
try {
|
|
3308
|
-
return await Promise.race([pending, deadline]);
|
|
3309
|
-
}
|
|
3310
|
-
finally {
|
|
3311
|
-
if (timeout)
|
|
3312
|
-
clearTimeout(timeout);
|
|
3313
|
-
}
|
|
3314
|
-
};
|
|
3315
|
-
for (;;) {
|
|
3316
|
-
if (acquireWasCancelled())
|
|
3317
|
-
return { kind: 'cancelled', reason: codexAcquireStopReason };
|
|
3318
|
-
// Check the budget before spawning more work. awaitWithinDeadline()
|
|
3319
|
-
// only measures what remains once the promise already exists, so a
|
|
3320
|
-
// backoff that consumed the last of the budget would still get to
|
|
3321
|
-
// start one more prepare.
|
|
3322
|
-
if (deadlineAt - Date.now() <= 0)
|
|
3323
|
-
throw lastStaleError ?? deadlineError('prepare');
|
|
3324
|
-
try {
|
|
3325
|
-
const prepared = await awaitWithinDeadline('prepare', prepareCodexAppHost({
|
|
3326
|
-
binary: detected.path || 'codex', cwd: spawnCwd,
|
|
3327
|
-
fastMode: effectiveFastMode, env: spawnEnv, model, effort,
|
|
3328
|
-
}));
|
|
3329
|
-
if (acquireWasCancelled())
|
|
3330
|
-
return { kind: 'cancelled', reason: codexAcquireStopReason };
|
|
3331
|
-
const lease = await awaitWithinDeadline('acquire', acquireCodexAppLane(prepared, {
|
|
3332
|
-
scopeKey,
|
|
3333
|
-
bucketKey: currentBucket,
|
|
3334
|
-
storedThreadId: resumeSessionId || null,
|
|
3335
|
-
instructions: sysPrompt,
|
|
3336
|
-
forceNew: forceNew || Boolean(slackToolGrant),
|
|
3337
|
-
waitMs: deadlineAt - Date.now(),
|
|
3338
|
-
}), (lateLease) => { lateLease.release(); });
|
|
3339
|
-
if (acquireWasCancelled()) {
|
|
3340
|
-
lease.release();
|
|
3341
|
-
return { kind: 'cancelled', reason: codexAcquireStopReason };
|
|
3342
|
-
}
|
|
3343
|
-
return { kind: 'lease', lease };
|
|
3344
|
-
}
|
|
3345
|
-
catch (err) {
|
|
3346
|
-
if (!(err instanceof CodexHostGenerationStaleError))
|
|
3347
|
-
throw err;
|
|
3348
|
-
lastStaleError = err;
|
|
3349
|
-
if (acquireWasCancelled())
|
|
3350
|
-
return { kind: 'cancelled', reason: codexAcquireStopReason };
|
|
3351
|
-
const remainingMs = deadlineAt - Date.now();
|
|
3352
|
-
if (remainingMs <= 0)
|
|
3353
|
-
throw lastStaleError;
|
|
3354
|
-
staleAttempts += 1;
|
|
3355
|
-
const backoffMs = Math.min(remainingMs, CODEX_APP_ACQUIRE_RETRY_BACKOFF_MAX_MS, 25 * staleAttempts);
|
|
3356
|
-
await new Promise((done) => { setTimeout(done, backoffMs); });
|
|
3357
|
-
}
|
|
3358
|
-
}
|
|
3359
|
-
}
|
|
3360
|
-
finally {
|
|
3361
|
-
// The next promise continuation still owns the acquired lease.
|
|
3362
|
-
// Keep its cancellation hook until it hands off or abandons it.
|
|
3363
|
-
mainRun.starting = false;
|
|
3364
|
-
}
|
|
3365
|
-
};
|
|
3366
|
-
// A run that never started a turn owns nothing but its own map slot.
|
|
3367
|
-
// releaseMainRun() matches on (process, ownerGeneration), and a pending
|
|
3368
|
-
// run has process=null while sharing the global generation with whatever
|
|
3369
|
-
// replaced it, so calling it here would delete the replacement's entry.
|
|
3370
|
-
// Compare the captured object instead and only drop our own slot.
|
|
3371
|
-
const abandonTurn = (lease) => {
|
|
3372
|
-
lease?.release();
|
|
3373
|
-
activity.close({ kind: 'turn-end', status: 'stopped', finalText: null });
|
|
3374
|
-
finalizeTraceRun(traceRunId, 'interrupted');
|
|
3375
|
-
if (!activeMainProcesses.has(scopeKey) || activeMainProcesses.get(scopeKey) === mainRun) {
|
|
3376
|
-
const currentLive = getLiveRun(liveScope);
|
|
3377
|
-
const ownsLive = currentLive.traceRunId === traceRunId;
|
|
3378
|
-
if (ownsLive)
|
|
3379
|
-
clearLiveRun(liveScope);
|
|
3380
|
-
if (ownsLive || (!currentLive.running && currentLive.traceRunId === undefined)) {
|
|
3381
|
-
broadcast('agent_status', { running: false, agentId: agentLabel });
|
|
3382
|
-
}
|
|
3383
|
-
}
|
|
3384
|
-
resolve(stoppedBeforeStart(codexAcquireStopReason, traceRunId));
|
|
3385
|
-
if (activeMainProcesses.get(scopeKey) === mainRun)
|
|
3386
|
-
activeMainProcesses.delete(scopeKey);
|
|
3387
|
-
void processQueue(scopeKey);
|
|
3388
|
-
};
|
|
3389
|
-
void acquireCodexAppForTurn().then(async (outcome) => {
|
|
3390
|
-
releaseCodexAcquireHook();
|
|
3391
|
-
if (outcome.kind === 'cancelled') {
|
|
3392
|
-
abandonTurn(null);
|
|
3393
|
-
return;
|
|
3394
|
-
}
|
|
3395
|
-
const lease = outcome.lease;
|
|
3396
|
-
if (codexAcquireStopReason !== undefined || activeMainProcesses.get(scopeKey) !== mainRun) {
|
|
3397
|
-
abandonTurn(lease);
|
|
3398
|
-
return;
|
|
3399
|
-
}
|
|
3400
|
-
await runCodexAppTurn(lease.client, lease, lease.laneScope);
|
|
3401
|
-
}).catch((err) => {
|
|
3402
|
-
releaseCodexAcquireHook();
|
|
3403
|
-
if (codexAcquireStopReason !== undefined) {
|
|
3404
|
-
abandonTurn(null);
|
|
3405
|
-
return;
|
|
3406
|
-
}
|
|
3407
|
-
console.error(`[codex-app:pool] acquire failed: ${err.message}`);
|
|
3408
|
-
activity.close({ kind: 'turn-end', status: 'error', finalText: null, error: 'Codex acquisition failed' });
|
|
3409
|
-
try {
|
|
3410
|
-
finalizeTraceRun(traceRunId, 'error', 'Codex acquisition failed');
|
|
3411
|
-
}
|
|
3412
|
-
catch {
|
|
3413
|
-
console.warn('[runtime] Codex acquisition trace finalization failed');
|
|
3414
|
-
}
|
|
3415
|
-
const ownsRun = activeMainProcesses.get(scopeKey) === mainRun;
|
|
3416
|
-
if (ownsRun) {
|
|
3417
|
-
clearLiveRun(liveScope);
|
|
3418
|
-
broadcast('agent_status', { running: false, agentId: agentLabel });
|
|
3419
|
-
broadcast('agent_done', { ...runPin, text: `❌ Codex AppServer acquire failed: ${err.message}`, error: true, origin }, 'public');
|
|
3420
|
-
releaseMainRun(scopeKey, null, ownerGeneration);
|
|
3421
|
-
}
|
|
3422
|
-
resolve({ text: '', code: 1 });
|
|
3423
|
-
if (ownsRun) {
|
|
3424
|
-
settleExit(scopeKey);
|
|
3425
|
-
void processQueue(scopeKey);
|
|
3426
|
-
}
|
|
3427
|
-
});
|
|
3428
|
-
return { child: null, promise: resultPromise };
|
|
3429
|
-
}
|
|
1613
|
+
if (cli === 'codex-app')
|
|
1614
|
+
return runCodexAppBackend(backendLocals, backendHost);
|
|
3430
1615
|
// ─── Standard CLI branch (claude/codex/opencode) ──────
|
|
3431
1616
|
const spawnCommand = cli === 'opencode' && process.platform !== 'win32'
|
|
3432
1617
|
? (resolvedOpencodeBinary || detected.path || cli)
|
|
@@ -3568,7 +1753,8 @@ export function spawnAgent(prompt, opts = {}) {
|
|
|
3568
1753
|
void processQueue(scopeKey);
|
|
3569
1754
|
});
|
|
3570
1755
|
if (mainManaged && !opts.internal && !opts._skipInsert) {
|
|
3571
|
-
|
|
1756
|
+
// The runtime gets the inline-skill block; the chat row keeps what was written.
|
|
1757
|
+
insertMessage.run('user', stripSkillMentionBlock(prompt), cli, runtimeModel, settings["workingDir"] || null, chatSessionId);
|
|
3572
1758
|
}
|
|
3573
1759
|
if (cli === 'claude') {
|
|
3574
1760
|
child.stdin.write(withSteerContext(isResume ? prompt : withHistoryPrompt(prompt, historyBlock), opts.steerContext));
|