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
|
@@ -6,10 +6,11 @@ import type { CodeControllerModel, CodeControllerOptions, CodeSessionFilter, Cod
|
|
|
6
6
|
import { CodeClientError, codeBaseOrigin, createCodeSessionClient, type CodeGitInfo } from './code-session-client';
|
|
7
7
|
import { emptyCodeSession, reduceCodeSession, codeSessionBusy, type CodeSessionState } from './code-session-state';
|
|
8
8
|
import {
|
|
9
|
-
acknowledgeCodeSend, catalogSelection, codeDraftBook, createCodeDraft, persistCodeDraftBook, sessionSelection,
|
|
9
|
+
acknowledgeCodeSend, acknowledgeCodeSteer, catalogSelection, codeDraftBook, createCodeDraft, persistCodeDraftBook, sessionSelection,
|
|
10
10
|
type CodeDraft, type CodeDraftBook,
|
|
11
11
|
} from './code-controller-drafts';
|
|
12
12
|
import { withPendingUserItem } from './pending-user-item';
|
|
13
|
+
import { codeCanResume, codeCanRollback, codeResendCopy, type CodeResendReason } from './code-types';
|
|
13
14
|
|
|
14
15
|
const MAX_DETAILS = 6;
|
|
15
16
|
const MAX_INDEX = 1000;
|
|
@@ -18,6 +19,14 @@ const MAX_CATCHUP_PAGES = 32;
|
|
|
18
19
|
const INDEX_DEBOUNCE_MS = 150;
|
|
19
20
|
const message = (error: unknown) => error instanceof CodeClientError ? error.message : 'Connection lost. Refresh to check the current state.';
|
|
20
21
|
const rejected = (error: unknown) => error instanceof CodeClientError && error.status >= 400 && error.status < 500;
|
|
22
|
+
// A lost response may still be committed, so its message can arrive; a `steer_outcome_unknown` answer
|
|
23
|
+
// means the server could not record it, so it never will.
|
|
24
|
+
const FOLLOW_UP_UNCONFIRMED = 'Follow-up delivery not confirmed. It was not resent; it appears in the conversation if Claude received it.';
|
|
25
|
+
const FOLLOW_UP_UNRECORDED = 'Follow-up delivery not confirmed. Claude may have received it, but it will not appear in the conversation. It was not resent.';
|
|
26
|
+
// A rollback posts the revision and epoch the user chose the row at; PATCH's settings copy does not fit its conflict.
|
|
27
|
+
const ROLLBACK_CONFLICT = 'The conversation changed since you chose this point. Review it and try again.';
|
|
28
|
+
// A rollback seen from elsewhere, for a send whose key nothing ties to a removed turn.
|
|
29
|
+
const ROLLED_BACK_ELSEWHERE = 'The conversation was rolled back elsewhere; this message was not sent.';
|
|
21
30
|
const newer = (incoming: CodeSessionInfo, current?: CodeSessionInfo | null) => !current
|
|
22
31
|
|| incoming.epoch > current.epoch || (incoming.epoch === current.epoch && (incoming.sequence > current.sequence
|
|
23
32
|
|| (incoming.sequence === current.sequence && incoming.revision >= current.revision)));
|
|
@@ -89,6 +98,7 @@ export class CodeController {
|
|
|
89
98
|
private gitError: string | null = null;
|
|
90
99
|
private gitGeneration = 0;
|
|
91
100
|
private pickerGeneration = 0;
|
|
101
|
+
private workspacePicking = false;
|
|
92
102
|
private indexGeneration = 0;
|
|
93
103
|
private catalogGeneration = 0;
|
|
94
104
|
private lifetime = 0;
|
|
@@ -213,7 +223,14 @@ export class CodeController {
|
|
|
213
223
|
}
|
|
214
224
|
private accept(incoming: CodeSessionInfo): void {
|
|
215
225
|
if (!this.active) return;
|
|
216
|
-
|
|
226
|
+
const before = this.info(incoming.sessionId);
|
|
227
|
+
if (!newer(incoming, before)) return;
|
|
228
|
+
// A turn that finishes while its session is open was seen: receipt it so the
|
|
229
|
+
// sidebar does not mark it unread. The receipt's own echo leaves the clock alone.
|
|
230
|
+
if (incoming.sessionId === this.book.selectedId && before
|
|
231
|
+
&& (incoming.lastTurnCompletedAt ?? 0) > (before.lastTurnCompletedAt ?? 0)) {
|
|
232
|
+
void this.markVisited(incoming.sessionId);
|
|
233
|
+
}
|
|
217
234
|
// The ranked record carries identity and lifecycle only. Attached fields are
|
|
218
235
|
// dropped by copy, never by mutation: readIndex hands the same row to
|
|
219
236
|
// observe(), and update() hands over details.session, which the reducer may
|
|
@@ -225,6 +242,8 @@ export class CodeController {
|
|
|
225
242
|
if (victim) { this.summaries.delete(victim); this.attention.delete(victim); }
|
|
226
243
|
}
|
|
227
244
|
const draft = this.book.sessions.get(session.sessionId);
|
|
245
|
+
// Once the server reports the turn, its own status carries the working state.
|
|
246
|
+
if (draft?.awaitingTurn && codeSessionBusy(session)) draft.awaitingTurn = null;
|
|
228
247
|
if (draft?.stopTarget && (session.turnId !== draft.stopTarget.turnId || session.epoch !== draft.stopTarget.epoch
|
|
229
248
|
|| !codeSessionBusy(session))) {
|
|
230
249
|
draft.stopTarget = null;
|
|
@@ -253,13 +272,36 @@ export class CodeController {
|
|
|
253
272
|
}
|
|
254
273
|
}
|
|
255
274
|
private update(id: string, state: CodeSessionState): void {
|
|
275
|
+
// A trimmed or reloaded detail holds no session of its own; the index copy, read before
|
|
276
|
+
// accept() below can replace it, is then the last generation this page saw.
|
|
277
|
+
const before = this.details.get(id)?.session ?? this.summaries.get(id);
|
|
256
278
|
this.details.set(id, state);
|
|
257
279
|
if (state.session) this.accept(state.session);
|
|
258
280
|
const draft = this.book.sessions.get(id);
|
|
281
|
+
if (draft && state.session && state.hydrated && !state.needsSnapshot) {
|
|
282
|
+
const generation = state.session.historyGeneration ?? 0;
|
|
283
|
+
if (before && generation > (before.historyGeneration ?? 0)) this.rolledBack(draft, state);
|
|
284
|
+
// Also without that copy: once any rollback happened, a follow-up whose turn is gone
|
|
285
|
+
// can no longer appear, so its unconfirmed notice goes.
|
|
286
|
+
const steer = draft.steer;
|
|
287
|
+
if (generation > 0 && steer?.state === 'unknown' && !state.items.some(item => item.turnId === steer.turnId)) draft.steer = null;
|
|
288
|
+
}
|
|
259
289
|
if (draft?.retry && state.items.some(item => item.kind === 'user_message' && item.clientTurnKey === draft.retry!.key)) {
|
|
260
290
|
if (deadSend(state, draft.retry.key)) this.requireNewKey(draft);
|
|
261
291
|
else acknowledgeCodeSend(draft, draft.retry.key);
|
|
262
292
|
}
|
|
293
|
+
if (draft?.steer && state.items.some(item => item.kind === 'user_message' && item.clientTurnKey === draft.steer!.key)) {
|
|
294
|
+
acknowledgeCodeSteer(draft, draft.steer.key);
|
|
295
|
+
}
|
|
296
|
+
if (draft?.awaitingTurn) {
|
|
297
|
+
// Only this send's own turn ending clears the marker: a stale read receipt or
|
|
298
|
+
// an older sequence must not switch a fresh send's spinner off.
|
|
299
|
+
const turnId = state.items.find(item => item.kind === 'user_message' && item.clientTurnKey === draft.awaitingTurn)?.turnId;
|
|
300
|
+
if (turnId && state.items.some(item => item.turnId === turnId
|
|
301
|
+
&& (item.kind === 'turn_completed' || item.kind === 'turn_failed' || item.kind === 'turn_cancelled'))) {
|
|
302
|
+
draft.awaitingTurn = null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
263
305
|
if (draft && state.synced) {
|
|
264
306
|
const current = new Set(state.permissions.map(p => p.permissionId));
|
|
265
307
|
for (const key of Object.keys(draft.permissionOperations)) {
|
|
@@ -276,10 +318,40 @@ export class CodeController {
|
|
|
276
318
|
* can actually be admitted, and it also stops the settled turn's own
|
|
277
319
|
* `user_message` from matching, so this fires once rather than on every read.
|
|
278
320
|
*/
|
|
279
|
-
private requireNewKey(draft: CodeDraft): void {
|
|
321
|
+
private requireNewKey(draft: CodeDraft, reason?: CodeResendReason): void {
|
|
280
322
|
if (!draft.retry) return;
|
|
281
|
-
|
|
282
|
-
draft.
|
|
323
|
+
const { resendReason: _previous, ...retry } = draft.retry;
|
|
324
|
+
draft.retry = { ...retry, key: crypto.randomUUID(), resend: true, ...(reason ? { resendReason: reason } : {}) };
|
|
325
|
+
draft.operation = { kind: 'unknown-send', error: codeResendCopy(reason).error };
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* A rollback removed turns, possibly the one this draft is waiting on. A send whose own
|
|
329
|
+
* row is gone no longer has a turn to wait for. An unconfirmed send keeps its key: seeing
|
|
330
|
+
* its row would have settled it, so nothing here ties the key to a removed turn. A same-key
|
|
331
|
+
* retry is then admitted once, or answers `cancelled` and submit() names the rollback. A
|
|
332
|
+
* key already retired says it was not sent. (An unconfirmed follow-up of a removed turn is
|
|
333
|
+
* dropped by update(); one still in flight settles on its own answer.)
|
|
334
|
+
*/
|
|
335
|
+
private rolledBack(draft: CodeDraft, state: CodeSessionState): void {
|
|
336
|
+
const kept = (key: string) => state.items.some(item => item.kind === 'user_message' && item.clientTurnKey === key);
|
|
337
|
+
if (draft.awaitingTurn && !kept(draft.awaitingTurn)) draft.awaitingTurn = null;
|
|
338
|
+
if (draft.retry && !draft.retry.resend && !kept(draft.retry.key)
|
|
339
|
+
&& (draft.operation.kind === 'idle' || draft.operation.kind === 'unknown-send')) {
|
|
340
|
+
draft.operation = { kind: 'unknown-send', error: ROLLED_BACK_ELSEWHERE };
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* A spent key answers `cancelled` both for a turn that was stopped and for one a rollback
|
|
345
|
+
* removed. Every turn this detail has read past (its cursor is at or after the receipt), in
|
|
346
|
+
* a window that reaches back to it, still has items unless a rollback deleted them, so the
|
|
347
|
+
* turn's absence there says which. The HTTP receipt is all a reloaded page has: the
|
|
348
|
+
* transcript that held the turn is gone with the rollback.
|
|
349
|
+
*/
|
|
350
|
+
private removedByRollback(id: string, receipt: CodePromptReceipt): boolean {
|
|
351
|
+
const state = this.details.get(id);
|
|
352
|
+
return receipt.status === 'cancelled' && !!state?.hydrated && (state.session?.historyGeneration ?? 0) > 0
|
|
353
|
+
&& receipt.sequence <= state.cursor && (!state.hasOlder || (state.beforeSequence ?? Infinity) <= receipt.sequence)
|
|
354
|
+
&& !state.items.some(item => item.turnId === receipt.turnId);
|
|
283
355
|
}
|
|
284
356
|
private makeModel(): CodeControllerModel {
|
|
285
357
|
const id = this.book.selectedId;
|
|
@@ -295,28 +367,39 @@ export class CodeController {
|
|
|
295
367
|
const session = canonical ? publish(canonical, detail) : canonical;
|
|
296
368
|
const operation = draft.operation;
|
|
297
369
|
const persistenceWarning = this.book.storageWarning ?? this.book.recoveryWarning;
|
|
298
|
-
const pending = ['creating', 'sending', 'stopping', 'resuming', 'patching'].includes(operation.kind) && !operation.error;
|
|
370
|
+
const pending = ['creating', 'sending', 'stopping', 'resuming', 'patching', 'rolling-back'].includes(operation.kind) && !operation.error;
|
|
299
371
|
const synced = id === null ? !!this.catalog : !!detail?.synced && (!session || session.sequence <= detail.cursor) && draft.requiredSequence <= (detail?.cursor ?? 0);
|
|
372
|
+
const followUp = !!session && synced && session.provider === 'claude' && session.status === 'streaming'
|
|
373
|
+
&& !!session.turnId && session.archivedAt === null;
|
|
374
|
+
// The turn's one follow-up is already in the transcript.
|
|
375
|
+
const followUpSent = followUp && !!session && (detail?.items ?? []).some(item => item.kind === 'user_message'
|
|
376
|
+
&& item.itemId.startsWith(`${session.turnId}:steer:`));
|
|
377
|
+
const unconfirmed = draft.steer?.state !== 'unknown' ? null : draft.steer.unrecorded ? FOLLOW_UP_UNRECORDED : FOLLOW_UP_UNCONFIRMED;
|
|
300
378
|
return {
|
|
301
379
|
catalog: this.catalog, sessions: this.rows.map(id => {
|
|
302
380
|
const row = this.info(id), detail = this.details.get(id);
|
|
303
381
|
return row ? publish(row, detail) : row;
|
|
304
382
|
}).filter((row): row is CodeSessionInfo => !!row),
|
|
305
383
|
selectedId: id, session, items: withPendingUserItem(draft, detail?.items ?? []),
|
|
384
|
+
hasUnsentDraft: !!this.book.fresh.input.trim() || this.book.fresh.createUnknown || !!this.book.fresh.retry,
|
|
306
385
|
permissions: detail?.permissions ?? [],
|
|
307
386
|
input: draft.input, selection: session ? sessionSelection(session) : draft.selection,
|
|
308
387
|
gitInfo: this.gitInfo, loading: this.indexLoading || !!detail?.hydrating || (!!id && !detail?.hydrated && !detail?.error),
|
|
309
|
-
pending, busy: codeSessionBusy(session),
|
|
310
|
-
|
|
388
|
+
pending, busy: codeSessionBusy(session), working: !!draft.awaitingTurn || codeSessionBusy(session),
|
|
389
|
+
workingIds: new Set(this.rows.filter(rowId => {
|
|
390
|
+
const row = this.info(rowId);
|
|
391
|
+
return !!this.book.sessions.get(rowId)?.awaitingTurn || (!!row && codeSessionBusy(row));
|
|
392
|
+
})), followUp, followUpSent, steering: draft.steer?.state === 'sending', synced, transport: this.transport, workspacePicking: this.workspacePicking,
|
|
393
|
+
error: [operation.error ?? unconfirmed ?? detail?.error ?? this.indexError ?? this.catalogError ?? this.gitError ?? session?.error?.message, persistenceWarning].filter(Boolean).join(' ') || null,
|
|
311
394
|
operation: { ...operation, error: operation.error && persistenceWarning ? `${operation.error} ${persistenceWarning}` : operation.error }, retryText: draft.retry?.text ?? null,
|
|
312
395
|
canRetrySameSend: !!id && operation.kind === 'unknown-send' && !!draft.retry && synced && session?.archivedAt === null,
|
|
313
|
-
resendRequired: !!draft.retry?.resend,
|
|
396
|
+
resendRequired: !!draft.retry?.resend, resendReason: draft.retry?.resend ? draft.retry.resendReason ?? null : null,
|
|
314
397
|
permissionOperations: { ...draft.permissionOperations }, hasMoreSessions: this.moreSessions,
|
|
315
398
|
hasOlderHistory: detail?.hasOlder ?? false, filter: this.filter,
|
|
316
399
|
creationUnknown: id === null && draft.createUnknown, startAnotherSession: this.startAnotherSession,
|
|
317
400
|
newSession: this.newSession, selectSession: this.selectSession, setInput: this.setInput,
|
|
318
401
|
setSelection: this.setSelection, pickWorkspace: this.pickWorkspace, send: this.send,
|
|
319
|
-
retrySameSend: this.retrySameSend, stop: this.stop, resume: this.resume, rename: this.rename,
|
|
402
|
+
retrySameSend: this.retrySameSend, stop: this.stop, resume: this.resume, rollbackSession: this.rollbackSession, rename: this.rename,
|
|
320
403
|
archive: this.archive, answer: this.answer, refresh: this.refresh, loadMoreSessions: this.loadMoreSessions,
|
|
321
404
|
loadOlderHistory: this.loadOlderHistory, setFilter: this.setFilter, clearError: this.clearError,
|
|
322
405
|
};
|
|
@@ -445,6 +528,10 @@ export class CodeController {
|
|
|
445
528
|
if (event.session?.sessionId === event.sessionId) this.accept(event.session);
|
|
446
529
|
if (event.event === 'code_session' || event.item?.kind === 'permission_request' || event.update?.status) this.scheduleIndex();
|
|
447
530
|
const draft = this.book.sessions.get(event.sessionId);
|
|
531
|
+
if (event.item?.kind === 'user_message' && event.item.clientTurnKey && draft?.steer?.key === event.item.clientTurnKey) {
|
|
532
|
+
draft.requiredSequence = Math.max(draft.requiredSequence, event.sequence);
|
|
533
|
+
acknowledgeCodeSteer(draft, event.item.clientTurnKey);
|
|
534
|
+
}
|
|
448
535
|
if (event.item?.kind === 'user_message' && event.item.clientTurnKey && draft?.retry?.key === event.item.clientTurnKey) {
|
|
449
536
|
draft.requiredSequence = Math.max(draft.requiredSequence, event.sequence);
|
|
450
537
|
if (deadSend(this.details.get(event.sessionId), event.item.clientTurnKey)) this.requireNewKey(draft);
|
|
@@ -492,12 +579,21 @@ export class CodeController {
|
|
|
492
579
|
this.gitInfo = null;
|
|
493
580
|
this.notify();
|
|
494
581
|
await this.sync(id, true);
|
|
582
|
+
if (this.book.selectedId === id) void this.markVisited(id);
|
|
495
583
|
if (this.book.selectedId === id) await this.readGit();
|
|
496
584
|
};
|
|
585
|
+
/** Read receipt for the unread marker. A failure only leaves the marker on; selection never waits for it. */
|
|
586
|
+
private async markVisited(id: string): Promise<void> {
|
|
587
|
+
try {
|
|
588
|
+
this.accept(await this.client.visitSession(id));
|
|
589
|
+
this.notify();
|
|
590
|
+
} catch { /* the next open retries */ }
|
|
591
|
+
}
|
|
497
592
|
setInput = (text: string): void => { const draft = this.draft(); draft.input = text; draft.edit++; this.notify(); };
|
|
498
593
|
clearError = (): void => {
|
|
499
594
|
const draft = this.draft();
|
|
500
595
|
if (draft.operation.kind === 'idle') draft.operation = { kind: 'idle', error: null };
|
|
596
|
+
if (draft.steer?.state === 'unknown') draft.steer = null;
|
|
501
597
|
this.indexError = null;
|
|
502
598
|
this.book.recoveryWarning = null;
|
|
503
599
|
this.notify();
|
|
@@ -521,8 +617,10 @@ export class CodeController {
|
|
|
521
617
|
}
|
|
522
618
|
pickWorkspace = async (): Promise<void> => {
|
|
523
619
|
const draft = this.book.fresh;
|
|
524
|
-
if (this.book.selectedId !== null || draft.operation.kind !== 'idle' || draft.createUnknown) return;
|
|
620
|
+
if (this.book.selectedId !== null || draft.operation.kind !== 'idle' || draft.createUnknown || this.workspacePicking) return;
|
|
525
621
|
const navigation = this.book.navigation, selectionEdit = draft.selectionEdit, generation = ++this.pickerGeneration;
|
|
622
|
+
this.workspacePicking = true;
|
|
623
|
+
this.notify();
|
|
526
624
|
try {
|
|
527
625
|
const result = await this.client.pickWorkspace();
|
|
528
626
|
if (!this.active || generation !== this.pickerGeneration || navigation !== this.book.navigation
|
|
@@ -534,6 +632,9 @@ export class CodeController {
|
|
|
534
632
|
} catch (error) {
|
|
535
633
|
if (this.active && generation === this.pickerGeneration && navigation === this.book.navigation
|
|
536
634
|
&& selectionEdit === draft.selectionEdit && draft === this.book.fresh && draft.operation.kind === 'idle') { draft.operation.error = message(error); this.notify(); }
|
|
635
|
+
} finally {
|
|
636
|
+
this.workspacePicking = false;
|
|
637
|
+
this.notify();
|
|
537
638
|
}
|
|
538
639
|
};
|
|
539
640
|
setSelection = async (patch: Partial<CodeCreateSessionRequest>): Promise<void> => {
|
|
@@ -557,7 +658,8 @@ export class CodeController {
|
|
|
557
658
|
}
|
|
558
659
|
await this.patch(id, { model: patch.model ?? session.model,
|
|
559
660
|
effort: patch.effort === undefined ? session.effort : patch.effort,
|
|
560
|
-
permissionMode: patch.permissionMode ?? session.permissionMode
|
|
661
|
+
permissionMode: patch.permissionMode ?? session.permissionMode,
|
|
662
|
+
...(session.provider === 'claude' ? { thinking: patch.thinking ?? session.thinking ?? true } : {}) });
|
|
561
663
|
return;
|
|
562
664
|
}
|
|
563
665
|
if (id) return;
|
|
@@ -605,8 +707,24 @@ export class CodeController {
|
|
|
605
707
|
send = async (): Promise<void> => {
|
|
606
708
|
let id = this.book.selectedId;
|
|
607
709
|
let draft = this.draft(id);
|
|
608
|
-
if (draft.operation.kind !== 'idle' || draft.createUnknown || !draft.input.trim()) return;
|
|
609
|
-
|
|
710
|
+
if (draft.operation.kind !== 'idle' || draft.createUnknown || !draft.input.trim() || draft.steer?.state === 'sending') return;
|
|
711
|
+
let session = this.info(id);
|
|
712
|
+
// A running Claude turn takes the text as its follow-up; an idle session starts a new turn.
|
|
713
|
+
if (id && session && this.model.followUp) {
|
|
714
|
+
if (!this.model.followUpSent) await this.steer(id, draft, session);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
if (draft.steer?.state === 'unknown') draft.steer = null;
|
|
718
|
+
// t3code continues a session on the next send; a suspended (or recoverably
|
|
719
|
+
// failed) session attaches first instead of asking for a separate Resume.
|
|
720
|
+
if (id && session && codeCanResume(session) && this.model.synced) {
|
|
721
|
+
draft.operation = { kind: 'resuming', error: null }; this.notify();
|
|
722
|
+
try { this.accept(await this.client.attachSession(id)); draft.operation = { kind: 'idle', error: null }; }
|
|
723
|
+
catch (error) { draft.operation = { kind: 'idle', error: message(error) }; this.notify(); this.scheduleIndex(); return; }
|
|
724
|
+
this.notify(); this.scheduleIndex();
|
|
725
|
+
if (this.active) await this.sync(id, true);
|
|
726
|
+
session = this.info(id);
|
|
727
|
+
}
|
|
610
728
|
if (id && (!session || session.status !== 'idle' || session.archivedAt !== null || !this.model.synced)) {
|
|
611
729
|
draft.operation.error = 'Wait for the session to be idle and synchronized before sending.'; this.notify(); return;
|
|
612
730
|
}
|
|
@@ -648,7 +766,9 @@ export class CodeController {
|
|
|
648
766
|
private async submit(id: string, draft: CodeDraft): Promise<void> {
|
|
649
767
|
const attempt = draft.retry;
|
|
650
768
|
if (!attempt) return;
|
|
651
|
-
draft.operation = { kind: 'sending', error: null };
|
|
769
|
+
draft.operation = { kind: 'sending', error: null };
|
|
770
|
+
draft.awaitingTurn = attempt.key;
|
|
771
|
+
this.notify();
|
|
652
772
|
try {
|
|
653
773
|
const receipt = await this.client.sendPrompt(id, { text: attempt.text, clientTurnKey: attempt.key });
|
|
654
774
|
if (receipt.clientTurnKey !== attempt.key) throw new Error('Mismatched prompt receipt');
|
|
@@ -657,14 +777,51 @@ export class CodeController {
|
|
|
657
777
|
if (state) this.details.set(id, reduceCodeSession(state, { type: 'stale' }));
|
|
658
778
|
// A duplicate receipt for a spent key is a report about a turn that is
|
|
659
779
|
// already over, not an admission of this one.
|
|
660
|
-
if (spent(receipt.status))
|
|
780
|
+
if (spent(receipt.status)) {
|
|
781
|
+
this.requireNewKey(draft, this.removedByRollback(id, receipt) ? 'rolled-back' : undefined);
|
|
782
|
+
draft.awaitingTurn = null;
|
|
783
|
+
}
|
|
661
784
|
else acknowledgeCodeSend(draft, attempt.key);
|
|
662
785
|
} catch (error) {
|
|
663
786
|
// The committed user event may already have acknowledged a lost HTTP response.
|
|
664
787
|
if (draft.retry?.key === attempt.key) {
|
|
665
788
|
draft.operation = { kind: rejected(error) ? 'idle' : 'unknown-send', error: rejected(error) ? message(error)
|
|
666
789
|
: 'Message acceptance is unknown. Refresh, or explicitly retry the original message with the same key.' };
|
|
667
|
-
if (rejected(error)) draft.retry = null;
|
|
790
|
+
if (rejected(error)) { draft.retry = null; draft.awaitingTurn = null; }
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
this.notify(); this.scheduleIndex();
|
|
794
|
+
if (this.active) await this.sync(id, true);
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Post one follow-up for the captured turn. Only a matching receipt or the follow-up's own
|
|
798
|
+
* `user_message` clears the input; a refusal keeps it; an unconfirmed outcome is never resent.
|
|
799
|
+
* The send's operation is left alone, so a Stop in progress keeps its state.
|
|
800
|
+
*/
|
|
801
|
+
private async steer(id: string, draft: CodeDraft, session: CodeSessionInfo): Promise<void> {
|
|
802
|
+
if (!session.turnId || (draft.stopTarget && draft.stopTarget.outcome !== 'retryable')) return;
|
|
803
|
+
const attempt: NonNullable<CodeDraft['steer']> = { key: crypto.randomUUID(), text: draft.input, edit: draft.edit,
|
|
804
|
+
turnId: session.turnId, epoch: session.epoch, state: 'sending' };
|
|
805
|
+
draft.steer = attempt;
|
|
806
|
+
if (draft.operation.error) draft.operation = { ...draft.operation, error: null };
|
|
807
|
+
this.notify();
|
|
808
|
+
try {
|
|
809
|
+
const receipt = await this.client.steerSession(id, { text: attempt.text, clientTurnKey: attempt.key,
|
|
810
|
+
turnId: attempt.turnId, epoch: attempt.epoch });
|
|
811
|
+
if (receipt.clientTurnKey !== attempt.key) throw new Error('Mismatched follow-up receipt');
|
|
812
|
+
draft.requiredSequence = Math.max(draft.requiredSequence, receipt.sequence);
|
|
813
|
+
const state = this.details.get(id);
|
|
814
|
+
if (state) this.details.set(id, reduceCodeSession(state, { type: 'stale' }));
|
|
815
|
+
acknowledgeCodeSteer(draft, attempt.key);
|
|
816
|
+
} catch (error) {
|
|
817
|
+
if (draft.steer === attempt) {
|
|
818
|
+
if (!rejected(error)) {
|
|
819
|
+
attempt.state = 'unknown';
|
|
820
|
+
attempt.unrecorded = error instanceof CodeClientError && error.code === 'steer_outcome_unknown';
|
|
821
|
+
} else {
|
|
822
|
+
draft.steer = null;
|
|
823
|
+
if (draft.operation.kind === 'idle') draft.operation = { kind: 'idle', error: message(error) };
|
|
824
|
+
}
|
|
668
825
|
}
|
|
669
826
|
}
|
|
670
827
|
this.notify(); this.scheduleIndex();
|
|
@@ -729,6 +886,21 @@ export class CodeController {
|
|
|
729
886
|
this.notify(); this.scheduleIndex();
|
|
730
887
|
if (this.active) await this.sync(id, true);
|
|
731
888
|
};
|
|
889
|
+
rollbackSession = async (itemId: string): Promise<void> => {
|
|
890
|
+
const id = this.book.selectedId, session = this.info(id), draft = this.draft(id);
|
|
891
|
+
// A follow-up still in flight settles first: its answer may name the turn this would remove.
|
|
892
|
+
if (!id || !session || !codeCanRollback(session, this.model.synced, draft.operation.kind === 'idle' && draft.steer?.state !== 'sending')) return;
|
|
893
|
+
draft.operation = { kind: 'rolling-back', error: null }; this.notify();
|
|
894
|
+
try {
|
|
895
|
+
this.accept(await this.client.rollbackSession(id, { expectedRevision: session.revision, expectedEpoch: session.epoch, upToItemId: itemId }));
|
|
896
|
+
draft.operation = { kind: 'idle', error: null };
|
|
897
|
+
} catch (error) {
|
|
898
|
+
if (error instanceof CodeClientError && error.session?.sessionId === id) { this.observe(error.session); this.accept(error.session); }
|
|
899
|
+
draft.operation = { kind: 'idle', error: error instanceof CodeClientError && error.code === 'revision_conflict' ? ROLLBACK_CONFLICT : message(error) };
|
|
900
|
+
}
|
|
901
|
+
this.notify(); this.scheduleIndex();
|
|
902
|
+
if (this.active) await this.sync(id, true);
|
|
903
|
+
};
|
|
732
904
|
answer = async (permission: CodePermissionRequest, optionId: string): Promise<void> => {
|
|
733
905
|
const id = permission.sessionId;
|
|
734
906
|
const state = this.details.get(id), session = this.info(id), draft = this.draft(id);
|
|
@@ -2,10 +2,11 @@ import type {
|
|
|
2
2
|
CodeCreateSessionRequest, CodeItem, CodeModelCatalog, CodePermissionRequest, CodeSessionInfo,
|
|
3
3
|
} from '../../../../src/code-mode/wire';
|
|
4
4
|
import type { CodeGitInfo } from './code-session-client';
|
|
5
|
+
import type { CodeResendReason } from './code-types';
|
|
5
6
|
|
|
6
7
|
export type CodeTransportState = 'connected' | 'reconnecting' | 'disconnected';
|
|
7
8
|
export type CodeSessionFilter = { scope: 'all' | 'cwd'; archived: boolean };
|
|
8
|
-
export type CodeOperationKind = 'idle' | 'creating' | 'sending' | 'stopping' | 'resuming' | 'patching' | 'unknown-send';
|
|
9
|
+
export type CodeOperationKind = 'idle' | 'creating' | 'sending' | 'stopping' | 'resuming' | 'patching' | 'rolling-back' | 'unknown-send';
|
|
9
10
|
export interface CodeControllerOptions { port: number; workingDir: string }
|
|
10
11
|
|
|
11
12
|
export interface CodeControllerModel {
|
|
@@ -16,11 +17,24 @@ export interface CodeControllerModel {
|
|
|
16
17
|
items: CodeItem[];
|
|
17
18
|
permissions: CodePermissionRequest[];
|
|
18
19
|
input: string;
|
|
20
|
+
/** The fresh (un-sent) draft holds content or an unreconciled attempt. */
|
|
21
|
+
hasUnsentDraft: boolean;
|
|
19
22
|
selection: CodeCreateSessionRequest;
|
|
20
23
|
gitInfo: CodeGitInfo | null;
|
|
21
24
|
loading: boolean;
|
|
22
25
|
pending: boolean;
|
|
23
26
|
busy: boolean;
|
|
27
|
+
/** The selected session is working: a send awaiting its turn, or a busy runtime. */
|
|
28
|
+
working: boolean;
|
|
29
|
+
/** Rows that are working, by session id. */
|
|
30
|
+
workingIds: ReadonlySet<string>;
|
|
31
|
+
/** The selected, synchronized Claude session is streaming a turn that takes a follow-up. */
|
|
32
|
+
followUp: boolean;
|
|
33
|
+
/** That turn's one follow-up is already in the transcript, so Send follow-up stays closed. */
|
|
34
|
+
followUpSent: boolean;
|
|
35
|
+
/** A follow-up for the selected session is in flight. */
|
|
36
|
+
steering: boolean;
|
|
37
|
+
workspacePicking: boolean;
|
|
24
38
|
synced: boolean;
|
|
25
39
|
error: string | null;
|
|
26
40
|
transport: CodeTransportState;
|
|
@@ -29,6 +43,8 @@ export interface CodeControllerModel {
|
|
|
29
43
|
canRetrySameSend: boolean;
|
|
30
44
|
/** The previous key is spent: Retry sends a new message rather than reusing it. */
|
|
31
45
|
resendRequired?: boolean;
|
|
46
|
+
/** Why the key was spent, when a rollback removed its turn rather than the server settling it. */
|
|
47
|
+
resendReason?: CodeResendReason | null;
|
|
32
48
|
permissionOperations: Record<string, { pending: boolean; error: string | null }>;
|
|
33
49
|
hasMoreSessions: boolean;
|
|
34
50
|
hasOlderHistory: boolean;
|
|
@@ -44,6 +60,8 @@ export interface CodeControllerModel {
|
|
|
44
60
|
retrySameSend(): Promise<void>;
|
|
45
61
|
stop(): Promise<void>;
|
|
46
62
|
resume(): Promise<void>;
|
|
63
|
+
/** Claude only: remove the turns after this `${turnId}:user` row. Files are not reverted and nothing is sent. */
|
|
64
|
+
rollbackSession(itemId: string): Promise<void>;
|
|
47
65
|
rename(id: string, title: string): Promise<void>;
|
|
48
66
|
archive(id: string, archived: boolean): Promise<void>;
|
|
49
67
|
answer(permission: CodePermissionRequest, optionId: string): Promise<void>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CodeSessionInfo } from '../../../../src/code-mode/wire';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which workspaces the draft empty state offers, kept apart from rendering so
|
|
5
|
+
* the rule can be tested on its own.
|
|
6
|
+
*
|
|
7
|
+
* "Recent" is last use, not creation: a session created months ago and used
|
|
8
|
+
* this morning is a more useful suggestion than one created yesterday and
|
|
9
|
+
* abandoned. Uniqueness is on the directory — the workspace, not the session —
|
|
10
|
+
* so the list never offers the same folder twice.
|
|
11
|
+
*/
|
|
12
|
+
export function recentCodeWorkspaces(sessions: readonly CodeSessionInfo[], max = 5): string[] {
|
|
13
|
+
const seen = new Set<string>();
|
|
14
|
+
const out: string[] = [];
|
|
15
|
+
const ordered = [...sessions].sort((left, right) => right.lastUsedAt - left.lastUsedAt
|
|
16
|
+
|| right.createdAt - left.createdAt || left.sessionId.localeCompare(right.sessionId));
|
|
17
|
+
for (const session of ordered) {
|
|
18
|
+
const cwd = session.cwd;
|
|
19
|
+
if (!cwd.trim() || seen.has(cwd)) continue;
|
|
20
|
+
seen.add(cwd);
|
|
21
|
+
out.push(cwd);
|
|
22
|
+
if (out.length >= max) break;
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
CodeCancelRequest, CodeCreateSessionRequest, CodeEventsPage, CodeHistoryPage, CodeModelCatalog,
|
|
3
3
|
CodePatchSessionRequest, CodePermissionAnswer, CodePromptReceipt, CodePromptRequest,
|
|
4
|
-
CodeSessionInfo, CodeSessionPage, CodeSnapshot,
|
|
4
|
+
CodeRollbackRequest, CodeSessionInfo, CodeSessionPage, CodeSnapshot, CodeSteerRequest,
|
|
5
5
|
} from '../../../../src/code-mode/wire';
|
|
6
6
|
|
|
7
7
|
export interface CodeGitInfo {
|
|
@@ -40,8 +40,12 @@ export interface CodeSessionClient {
|
|
|
40
40
|
createSession(input: CodeCreateSessionRequest): Promise<CodeSessionInfo>;
|
|
41
41
|
patchSession(id: string, input: CodePatchSessionRequest): Promise<CodeSessionInfo>;
|
|
42
42
|
sendPrompt(id: string, input: CodePromptRequest): Promise<CodePromptReceipt>;
|
|
43
|
+
/** A Claude follow-up for the captured running turn; never retried. */
|
|
44
|
+
steerSession(id: string, input: CodeSteerRequest): Promise<CodePromptReceipt>;
|
|
43
45
|
cancelPrompt(id: string, input: CodeCancelRequest): Promise<CodeSessionInfo>;
|
|
44
46
|
attachSession(id: string): Promise<CodeSessionInfo>;
|
|
47
|
+
rollbackSession(id: string, input: CodeRollbackRequest): Promise<CodeSessionInfo>;
|
|
48
|
+
visitSession(id: string): Promise<CodeSessionInfo>;
|
|
45
49
|
answerPermission(id: string, input: CodePermissionAnswer): Promise<void>;
|
|
46
50
|
getGitInfo(cwd: string, signal?: AbortSignal): Promise<CodeGitInfo>;
|
|
47
51
|
pickWorkspace(): Promise<{ ok: boolean; path?: string; cancelled?: boolean }>;
|
|
@@ -61,7 +65,18 @@ const ERROR_COPY: Record<string, string> = {
|
|
|
61
65
|
unsupported_effort: 'This effort is not supported by the selected runtime.',
|
|
62
66
|
unsupported_policy: 'This permission mode is not supported by the selected runtime.',
|
|
63
67
|
unsupported_capability: 'This runtime does not support this action.',
|
|
68
|
+
session_not_steerable: 'This turn cannot take a follow-up right now. Your text is kept.',
|
|
69
|
+
steer_queue_full: 'This turn already has its follow-up. Send this after the turn finishes.',
|
|
70
|
+
steer_in_flight: 'This follow-up is still being delivered.',
|
|
71
|
+
steer_key_spent: 'This follow-up was not delivered. Your text is kept; send it again when ready.',
|
|
72
|
+
steer_outcome_unknown: 'Follow-up delivery not confirmed. It was not resent.',
|
|
73
|
+
steer_command_unsupported: 'A slash command cannot be sent as a follow-up. Send it after this turn finishes.',
|
|
64
74
|
session_not_found: 'This session is no longer available. Its local draft is retained.',
|
|
75
|
+
rollback_unavailable: 'Rollback is unavailable after this conversation was compacted, or while its history cannot be read.',
|
|
76
|
+
rollback_boundary_unavailable: "This turn can't be a rollback point: it or a later turn never reached Claude, or the conversation was compacted.",
|
|
77
|
+
rollback_noop: 'This is already the latest turn, so there is nothing to roll back.',
|
|
78
|
+
rollback_target_not_found: 'This message is no longer in the conversation. Refreshing the session.',
|
|
79
|
+
cleanup_pending: 'The previous runtime is still closing. Try again in a moment.',
|
|
65
80
|
unauthorized: 'Authentication is required to access this instance.',
|
|
66
81
|
};
|
|
67
82
|
|
|
@@ -106,8 +121,11 @@ export function createCodeSessionClient(port: number): CodeSessionClient {
|
|
|
106
121
|
createSession: input => sessionRequest('POST', '/sessions', input),
|
|
107
122
|
patchSession: (id, input) => sessionRequest('PATCH', sessionPath(id), input),
|
|
108
123
|
sendPrompt: (id, input) => request('POST', `${sessionPath(id)}/prompt`, input),
|
|
124
|
+
steerSession: (id, input) => request('POST', `${sessionPath(id)}/steer`, input),
|
|
109
125
|
cancelPrompt: (id, input) => sessionRequest('POST', `${sessionPath(id)}/cancel`, input),
|
|
110
126
|
attachSession: id => sessionRequest('POST', `${sessionPath(id)}/attach`, {}),
|
|
127
|
+
rollbackSession: (id, input) => sessionRequest('POST', `${sessionPath(id)}/rollback`, input),
|
|
128
|
+
visitSession: id => sessionRequest('POST', `${sessionPath(id)}/visit`, {}),
|
|
111
129
|
async answerPermission(id, input) { await request('POST', `/permissions/${encodeURIComponent(id)}`, input); },
|
|
112
130
|
getGitInfo: (cwd, signal) => request('GET', `/git-info?cwd=${encodeURIComponent(cwd)}`, undefined, signal),
|
|
113
131
|
pickWorkspace: () => request('POST', '/workspace/pick'),
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* Code sidebar: Projects/Activity header, bell toggle, unread markers. */
|
|
2
|
+
.code-session-list-header { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
|
|
3
|
+
.code-session-header-actions { display: inline-flex; align-items: center; gap: 2px; }
|
|
4
|
+
.code-session-header-btn {
|
|
5
|
+
position: relative; display: inline-flex; align-items: center; justify-content: center;
|
|
6
|
+
width: 24px; height: 24px; min-height: 24px; padding: 0; border: none; border-radius: 6px;
|
|
7
|
+
background: transparent; color: var(--text-dim); cursor: pointer;
|
|
8
|
+
}
|
|
9
|
+
.code-session-header-btn:hover { background: var(--bg-raised); color: var(--text-primary); }
|
|
10
|
+
.code-session-header-btn.active { color: var(--accent); background: var(--accent-soft); }
|
|
11
|
+
.code-session-header-btn:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }
|
|
12
|
+
.code-session-search-row { display: flex; align-items: center; gap: 6px; margin: 0 8px 6px; }
|
|
13
|
+
.code-session-search-row .code-session-search { flex: 1 1 auto; width: auto; min-width: 0; margin: 0; }
|
|
14
|
+
.code-session-archived-btn {
|
|
15
|
+
flex: none; padding: 3px 8px; min-height: 24px; border: 1px solid var(--border-subtle); border-radius: 4px;
|
|
16
|
+
background: transparent; color: var(--text-dim); font-size: 11px; cursor: pointer;
|
|
17
|
+
}
|
|
18
|
+
.code-session-archived-btn.active { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
|
|
19
|
+
.code-session-archived-btn:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }
|
|
20
|
+
.code-session-bell .code-session-unread-dot { position: absolute; top: 3px; right: 3px; margin: 0; }
|
|
21
|
+
.code-session-unread-dot {
|
|
22
|
+
display: inline-block; flex: none; width: 7px; height: 7px; margin-right: 6px;
|
|
23
|
+
border-radius: 50%; background: var(--accent); vertical-align: 1px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Working indicator: sidebar rows and the transcript tail, from Send until the turn ends. */
|
|
27
|
+
.code-session-spinner {
|
|
28
|
+
display: inline-block; flex: none; width: 9px; height: 9px; margin-right: 4px; vertical-align: -1px;
|
|
29
|
+
border: 1.5px solid currentColor; border-right-color: transparent; border-radius: 50%;
|
|
30
|
+
}
|
|
31
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
32
|
+
.code-session-spinner { animation: code-session-spin 0.8s linear infinite; }
|
|
33
|
+
}
|
|
34
|
+
@keyframes code-session-spin { to { transform: rotate(360deg); } }
|
|
35
|
+
.code-transcript-working {
|
|
36
|
+
display: flex; align-items: center; gap: 6px; padding: 8px 12px 12px;
|
|
37
|
+
color: var(--text-dim); font-size: 12px;
|
|
38
|
+
}
|
|
@@ -61,6 +61,8 @@ function apply(state: CodeSessionState, event: CodeWireEvent): CodeSessionState
|
|
|
61
61
|
let { items, session, permissions } = state;
|
|
62
62
|
if (event.event === 'code_session') {
|
|
63
63
|
if (!event.session || event.session.sessionId !== state.sessionId) return { ...state, needsSnapshot: true, synced: false };
|
|
64
|
+
// A rollback removed items this state still holds; only a new snapshot drops them.
|
|
65
|
+
if ((event.session.historyGeneration ?? 0) > (state.session?.historyGeneration ?? 0)) return { ...state, needsSnapshot: true, synced: false };
|
|
64
66
|
// Usage is attached at read time, so a stored session event does not
|
|
65
67
|
// carry it. Replacing the record wholesale would blank the meter on
|
|
66
68
|
// every unrelated session change; the last figure stands until a
|
|
@@ -5,11 +5,16 @@ export const CODE_RUNTIME_LABELS: Record<CodeProviderId, string> = {
|
|
|
5
5
|
};
|
|
6
6
|
export const CODE_POLICY_LABELS: Record<CodePermissionMode, string> = {
|
|
7
7
|
ask: 'Ask first', auto: 'Auto (YOLO)', 'read-only': 'Read only',
|
|
8
|
+
plan: 'Plan', 'accept-edits': 'Accept edits', 'dont-ask': "Don't ask", 'auto-review': 'Auto review',
|
|
8
9
|
};
|
|
9
10
|
export const CODE_POLICY_DETAILS: Record<CodePermissionMode, string> = {
|
|
10
11
|
ask: 'Review native permission requests before allowing actions.',
|
|
11
12
|
auto: 'The native runtime may execute actions without asking for approval.',
|
|
12
13
|
'read-only': 'The native runtime restricts actions to read-only access.',
|
|
14
|
+
plan: 'Claude plans without applying edits.',
|
|
15
|
+
'accept-edits': 'File edits proceed; other actions still ask.',
|
|
16
|
+
'dont-ask': 'Actions not already allowed for this session are denied without asking.',
|
|
17
|
+
'auto-review': 'A classifier approves or denies each action; unsure calls still ask.',
|
|
13
18
|
};
|
|
14
19
|
export const CODE_SESSION_LABELS: Record<CodeSessionStatus, string> = {
|
|
15
20
|
idle: 'Ready', starting: 'Starting', streaming: 'Running', stopping: 'Stopping', suspended: 'Suspended', failed: 'Runtime failed',
|
|
@@ -17,10 +22,52 @@ export const CODE_SESSION_LABELS: Record<CodeSessionStatus, string> = {
|
|
|
17
22
|
export function codeSessionBusy(session: CodeSessionInfo): boolean {
|
|
18
23
|
return session.status === 'starting' || session.status === 'streaming' || session.status === 'stopping';
|
|
19
24
|
}
|
|
25
|
+
/** Why a send's key was retired: the server spent it without running, or a rollback removed its turn. */
|
|
26
|
+
export type CodeResendReason = 'rolled-back';
|
|
27
|
+
/** What the composer says about a retired send: `error` in the alert, `heading`/`detail` in its recovery strip. */
|
|
28
|
+
export function codeResendCopy(reason: CodeResendReason | undefined): { heading: string; detail: string; error: string } {
|
|
29
|
+
return reason === 'rolled-back' ? {
|
|
30
|
+
heading: 'Turn rolled back',
|
|
31
|
+
detail: "This message's turn was removed by a rollback. Files it changed were not reverted. Retry sends it as a new message.",
|
|
32
|
+
error: "This message's turn was removed by a rollback. The message was not resent; Retry will submit it as a new message.",
|
|
33
|
+
} : {
|
|
34
|
+
heading: 'Send did not start',
|
|
35
|
+
detail: 'The original attempt ended on the server without running. Retry sends this as a new message.',
|
|
36
|
+
error: 'The original attempt ended on the server without running. The message was not resent; Retry will submit it as a new message.',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
20
39
|
export function codeCanResume(session: CodeSessionInfo): boolean {
|
|
21
40
|
return session.archivedAt === null && session.capabilities.resume && session.resume.available
|
|
22
41
|
&& (session.status === 'suspended' || (session.status === 'failed' && session.error?.recoverable === true));
|
|
23
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Whether the open session may roll its conversation back right now. An older server
|
|
45
|
+
* without the `rollback` field reads as unavailable.
|
|
46
|
+
*/
|
|
47
|
+
export function codeCanRollback(session: CodeSessionInfo | null, synced: boolean, idleOperation: boolean): boolean {
|
|
48
|
+
return !!session && session.rollback?.available === true && session.archivedAt === null && synced && idleOperation
|
|
49
|
+
&& (session.status === 'idle' || session.status === 'failed');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* User rows a rollback may target: a settled turn's own `${turnId}:user` row (never the
|
|
54
|
+
* unsent row or a follow-up), at or after the first recorded boundary, with a later turn.
|
|
55
|
+
*/
|
|
56
|
+
export function codeRollbackRows(items: readonly CodeItem[], sinceSequence: number | null | undefined): ReadonlySet<string> {
|
|
57
|
+
const rows = new Set<string>();
|
|
58
|
+
if (sinceSequence == null) return rows;
|
|
59
|
+
const settled = new Set(items.filter(item => item.kind === 'turn_completed' || item.kind === 'turn_failed'
|
|
60
|
+
|| item.kind === 'turn_cancelled').map(item => item.turnId));
|
|
61
|
+
const turns = items.filter(item => item.kind === 'user_message' && item.turnId !== null
|
|
62
|
+
&& item.itemId === `${item.turnId}:user` && item.firstSequence !== undefined);
|
|
63
|
+
// One row per turn, so a later turn exists exactly when the row is not the newest one.
|
|
64
|
+
const newest = Math.max(...turns.map(row => row.firstSequence!));
|
|
65
|
+
for (const row of turns) {
|
|
66
|
+
if (row.firstSequence! >= sinceSequence && row.firstSequence! < newest && settled.has(row.turnId)) rows.add(row.itemId);
|
|
67
|
+
}
|
|
68
|
+
return rows;
|
|
69
|
+
}
|
|
70
|
+
|
|
24
71
|
export function codeItemStatus(item: CodeItem): string {
|
|
25
72
|
if (item.kind === 'turn_cancelled' || item.status === 'cancelled') return 'Stopped';
|
|
26
73
|
if (item.kind === 'turn_failed' || item.status === 'error') return 'Failed';
|