@jingyi0605/codingns4dsh 0.2.0-rc.2 → 0.2.0-rc3
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.en.md +4 -4
- package/README.md +15 -6
- package/assets/provider-icons/antigravity.svg +39 -0
- package/assets/provider-icons/codebuddy.svg +37 -0
- package/assets/provider-icons/cursor-cli.svg +1 -0
- package/assets/provider-icons/kiro-cli.svg +11 -0
- package/assets/provider-icons/qoder-cn.svg +5 -0
- package/assets/provider-icons/qoder.svg +5 -0
- package/assets/provider-icons/workbuddy.svg +29 -0
- package/data/build/dist/bootstrap/dsh-peer-host-preboot-shim.d.ts.map +1 -1
- package/data/build/dist/bootstrap/dsh-peer-host-preboot-shim.js +78 -1
- package/data/build/dist/bootstrap/dsh-peer-host-preboot-shim.js.map +1 -1
- package/data/build/dist/client/bundle.js +3864 -507
- package/data/build/dist/client/bundle.js.map +1 -1
- package/data/build/dist/client/cli-catalog.d.ts +1 -1
- package/data/build/dist/client/cli-catalog.d.ts.map +1 -1
- package/data/build/dist/client/cli-catalog.js +11 -5
- package/data/build/dist/client/cli-catalog.js.map +1 -1
- package/data/build/dist/client/cli-slots.d.ts.map +1 -1
- package/data/build/dist/client/cli-slots.js +111 -13
- package/data/build/dist/client/cli-slots.js.map +1 -1
- package/data/build/dist/client/debug/ui.d.ts.map +1 -1
- package/data/build/dist/client/debug/ui.js +2 -1
- package/data/build/dist/client/debug/ui.js.map +1 -1
- package/data/build/dist/client/delegate-command.d.ts +1 -0
- package/data/build/dist/client/delegate-command.d.ts.map +1 -1
- package/data/build/dist/client/delegate-command.js +486 -27
- package/data/build/dist/client/delegate-command.js.map +1 -1
- package/data/build/dist/client/delegate-plan.d.ts +13 -4
- package/data/build/dist/client/delegate-plan.d.ts.map +1 -1
- package/data/build/dist/client/delegate-plan.js +10 -6
- package/data/build/dist/client/delegate-plan.js.map +1 -1
- package/data/build/dist/client/delegate-ui-dom.d.ts +46 -0
- package/data/build/dist/client/delegate-ui-dom.d.ts.map +1 -1
- package/data/build/dist/client/delegate-ui-dom.js +291 -7
- package/data/build/dist/client/delegate-ui-dom.js.map +1 -1
- package/data/build/dist/client/features/cli-adapters.d.ts.map +1 -1
- package/data/build/dist/client/features/cli-adapters.js +117 -8
- package/data/build/dist/client/features/cli-adapters.js.map +1 -1
- package/data/build/dist/client/features/mobile-access-panel.d.ts.map +1 -1
- package/data/build/dist/client/features/mobile-access-panel.js +8 -7
- package/data/build/dist/client/features/mobile-access-panel.js.map +1 -1
- package/data/build/dist/client/features/peer-host.d.ts +6 -4
- package/data/build/dist/client/features/peer-host.d.ts.map +1 -1
- package/data/build/dist/client/features/peer-host.js +346 -18
- package/data/build/dist/client/features/peer-host.js.map +1 -1
- package/data/build/dist/client/git-history-graph.d.ts +2 -2
- package/data/build/dist/client/git-history-graph.d.ts.map +1 -1
- package/data/build/dist/client/git-history-graph.js +2 -2
- package/data/build/dist/client/git-history-graph.js.map +1 -1
- package/data/build/dist/client/git-management.d.ts.map +1 -1
- package/data/build/dist/client/git-management.js +21 -1
- package/data/build/dist/client/git-management.js.map +1 -1
- package/data/build/dist/client/locale.d.ts.map +1 -1
- package/data/build/dist/client/locale.js +60 -6
- package/data/build/dist/client/locale.js.map +1 -1
- package/data/build/dist/client/locales/terminalAuth.d.ts.map +1 -1
- package/data/build/dist/client/locales/terminalAuth.js +4 -0
- package/data/build/dist/client/locales/terminalAuth.js.map +1 -1
- package/data/build/dist/client/locales/usage.d.ts.map +1 -1
- package/data/build/dist/client/locales/usage.js +20 -0
- package/data/build/dist/client/locales/usage.js.map +1 -1
- package/data/build/dist/client/mobile-session-interaction.d.ts.map +1 -1
- package/data/build/dist/client/mobile-session-interaction.js +24 -7
- package/data/build/dist/client/mobile-session-interaction.js.map +1 -1
- package/data/build/dist/client/mobile-sidebar-gestures.d.ts +55 -7
- package/data/build/dist/client/mobile-sidebar-gestures.d.ts.map +1 -1
- package/data/build/dist/client/mobile-sidebar-gestures.js +339 -40
- package/data/build/dist/client/mobile-sidebar-gestures.js.map +1 -1
- package/data/build/dist/client/model-catalog-cache.d.ts +5 -0
- package/data/build/dist/client/model-catalog-cache.d.ts.map +1 -0
- package/data/build/dist/client/model-catalog-cache.js +58 -0
- package/data/build/dist/client/model-catalog-cache.js.map +1 -0
- package/data/build/dist/client/peer-host-aggregate-refresh.d.ts +7 -0
- package/data/build/dist/client/peer-host-aggregate-refresh.d.ts.map +1 -1
- package/data/build/dist/client/peer-host-aggregate-refresh.js +13 -1
- package/data/build/dist/client/peer-host-aggregate-refresh.js.map +1 -1
- package/data/build/dist/client/peer-host-native-projection.d.ts +16 -1
- package/data/build/dist/client/peer-host-native-projection.d.ts.map +1 -1
- package/data/build/dist/client/peer-host-native-projection.js +49 -8
- package/data/build/dist/client/peer-host-native-projection.js.map +1 -1
- package/data/build/dist/client/peer-host-native-store-projection.d.ts +6 -2
- package/data/build/dist/client/peer-host-native-store-projection.d.ts.map +1 -1
- package/data/build/dist/client/peer-host-native-store-projection.js +308 -10
- package/data/build/dist/client/peer-host-native-store-projection.js.map +1 -1
- package/data/build/dist/client/provider-icon-assets.js +14 -0
- package/data/build/dist/client/provider-icon-assets.js.map +1 -1
- package/data/build/dist/client/provider-icons.d.ts.map +1 -1
- package/data/build/dist/client/provider-icons.js +15 -3
- package/data/build/dist/client/provider-icons.js.map +1 -1
- package/data/build/dist/client/remote-web-context.js +10 -6
- package/data/build/dist/client/remote-web-context.js.map +1 -1
- package/data/build/dist/client/service-tier.d.ts +54 -0
- package/data/build/dist/client/service-tier.d.ts.map +1 -0
- package/data/build/dist/client/service-tier.js +78 -0
- package/data/build/dist/client/service-tier.js.map +1 -0
- package/data/build/dist/client/settings-promo-styles.d.ts +28 -0
- package/data/build/dist/client/settings-promo-styles.d.ts.map +1 -0
- package/data/build/dist/client/settings-promo-styles.js +58 -0
- package/data/build/dist/client/settings-promo-styles.js.map +1 -0
- package/data/build/dist/client/settings-promo.d.ts +14 -0
- package/data/build/dist/client/settings-promo.d.ts.map +1 -0
- package/data/build/dist/client/settings-promo.js +68 -0
- package/data/build/dist/client/settings-promo.js.map +1 -0
- package/data/build/dist/client/settings-section.d.ts +2 -1
- package/data/build/dist/client/settings-section.d.ts.map +1 -1
- package/data/build/dist/client/settings-section.js +7 -4
- package/data/build/dist/client/settings-section.js.map +1 -1
- package/data/build/dist/client/subscription-slot.d.ts.map +1 -1
- package/data/build/dist/client/subscription-slot.js +181 -16
- package/data/build/dist/client/subscription-slot.js.map +1 -1
- package/data/build/dist/client/terminal/model.d.ts +82 -3
- package/data/build/dist/client/terminal/model.d.ts.map +1 -1
- package/data/build/dist/client/terminal/model.js +368 -29
- package/data/build/dist/client/terminal/model.js.map +1 -1
- package/data/build/dist/client/terminal/recovery.d.ts +16 -1
- package/data/build/dist/client/terminal/recovery.d.ts.map +1 -1
- package/data/build/dist/client/terminal/recovery.js +101 -39
- package/data/build/dist/client/terminal/recovery.js.map +1 -1
- package/data/build/dist/client/terminal/styles.d.ts +10 -0
- package/data/build/dist/client/terminal/styles.d.ts.map +1 -1
- package/data/build/dist/client/terminal/styles.js +27 -3
- package/data/build/dist/client/terminal/styles.js.map +1 -1
- package/data/build/dist/client/terminal/ui.d.ts +3 -2
- package/data/build/dist/client/terminal/ui.d.ts.map +1 -1
- package/data/build/dist/client/terminal/ui.js +215 -104
- package/data/build/dist/client/terminal/ui.js.map +1 -1
- package/data/build/dist/client/terminal/xterm-view.d.ts +5 -1
- package/data/build/dist/client/terminal/xterm-view.d.ts.map +1 -1
- package/data/build/dist/client/terminal/xterm-view.js +378 -20
- package/data/build/dist/client/terminal/xterm-view.js.map +1 -1
- package/data/build/dist/host/cli-adapters/acp-cli-driver.d.ts +58 -0
- package/data/build/dist/host/cli-adapters/acp-cli-driver.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/acp-cli-driver.js +271 -0
- package/data/build/dist/host/cli-adapters/acp-cli-driver.js.map +1 -0
- package/data/build/dist/host/cli-adapters/antigravity-driver.d.ts +30 -0
- package/data/build/dist/host/cli-adapters/antigravity-driver.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/antigravity-driver.js +210 -0
- package/data/build/dist/host/cli-adapters/antigravity-driver.js.map +1 -0
- package/data/build/dist/host/cli-adapters/claude-driver.js +1 -1
- package/data/build/dist/host/cli-adapters/claude-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/codebuddy-driver.d.ts +139 -0
- package/data/build/dist/host/cli-adapters/codebuddy-driver.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/codebuddy-driver.js +1721 -0
- package/data/build/dist/host/cli-adapters/codebuddy-driver.js.map +1 -0
- package/data/build/dist/host/cli-adapters/codebuddy-subscription.d.ts +53 -0
- package/data/build/dist/host/cli-adapters/codebuddy-subscription.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/codebuddy-subscription.js +778 -0
- package/data/build/dist/host/cli-adapters/codebuddy-subscription.js.map +1 -0
- package/data/build/dist/host/cli-adapters/codex-driver.d.ts +12 -0
- package/data/build/dist/host/cli-adapters/codex-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/codex-driver.js +153 -6
- package/data/build/dist/host/cli-adapters/codex-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/command-code-driver.d.ts +43 -3
- package/data/build/dist/host/cli-adapters/command-code-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/command-code-driver.js +637 -72
- package/data/build/dist/host/cli-adapters/command-code-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/command-code-history.d.ts +147 -0
- package/data/build/dist/host/cli-adapters/command-code-history.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/command-code-history.js +659 -0
- package/data/build/dist/host/cli-adapters/command-code-history.js.map +1 -0
- package/data/build/dist/host/cli-adapters/cursor-driver.d.ts +12 -0
- package/data/build/dist/host/cli-adapters/cursor-driver.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/cursor-driver.js +20 -0
- package/data/build/dist/host/cli-adapters/cursor-driver.js.map +1 -0
- package/data/build/dist/host/cli-adapters/custom-upstream-classifier.d.ts +67 -0
- package/data/build/dist/host/cli-adapters/custom-upstream-classifier.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/custom-upstream-classifier.js +169 -0
- package/data/build/dist/host/cli-adapters/custom-upstream-classifier.js.map +1 -0
- package/data/build/dist/host/cli-adapters/delegate-dispatch.d.ts +2 -1
- package/data/build/dist/host/cli-adapters/delegate-dispatch.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/delegate-dispatch.js +14 -61
- package/data/build/dist/host/cli-adapters/delegate-dispatch.js.map +1 -1
- package/data/build/dist/host/cli-adapters/delegation-authorization.d.ts +19 -0
- package/data/build/dist/host/cli-adapters/delegation-authorization.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/delegation-authorization.js +49 -0
- package/data/build/dist/host/cli-adapters/delegation-authorization.js.map +1 -0
- package/data/build/dist/host/cli-adapters/delegation-mention-rewrite.d.ts +34 -0
- package/data/build/dist/host/cli-adapters/delegation-mention-rewrite.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/delegation-mention-rewrite.js +121 -0
- package/data/build/dist/host/cli-adapters/delegation-mention-rewrite.js.map +1 -0
- package/data/build/dist/host/cli-adapters/driver.d.ts +6 -0
- package/data/build/dist/host/cli-adapters/driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/feature.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/feature.js +269 -18
- package/data/build/dist/host/cli-adapters/feature.js.map +1 -1
- package/data/build/dist/host/cli-adapters/kiro-driver.d.ts +12 -0
- package/data/build/dist/host/cli-adapters/kiro-driver.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/kiro-driver.js +18 -0
- package/data/build/dist/host/cli-adapters/kiro-driver.js.map +1 -0
- package/data/build/dist/host/cli-adapters/legacy-session-adapter.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/legacy-session-adapter.js +11 -0
- package/data/build/dist/host/cli-adapters/legacy-session-adapter.js.map +1 -1
- package/data/build/dist/host/cli-adapters/model-catalog.d.ts +5 -0
- package/data/build/dist/host/cli-adapters/model-catalog.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/model-catalog.js +130 -0
- package/data/build/dist/host/cli-adapters/model-catalog.js.map +1 -1
- package/data/build/dist/host/cli-adapters/native-subagent-dispatch.d.ts +33 -3
- package/data/build/dist/host/cli-adapters/native-subagent-dispatch.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/native-subagent-dispatch.js +115 -9
- package/data/build/dist/host/cli-adapters/native-subagent-dispatch.js.map +1 -1
- package/data/build/dist/host/cli-adapters/native-team-subagent.d.ts +14 -2
- package/data/build/dist/host/cli-adapters/native-team-subagent.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/native-team-subagent.js +23 -4
- package/data/build/dist/host/cli-adapters/native-team-subagent.js.map +1 -1
- package/data/build/dist/host/cli-adapters/new-api-subscription.d.ts +75 -0
- package/data/build/dist/host/cli-adapters/new-api-subscription.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/new-api-subscription.js +959 -0
- package/data/build/dist/host/cli-adapters/new-api-subscription.js.map +1 -0
- package/data/build/dist/host/cli-adapters/opencode-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/opencode-driver.js +9 -7
- package/data/build/dist/host/cli-adapters/opencode-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +33 -3
- package/data/build/dist/host/cli-adapters/provider-subscription.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/provider-subscription.js +158 -40
- package/data/build/dist/host/cli-adapters/provider-subscription.js.map +1 -1
- package/data/build/dist/host/cli-adapters/qoder-driver.d.ts +71 -0
- package/data/build/dist/host/cli-adapters/qoder-driver.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/qoder-driver.js +553 -0
- package/data/build/dist/host/cli-adapters/qoder-driver.js.map +1 -0
- package/data/build/dist/host/cli-adapters/qoder-subscription.d.ts +31 -0
- package/data/build/dist/host/cli-adapters/qoder-subscription.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/qoder-subscription.js +178 -0
- package/data/build/dist/host/cli-adapters/qoder-subscription.js.map +1 -0
- package/data/build/dist/host/cli-adapters/registry.d.ts +41 -2
- package/data/build/dist/host/cli-adapters/registry.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/registry.js +187 -18
- package/data/build/dist/host/cli-adapters/registry.js.map +1 -1
- package/data/build/dist/host/cli-adapters/session-store.d.ts +4 -0
- package/data/build/dist/host/cli-adapters/session-store.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/session-store.js +18 -2
- package/data/build/dist/host/cli-adapters/session-store.js.map +1 -1
- package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +14 -1
- package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/standard-stream-driver.js +121 -23
- package/data/build/dist/host/cli-adapters/standard-stream-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/stream-normalizer.d.ts +10 -1
- package/data/build/dist/host/cli-adapters/stream-normalizer.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/stream-normalizer.js +23 -0
- package/data/build/dist/host/cli-adapters/stream-normalizer.js.map +1 -1
- package/data/build/dist/host/cli-adapters/subagent-tool.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/subagent-tool.js +53 -7
- package/data/build/dist/host/cli-adapters/subagent-tool.js.map +1 -1
- package/data/build/dist/host/cli-bridge/bridge-server.d.ts +5 -0
- package/data/build/dist/host/cli-bridge/bridge-server.d.ts.map +1 -1
- package/data/build/dist/host/cli-bridge/bridge-server.js +7 -2
- package/data/build/dist/host/cli-bridge/bridge-server.js.map +1 -1
- package/data/build/dist/host/cli-bridge/command-code-mod.d.ts +5 -1
- package/data/build/dist/host/cli-bridge/command-code-mod.d.ts.map +1 -1
- package/data/build/dist/host/cli-bridge/command-code-mod.js +43 -12
- package/data/build/dist/host/cli-bridge/command-code-mod.js.map +1 -1
- package/data/build/dist/host/cli-bridge/dispatch.d.ts.map +1 -1
- package/data/build/dist/host/cli-bridge/dispatch.js +43 -5
- package/data/build/dist/host/cli-bridge/dispatch.js.map +1 -1
- package/data/build/dist/host/cli-bridge/mcp-stdio-entry.js +24 -5
- package/data/build/dist/host/cli-bridge/mcp-stdio-entry.js.map +1 -1
- package/data/build/dist/host/features/peer-host.d.ts +21 -1
- package/data/build/dist/host/features/peer-host.d.ts.map +1 -1
- package/data/build/dist/host/features/peer-host.js +174 -30
- package/data/build/dist/host/features/peer-host.js.map +1 -1
- package/data/build/dist/host/features/types.d.ts +2 -0
- package/data/build/dist/host/features/types.d.ts.map +1 -1
- package/data/build/dist/host/index.d.ts +11 -1
- package/data/build/dist/host/index.d.ts.map +1 -1
- package/data/build/dist/host/index.js +12 -2
- package/data/build/dist/host/index.js.map +1 -1
- package/data/build/dist/host/modules/peer-host/peer-host-native-protocol.js +6 -1
- package/data/build/dist/host/modules/peer-host/peer-host-native-protocol.js.map +1 -1
- package/data/build/dist/host/modules/peer-host/peer-host-virtual-registry.d.ts +2 -0
- package/data/build/dist/host/modules/peer-host/peer-host-virtual-registry.d.ts.map +1 -1
- package/data/build/dist/host/modules/peer-host/peer-host-virtual-registry.js +26 -7
- package/data/build/dist/host/modules/peer-host/peer-host-virtual-registry.js.map +1 -1
- package/data/build/dist/host/native-session-bridge.d.ts.map +1 -1
- package/data/build/dist/host/native-session-bridge.js +36 -7
- package/data/build/dist/host/native-session-bridge.js.map +1 -1
- package/data/build/dist/host/rpc.js +5 -2
- package/data/build/dist/host/rpc.js.map +1 -1
- package/data/build/dist/host/settings.d.ts.map +1 -1
- package/data/build/dist/host/settings.js +12 -5
- package/data/build/dist/host/settings.js.map +1 -1
- package/data/build/dist/host/terminal/backends/local-pty-backend.d.ts.map +1 -1
- package/data/build/dist/host/terminal/backends/local-pty-backend.js +14 -0
- package/data/build/dist/host/terminal/backends/local-pty-backend.js.map +1 -1
- package/data/build/dist/host/terminal/backends/tmux-backend.d.ts +40 -1
- package/data/build/dist/host/terminal/backends/tmux-backend.d.ts.map +1 -1
- package/data/build/dist/host/terminal/backends/tmux-backend.js +291 -18
- package/data/build/dist/host/terminal/backends/tmux-backend.js.map +1 -1
- package/data/build/dist/host/terminal/runtime-adapter.d.ts +32 -0
- package/data/build/dist/host/terminal/runtime-adapter.d.ts.map +1 -1
- package/data/build/dist/host/terminal/runtime-adapter.js +8 -0
- package/data/build/dist/host/terminal/runtime-adapter.js.map +1 -1
- package/data/build/dist/host/terminal/runtime-manager.d.ts +3 -0
- package/data/build/dist/host/terminal/runtime-manager.d.ts.map +1 -1
- package/data/build/dist/host/terminal/runtime-manager.js +92 -4
- package/data/build/dist/host/terminal/runtime-manager.js.map +1 -1
- package/data/build/dist/host/terminal/terminal-controller.d.ts.map +1 -1
- package/data/build/dist/host/terminal/terminal-controller.js +58 -6
- package/data/build/dist/host/terminal/terminal-controller.js.map +1 -1
- package/data/build/dist/host/terminal/terminal-process-service.d.ts.map +1 -1
- package/data/build/dist/host/terminal/terminal-process-service.js +12 -2
- package/data/build/dist/host/terminal/terminal-process-service.js.map +1 -1
- package/data/build/dist/host/terminal/terminal-service.d.ts +41 -4
- package/data/build/dist/host/terminal/terminal-service.d.ts.map +1 -1
- package/data/build/dist/host/terminal/terminal-service.js +551 -61
- package/data/build/dist/host/terminal/terminal-service.js.map +1 -1
- package/data/build/dist/index.d.ts +2 -2
- package/data/build/dist/index.d.ts.map +1 -1
- package/data/build/dist/index.js +2 -2
- package/data/build/dist/index.js.map +1 -1
- package/data/build/dist/shared/contracts/cli-adapter.d.ts +64 -2
- package/data/build/dist/shared/contracts/cli-adapter.d.ts.map +1 -1
- package/data/build/dist/shared/contracts/config.d.ts +54 -11
- package/data/build/dist/shared/contracts/config.d.ts.map +1 -1
- package/data/build/dist/shared/contracts/config.js +52 -8
- package/data/build/dist/shared/contracts/config.js.map +1 -1
- package/data/build/dist/shared/contracts/subscription.d.ts +5 -1
- package/data/build/dist/shared/contracts/subscription.d.ts.map +1 -1
- package/data/build/dist/shared/contracts/subscription.js.map +1 -1
- package/data/build/dist/shared/contracts/terminal.d.ts +8 -1
- package/data/build/dist/shared/contracts/terminal.d.ts.map +1 -1
- package/data/build/dist/shared/contracts/version.d.ts +1 -1
- package/data/build/dist/shared/contracts/version.d.ts.map +1 -1
- package/data/build/dist/shared/contracts/version.js +1 -1
- package/data/build/dist/shared/contracts/version.js.map +1 -1
- package/data/build/dist/shared/debug.d.ts +8 -0
- package/data/build/dist/shared/debug.d.ts.map +1 -1
- package/data/build/dist/shared/debug.js +43 -7
- package/data/build/dist/shared/debug.js.map +1 -1
- package/data/build/dist/shared/delegation-carrier.d.ts +24 -0
- package/data/build/dist/shared/delegation-carrier.d.ts.map +1 -0
- package/data/build/dist/shared/delegation-carrier.js +111 -0
- package/data/build/dist/shared/delegation-carrier.js.map +1 -0
- package/data/build/dist/shared/index.d.ts +2 -2
- package/data/build/dist/shared/index.d.ts.map +1 -1
- package/data/build/dist/shared/index.js +2 -2
- package/data/build/dist/shared/index.js.map +1 -1
- package/data/build/dist/shared/terminal-input.d.ts +8 -0
- package/data/build/dist/shared/terminal-input.d.ts.map +1 -0
- package/data/build/dist/shared/terminal-input.js +10 -0
- package/data/build/dist/shared/terminal-input.js.map +1 -0
- package/data/build/dist/transport/debug.d.ts +7 -0
- package/data/build/dist/transport/debug.d.ts.map +1 -1
- package/data/build/dist/transport/debug.js +33 -5
- package/data/build/dist/transport/debug.js.map +1 -1
- package/data/build/dist/transport/index.d.ts +1 -1
- package/data/build/dist/transport/index.d.ts.map +1 -1
- package/data/build/dist/transport/index.js +1 -1
- package/data/build/dist/transport/index.js.map +1 -1
- package/data/build/dist/typert.host.js +1 -1
- package/data/build/dist/typert.host.js.map +1 -1
- package/package.json +1 -1
- package/profile/README.md +2 -2
- package/profile/package.json +2 -2
- package/profile/version.json +1 -1
- package/version.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
-
import { copyFileSync, existsSync,
|
|
3
|
-
import { homedir
|
|
4
|
-
import { basename, dirname, join } from 'node:path';
|
|
2
|
+
import { closeSync, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, readSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
5
5
|
import readline from 'node:readline';
|
|
6
6
|
import { firstToolText, serializeToolValue } from './tool-observation.js';
|
|
7
7
|
import { usageChunk } from './rpc-driver-utils.js';
|
|
@@ -9,6 +9,7 @@ import { commandEnvironment, resolveCommandPath, terminateChildProcess } from '.
|
|
|
9
9
|
import { promptWithAttachmentPaths } from './attachment-utils.js';
|
|
10
10
|
import { commandCodeBridgeArgs, commandCodeBridgeEnvironment } from '../cli-bridge/injections.js';
|
|
11
11
|
import { getSubagentBridge } from '../cli-bridge/bridge-holder.js';
|
|
12
|
+
import { CommandCodeHistory, } from './command-code-history.js';
|
|
12
13
|
const WINDOWS = process.platform === 'win32';
|
|
13
14
|
const COMMAND_CODE_BINARIES = WINDOWS
|
|
14
15
|
? ['command-code', 'commandcode', 'cmdc']
|
|
@@ -93,6 +94,60 @@ const CATALOG_EFFORTS = new Map([
|
|
|
93
94
|
['xai/grok-4.5', ['low', 'medium', 'high']],
|
|
94
95
|
['xai/grok-4.7', ['low', 'medium', 'high', 'xhigh']],
|
|
95
96
|
]);
|
|
97
|
+
/**
|
|
98
|
+
* 按模型名的最后一段回退查找思考强度。
|
|
99
|
+
*
|
|
100
|
+
* BYOK 提供者的模型 id 带自定义前缀(如 `mcgrox/deepseek-v4.1-flash`),
|
|
101
|
+
* 而 CATALOG_EFFORTS 的键是 Command Code 内置目录 id(`deepseek/deepseek-v4.1-flash`),
|
|
102
|
+
* 直接查表会落空,导致菜单只剩 Default。表内 77 个键的最后一段互不重复,
|
|
103
|
+
* 因此可以安全地按末段匹配;命中多个时按歧义处理并返回空,避免误判。
|
|
104
|
+
*/
|
|
105
|
+
function catalogEffortsFor(id) {
|
|
106
|
+
const key = id.toLowerCase();
|
|
107
|
+
const direct = CATALOG_EFFORTS.get(key);
|
|
108
|
+
if (direct !== undefined)
|
|
109
|
+
return direct;
|
|
110
|
+
const bare = key.includes('/') ? key.slice(key.lastIndexOf('/') + 1) : key;
|
|
111
|
+
if (bare === key)
|
|
112
|
+
return [];
|
|
113
|
+
let matched;
|
|
114
|
+
for (const [candidate, efforts] of CATALOG_EFFORTS) {
|
|
115
|
+
if (!candidate.endsWith(`/${bare}`))
|
|
116
|
+
continue;
|
|
117
|
+
if (matched !== undefined)
|
|
118
|
+
return []; // 末段歧义,宁可不给强度也不猜
|
|
119
|
+
matched = efforts;
|
|
120
|
+
}
|
|
121
|
+
return matched ?? [];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 读取 BYOK 提供者在 `~/.commandcode/providers.json` 里声明的思考强度。
|
|
125
|
+
*
|
|
126
|
+
* 这是 BYOK 模型强度的权威来源:用户在 providers.json 的模型条目上写
|
|
127
|
+
* `reasoningEfforts: [...]`,Command Code 据此校验 `--effort`。内置目录查不到
|
|
128
|
+
* 的自定义模型只能从这里拿到强度。键为 `提供者/模型` 小写形式,空 Map 表示无声明。
|
|
129
|
+
*/
|
|
130
|
+
function readDeclaredEfforts(homeDirectory) {
|
|
131
|
+
const declared = new Map();
|
|
132
|
+
const config = readJson(join(homeDirectory, 'providers.json'));
|
|
133
|
+
if (config === null)
|
|
134
|
+
return declared;
|
|
135
|
+
const providers = isRecord(config.provider) ? config.provider : isRecord(config.providers) ? config.providers : null;
|
|
136
|
+
if (providers === null)
|
|
137
|
+
return declared;
|
|
138
|
+
for (const [providerId, provider] of Object.entries(providers)) {
|
|
139
|
+
if (!isRecord(provider) || !isRecord(provider.models))
|
|
140
|
+
continue;
|
|
141
|
+
for (const [modelName, entry] of Object.entries(provider.models)) {
|
|
142
|
+
if (!isRecord(entry) || !Array.isArray(entry.reasoningEfforts))
|
|
143
|
+
continue;
|
|
144
|
+
const efforts = entry.reasoningEfforts.filter((value) => typeof value === 'string' && VALID_EFFORTS.has(value));
|
|
145
|
+
if (efforts.length > 0)
|
|
146
|
+
declared.set(`${providerId}/${modelName}`.toLowerCase(), efforts);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return declared;
|
|
150
|
+
}
|
|
96
151
|
/**
|
|
97
152
|
* CLI 的 `--max-turns` 默认只有 100,复杂任务经常在半途被截断。
|
|
98
153
|
*
|
|
@@ -103,8 +158,22 @@ const DEFAULT_AUTO_CONTINUE_ATTEMPTS = 3;
|
|
|
103
158
|
const AUTO_CONTINUE_PROMPT = '继续';
|
|
104
159
|
/** CLI 在 -p 模式撞到 --max-turns 时的退出码(MAX_TURNS_REACHED)。 */
|
|
105
160
|
const COMMAND_CODE_MAX_TURNS_EXIT_CODE = 8;
|
|
161
|
+
/** 发送 SIGINT 后等待 CLI 自己收尾的时间;超时再强制清理进程树。 */
|
|
162
|
+
const COMMAND_CODE_INTERRUPT_GRACE_MS = 1_500;
|
|
163
|
+
/** 将 DSH 权限状态映射为 Command Code 参数;未知状态保持 CLI 默认审批。 */
|
|
164
|
+
function commandCodePermissionArgs(permission) {
|
|
165
|
+
if (permission?.sandboxMode === 'danger-full-access' && permission.approvalPolicy === 'never')
|
|
166
|
+
return ['--yolo'];
|
|
167
|
+
if (permission?.sandboxMode === 'workspace-write' && permission.approvalPolicy === 'never')
|
|
168
|
+
return ['--permission-mode', 'accept-edits'];
|
|
169
|
+
if (permission?.sandboxMode === 'read-only')
|
|
170
|
+
return ['--plan'];
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
106
173
|
/**
|
|
107
|
-
* Command Code
|
|
174
|
+
* Command Code 驱动:使用 `--session` 建立首轮输入,随后绑定 Provider 原生会话并用
|
|
175
|
+
* `--resume + -p + --output-format json` 续接。首轮历史只作为 CLI 的输入快照,不能替代
|
|
176
|
+
* Provider 自己维护的 canonical transcript。
|
|
108
177
|
*
|
|
109
178
|
* 与 Codex 驱动保持同一套消息优化:正文/推理增量携带 assistant 消息身份,工具完成后
|
|
110
179
|
* 的下一条 assistant 消息之前结束当前 DSH step,因此一个 Provider 运行会被切成多个
|
|
@@ -116,7 +185,7 @@ export class CommandCodeDriver {
|
|
|
116
185
|
id: 'command-code',
|
|
117
186
|
name: 'Command Code',
|
|
118
187
|
protocol: 'command',
|
|
119
|
-
capabilities: ['models', 'stream', 'resume', 'interrupt', 'tool-events', 'reasoning', 'usage'],
|
|
188
|
+
capabilities: ['models', 'stream', 'resume', 'interrupt', 'tool-events', 'reasoning', 'usage', 'history'],
|
|
120
189
|
};
|
|
121
190
|
/** 驱动自己维护 Provider turn 边界,Host 可以把工具边界映射为 DSH step。 */
|
|
122
191
|
supportsSegmentedTurns = true;
|
|
@@ -127,6 +196,7 @@ export class CommandCodeDriver {
|
|
|
127
196
|
maxTurns;
|
|
128
197
|
autoContinueMaxAttempts;
|
|
129
198
|
autoContinuePrompt;
|
|
199
|
+
history;
|
|
130
200
|
cachedBinary = null;
|
|
131
201
|
cachedEnvironment;
|
|
132
202
|
processes = new Set();
|
|
@@ -139,6 +209,54 @@ export class CommandCodeDriver {
|
|
|
139
209
|
this.maxTurns = normalizePositiveInteger(options.maxTurns ?? readOptionalIntegerEnv('CODINGNS_COMMAND_CODE_MAX_TURNS'), DEFAULT_MAX_TURNS);
|
|
140
210
|
this.autoContinueMaxAttempts = normalizeNonNegativeInteger(options.autoContinueMaxAttempts ?? readOptionalIntegerEnv('CODINGNS_COMMAND_CODE_AUTO_CONTINUE_ATTEMPTS'), DEFAULT_AUTO_CONTINUE_ATTEMPTS);
|
|
141
211
|
this.autoContinuePrompt = options.autoContinuePrompt?.trim() || AUTO_CONTINUE_PROMPT;
|
|
212
|
+
const readStatus = options.readStatus ?? ((workspacePath) => this.readCommandCodeStatus(workspacePath));
|
|
213
|
+
this.history = new CommandCodeHistory(this.homeDirectory, { readStatus });
|
|
214
|
+
}
|
|
215
|
+
/** 读取 Command Code 原生会话目录,供 Provider History 层使用。 */
|
|
216
|
+
detectSessions(workspacePath) {
|
|
217
|
+
return this.history.detectSessions(workspacePath);
|
|
218
|
+
}
|
|
219
|
+
detectSessionsDetailed(workspacePath) {
|
|
220
|
+
return this.history.detectSessionsDetailed(workspacePath);
|
|
221
|
+
}
|
|
222
|
+
startSession(workspacePath, options = {}) {
|
|
223
|
+
return this.history.startSession(workspacePath, options);
|
|
224
|
+
}
|
|
225
|
+
resumeSession(providerSessionId, rawStoreRef) {
|
|
226
|
+
return this.history.resumeSession(providerSessionId, rawStoreRef);
|
|
227
|
+
}
|
|
228
|
+
sendMessage(providerSessionId, rawStoreRef, content) {
|
|
229
|
+
return this.history.sendMessage(providerSessionId, rawStoreRef, content);
|
|
230
|
+
}
|
|
231
|
+
readSessionHistory(providerSessionId, rawStoreRef, cursor, limit, direction = 'forward') {
|
|
232
|
+
return this.history.readSessionHistory(providerSessionId, rawStoreRef, cursor, limit, direction);
|
|
233
|
+
}
|
|
234
|
+
readSessionHistoryDelta(providerSessionId, rawStoreRef, cursor, limit, direction = 'forward') {
|
|
235
|
+
return this.history.readSessionHistoryDelta(providerSessionId, rawStoreRef, cursor, limit, direction);
|
|
236
|
+
}
|
|
237
|
+
subscribeSession(providerSessionId, rawStoreRef, cursor, limit, onEvent) {
|
|
238
|
+
return this.history.subscribeSession(providerSessionId, rawStoreRef, cursor, limit, onEvent);
|
|
239
|
+
}
|
|
240
|
+
readSessionTitle(providerSessionId, rawStoreRef) {
|
|
241
|
+
return this.history.readSessionTitle(providerSessionId, rawStoreRef);
|
|
242
|
+
}
|
|
243
|
+
renameSessionTitle(providerSessionId, rawStoreRef, title) {
|
|
244
|
+
return this.history.renameSessionTitle(providerSessionId, rawStoreRef, title);
|
|
245
|
+
}
|
|
246
|
+
updateSessionArchiveState(providerSessionId, rawStoreRef, isArchived) {
|
|
247
|
+
return this.history.updateSessionArchiveState(providerSessionId, rawStoreRef, isArchived);
|
|
248
|
+
}
|
|
249
|
+
deleteSession(providerSessionId, rawStoreRef) {
|
|
250
|
+
return this.history.deleteSession(providerSessionId, rawStoreRef);
|
|
251
|
+
}
|
|
252
|
+
readContextUsage(providerSessionId, rawStoreRef) {
|
|
253
|
+
return this.history.readContextUsage(providerSessionId, rawStoreRef);
|
|
254
|
+
}
|
|
255
|
+
readSessionStats(providerSessionId, rawStoreRef) {
|
|
256
|
+
return this.history.readSessionStats(providerSessionId, rawStoreRef);
|
|
257
|
+
}
|
|
258
|
+
forkSession(providerSessionId, workspacePath, options) {
|
|
259
|
+
return this.history.forkSession(providerSessionId, workspacePath, options);
|
|
142
260
|
}
|
|
143
261
|
async detect() {
|
|
144
262
|
for (const command of this.binaries) {
|
|
@@ -189,6 +307,7 @@ export class CommandCodeDriver {
|
|
|
189
307
|
return emptyCatalog();
|
|
190
308
|
}
|
|
191
309
|
const groups = [];
|
|
310
|
+
const declared = readDeclaredEfforts(this.homeDirectory);
|
|
192
311
|
let currentGroup;
|
|
193
312
|
for (const rawLine of stdout.split(/\r?\n/u)) {
|
|
194
313
|
const line = rawLine.trim();
|
|
@@ -204,20 +323,101 @@ export class CommandCodeDriver {
|
|
|
204
323
|
continue;
|
|
205
324
|
const id = match[1];
|
|
206
325
|
const description = match[2].trim();
|
|
207
|
-
|
|
326
|
+
// BYOK 模型以 providers.json 的声明为准,内置目录作为回退;
|
|
327
|
+
// 目录回退按末段匹配,使 `mcgrox/deepseek-v4.1-flash` 也能拿到内置强度。
|
|
328
|
+
const efforts = declared.get(id.toLowerCase()) ?? catalogEffortsFor(id);
|
|
329
|
+
currentGroup.models.push({ id, name: id, ...(description ? { description } : {}), efforts });
|
|
208
330
|
}
|
|
209
331
|
const config = readJson(join(this.homeDirectory, 'config.json'));
|
|
210
|
-
|
|
332
|
+
// status --json 反映 CLI 当前真实模型;配置文件可能仍是旧值,优先使用运行时状态。
|
|
333
|
+
let statusModel = null;
|
|
334
|
+
try {
|
|
335
|
+
const status = this.runSpawnSync(detection.command, ['status', '--json'], { encoding: 'utf8', timeout: 5_000, windowsHide: true, shell: WINDOWS, ...(this.cachedEnvironment === undefined ? {} : { env: this.cachedEnvironment }) });
|
|
336
|
+
const parsed = parseJson(status.stdout ?? '');
|
|
337
|
+
statusModel = typeof parsed?.model === 'string' && parsed.model.trim() !== '' ? parsed.model.trim() : null;
|
|
338
|
+
}
|
|
339
|
+
catch { /* 状态读取失败时回退配置文件 */ }
|
|
340
|
+
const currentModel = statusModel ?? (typeof config?.model === 'string' ? config.model : null);
|
|
211
341
|
const configuredEffort = currentModel !== null && isRecord(config?.reasoningEffort) ? config.reasoningEffort[currentModel] : undefined;
|
|
212
342
|
const currentEffort = typeof configuredEffort === 'string' && VALID_EFFORTS.has(configuredEffort) ? configuredEffort : null;
|
|
213
343
|
const result = { groups, currentModel, currentEffort };
|
|
214
344
|
return result;
|
|
215
345
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
346
|
+
/** 读取 Provider 当前 status;历史层只在模型一致时使用其中的上下文窗口。 */
|
|
347
|
+
async readCommandCodeStatus(workspacePath) {
|
|
348
|
+
const command = this.cachedBinary ?? this.binaries[0];
|
|
349
|
+
if (command === undefined)
|
|
350
|
+
return null;
|
|
351
|
+
try {
|
|
352
|
+
const result = this.runSpawnSync(command, ['status', '--json'], {
|
|
353
|
+
cwd: workspacePath,
|
|
354
|
+
encoding: 'utf8',
|
|
355
|
+
timeout: 5_000,
|
|
356
|
+
windowsHide: true,
|
|
357
|
+
shell: WINDOWS,
|
|
358
|
+
env: this.cachedEnvironment ?? commandEnvironment(command),
|
|
359
|
+
});
|
|
360
|
+
return parseJson(result.stdout ?? '');
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
async probeSession(input) {
|
|
367
|
+
throwIfCommandCodeProbeAborted(input.signal);
|
|
368
|
+
const providerSessionId = input.providerSessionId?.trim();
|
|
369
|
+
if (!providerSessionId)
|
|
370
|
+
return { state: 'unknown', reason: '缺少 Provider 会话标识' };
|
|
371
|
+
const candidates = new Set();
|
|
372
|
+
const rawStoreRef = input.rawStoreRef?.trim();
|
|
373
|
+
const safeRawStoreRef = rawStoreRef !== undefined && isSafeCommandCodeTranscriptPath(rawStoreRef, this.homeDirectory)
|
|
374
|
+
? rawStoreRef
|
|
375
|
+
: undefined;
|
|
376
|
+
if (safeRawStoreRef !== undefined)
|
|
377
|
+
candidates.add(safeRawStoreRef);
|
|
378
|
+
const discovered = resolveCanonicalTranscriptPath(join(this.homeDirectory, 'projects', `${providerSessionId}.jsonl`), this.homeDirectory, input.cwd);
|
|
379
|
+
if (discovered !== null)
|
|
380
|
+
candidates.add(discovered);
|
|
381
|
+
const checkpoint = resolveCommandCodeCheckpointPath(this.homeDirectory, input.cwd ?? process.cwd(), providerSessionId);
|
|
382
|
+
if (checkpoint !== null)
|
|
383
|
+
candidates.add(checkpoint);
|
|
384
|
+
if (candidates.size === 0)
|
|
385
|
+
return { state: 'missing', reason: 'Command Code 原生会话文件不存在' };
|
|
386
|
+
let lastFailure;
|
|
387
|
+
for (const candidate of candidates) {
|
|
388
|
+
throwIfCommandCodeProbeAborted(input.signal);
|
|
389
|
+
if (!existsSync(candidate))
|
|
390
|
+
continue;
|
|
391
|
+
try {
|
|
392
|
+
if (!statSync(candidate).isFile()) {
|
|
393
|
+
lastFailure = { state: 'missing', reason: 'Command Code 原生会话路径不是文件', rawStoreRef: candidate };
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
// 探测只需验证首条记录;CLI 正在追加时末尾可能存在半行,不能因此拒绝
|
|
397
|
+
// 一个仍可由 --resume 找到的会话。完整历史解析由独立的读取入口负责。
|
|
398
|
+
const firstRecord = readFirstJsonRecord(candidate);
|
|
399
|
+
if (firstRecord === null) {
|
|
400
|
+
lastFailure = { state: 'corrupt', reason: 'Command Code 原生会话首条记录不是有效 JSONL', rawStoreRef: candidate };
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (candidate.endsWith('.checkpoints.jsonl')) {
|
|
404
|
+
// checkpoint 只保存回退点,Command Code 的 --resume 索引仍要求同名
|
|
405
|
+
// `.jsonl` transcript。仅有 checkpoint 的旧会话不能宣称可恢复。
|
|
406
|
+
lastFailure = { state: 'missing', reason: 'Command Code 只有 checkpoint,没有可恢复 transcript', rawStoreRef: candidate };
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
const recordedId = textValue(firstRecord.id ?? firstRecord.sessionId).trim();
|
|
410
|
+
if (recordedId !== '' && recordedId !== providerSessionId) {
|
|
411
|
+
lastFailure = { state: 'corrupt', reason: 'Command Code 原生会话标识与绑定不一致', rawStoreRef: candidate };
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
return { state: 'available', reason: 'Command Code 原生会话可用', rawStoreRef: candidate };
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
lastFailure = { state: 'unreachable', reason: `无法读取 Command Code 原生会话:${error instanceof Error ? error.message : String(error)}`, rawStoreRef: candidate };
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return lastFailure ?? { state: 'missing', reason: 'Command Code 原生会话文件不存在', ...(safeRawStoreRef ? { rawStoreRef: safeRawStoreRef } : {}) };
|
|
221
421
|
}
|
|
222
422
|
async *executeTurn(input) {
|
|
223
423
|
const binary = this.cachedBinary ?? (await this.detect()).command;
|
|
@@ -226,7 +426,7 @@ export class CommandCodeDriver {
|
|
|
226
426
|
const segmented = input.splitToolSteps === true;
|
|
227
427
|
const turn = segmented ? this.acquireTurn(input, binary) : this.startTurn(input, binary);
|
|
228
428
|
let suspended = false;
|
|
229
|
-
const onAbort = () => {
|
|
429
|
+
const onAbort = () => { this.requestGracefulStop(turn); };
|
|
230
430
|
input.signal?.addEventListener('abort', onAbort, { once: true });
|
|
231
431
|
if (input.signal?.aborted)
|
|
232
432
|
onAbort();
|
|
@@ -269,12 +469,35 @@ export class CommandCodeDriver {
|
|
|
269
469
|
return turn;
|
|
270
470
|
}
|
|
271
471
|
startTurn(input, binary) {
|
|
272
|
-
const
|
|
273
|
-
|
|
472
|
+
const cwd = input.cwd ?? process.cwd();
|
|
473
|
+
const requestedProviderSessionId = input.providerSessionId?.trim() || undefined;
|
|
474
|
+
// 旧 synthetic ID 可能只对应 checkpoint;回退时必须以当前 DSH 会话 ID
|
|
475
|
+
// 作为文件名,保证后续 session-binding、索引和 --resume 使用同一个标识。
|
|
476
|
+
const canonicalKey = requestedProviderSessionId !== undefined
|
|
477
|
+
&& !isLegacySyntheticSessionId(requestedProviderSessionId)
|
|
478
|
+
? requestedProviderSessionId
|
|
479
|
+
: input.sessionId;
|
|
480
|
+
const canonicalPath = resolveCommandCodeTranscriptPath(this.homeDirectory, cwd, canonicalKey);
|
|
481
|
+
const hasCanonicalTranscript = existsSync(canonicalPath) && isSafeCommandCodeTranscriptPath(canonicalPath, this.homeDirectory);
|
|
482
|
+
const hasRequestedRawTranscript = input.rawStoreRef?.trim() !== undefined
|
|
483
|
+
&& existsSync(input.rawStoreRef.trim())
|
|
484
|
+
&& isSafeCommandCodeTranscriptPath(input.rawStoreRef.trim(), this.homeDirectory);
|
|
485
|
+
// 旧版本会把只有 checkpoint 的外部路径登记成 synthetic session ID;该 ID
|
|
486
|
+
// 无法被 Command Code 的 --resume 索引解析。没有可读 JSONL 时重新从 DSH
|
|
487
|
+
// 历史建立 canonical transcript,避免把坏绑定继续传给 Provider。
|
|
488
|
+
const canResume = requestedProviderSessionId !== undefined
|
|
489
|
+
&& (hasCanonicalTranscript || hasRequestedRawTranscript || !isLegacySyntheticSessionId(requestedProviderSessionId));
|
|
490
|
+
const providerSessionId = canResume ? requestedProviderSessionId : input.sessionId;
|
|
491
|
+
const transcriptPath = canResume ? undefined : canonicalPath;
|
|
492
|
+
if (transcriptPath !== undefined)
|
|
493
|
+
writeTranscript(transcriptPath, input);
|
|
274
494
|
const turn = {
|
|
275
495
|
sessionId: input.sessionId,
|
|
276
496
|
child: undefined,
|
|
277
497
|
transcriptPath,
|
|
498
|
+
providerSessionId,
|
|
499
|
+
rawStoreRef: canResume ? input.rawStoreRef?.trim() || undefined : transcriptPath,
|
|
500
|
+
cwd,
|
|
278
501
|
queue: createEventQueue(),
|
|
279
502
|
currentMessageId: undefined,
|
|
280
503
|
sawCompletedTool: false,
|
|
@@ -287,7 +510,15 @@ export class CommandCodeDriver {
|
|
|
287
510
|
attempt: 0,
|
|
288
511
|
maxTurnsReached: false,
|
|
289
512
|
messageSequence: 0,
|
|
513
|
+
stderrTail: '',
|
|
290
514
|
};
|
|
515
|
+
// 首轮固定使用 canonical transcript;CLI 事件里的 checkpoint ID 不可用于
|
|
516
|
+
// --resume,因此先登记稳定的 DSH 会话 ID,后续回合沿用同一路径恢复。
|
|
517
|
+
if (!canResume && transcriptPath !== undefined) {
|
|
518
|
+
turn.queue.push({ type: 'session-binding', providerSessionId: providerSessionId, rawStoreRef: transcriptPath });
|
|
519
|
+
}
|
|
520
|
+
// 普通回合也登记活动 turn;Registry 的 interrupt 不应只对分段回合生效。
|
|
521
|
+
this.turns.set(input.sessionId, turn);
|
|
291
522
|
void this.runTurnAttempts(turn, input, binary);
|
|
292
523
|
return turn;
|
|
293
524
|
}
|
|
@@ -304,22 +535,28 @@ export class CommandCodeDriver {
|
|
|
304
535
|
try {
|
|
305
536
|
while (true) {
|
|
306
537
|
turn.attempt += 1;
|
|
538
|
+
turn.stderrTail = '';
|
|
307
539
|
// 自动续跑是同一个 DSH 运行里的下一段;消息序号必须跨尝试连续,
|
|
308
540
|
// 否则新进程会从 command-code-message-1 重新编号,公共投影层会把它
|
|
309
541
|
// 当成同一条消息继续追加,而不是开启新段。
|
|
310
|
-
const state = createStreamState(() => turn.aborted, turn.messageSequence,
|
|
542
|
+
const state = createStreamState(() => turn.aborted, turn.messageSequence, turn.providerSessionId ?? null, turn.sessionId);
|
|
311
543
|
const child = this.spawnAttempt(turn, input, binary);
|
|
312
544
|
const code = await this.readAttempt(turn, child, state);
|
|
313
545
|
turn.messageSequence = state.messageSequence;
|
|
546
|
+
// 首轮使用临时 `--session` 文件。Provider 会话 ID 在流中确认后,必须在
|
|
547
|
+
// 本次尝试结束时把临时 transcript 提升到 canonical 路径;否则下一轮拿着
|
|
548
|
+
// 已登记的 ID 执行 `--resume` 时,Command Code 找不到对应原生会话。
|
|
549
|
+
if (turn.transcriptPath !== undefined && turn.providerSessionId !== undefined) {
|
|
550
|
+
syncCommandCodeTranscript(turn.transcriptPath, this.homeDirectory, input.cwd, turn.providerSessionId);
|
|
551
|
+
}
|
|
314
552
|
if (turn.disposed || turn.finished)
|
|
315
553
|
return;
|
|
316
554
|
const capped = state.maxTurnsReached || code === COMMAND_CODE_MAX_TURNS_EXIT_CODE;
|
|
317
555
|
const autoContinueUsed = turn.attempt - 1;
|
|
318
|
-
if (capped && !turn.aborted && !turn.terminal && autoContinueUsed < this.autoContinueMaxAttempts) {
|
|
556
|
+
if (capped && !turn.aborted && !turn.terminal && turn.providerSessionId !== undefined && autoContinueUsed < this.autoContinueMaxAttempts) {
|
|
319
557
|
// 续跑必须落在同一个会话上。CLI 可能把 transcript 写回会话文件、也可能
|
|
320
558
|
// 写进按 cwd 归档的 canonical 目录;两种落点都要先归位到同一个文件,
|
|
321
559
|
// 否则“继续”会开出一个没有上文的新会话,等于白跑一轮预算。
|
|
322
|
-
syncCommandCodeTranscript(turn.transcriptPath, this.homeDirectory, input.cwd);
|
|
323
560
|
continue;
|
|
324
561
|
}
|
|
325
562
|
if (capped && !turn.aborted && !turn.terminal) {
|
|
@@ -343,7 +580,7 @@ export class CommandCodeDriver {
|
|
|
343
580
|
const bridgeEnvironment = commandCodeBridgeEnvironment(input.sessionId, this.descriptor.id);
|
|
344
581
|
const child = this.runSpawn(binary, args, {
|
|
345
582
|
cwd: input.cwd ?? process.cwd(),
|
|
346
|
-
env: { ...(this.cachedEnvironment ?? commandEnvironment(binary)), ...bridgeEnvironment },
|
|
583
|
+
env: { ...(this.cachedEnvironment ?? commandEnvironment(binary)), ...(input.runtimeEnv ?? {}), ...bridgeEnvironment },
|
|
347
584
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
348
585
|
windowsHide: true,
|
|
349
586
|
shell: WINDOWS,
|
|
@@ -352,26 +589,69 @@ export class CommandCodeDriver {
|
|
|
352
589
|
if (turn.child !== undefined)
|
|
353
590
|
this.processes.delete(turn.child);
|
|
354
591
|
this.processes.add(child);
|
|
355
|
-
// 必须消费 stderr
|
|
356
|
-
child.stderr?.on('data', () =>
|
|
592
|
+
// 必须消费 stderr,错误内容只保留尾部用于诊断,避免泄露整段命令输出。
|
|
593
|
+
child.stderr?.on('data', (chunk) => {
|
|
594
|
+
turn.stderrTail = `${turn.stderrTail}${chunk.toString()}`.slice(-512);
|
|
595
|
+
});
|
|
357
596
|
turn.child = child;
|
|
358
597
|
return child;
|
|
359
598
|
}
|
|
360
599
|
buildTurnArgs(input, turn) {
|
|
361
|
-
// 续跑沿用同一个
|
|
600
|
+
// 续跑沿用同一个 Provider 会话,只把输入换成续跑提示;不能再次写入历史,
|
|
362
601
|
// 否则会把 CLI 已经落盘的进度覆盖成空会话。
|
|
363
602
|
const prompt = turn.attempt > 1 ? this.autoContinuePrompt : promptWithAttachmentPaths(input.prompt, input.attachments ?? []);
|
|
364
|
-
const args =
|
|
603
|
+
const args = turn.transcriptPath !== undefined && turn.attempt === 1
|
|
604
|
+
? ['--session', turn.transcriptPath, '-p', prompt, '--output-format', 'json', '--skip-onboarding', '--max-turns', String(this.maxTurns)]
|
|
605
|
+
: turn.providerSessionId !== undefined
|
|
606
|
+
? ['-p', prompt, '--output-format', 'json', '--skip-onboarding', '--max-turns', String(this.maxTurns), '--resume', turn.providerSessionId]
|
|
607
|
+
: ['--session', turn.transcriptPath, '-p', prompt, '--output-format', 'json', '--skip-onboarding', '--max-turns', String(this.maxTurns)];
|
|
608
|
+
// 只有 Host 明确确认“完全访问且永不询问”时才允许 --yolo。
|
|
609
|
+
// 权限状态缺省表示尚未读取,必须保持 CLI 的保守默认,不能把未知状态升级为完全访问。
|
|
610
|
+
args.push(...commandCodePermissionArgs(input.permission));
|
|
611
|
+
if (input.plan === true && !args.includes('--plan'))
|
|
612
|
+
args.push('--plan');
|
|
613
|
+
// Command Code 只允许从已有 --resume 会话分叉;首轮临时 transcript 不能带该参数。
|
|
614
|
+
if (input.forkSession === true && turn.providerSessionId !== undefined)
|
|
615
|
+
args.push('--fork-session');
|
|
616
|
+
if (input.enableAskUserQuestion === true || input.runtimeEnv?.CMD_TOOLS_ASK_USER_QUESTION_ENABLE === 'true') {
|
|
617
|
+
args.push('--tools-enable', 'ask_user_question');
|
|
618
|
+
}
|
|
365
619
|
// 子代理托管开启时加载桥接 mod:内建 agent 调用会被转投成 DSH 原生子会话。
|
|
366
620
|
args.push(...commandCodeBridgeArgs(input.sessionId));
|
|
367
621
|
for (const directory of new Set((input.attachments ?? []).map((attachment) => dirname(attachment.path))))
|
|
368
622
|
args.push('--add-dir', directory);
|
|
369
|
-
if (input.modelId)
|
|
370
|
-
args.push('
|
|
371
|
-
|
|
372
|
-
|
|
623
|
+
if (input.modelId && input.modelId !== 'provider-default')
|
|
624
|
+
args.push('--model', input.modelId);
|
|
625
|
+
const effort = input.effortId?.trim().toLowerCase();
|
|
626
|
+
if (effort !== undefined && VALID_EFFORTS.has(effort))
|
|
627
|
+
args.push('--effort', effort);
|
|
373
628
|
return args;
|
|
374
629
|
}
|
|
630
|
+
/** 中断当前会话并清理 CLI 进程;不存在活动运行时视为幂等成功。 */
|
|
631
|
+
async interrupt(sessionId) {
|
|
632
|
+
const turn = this.turns.get(sessionId);
|
|
633
|
+
if (turn === undefined)
|
|
634
|
+
return;
|
|
635
|
+
this.requestGracefulStop(turn);
|
|
636
|
+
}
|
|
637
|
+
/** 先让 CLI 保存检查点并自行退出,超时后才清理进程树。 */
|
|
638
|
+
requestGracefulStop(turn) {
|
|
639
|
+
turn.aborted = true;
|
|
640
|
+
const child = turn.child;
|
|
641
|
+
if (child === undefined) {
|
|
642
|
+
this.disposeTurn(turn);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
try {
|
|
646
|
+
child.kill('SIGINT');
|
|
647
|
+
}
|
|
648
|
+
catch { /* 进程可能已经退出 */ }
|
|
649
|
+
const timer = setTimeout(() => {
|
|
650
|
+
if (!turn.disposed)
|
|
651
|
+
this.disposeTurn(turn);
|
|
652
|
+
}, COMMAND_CODE_INTERRUPT_GRACE_MS);
|
|
653
|
+
timer.unref?.();
|
|
654
|
+
}
|
|
375
655
|
/** 读取一次尝试的 stdout,直到进程结束;队列在整个运行结束前保持打开。 */
|
|
376
656
|
async readAttempt(turn, child, state) {
|
|
377
657
|
// 测试替身可能只提供 stdout/kill,没有 ChildProcess 的事件接口。
|
|
@@ -379,6 +659,7 @@ export class CommandCodeDriver {
|
|
|
379
659
|
? child
|
|
380
660
|
: null;
|
|
381
661
|
let exitCode = null;
|
|
662
|
+
let exitSignal = null;
|
|
382
663
|
let resolveClose = null;
|
|
383
664
|
const closed = new Promise((resolve) => { resolveClose = resolve; });
|
|
384
665
|
if (emitter !== null) {
|
|
@@ -389,8 +670,9 @@ export class CommandCodeDriver {
|
|
|
389
670
|
turn.failure ??= error;
|
|
390
671
|
resolveClose?.();
|
|
391
672
|
});
|
|
392
|
-
emitter.on('close', (code) => {
|
|
673
|
+
emitter.on('close', (code, signal) => {
|
|
393
674
|
exitCode = code;
|
|
675
|
+
exitSignal = signal ?? null;
|
|
394
676
|
if (state.maxTurnsReached)
|
|
395
677
|
turn.maxTurnsReached = true;
|
|
396
678
|
resolveClose?.();
|
|
@@ -398,15 +680,32 @@ export class CommandCodeDriver {
|
|
|
398
680
|
}
|
|
399
681
|
try {
|
|
400
682
|
const lines = readline.createInterface({ input: child.stdout });
|
|
683
|
+
let lineNumber = 0;
|
|
401
684
|
try {
|
|
402
685
|
for await (const line of lines) {
|
|
403
686
|
if (!line.trim())
|
|
404
687
|
continue;
|
|
688
|
+
lineNumber += 1;
|
|
405
689
|
const item = parseJson(line);
|
|
406
|
-
if (item === null)
|
|
690
|
+
if (item === null) {
|
|
691
|
+
// CLI 升级后可能输出非对象或损坏的 NDJSON;保留行号诊断,不能静默吞掉。
|
|
692
|
+
console.warn(`[codingns4dsh] Command Code 忽略非法 NDJSON(第 ${lineNumber} 行)`);
|
|
407
693
|
continue;
|
|
694
|
+
}
|
|
408
695
|
const event = item.type === 'event' && isRecord(item.event) ? item.event : item;
|
|
409
|
-
for (const
|
|
696
|
+
for (const rawChunk of commandCodeEventChunks(event, state)) {
|
|
697
|
+
let chunk = rawChunk;
|
|
698
|
+
if (rawChunk.type === 'session-binding') {
|
|
699
|
+
// `--session <path>` 模式下 CLI 发出的 sessionId 是 checkpoint
|
|
700
|
+
// 内部 ID,不是可供 --resume 查找的 transcript ID;保持 Host 绑定。
|
|
701
|
+
if (turn.transcriptPath !== undefined)
|
|
702
|
+
continue;
|
|
703
|
+
turn.providerSessionId = rawChunk.providerSessionId;
|
|
704
|
+
turn.rawStoreRef = resolveCommandCodeTranscriptPath(this.homeDirectory, turn.cwd, rawChunk.providerSessionId);
|
|
705
|
+
// Provider ID 一旦确认就立即暴露 canonical 路径;文件可能稍后才落盘,
|
|
706
|
+
// 但 Registry 必须先保存稳定绑定,冷恢复时才能直接定位原生会话。
|
|
707
|
+
chunk = { ...rawChunk, rawStoreRef: turn.rawStoreRef };
|
|
708
|
+
}
|
|
410
709
|
if (chunk.type === 'finish')
|
|
411
710
|
turn.terminal = true;
|
|
412
711
|
turn.queue.push(chunk);
|
|
@@ -428,6 +727,23 @@ export class CommandCodeDriver {
|
|
|
428
727
|
else {
|
|
429
728
|
await closed;
|
|
430
729
|
}
|
|
730
|
+
if (!turn.terminal && !state.maxTurnsReached) {
|
|
731
|
+
if (turn.aborted || exitSignal === 'SIGINT' || exitSignal === 'SIGTERM') {
|
|
732
|
+
turn.terminal = true;
|
|
733
|
+
turn.queue.push({ type: 'finish', reason: 'cancel' });
|
|
734
|
+
}
|
|
735
|
+
else if (turn.failure !== null || (exitCode !== null && exitCode !== 0) || exitSignal !== null) {
|
|
736
|
+
const message = turn.failure?.message
|
|
737
|
+
|| (turn.stderrTail.trim() !== '' ? `Command Code 执行失败:${turn.stderrTail.trim()}` : `Command Code 进程退出(代码 ${exitCode ?? exitSignal ?? 'unknown'})`);
|
|
738
|
+
turn.failure = null;
|
|
739
|
+
turn.terminal = true;
|
|
740
|
+
turn.queue.push({
|
|
741
|
+
type: 'finish',
|
|
742
|
+
reason: 'error',
|
|
743
|
+
failure: { message, code: exitCode === null ? 'COMMAND_CODE_SPAWN_ERROR' : `COMMAND_CODE_EXIT_${exitCode}` },
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
}
|
|
431
747
|
return exitCode;
|
|
432
748
|
}
|
|
433
749
|
async *consumeTurn(turn, input, segmented, suspend) {
|
|
@@ -490,13 +806,10 @@ export class CommandCodeDriver {
|
|
|
490
806
|
this.processes.delete(child);
|
|
491
807
|
terminateChildProcess(child);
|
|
492
808
|
}
|
|
493
|
-
|
|
494
|
-
rmSync(turn.transcriptPath, { force: true });
|
|
495
|
-
}
|
|
496
|
-
catch { /* 临时文件清理尽力而为 */ }
|
|
809
|
+
// 首轮 transcript 已经是 canonical 文件,必须保留给下一轮 --resume 及冷恢复。
|
|
497
810
|
}
|
|
498
811
|
}
|
|
499
|
-
function createStreamState(aborted, messageSequence = 0, sessionId = null) {
|
|
812
|
+
function createStreamState(aborted, messageSequence = 0, sessionId = null, bridgeSessionId = '') {
|
|
500
813
|
return {
|
|
501
814
|
messageSequence,
|
|
502
815
|
messageId: null,
|
|
@@ -506,8 +819,10 @@ function createStreamState(aborted, messageSequence = 0, sessionId = null) {
|
|
|
506
819
|
perRequestUsageSeen: false,
|
|
507
820
|
turnUsageSeen: false,
|
|
508
821
|
sessionId,
|
|
822
|
+
bridgeSessionId,
|
|
509
823
|
aborted,
|
|
510
824
|
maxTurnsReached: false,
|
|
825
|
+
terminalEmitted: false,
|
|
511
826
|
};
|
|
512
827
|
}
|
|
513
828
|
function createEventQueue() {
|
|
@@ -561,6 +876,16 @@ function commandCodeEventChunks(event, state) {
|
|
|
561
876
|
chunks.push({ type: 'session-binding', providerSessionId: sessionId });
|
|
562
877
|
}
|
|
563
878
|
switch (type) {
|
|
879
|
+
case 'run_start':
|
|
880
|
+
case 'run-start':
|
|
881
|
+
case 'session_created':
|
|
882
|
+
case 'session-created':
|
|
883
|
+
case 'model_request_start':
|
|
884
|
+
case 'model-request-start':
|
|
885
|
+
case 'model_trace':
|
|
886
|
+
case 'model-trace':
|
|
887
|
+
// 这些事件只标记 Provider 生命周期,不应制造正文或终态。
|
|
888
|
+
break;
|
|
564
889
|
case 'turn_start':
|
|
565
890
|
case 'turn-start':
|
|
566
891
|
beginMessage(state);
|
|
@@ -625,19 +950,36 @@ function commandCodeEventChunks(event, state) {
|
|
|
625
950
|
}
|
|
626
951
|
case 'run_end':
|
|
627
952
|
case 'run-end':
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
953
|
+
if (!state.sawText) {
|
|
954
|
+
const finalText = readFinalText(event);
|
|
955
|
+
if (finalText) {
|
|
956
|
+
ensureMessage(state);
|
|
957
|
+
state.sawText = true;
|
|
958
|
+
state.emittedText = finalText;
|
|
959
|
+
chunks.push(textDelta(finalText, state));
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (isMaxTurnsOutcome(event)) {
|
|
963
|
+
// 撞到 CLI 的轮次上限不是终态,但 run_end 可能同时携带本次已生成的
|
|
964
|
+
// 最后一段正文。正文必须先投影出去,续跑只负责继续执行,不能吞掉这段结果。
|
|
631
965
|
state.maxTurnsReached = true;
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
if (!state.terminalEmitted) {
|
|
969
|
+
state.terminalEmitted = true;
|
|
970
|
+
chunks.push({ type: 'finish', reason: state.aborted() ? 'cancel' : resultReason(event) });
|
|
971
|
+
}
|
|
632
972
|
break;
|
|
633
973
|
case 'result': {
|
|
974
|
+
if (state.terminalEmitted)
|
|
975
|
+
break;
|
|
634
976
|
if (!state.perRequestUsageSeen) {
|
|
635
977
|
const usage = usageChunk(recordValue(event.usage));
|
|
636
978
|
if (usage !== null)
|
|
637
979
|
chunks.push(usage);
|
|
638
980
|
}
|
|
639
981
|
if (!state.sawText) {
|
|
640
|
-
const finalText =
|
|
982
|
+
const finalText = readFinalText(event);
|
|
641
983
|
if (finalText) {
|
|
642
984
|
ensureMessage(state);
|
|
643
985
|
state.sawText = true;
|
|
@@ -652,6 +994,7 @@ function commandCodeEventChunks(event, state) {
|
|
|
652
994
|
break;
|
|
653
995
|
}
|
|
654
996
|
const reason = state.aborted() ? 'cancel' : resultReason(event);
|
|
997
|
+
state.terminalEmitted = true;
|
|
655
998
|
chunks.push({
|
|
656
999
|
type: 'finish',
|
|
657
1000
|
reason,
|
|
@@ -659,7 +1002,19 @@ function commandCodeEventChunks(event, state) {
|
|
|
659
1002
|
});
|
|
660
1003
|
break;
|
|
661
1004
|
}
|
|
1005
|
+
case 'error':
|
|
1006
|
+
case 'fatal_error':
|
|
1007
|
+
case 'run_error':
|
|
1008
|
+
if (!state.terminalEmitted) {
|
|
1009
|
+
state.terminalEmitted = true;
|
|
1010
|
+
chunks.push({ type: 'finish', reason: state.aborted() ? 'cancel' : 'error', ...(state.aborted() ? {} : { failure: commandCodeFailure(event) }) });
|
|
1011
|
+
}
|
|
1012
|
+
break;
|
|
662
1013
|
default:
|
|
1014
|
+
if (type !== '' && !isToolStart(type) && !isToolResult(type)) {
|
|
1015
|
+
// 未知事件不应改变正常流,但必须留下可定位的升级诊断。
|
|
1016
|
+
console.warn(`[codingns4dsh] Command Code 忽略未知事件:${type}`);
|
|
1017
|
+
}
|
|
663
1018
|
break;
|
|
664
1019
|
}
|
|
665
1020
|
if (isToolStart(type)) {
|
|
@@ -683,15 +1038,25 @@ function commandCodeEventChunks(event, state) {
|
|
|
683
1038
|
}
|
|
684
1039
|
return chunks;
|
|
685
1040
|
}
|
|
1041
|
+
function readFinalText(event) {
|
|
1042
|
+
const result = recordValue(event.result);
|
|
1043
|
+
return textValue(event.finalText
|
|
1044
|
+
?? result?.finalText
|
|
1045
|
+
?? result?.output
|
|
1046
|
+
?? result?.text
|
|
1047
|
+
?? (typeof event.result === 'string' ? event.result : undefined)
|
|
1048
|
+
?? event.output
|
|
1049
|
+
?? event.text);
|
|
1050
|
+
}
|
|
686
1051
|
/** 该 blocked 事件是否来自桥接转投;命中后消费一次记录,避免重复投影。 */
|
|
687
1052
|
function consumeBridgeRedirect(state, event) {
|
|
688
|
-
if (state.
|
|
1053
|
+
if (state.bridgeSessionId === '')
|
|
689
1054
|
return false;
|
|
690
1055
|
const callId = firstToolText(event.callId, event.call_id, event.toolCallId, event.tool_call_id, event.toolUseId, event.tool_use_id, event.id);
|
|
691
1056
|
if (callId === undefined || callId === '')
|
|
692
1057
|
return false;
|
|
693
1058
|
try {
|
|
694
|
-
return getSubagentBridge()?.consumeRedirect(state.
|
|
1059
|
+
return getSubagentBridge()?.consumeRedirect(state.bridgeSessionId, callId) !== undefined;
|
|
695
1060
|
}
|
|
696
1061
|
catch {
|
|
697
1062
|
return false;
|
|
@@ -724,8 +1089,17 @@ function reasoningDelta(text, state) {
|
|
|
724
1089
|
function appendContentFallback(chunks, content, state) {
|
|
725
1090
|
if (!Array.isArray(content))
|
|
726
1091
|
return;
|
|
727
|
-
let
|
|
728
|
-
let
|
|
1092
|
+
let snapshotText = '';
|
|
1093
|
+
let snapshotReasoning = '';
|
|
1094
|
+
let pendingKind = null;
|
|
1095
|
+
let pendingDelta = '';
|
|
1096
|
+
const flushPending = () => {
|
|
1097
|
+
if (pendingKind === null || pendingDelta === '')
|
|
1098
|
+
return;
|
|
1099
|
+
chunks.push(pendingKind === 'text' ? textDelta(pendingDelta, state) : reasoningDelta(pendingDelta, state));
|
|
1100
|
+
pendingKind = null;
|
|
1101
|
+
pendingDelta = '';
|
|
1102
|
+
};
|
|
729
1103
|
for (const block of content) {
|
|
730
1104
|
const value = recordValue(block);
|
|
731
1105
|
if (value === null)
|
|
@@ -733,24 +1107,36 @@ function appendContentFallback(chunks, content, state) {
|
|
|
733
1107
|
const blockType = textValue(value.type).toLowerCase();
|
|
734
1108
|
const isReasoning = blockType.includes('thinking') || blockType.includes('reasoning')
|
|
735
1109
|
|| typeof value.thinking === 'string' || typeof value.reasoning === 'string';
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
1110
|
+
const blockText = textValue(isReasoning
|
|
1111
|
+
? value.thinking ?? value.reasoning ?? value.text ?? value.content
|
|
1112
|
+
: value.text ?? value.content);
|
|
1113
|
+
if (isReasoning) {
|
|
1114
|
+
snapshotReasoning += blockText;
|
|
1115
|
+
if (snapshotReasoning.length > state.emittedReasoning.length) {
|
|
1116
|
+
const delta = snapshotReasoning.slice(state.emittedReasoning.length);
|
|
1117
|
+
ensureMessage(state);
|
|
1118
|
+
state.emittedReasoning = snapshotReasoning;
|
|
1119
|
+
if (pendingKind !== 'reasoning')
|
|
1120
|
+
flushPending();
|
|
1121
|
+
pendingKind = 'reasoning';
|
|
1122
|
+
pendingDelta += delta;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
else {
|
|
1126
|
+
snapshotText += blockText;
|
|
1127
|
+
if (snapshotText.length > state.emittedText.length) {
|
|
1128
|
+
const delta = snapshotText.slice(state.emittedText.length);
|
|
1129
|
+
ensureMessage(state);
|
|
1130
|
+
state.emittedText = snapshotText;
|
|
1131
|
+
state.sawText = true;
|
|
1132
|
+
if (pendingKind !== 'text')
|
|
1133
|
+
flushPending();
|
|
1134
|
+
pendingKind = 'text';
|
|
1135
|
+
pendingDelta += delta;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
753
1138
|
}
|
|
1139
|
+
flushPending();
|
|
754
1140
|
}
|
|
755
1141
|
function readToolChunk(event, status) {
|
|
756
1142
|
const callId = firstToolText(event.callId, event.call_id, event.toolCallId, event.tool_call_id, event.toolUseId, event.tool_use_id, event.id);
|
|
@@ -825,20 +1211,36 @@ function isMaxTurnsOutcome(event) {
|
|
|
825
1211
|
*
|
|
826
1212
|
* 实测 CLI 的落盘位置取决于会话文件里是否已有历史:空会话(只有 session 头)时
|
|
827
1213
|
* 会把消息写进 `~/.commandcode/projects/<slug>/<id>.jsonl`,带上历史时则写回
|
|
828
|
-
* `--session`
|
|
829
|
-
*
|
|
1214
|
+
* `--session` 指定的文件。两种落点都要统一,否则下一轮恢复会落在一个没有上文的
|
|
1215
|
+
* 会话上。两边都存在时按修改时间同步最新内容,首轮则创建 canonical 文件。
|
|
830
1216
|
*/
|
|
831
|
-
function syncCommandCodeTranscript(transcriptPath, homeDirectory, cwd) {
|
|
832
|
-
const canonicalPath =
|
|
1217
|
+
function syncCommandCodeTranscript(transcriptPath, homeDirectory, cwd, providerSessionId) {
|
|
1218
|
+
const canonicalPath = providerSessionId === undefined
|
|
1219
|
+
? resolveCanonicalTranscriptPath(transcriptPath, homeDirectory, cwd)
|
|
1220
|
+
: resolveCommandCodeTranscriptPath(homeDirectory, cwd ?? process.cwd(), providerSessionId);
|
|
833
1221
|
if (canonicalPath === null)
|
|
834
1222
|
return;
|
|
1223
|
+
if (providerSessionId !== undefined && !isSafeCommandCodeTranscriptTarget(canonicalPath, homeDirectory))
|
|
1224
|
+
return;
|
|
835
1225
|
try {
|
|
836
|
-
if (
|
|
1226
|
+
if (!existsSync(transcriptPath) || !statSync(transcriptPath).isFile())
|
|
1227
|
+
return;
|
|
1228
|
+
const sourceMtime = statSync(transcriptPath).mtimeMs;
|
|
1229
|
+
if (!existsSync(canonicalPath)) {
|
|
1230
|
+
mkdirSync(dirname(canonicalPath), { recursive: true });
|
|
1231
|
+
copyFileSync(transcriptPath, canonicalPath);
|
|
837
1232
|
return;
|
|
838
|
-
|
|
1233
|
+
}
|
|
1234
|
+
if (!statSync(canonicalPath).isFile())
|
|
1235
|
+
return;
|
|
1236
|
+
const canonicalMtime = statSync(canonicalPath).mtimeMs;
|
|
1237
|
+
if (sourceMtime > canonicalMtime)
|
|
1238
|
+
copyFileSync(transcriptPath, canonicalPath);
|
|
1239
|
+
else if (canonicalMtime > sourceMtime)
|
|
1240
|
+
copyFileSync(canonicalPath, transcriptPath);
|
|
839
1241
|
}
|
|
840
1242
|
catch {
|
|
841
|
-
// transcript
|
|
1243
|
+
// transcript 归位是恢复前的必要同步;文件竞态或不可读时保持当前运行结果。
|
|
842
1244
|
}
|
|
843
1245
|
}
|
|
844
1246
|
/** 按 CLI 的 `<cwd slug>/<session id>` 规则定位 canonical transcript。 */
|
|
@@ -868,6 +1270,86 @@ function resolveCanonicalTranscriptPath(transcriptPath, homeDirectory, cwd) {
|
|
|
868
1270
|
}
|
|
869
1271
|
return null;
|
|
870
1272
|
}
|
|
1273
|
+
/** 按 Provider 会话 ID 计算 Command Code 的 canonical transcript 路径。 */
|
|
1274
|
+
function resolveCommandCodeTranscriptPath(homeDirectory, cwd, providerSessionId) {
|
|
1275
|
+
return join(homeDirectory, 'projects', workspaceSlug(cwd), `${safeId(providerSessionId)}.jsonl`);
|
|
1276
|
+
}
|
|
1277
|
+
/** Provider 索引只能指向 Command Code 项目目录中的 JSONL,避免旧配置污染探测边界。 */
|
|
1278
|
+
function isSafeCommandCodeTranscriptPath(candidate, homeDirectory) {
|
|
1279
|
+
const projectsRootPath = resolve(join(homeDirectory, 'projects'));
|
|
1280
|
+
let projectsRoot = projectsRootPath;
|
|
1281
|
+
try {
|
|
1282
|
+
if (existsSync(projectsRootPath))
|
|
1283
|
+
projectsRoot = realpathSync(projectsRootPath);
|
|
1284
|
+
}
|
|
1285
|
+
catch {
|
|
1286
|
+
return false;
|
|
1287
|
+
}
|
|
1288
|
+
const resolvedCandidate = resolve(candidate);
|
|
1289
|
+
let actualCandidate = resolvedCandidate;
|
|
1290
|
+
try {
|
|
1291
|
+
if (existsSync(resolvedCandidate))
|
|
1292
|
+
actualCandidate = realpathSync(resolvedCandidate);
|
|
1293
|
+
}
|
|
1294
|
+
catch {
|
|
1295
|
+
return false;
|
|
1296
|
+
}
|
|
1297
|
+
const relativePath = relative(projectsRoot, actualCandidate);
|
|
1298
|
+
const segments = relativePath.split(/[\\/]+/u).filter(Boolean);
|
|
1299
|
+
return relativePath !== ''
|
|
1300
|
+
&& !relativePath.startsWith('..')
|
|
1301
|
+
&& segments.length === 2
|
|
1302
|
+
&& actualCandidate.endsWith('.jsonl')
|
|
1303
|
+
// checkpoint 不是 --resume 使用的 transcript,即使扩展名同为 .jsonl
|
|
1304
|
+
// 也不能把它当作可恢复会话绑定。
|
|
1305
|
+
&& !actualCandidate.endsWith('.checkpoints.jsonl');
|
|
1306
|
+
}
|
|
1307
|
+
/** 允许同步创建的 canonical transcript 目标,路径必须严格位于 projects/<slug>/*.jsonl。 */
|
|
1308
|
+
function isSafeCommandCodeTranscriptTarget(candidate, homeDirectory) {
|
|
1309
|
+
const projectsRoot = resolve(join(homeDirectory, 'projects'));
|
|
1310
|
+
const resolvedCandidate = resolve(candidate);
|
|
1311
|
+
const relativePath = relative(projectsRoot, resolvedCandidate);
|
|
1312
|
+
const segments = relativePath.split(/[\\/]+/u).filter(Boolean);
|
|
1313
|
+
return relativePath !== ''
|
|
1314
|
+
&& !relativePath.startsWith('..')
|
|
1315
|
+
&& segments.length === 2
|
|
1316
|
+
&& resolvedCandidate.endsWith('.jsonl')
|
|
1317
|
+
&& !resolvedCandidate.endsWith('.checkpoints.jsonl');
|
|
1318
|
+
}
|
|
1319
|
+
/** 只读 JSONL 首条记录,避免探测大 transcript 时把整份文件载入内存。 */
|
|
1320
|
+
function readFirstJsonRecord(path) {
|
|
1321
|
+
const descriptor = openSync(path, 'r');
|
|
1322
|
+
try {
|
|
1323
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
1324
|
+
const bytes = readSync(descriptor, buffer, 0, buffer.length, 0);
|
|
1325
|
+
const firstLine = buffer.subarray(0, bytes).toString('utf8').split(/\r?\n/u).find((line) => line.trim() !== '');
|
|
1326
|
+
return firstLine === undefined ? null : parseJson(firstLine);
|
|
1327
|
+
}
|
|
1328
|
+
finally {
|
|
1329
|
+
closeSync(descriptor);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Command Code 的 `--session <临时路径>` 在部分版本只落检查点文件,不创建主 transcript。
|
|
1334
|
+
* 检查点足以证明会话仍可被 `--resume` 找到,但不包含完整消息历史,因此只用于探测回退。
|
|
1335
|
+
*/
|
|
1336
|
+
function resolveCommandCodeCheckpointPath(homeDirectory, cwd, providerSessionId) {
|
|
1337
|
+
const expected = join(homeDirectory, 'projects', workspaceSlug(cwd), `${providerSessionId}.checkpoints.jsonl`);
|
|
1338
|
+
if (existsSync(expected))
|
|
1339
|
+
return expected;
|
|
1340
|
+
const projectsRoot = join(homeDirectory, 'projects');
|
|
1341
|
+
try {
|
|
1342
|
+
for (const entry of readdirSync(projectsRoot)) {
|
|
1343
|
+
const candidate = join(projectsRoot, entry, `${providerSessionId}.checkpoints.jsonl`);
|
|
1344
|
+
if (existsSync(candidate))
|
|
1345
|
+
return candidate;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
catch {
|
|
1349
|
+
// projects 目录不存在说明 CLI 还没写过检查点。
|
|
1350
|
+
}
|
|
1351
|
+
return null;
|
|
1352
|
+
}
|
|
871
1353
|
/** CLI 的 canonical 目录名:绝对路径去掉前导分隔符,非字母数字统一换成连字符并转小写。 */
|
|
872
1354
|
function workspaceSlug(workspacePath) {
|
|
873
1355
|
return workspacePath.replace(/[\\/]+$/, '')
|
|
@@ -906,6 +1388,14 @@ function readSessionId(event) {
|
|
|
906
1388
|
const value = textValue(event.sessionId ?? event.session_id ?? recordValue(event.session)?.id ?? recordValue(event.result)?.sessionId).trim();
|
|
907
1389
|
return value === '' ? null : value;
|
|
908
1390
|
}
|
|
1391
|
+
/** 会话探测由 Registry 控制超时;文件扫描期间也必须尊重取消信号。 */
|
|
1392
|
+
function throwIfCommandCodeProbeAborted(signal) {
|
|
1393
|
+
if (signal?.aborted !== true)
|
|
1394
|
+
return;
|
|
1395
|
+
const error = new Error('Command Code 会话探测已取消');
|
|
1396
|
+
error.name = 'AbortError';
|
|
1397
|
+
throw error;
|
|
1398
|
+
}
|
|
909
1399
|
function textValue(value) {
|
|
910
1400
|
return typeof value === 'string' ? value : value === undefined || value === null ? '' : structuredText(value);
|
|
911
1401
|
}
|
|
@@ -923,22 +1413,97 @@ function recordValue(value) {
|
|
|
923
1413
|
return isRecord(value) ? value : null;
|
|
924
1414
|
}
|
|
925
1415
|
function writeTranscript(path, input) {
|
|
1416
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
926
1417
|
const history = input.messages.length > 0 ? input.messages.slice(0, -1) : [];
|
|
927
1418
|
let parentId = null;
|
|
928
1419
|
const lines = [JSON.stringify({ type: 'session', version: 3, id: input.sessionId, timestamp: new Date().toISOString(), cwd: input.cwd ?? process.cwd() })];
|
|
929
1420
|
history.forEach((message, index) => {
|
|
930
|
-
if (message.role !== 'user' && message.role !== 'assistant')
|
|
1421
|
+
if (message.role !== 'user' && message.role !== 'assistant' && message.role !== 'tool')
|
|
1422
|
+
return;
|
|
1423
|
+
const content = extractText(message.content, message);
|
|
1424
|
+
if (content.length === 0)
|
|
931
1425
|
return;
|
|
932
1426
|
const id = message.id ?? `message-${index}`;
|
|
933
|
-
|
|
1427
|
+
const role = message.role === 'tool' ? 'user' : message.role;
|
|
1428
|
+
lines.push(JSON.stringify({ type: 'message', id, parentId, timestamp: new Date().toISOString(), message: { role, content } }));
|
|
934
1429
|
parentId = id;
|
|
935
1430
|
});
|
|
936
1431
|
writeFileSync(path, `${lines.join('\n')}\n`, 'utf8');
|
|
937
1432
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1433
|
+
/** 把 DSH 消息块转换为 Command Code 原生 transcript 块,保留工具和推理语义。 */
|
|
1434
|
+
function extractText(content, message) {
|
|
1435
|
+
if (message.role === 'tool') {
|
|
1436
|
+
const toolCallId = message.toolCallId ?? message.source?.callId;
|
|
1437
|
+
if (toolCallId === undefined || toolCallId.trim() === '')
|
|
1438
|
+
return [];
|
|
1439
|
+
return [{ type: 'tool_result', tool_use_id: toolCallId, content: extractToolResultContent(content) }];
|
|
1440
|
+
}
|
|
1441
|
+
if (typeof content === 'string')
|
|
1442
|
+
return content === '' ? [] : [{ type: 'text', text: content }];
|
|
1443
|
+
if (!Array.isArray(content))
|
|
1444
|
+
return [];
|
|
1445
|
+
const blocks = [];
|
|
1446
|
+
for (const part of content) {
|
|
1447
|
+
if (!isRecord(part))
|
|
1448
|
+
continue;
|
|
1449
|
+
if (part.type === 'tool-call') {
|
|
1450
|
+
const id = typeof part.id === 'string' ? part.id.trim() : '';
|
|
1451
|
+
const name = typeof part.name === 'string' ? part.name.trim() : '';
|
|
1452
|
+
if (id === '' || name === '')
|
|
1453
|
+
continue;
|
|
1454
|
+
blocks.push({ type: 'tool_use', id, name, input: parseToolInput(part.arguments ?? part.input) });
|
|
1455
|
+
continue;
|
|
1456
|
+
}
|
|
1457
|
+
if (part.type === 'tool-result') {
|
|
1458
|
+
const toolUseId = firstText(part.toolCallId, part.tool_use_id, part.toolUseId);
|
|
1459
|
+
if (toolUseId !== '')
|
|
1460
|
+
blocks.push({ type: 'tool_result', tool_use_id: toolUseId, content: extractToolResultContent(part.content ?? part.output ?? part.result) });
|
|
1461
|
+
continue;
|
|
1462
|
+
}
|
|
1463
|
+
if (part.type === 'thinking' || part.type === 'reasoning') {
|
|
1464
|
+
const thinking = firstText(part.thinking, part.reasoning, part.text, part.content);
|
|
1465
|
+
if (thinking !== '')
|
|
1466
|
+
blocks.push({ type: 'thinking', thinking });
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1469
|
+
const text = typeof part.text === 'string' ? part.text : '';
|
|
1470
|
+
if (text !== '')
|
|
1471
|
+
blocks.push({ type: 'text', text });
|
|
1472
|
+
}
|
|
1473
|
+
return blocks;
|
|
1474
|
+
}
|
|
1475
|
+
function extractToolResultContent(content) {
|
|
1476
|
+
if (typeof content === 'string')
|
|
1477
|
+
return [{ type: 'text', text: content }];
|
|
1478
|
+
if (Array.isArray(content)) {
|
|
1479
|
+
const parts = content.filter(isRecord).map((part) => {
|
|
1480
|
+
if (typeof part.text === 'string')
|
|
1481
|
+
return { type: 'text', text: part.text };
|
|
1482
|
+
return null;
|
|
1483
|
+
}).filter((part) => part !== null);
|
|
1484
|
+
if (parts.length > 0)
|
|
1485
|
+
return parts;
|
|
1486
|
+
}
|
|
1487
|
+
return [{ type: 'text', text: structuredText(content) }];
|
|
1488
|
+
}
|
|
1489
|
+
function parseToolInput(value) {
|
|
1490
|
+
if (typeof value !== 'string')
|
|
1491
|
+
return value ?? {};
|
|
1492
|
+
try {
|
|
1493
|
+
return JSON.parse(value);
|
|
1494
|
+
}
|
|
1495
|
+
catch {
|
|
1496
|
+
return value;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
function firstText(...values) {
|
|
1500
|
+
return values.find((value) => typeof value === 'string' && value !== '') ?? '';
|
|
1501
|
+
}
|
|
941
1502
|
function safeId(value) { return value.replace(/[^a-zA-Z0-9._-]+/gu, '_').slice(0, 96) || 'default'; }
|
|
1503
|
+
/** 旧驱动生成的 Host synthetic ID;没有 canonical transcript 时必须放弃 --resume。 */
|
|
1504
|
+
function isLegacySyntheticSessionId(value) {
|
|
1505
|
+
return value.startsWith('codingns4dsh-cc-session-') || value.startsWith('dsh-codingns-cc-session-');
|
|
1506
|
+
}
|
|
942
1507
|
function parseJson(value) { try {
|
|
943
1508
|
const parsed = JSON.parse(value);
|
|
944
1509
|
return isRecord(parsed) ? parsed : null;
|