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
|
@@ -3,7 +3,7 @@ import { getDesktop, isElectron } from '../panels/desktop-bridge';
|
|
|
3
3
|
import type { BrowserPickedElement, BrowserWebviewCommand, BrowserWebviewNativeAction, BrowserWebviewScreenshot, BrowserWebviewTabState } from '../panels/desktop-bridge';
|
|
4
4
|
import { DEFAULT_BROWSER_URL, isRestrictedBrowserHost, normalizeBrowserTarget } from './browser-url';
|
|
5
5
|
import { BrowserAddressBar } from './browser-address-bar';
|
|
6
|
-
import { createAddressBarState, displayedAddress, reduceAddressBar, type AddressBarAction, type AddressBarState } from './browser-address-state';
|
|
6
|
+
import { createAddressBarState, displayedAddress, reduceAddressBar, shouldShowRecentVisits, type AddressBarAction, type AddressBarState } from './browser-address-state';
|
|
7
7
|
import { pickFaviconUrl, faviconInitial } from './browser-favicon';
|
|
8
8
|
import { loadBrowserHistory, saveBrowserHistory, upsertBrowserHistory, type BrowserHistoryEntry } from './browser-history-store';
|
|
9
9
|
import './browser-panel.css';
|
|
@@ -308,7 +308,6 @@ export function BrowserPanel(props: BrowserPanelProps = {}) {
|
|
|
308
308
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
309
309
|
const [addressState, setAddressState] = useState<AddressBarState>(() => createAddressBarState(initialTab.current.url));
|
|
310
310
|
const [historyEntries, setHistoryEntries] = useState<BrowserHistoryEntry[]>(() => loadBrowserHistory());
|
|
311
|
-
const committedByUserRef = useRef<Set<string>>(new Set());
|
|
312
311
|
const pendingNavigationRefs = useRef<Map<string, string>>(new Map());
|
|
313
312
|
/**
|
|
314
313
|
* The webview `src` attribute is bound ONCE per page tab and never
|
|
@@ -384,10 +383,17 @@ export function BrowserPanel(props: BrowserPanelProps = {}) {
|
|
|
384
383
|
const activeRegistrationId = registrationIdFor(activeTab.id);
|
|
385
384
|
const activeBridgeState = bridgeStates[activeRegistrationId] ?? null;
|
|
386
385
|
const nativeInspecting = activeBridgeState?.inspecting === true;
|
|
386
|
+
// Page coordinates from the guest (CDP box models) are CSS px; the panel
|
|
387
|
+
// overlays live in DIP, so bounds picked under a non-1 zoomFactor must be
|
|
388
|
+
// scaled before they are drawn.
|
|
389
|
+
const zoomFactorRef = useRef(1);
|
|
390
|
+
zoomFactorRef.current = activeBridgeState?.zoomFactor ?? activeTab.zoomFactor ?? 1;
|
|
387
391
|
|
|
388
392
|
const panelInstanceId = useRef<symbol>(Symbol('browser-panel'));
|
|
389
393
|
/** regId -> guest webContentsId for this panel's live registrations. */
|
|
390
394
|
const registeredWebContentsIds = useRef<Map<string, number>>(new Map());
|
|
395
|
+
/** Stable viewport box the active webview fills (survives tab switches). */
|
|
396
|
+
const webviewStackRef = useRef<HTMLDivElement | null>(null);
|
|
391
397
|
|
|
392
398
|
const registerWebviewTarget = useCallback((tabId: string, webview: ElectronWebviewElement) => {
|
|
393
399
|
const browserBridge = getDesktop()?.browser;
|
|
@@ -439,6 +445,39 @@ export function BrowserPanel(props: BrowserPanelProps = {}) {
|
|
|
439
445
|
});
|
|
440
446
|
}, [updateTab]);
|
|
441
447
|
|
|
448
|
+
// Fit-to-width: ask the guest to re-fit its zoom whenever the panel box
|
|
449
|
+
// resizes (drawer drag, sidebar toggle, window resize). The initial
|
|
450
|
+
// observe() fire also covers first layout; the guest no-ops while the
|
|
451
|
+
// user holds a manual zoom from the zoom menu.
|
|
452
|
+
useEffect(() => {
|
|
453
|
+
const browserBridge = getDesktop()?.browser;
|
|
454
|
+
const host = webviewStackRef.current;
|
|
455
|
+
if (!canUseElectronWebview || !host || typeof browserBridge?.controlWebview !== 'function') return undefined;
|
|
456
|
+
const controlWebview = browserBridge.controlWebview;
|
|
457
|
+
let timer = 0;
|
|
458
|
+
const requestFit = () => {
|
|
459
|
+
window.clearTimeout(timer);
|
|
460
|
+
timer = window.setTimeout(() => {
|
|
461
|
+
void controlWebview({ kind: 'fitToWidth', tabId: registrationIdFor(activeTabIdRef.current) });
|
|
462
|
+
}, 200);
|
|
463
|
+
};
|
|
464
|
+
const observer = new ResizeObserver(requestFit);
|
|
465
|
+
observer.observe(host);
|
|
466
|
+
return () => {
|
|
467
|
+
window.clearTimeout(timer);
|
|
468
|
+
observer.disconnect();
|
|
469
|
+
};
|
|
470
|
+
}, [canUseElectronWebview, registrationIdFor]);
|
|
471
|
+
|
|
472
|
+
// A hidden tab keeps whatever zoom it had when the panel resized; the
|
|
473
|
+
// observer above only covers the active tab and does not refire for an
|
|
474
|
+
// unchanged box, so re-fit once when a tab comes back to the front.
|
|
475
|
+
useEffect(() => {
|
|
476
|
+
const browserBridge = getDesktop()?.browser;
|
|
477
|
+
if (!canUseElectronWebview || !activeRegistrationId || typeof browserBridge?.controlWebview !== 'function') return;
|
|
478
|
+
void browserBridge.controlWebview({ kind: 'fitToWidth', tabId: activeRegistrationId });
|
|
479
|
+
}, [activeRegistrationId, canUseElectronWebview]);
|
|
480
|
+
|
|
442
481
|
// v5: native inspect returns the REAL element (selector/role/name/bounds).
|
|
443
482
|
// When it fires for this panel's active tab, pin the element and open the
|
|
444
483
|
// composer anchored to the element's box center.
|
|
@@ -449,7 +488,8 @@ export function BrowserPanel(props: BrowserPanelProps = {}) {
|
|
|
449
488
|
if (tabId !== activeRegistrationId) return;
|
|
450
489
|
setPickedElement({ ...element, pickedAt: Date.now(), pageUrl: activeTab.url, tabId: activeRegistrationId });
|
|
451
490
|
if (element.bounds) {
|
|
452
|
-
|
|
491
|
+
const zoom = zoomFactorRef.current;
|
|
492
|
+
setCommentAnchor({ x: Math.round((element.bounds.x + element.bounds.width / 2) * zoom), y: Math.round((element.bounds.y + element.bounds.height / 2) * zoom) });
|
|
453
493
|
}
|
|
454
494
|
setCommentMode(true);
|
|
455
495
|
const label = element.name || element.text || element.selector;
|
|
@@ -705,7 +745,6 @@ export function BrowserPanel(props: BrowserPanelProps = {}) {
|
|
|
705
745
|
pendingNavigationRefs.current.delete(id);
|
|
706
746
|
initialSrcRefs.current.delete(id);
|
|
707
747
|
lastKnownUrlRefs.current.delete(id);
|
|
708
|
-
committedByUserRef.current.delete(id);
|
|
709
748
|
stableWebviewRefCallbacks.current.delete(id);
|
|
710
749
|
unregisterWebviewTarget(id);
|
|
711
750
|
setTabs(current => {
|
|
@@ -727,16 +766,11 @@ export function BrowserPanel(props: BrowserPanelProps = {}) {
|
|
|
727
766
|
const navigate = useCallback(() => {
|
|
728
767
|
const rawTarget = addressState.focused ? addressState.draft : displayedAddress(addressState);
|
|
729
768
|
dispatchAddress({ type: 'submit' });
|
|
730
|
-
const normalized = normalizeBrowserTarget(rawTarget);
|
|
731
|
-
if (normalized && normalized !== DEFAULT_BROWSER_URL) {
|
|
732
|
-
committedByUserRef.current.add(activeTab.id);
|
|
733
|
-
}
|
|
734
769
|
openUrlInTab(activeTab.id, rawTarget);
|
|
735
770
|
inputRef.current?.blur();
|
|
736
771
|
}, [activeTab.id, addressState, dispatchAddress, openUrlInTab]);
|
|
737
772
|
|
|
738
773
|
const openHistoryEntry = useCallback((url: string) => {
|
|
739
|
-
committedByUserRef.current.add(activeTab.id);
|
|
740
774
|
dispatchAddress({ type: 'submit' });
|
|
741
775
|
openUrlInTab(activeTab.id, url);
|
|
742
776
|
inputRef.current?.blur();
|
|
@@ -1131,7 +1165,7 @@ export function BrowserPanel(props: BrowserPanelProps = {}) {
|
|
|
1131
1165
|
)}
|
|
1132
1166
|
<div className="browser-more-zoom" role="group" aria-label="Page zoom" onClick={event => event.stopPropagation()}>
|
|
1133
1167
|
<button type="button" className="browser-more-item" onClick={() => void handleZoom('zoomOut')}>Zoom out</button>
|
|
1134
|
-
<span className="browser-more-zoom-label">{Math.round((activeTab.zoomFactor ?? 1) * 100)}
|
|
1168
|
+
<span className="browser-more-zoom-label">{Math.round((activeTab.zoomFactor ?? 1) * 100)}%{activeBridgeState?.zoomMode === 'auto' && (activeTab.zoomFactor ?? 1) < 1 ? ' fit' : ''}</span>
|
|
1135
1169
|
<button type="button" className="browser-more-item" onClick={() => void handleZoom('zoomIn')}>Zoom in</button>
|
|
1136
1170
|
<button type="button" className="browser-more-item" onClick={() => void handleZoom('zoomReset')}>Reset</button>
|
|
1137
1171
|
</div>
|
|
@@ -1150,15 +1184,16 @@ export function BrowserPanel(props: BrowserPanelProps = {}) {
|
|
|
1150
1184
|
</div>
|
|
1151
1185
|
)}
|
|
1152
1186
|
{canUseElectronWebview ? (
|
|
1153
|
-
<div className="browser-webview-stack">
|
|
1187
|
+
<div className="browser-webview-stack" ref={webviewStackRef}>
|
|
1154
1188
|
<div key={activeTab.id} className="browser-webview-host is-active">
|
|
1155
|
-
{historyEntries.length > 0 &&
|
|
1189
|
+
{historyEntries.length > 0 && shouldShowRecentVisits(addressState) && (
|
|
1156
1190
|
<div className="browser-history-empty" aria-label="Recent visits">
|
|
1157
1191
|
{historyEntries.map(entry => (
|
|
1158
1192
|
<button
|
|
1159
1193
|
key={`${entry.url}:${entry.at}`}
|
|
1160
1194
|
type="button"
|
|
1161
1195
|
className="browser-history-item"
|
|
1196
|
+
onMouseDown={event => event.preventDefault()}
|
|
1162
1197
|
onClick={() => openHistoryEntry(entry.url)}
|
|
1163
1198
|
>
|
|
1164
1199
|
<span className="browser-history-title">{entry.title || entry.url}</span>
|
|
@@ -20,6 +20,17 @@ export function displayedAddress(state: AddressBarState): string {
|
|
|
20
20
|
return state.focused ? state.draft : state.liveUrl;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Recents overlay gate. The overlay covers the page, so it belongs to the
|
|
25
|
+
* address bar only while that bar holds focus: focus seeds the draft with the
|
|
26
|
+
* live URL, and an empty draft means the user cleared the field to search.
|
|
27
|
+
* Anything else typed is a query the user is composing, and an unfocused bar
|
|
28
|
+
* always leaves the page visible.
|
|
29
|
+
*/
|
|
30
|
+
export function shouldShowRecentVisits(state: AddressBarState): boolean {
|
|
31
|
+
return state.focused && (state.draft.trim() === '' || state.draft === state.liveUrl);
|
|
32
|
+
}
|
|
33
|
+
|
|
23
34
|
export function reduceAddressBar(state: AddressBarState, action: AddressBarAction): AddressBarState {
|
|
24
35
|
switch (action.type) {
|
|
25
36
|
case 'sync-live':
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Client-side OS detection for keyboard behavior and labels.
|
|
2
|
+
//
|
|
3
|
+
// This reads the browser or desktop window that receives the key events, never
|
|
4
|
+
// the server's process.platform: a dashboard served from a Mac can be opened from
|
|
5
|
+
// a Windows machine, and the keyboard in front of the user is what matters.
|
|
6
|
+
|
|
7
|
+
type NavigatorWithUserAgentData = Navigator & { userAgentData?: { platform?: string } };
|
|
8
|
+
|
|
9
|
+
export function currentClientPlatform(): string {
|
|
10
|
+
if (typeof navigator === 'undefined') return '';
|
|
11
|
+
const nav = navigator as NavigatorWithUserAgentData;
|
|
12
|
+
return nav.userAgentData?.platform || nav.platform || nav.userAgent || '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isMacLikePlatform(platform: string = currentClientPlatform()): boolean {
|
|
16
|
+
return /mac|iphone|ipad|ipod/i.test(platform);
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -9,9 +9,11 @@ type CodeCanvasProps = {
|
|
|
9
9
|
workingDir: string;
|
|
10
10
|
onWorkingDirChange?: (path: string | null) => void;
|
|
11
11
|
onOpenLocalFile?: (path: string) => void;
|
|
12
|
+
/** Resolved `newCodeSession` chord, shown as the button's shortcut hint. */
|
|
13
|
+
newSessionShortcut?: string | undefined;
|
|
12
14
|
};
|
|
13
15
|
|
|
14
|
-
export function CodeCanvas({ port, workingDir, onWorkingDirChange, onOpenLocalFile }: CodeCanvasProps) {
|
|
16
|
+
export function CodeCanvas({ port, workingDir, onWorkingDirChange, onOpenLocalFile, newSessionShortcut }: CodeCanvasProps) {
|
|
15
17
|
const controller = useCodeController({ port, workingDir });
|
|
16
18
|
const [sidebarHost, setSidebarHost] = useState<HTMLElement | null>(null);
|
|
17
19
|
const previousWorkspace = useRef({ port, id: controller.selectedId, cwd: controller.selection.cwd });
|
|
@@ -26,7 +28,7 @@ export function CodeCanvas({ port, workingDir, onWorkingDirChange, onOpenLocalFi
|
|
|
26
28
|
onWorkingDirChange?.(next.cwd || null);
|
|
27
29
|
}
|
|
28
30
|
}, [port, controller.selectedId, controller.selection.cwd, onWorkingDirChange]);
|
|
29
|
-
const navigator = <CodeSessionList key={port} controller={controller} />;
|
|
31
|
+
const navigator = <CodeSessionList key={port} controller={controller} newSessionShortcut={newSessionShortcut} />;
|
|
30
32
|
const workbench = <CodeWorkbench key={port} controller={controller} endpointKey={String(port)} onOpenLocalFile={onOpenLocalFile} />;
|
|
31
33
|
if (sidebarHost) return <>
|
|
32
34
|
{createPortal(<div className="code-manager-session-navigator-content">{navigator}</div>, sidebarHost)}
|
|
@@ -9,7 +9,16 @@ type CodeComposerProps = {
|
|
|
9
9
|
canStop: boolean;
|
|
10
10
|
stopping: boolean;
|
|
11
11
|
pending: boolean;
|
|
12
|
+
/** A busy Claude turn takes a follow-up: Send follow-up renders beside Stop. */
|
|
13
|
+
followUp?: boolean;
|
|
14
|
+
/** The running turn already has its one follow-up. */
|
|
15
|
+
followUpSent?: boolean;
|
|
16
|
+
/** A follow-up is in flight. */
|
|
17
|
+
steering?: boolean;
|
|
18
|
+
/** What the pending operation is doing, when it is not a send. */
|
|
19
|
+
pendingLabel?: string;
|
|
12
20
|
readOnly: boolean;
|
|
21
|
+
autoFocus?: boolean;
|
|
13
22
|
onInputChange: (text: string) => void;
|
|
14
23
|
onSubmit: () => Promise<void>;
|
|
15
24
|
onStop: () => Promise<void>;
|
|
@@ -52,9 +61,12 @@ export function CodeComposer(props: CodeComposerProps) {
|
|
|
52
61
|
return <div className="code-composer">
|
|
53
62
|
<div className="code-composer-input-shell">
|
|
54
63
|
<textarea className="code-composer-input" aria-label="Code prompt" value={props.inputText}
|
|
64
|
+
autoFocus={props.autoFocus}
|
|
55
65
|
onChange={event => props.onInputChange(event.target.value)} onKeyDown={handleKeyDown}
|
|
56
66
|
onCompositionStart={() => { composing.current = true; }} onCompositionEnd={() => { composing.current = false; }}
|
|
57
|
-
placeholder={props.
|
|
67
|
+
placeholder={props.followUpSent ? 'This turn already has its follow-up. Draft the next message…'
|
|
68
|
+
: props.followUp ? 'Send a follow-up while this turn runs…'
|
|
69
|
+
: props.busy ? 'Draft a follow-up while this turn runs…' : 'Describe a task or ask a question…'}
|
|
58
70
|
rows={2} readOnly={props.readOnly} />
|
|
59
71
|
<span className="code-composer-hint">Enter to send · Shift+Enter for a new line</span>
|
|
60
72
|
</div>
|
|
@@ -66,18 +78,18 @@ export function CodeComposer(props: CodeComposerProps) {
|
|
|
66
78
|
title={dictation.reason} onClick={dictation.toggle}>
|
|
67
79
|
<MicGlyph muted={dictation.status === 'unsupported'} />
|
|
68
80
|
</button>
|
|
69
|
-
{props.busy
|
|
70
|
-
?
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
{props.busy && <button type="button" className="code-composer-send code-composer-stop" aria-label="Stop current turn"
|
|
82
|
+
title={props.stopping ? 'Stopping' : 'Stop current turn'}
|
|
83
|
+
disabled={!props.canStop || props.stopping} onClick={() => void stop()}><StopGlyph /></button>}
|
|
84
|
+
{(!props.busy || props.followUp) && <button type="button" className="code-composer-send"
|
|
85
|
+
aria-label={props.busy ? 'Send follow-up' : 'Send prompt'}
|
|
86
|
+
title={props.steering ? 'Sending follow-up' : props.pending ? 'Sending' : props.busy ? 'Send follow-up' : 'Send prompt'}
|
|
87
|
+
disabled={!props.canSend || !props.inputText.trim()} onClick={() => void submit()}><SendGlyph /></button>}
|
|
76
88
|
</div>
|
|
77
89
|
{/* An icon button cannot announce progress on its own. */}
|
|
78
|
-
{(props.stopping || props.pending || dictation.status === 'recording' || dictation.status === 'transcribing') &&
|
|
90
|
+
{(props.stopping || props.steering || props.pending || dictation.status === 'recording' || dictation.status === 'transcribing') &&
|
|
79
91
|
<span className="code-composer-status" role="status">
|
|
80
|
-
{props.stopping ? 'Stopping…' : props.pending ? 'Sending…'
|
|
92
|
+
{props.stopping ? 'Stopping…' : props.steering ? 'Sending follow-up…' : props.pending ? props.pendingLabel ?? 'Sending…'
|
|
81
93
|
: dictation.status === 'recording' ? 'Recording…' : 'Transcribing…'}</span>}
|
|
82
94
|
{(error || dictation.error) && <div className="code-action-error" role="alert">{error ?? dictation.error}</div>}
|
|
83
95
|
</div>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { CodeControllerModel } from './code-controller-types';
|
|
2
|
+
import { CODE_RUNTIME_LABELS } from './code-types';
|
|
3
|
+
import { recentCodeWorkspaces } from './code-recent-workspaces';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What a fresh draft looks like before its first prompt. Modelled on ZCode's
|
|
7
|
+
* ConversationDraftEmptyState and T3 Code's NoActiveThreadState: the workspace
|
|
8
|
+
* is the decision that gates everything else, so it is the one prominent
|
|
9
|
+
* control; the suggested prompts only fill the composer, they never send.
|
|
10
|
+
*/
|
|
11
|
+
const SUGGESTED_PROMPTS: readonly string[] = [
|
|
12
|
+
'Explain this codebase',
|
|
13
|
+
'Find and fix a bug',
|
|
14
|
+
'Write tests for the module I name',
|
|
15
|
+
'Review uncommitted changes',
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
// A chip and a recent both unmount the control that was activated -- the chip
|
|
19
|
+
// disappears once the draft is non-empty and the chosen recent leaves the
|
|
20
|
+
// list -- so keyboard focus is handed to the composer instead of dropping to
|
|
21
|
+
// <body>.
|
|
22
|
+
function focusComposer(): void {
|
|
23
|
+
document.querySelector<HTMLTextAreaElement>('textarea[aria-label="Code prompt"]')?.focus();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function CodeDraftEmptyState({ controller: c }: { controller: CodeControllerModel }) {
|
|
27
|
+
const cwd = c.selection.cwd;
|
|
28
|
+
// Same freeze rule as CodeWorkspaceHeader: once creation is in flight the
|
|
29
|
+
// draft belongs to it, and an unconfirmed create must not be edited around.
|
|
30
|
+
const frozen = c.selectedId !== null || c.pending || c.creationUnknown || c.operation.kind !== 'idle';
|
|
31
|
+
// Shared with the header picker so only one folder dialog can be open.
|
|
32
|
+
const picking = c.workspacePicking;
|
|
33
|
+
// Exclude the current cwd before the cap; otherwise it silently eats a slot.
|
|
34
|
+
const recents = recentCodeWorkspaces(c.sessions.filter(session => session.cwd !== cwd));
|
|
35
|
+
function pick() {
|
|
36
|
+
if (frozen || picking) return;
|
|
37
|
+
void c.pickWorkspace();
|
|
38
|
+
}
|
|
39
|
+
function chooseWorkspace(dir: string) {
|
|
40
|
+
if (frozen) return;
|
|
41
|
+
void c.setSelection({ cwd: dir });
|
|
42
|
+
focusComposer();
|
|
43
|
+
}
|
|
44
|
+
return <section className="code-draft-empty" aria-label="New session draft">
|
|
45
|
+
<h2 className="code-draft-empty-title">New session</h2>
|
|
46
|
+
{frozen ? <span className="code-workspace-chip" title={cwd}>{cwd || 'Workspace not set'}</span>
|
|
47
|
+
: <button type="button" className="code-workspace-picker code-draft-empty-picker" aria-label="Choose workspace for the new session"
|
|
48
|
+
disabled={picking} title={cwd || 'Choose a folder'} onClick={pick}>
|
|
49
|
+
<span className="code-workspace-picker-label">{picking ? 'Choosing folder…' : cwd || 'Choose workspace'}</span>
|
|
50
|
+
</button>}
|
|
51
|
+
<p className="code-draft-empty-selection">
|
|
52
|
+
{CODE_RUNTIME_LABELS[c.selection.provider]} · {c.selection.model || 'No model selected'}
|
|
53
|
+
</p>
|
|
54
|
+
{recents.length > 0 && <div className="code-draft-recents">
|
|
55
|
+
<span className="code-draft-recents-label">Recent workspaces</span>
|
|
56
|
+
{recents.map(dir => <button type="button" key={dir} className="code-draft-recent" title={dir}
|
|
57
|
+
disabled={frozen} onClick={() => chooseWorkspace(dir)}>{dir}</button>)}
|
|
58
|
+
</div>}
|
|
59
|
+
{!c.input.trim() && <div className="code-draft-prompts">
|
|
60
|
+
{SUGGESTED_PROMPTS.map(text => <button type="button" key={text} className="code-draft-prompt"
|
|
61
|
+
disabled={frozen} onClick={() => { c.setInput(text); focusComposer(); }}>{text}</button>)}
|
|
62
|
+
</div>}
|
|
63
|
+
</section>;
|
|
64
|
+
}
|
|
@@ -1,10 +1,44 @@
|
|
|
1
|
-
import { useId, useRef, useState } from 'react';
|
|
1
|
+
import { useEffect, useId, useRef, useState } from 'react';
|
|
2
2
|
import type { CodeSessionInfo } from '../../../../src/code-mode/wire';
|
|
3
3
|
import type { CodeControllerModel } from './code-controller-types';
|
|
4
4
|
import { CODE_RUNTIME_LABELS, CODE_SESSION_LABELS, codeCanResume, codeSessionBusy } from './code-types';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
CODE_ACTIVITY_BUCKET_LABELS, codeSessionAttention, codeSessionAttentionLabel, codeSessionUnread, codeWorkspaceName,
|
|
7
|
+
groupCodeSessions, groupCodeSessionsByActivity, groupCodeSessionsByWorkspace,
|
|
8
|
+
} from './session-order';
|
|
9
|
+
import { DEFAULT_MANAGER_SHORTCUT_KEYMAP, formatShortcut } from '../manager-shortcuts';
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
const NEW_SESSION_SHORTCUT = DEFAULT_MANAGER_SHORTCUT_KEYMAP.newCodeSession;
|
|
12
|
+
|
|
13
|
+
type CodeSidebarView = 'projects' | 'activity';
|
|
14
|
+
const SIDEBAR_VIEW_KEY = 'jaw.code.sidebarView';
|
|
15
|
+
|
|
16
|
+
function readSidebarView(): CodeSidebarView {
|
|
17
|
+
try { return localStorage.getItem(SIDEBAR_VIEW_KEY) === 'activity' ? 'activity' : 'projects'; }
|
|
18
|
+
catch { return 'projects'; }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function BellGlyph() {
|
|
22
|
+
return <svg viewBox="0 0 16 16" width="15" height="15" aria-hidden="true" focusable="false">
|
|
23
|
+
<path d="M8 2.5a3.5 3.5 0 0 0-3.5 3.5v2.3L3.3 10.6h9.4L11.5 8.3V6A3.5 3.5 0 0 0 8 2.5Z M6.6 12.4a1.5 1.5 0 0 0 2.8 0"
|
|
24
|
+
fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinejoin="round" strokeLinecap="round" />
|
|
25
|
+
</svg>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function SearchGlyph() {
|
|
29
|
+
return <svg viewBox="0 0 16 16" width="15" height="15" aria-hidden="true" focusable="false">
|
|
30
|
+
<circle cx="7" cy="7" r="4.25" fill="none" stroke="currentColor" strokeWidth="1.4" />
|
|
31
|
+
<path d="M10.2 10.2 13.5 13.5" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
|
|
32
|
+
</svg>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function PlusGlyph() {
|
|
36
|
+
return <svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true" focusable="false">
|
|
37
|
+
<path d="M8 3.5v9M3.5 8h9" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" />
|
|
38
|
+
</svg>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function SessionRow({ session: s, controller: c, view }: { session: CodeSessionInfo; controller: CodeControllerModel; view: CodeSidebarView }) {
|
|
8
42
|
const [renaming, setRenaming] = useState(false);
|
|
9
43
|
const [title, setTitle] = useState(s.title ?? '');
|
|
10
44
|
const [pending, setPending] = useState(false);
|
|
@@ -16,6 +50,9 @@ function SessionRow({ session: s, controller: c }: { session: CodeSessionInfo; c
|
|
|
16
50
|
const busy = codeSessionBusy(s);
|
|
17
51
|
const count = active && c.synced ? c.permissions.length : s.pendingPermissionCount;
|
|
18
52
|
const attention = codeSessionAttention(count);
|
|
53
|
+
const unread = !active && codeSessionUnread(s);
|
|
54
|
+
const working = c.workingIds?.has(s.sessionId) ?? false;
|
|
55
|
+
const waiting = working && s.status === 'idle';
|
|
19
56
|
async function action(run: () => Promise<void>, after?: () => void) {
|
|
20
57
|
if (guard.current) return;
|
|
21
58
|
guard.current = true; setPending(true); setError(null);
|
|
@@ -27,13 +64,20 @@ function SessionRow({ session: s, controller: c }: { session: CodeSessionInfo; c
|
|
|
27
64
|
return <li className="code-session-row">
|
|
28
65
|
<button ref={selectButton} type="button" className={`code-session-item${active ? ' active' : ''}`}
|
|
29
66
|
aria-current={active ? 'true' : undefined} onClick={() => void action(() => c.selectSession(s.sessionId))}>
|
|
30
|
-
<span className="code-session-cwd">
|
|
31
|
-
|
|
67
|
+
<span className="code-session-cwd">
|
|
68
|
+
{unread && <span className="code-session-unread-dot" role="img" aria-label="Unread" />}
|
|
69
|
+
{s.title || 'Untitled session'}
|
|
70
|
+
</span>
|
|
71
|
+
{/* Projects view already names the workspace in the group heading. */}
|
|
72
|
+
<span className="code-session-meta" title={s.cwd}>{view === 'activity'
|
|
73
|
+
? `${codeWorkspaceName(s.cwd)} · ${CODE_RUNTIME_LABELS[s.provider]}` : CODE_RUNTIME_LABELS[s.provider]}</span>
|
|
32
74
|
{/* Ready is the common case and stays silent. A status on every row
|
|
33
75
|
is a status on no row, and it costs the one session that is
|
|
34
76
|
actually doing something its visibility. */}
|
|
35
|
-
<span className={`code-session-status code-session-status-${s.status}`}
|
|
36
|
-
data-quiet={s.status === 'idle' ? 'true' : undefined}>
|
|
77
|
+
<span className={`code-session-status code-session-status-${waiting ? 'starting' : s.status}`}
|
|
78
|
+
data-quiet={s.status === 'idle' && !working ? 'true' : undefined}>
|
|
79
|
+
{working && <span className="code-session-spinner" aria-hidden="true" />}
|
|
80
|
+
{waiting ? CODE_SESSION_LABELS.starting : CODE_SESSION_LABELS[s.status]}</span>
|
|
37
81
|
<span className={`code-session-attention code-session-attention-${attention.kind}`}
|
|
38
82
|
data-quiet={attention.kind === 'none' ? 'true' : undefined}>{codeSessionAttentionLabel(attention)}</span>
|
|
39
83
|
</button>
|
|
@@ -63,26 +107,68 @@ function SessionRow({ session: s, controller: c }: { session: CodeSessionInfo; c
|
|
|
63
107
|
</li>;
|
|
64
108
|
}
|
|
65
109
|
|
|
66
|
-
export function CodeSessionList({ controller: c }: { controller: CodeControllerModel }) {
|
|
110
|
+
export function CodeSessionList({ controller: c, newSessionShortcut = NEW_SESSION_SHORTCUT }: { controller: CodeControllerModel; newSessionShortcut?: string | undefined }) {
|
|
67
111
|
const [search, setSearch] = useState('');
|
|
68
|
-
const [
|
|
112
|
+
const [searching, setSearching] = useState(false);
|
|
113
|
+
const searchInput = useRef<HTMLInputElement>(null);
|
|
114
|
+
function toggleSearch() {
|
|
115
|
+
// Closing clears the query so the list is never silently filtered by a hidden box.
|
|
116
|
+
if (searching) { setSearching(false); setSearch(''); return; }
|
|
117
|
+
setSearching(true);
|
|
118
|
+
queueMicrotask(() => searchInput.current?.focus());
|
|
119
|
+
}
|
|
120
|
+
const draftBadgeId = useId();
|
|
121
|
+
const [view, setViewState] = useState<CodeSidebarView>(readSidebarView);
|
|
122
|
+
function setView(next: CodeSidebarView) {
|
|
123
|
+
setViewState(next);
|
|
124
|
+
try { localStorage.setItem(SIDEBAR_VIEW_KEY, next); } catch { /* storage unavailable: the choice lasts for this page */ }
|
|
125
|
+
}
|
|
69
126
|
const [error, setError] = useState<string | null>(null);
|
|
70
127
|
const [paging, setPaging] = useState(false);
|
|
71
128
|
const pagingRef = useRef(false);
|
|
72
129
|
const visible = c.sessions.filter(s => `${s.title ?? ''} ${s.cwd} ${CODE_RUNTIME_LABELS[s.provider]}`.toLowerCase().includes(search.toLowerCase()));
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
130
|
+
// Projects (the default) keeps rows in creation order under their workspace, so
|
|
131
|
+
// answering a prompt never moves a row under the reader's cursor. Activity is the
|
|
132
|
+
// reader's explicit choice: unread first, then last activity by day.
|
|
133
|
+
const anyUnread = c.sessions.some(row => row.sessionId !== c.selectedId && codeSessionUnread(row));
|
|
134
|
+
const live = visible.filter(row => row.archivedAt === null);
|
|
135
|
+
const archived = visible.filter(row => row.archivedAt !== null);
|
|
136
|
+
const groups: { key: string; title: string; cwd: string | null; sessions: CodeSessionInfo[] }[] = view === 'activity'
|
|
137
|
+
? groupCodeSessionsByActivity(live, Date.now(), c.selectedId).map(group => ({
|
|
138
|
+
key: group.bucket, title: CODE_ACTIVITY_BUCKET_LABELS[group.bucket], cwd: null, sessions: group.sessions }))
|
|
139
|
+
: groupCodeSessionsByWorkspace(live).map(group => ({ key: group.cwd, title: group.cwd, cwd: group.cwd, sessions: group.sessions }));
|
|
140
|
+
if (archived.length) groups.push({ key: 'archived', title: 'Archived', cwd: null, sessions: groupCodeSessions(archived).flatMap(group => group.sessions) });
|
|
141
|
+
// The chord resolves through the manager shortcut system (rebindable in
|
|
142
|
+
// Settings); the runner re-broadcasts it here so Code mode can be absent
|
|
143
|
+
// without the event reaching a dead handler.
|
|
144
|
+
const newSession = c.newSession;
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
function onShortcutAction(event: Event) {
|
|
147
|
+
if ((event as CustomEvent<string>).detail === 'newCodeSession') newSession();
|
|
148
|
+
}
|
|
149
|
+
document.addEventListener('jaw:shortcut-action', onShortcutAction);
|
|
150
|
+
return () => document.removeEventListener('jaw:shortcut-action', onShortcutAction);
|
|
151
|
+
}, [newSession]);
|
|
152
|
+
async function newInWorkspace(cwd: string) {
|
|
153
|
+
c.newSession();
|
|
154
|
+
// Silently retargeting a draft that holds unsent text or an unreconciled
|
|
155
|
+
// send would move that content to another workspace; setSelection also
|
|
156
|
+
// early-returns while the draft it is about to retarget is mid-operation.
|
|
157
|
+
// Say why instead, each reason in its own words.
|
|
158
|
+
if (c.hasUnsentDraft) {
|
|
159
|
+
setError('Send or clear the current draft before choosing another workspace.');
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// A busy *session* is not that case: newSession has already moved the
|
|
163
|
+
// reader onto a fresh draft, so the + still retargets that draft.
|
|
164
|
+
if (c.selectedId === null && c.pending) {
|
|
165
|
+
setError('Wait for the current change to finish before choosing another workspace.');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
setError(null);
|
|
169
|
+
try { await c.setSelection({ cwd }); }
|
|
170
|
+
catch (err) { setError(err instanceof Error ? err.message : String(err)); }
|
|
171
|
+
}
|
|
86
172
|
async function loadMore() {
|
|
87
173
|
if (pagingRef.current) return;
|
|
88
174
|
pagingRef.current = true; setPaging(true); setError(null);
|
|
@@ -91,28 +177,56 @@ export function CodeSessionList({ controller: c }: { controller: CodeControllerM
|
|
|
91
177
|
finally { pagingRef.current = false; setPaging(false); }
|
|
92
178
|
}
|
|
93
179
|
return <nav className="code-session-list" aria-label="Code sessions">
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
<
|
|
180
|
+
<button type="button" className={`code-session-new-primary${c.selectedId === null ? ' active' : ''}`}
|
|
181
|
+
aria-current={c.selectedId === null ? 'true' : undefined}
|
|
182
|
+
// The name is fixed so it survives the label, badge and chord
|
|
183
|
+
// changes; the draft state therefore travels as a description.
|
|
184
|
+
aria-label="New Code session"
|
|
185
|
+
aria-describedby={c.hasUnsentDraft ? draftBadgeId : undefined}
|
|
186
|
+
title={`Start a new session (${formatShortcut(newSessionShortcut)})`} onClick={c.newSession}>
|
|
187
|
+
<PlusGlyph />
|
|
188
|
+
<span className="code-session-new-label">New session</span>
|
|
189
|
+
{c.hasUnsentDraft && <span id={draftBadgeId} className="code-session-draft-badge">Draft</span>}
|
|
190
|
+
<kbd className="code-session-new-hint" aria-hidden="true">{formatShortcut(newSessionShortcut)}</kbd>
|
|
191
|
+
</button>
|
|
192
|
+
<div className="code-session-list-header">
|
|
193
|
+
<span className="code-session-list-title">{view === 'activity' ? 'Recent activity' : 'Projects'}</span>
|
|
194
|
+
<span className="code-session-header-actions">
|
|
195
|
+
<button type="button" className={`code-session-header-btn code-session-search-btn${searching ? ' active' : ''}`}
|
|
196
|
+
aria-pressed={searching} aria-label="Search sessions" title="Search sessions" onClick={toggleSearch}>
|
|
197
|
+
<SearchGlyph />
|
|
198
|
+
</button>
|
|
199
|
+
<button type="button" className={`code-session-header-btn code-session-bell${view === 'activity' ? ' active' : ''}`}
|
|
200
|
+
aria-pressed={view === 'activity'}
|
|
201
|
+
aria-label={anyUnread ? 'Recent activity (unread sessions)' : 'Recent activity'}
|
|
202
|
+
title={view === 'activity' ? 'Back to projects' : 'Recent activity'}
|
|
203
|
+
onClick={() => setView(view === 'activity' ? 'projects' : 'activity')}>
|
|
204
|
+
<BellGlyph />
|
|
205
|
+
{anyUnread && <span className="code-session-unread-dot" aria-hidden="true" />}
|
|
206
|
+
</button>
|
|
207
|
+
</span>
|
|
103
208
|
</div>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
209
|
+
{/* Projects already scopes by workspace, so the All / This cwd toggle is gone.
|
|
210
|
+
Search and the archived switch live behind the search icon. */}
|
|
211
|
+
{searching && <div className="code-session-search-row">
|
|
212
|
+
<input ref={searchInput} className="code-session-search" type="search" aria-label="Search loaded sessions"
|
|
213
|
+
placeholder="Search sessions…" value={search} onChange={event => setSearch(event.target.value)}
|
|
214
|
+
onKeyDown={event => { if (event.key === 'Escape') { event.preventDefault(); toggleSearch(); } }} />
|
|
215
|
+
<button type="button" className={`code-session-archived-btn${c.filter.archived ? ' active' : ''}`}
|
|
216
|
+
aria-pressed={c.filter.archived} title="Include archived sessions"
|
|
217
|
+
onClick={() => c.setFilter({ ...c.filter, archived: !c.filter.archived })}>Archived</button>
|
|
218
|
+
</div>}
|
|
110
219
|
{c.loading && <div className="code-session-list-loading" role="status">Loading sessions…</div>}
|
|
111
220
|
{groups.map(group => <section className="code-session-group" key={group.key}>
|
|
112
|
-
{group.title && <h3 className=
|
|
113
|
-
|
|
221
|
+
{group.title && <h3 className={`code-session-group-title${group.key === 'priority' ? ' code-session-group-priority' : ''}`} title={group.title}>
|
|
222
|
+
<span>{group.cwd !== null ? codeWorkspaceName(group.cwd) : group.title}</span>
|
|
223
|
+
{group.cwd !== null && <button type="button" className="code-session-group-new"
|
|
224
|
+
aria-label={`New session in ${group.cwd}`} title={`New session in ${group.cwd}`}
|
|
225
|
+
onClick={() => void newInWorkspace(group.cwd!)}>+</button>}
|
|
226
|
+
</h3>}
|
|
227
|
+
<ul className="code-session-list-items">{group.sessions.map(s => <SessionRow key={s.sessionId} session={s} controller={c} view={view} />)}</ul>
|
|
114
228
|
</section>)}
|
|
115
|
-
{!c.loading && !visible.length && <p className="code-session-list-empty">{search ? 'No loaded sessions match. Clear search or load more.' : 'No sessions here. Start
|
|
229
|
+
{!c.loading && !visible.length && <p className="code-session-list-empty">{search ? 'No loaded sessions match. Clear search or load more.' : 'No sessions here. Start a new session above.'}</p>}
|
|
116
230
|
{c.hasMoreSessions && <button type="button" className="code-inline-action" disabled={paging} onClick={() => void loadMore()}>{paging ? 'Loading…' : 'Load more sessions'}</button>}
|
|
117
231
|
<button type="button" className="code-inline-action" onClick={() => { setError(null); void c.refresh().catch(err => setError(err instanceof Error ? err.message : String(err))); }}>Refresh sessions</button>
|
|
118
232
|
{error && <div className="code-session-list-error" role="alert">{error}</div>}
|