@cjhyy/code-shell-core 0.5.0-rc.2 → 0.6.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -10
- package/dist/agent/agent-definition-registry.d.ts +3 -0
- package/dist/agent/agent-definition-registry.js +3 -1
- package/dist/agent/agent-definition.d.ts +15 -0
- package/dist/agent/agent-definition.js +27 -3
- package/dist/arena/context/context-tools.js +47 -4
- package/dist/arena/ledger.js +9 -1
- package/dist/arena/strategies/utils.d.ts +1 -4
- package/dist/arena/strategies/utils.js +7 -84
- package/dist/automation/cron-expr.d.ts +10 -0
- package/dist/automation/cron-expr.js +23 -5
- package/dist/automation/runner.d.ts +21 -4
- package/dist/automation/runner.js +48 -12
- package/dist/automation/scheduler.d.ts +38 -0
- package/dist/automation/scheduler.js +89 -12
- package/dist/automation/store.js +5 -2
- package/dist/capability-control/disabled-lists.d.ts +25 -0
- package/dist/capability-control/disabled-lists.js +57 -0
- package/dist/capability-control/overlay.d.ts +15 -0
- package/dist/capability-control/overlay.js +27 -0
- package/dist/cc-orchestrator/agent-adapter.d.ts +50 -0
- package/dist/cc-orchestrator/agent-adapter.js +133 -0
- package/dist/cc-orchestrator/cc-capability.d.ts +19 -0
- package/dist/cc-orchestrator/cc-capability.js +53 -0
- package/dist/cc-orchestrator/codex-session-discovery.d.ts +24 -0
- package/dist/cc-orchestrator/codex-session-discovery.js +191 -0
- package/dist/cc-orchestrator/codex-session-history.d.ts +25 -0
- package/dist/cc-orchestrator/codex-session-history.js +187 -0
- package/dist/cc-orchestrator/external-agent-changes.d.ts +19 -0
- package/dist/cc-orchestrator/external-agent-changes.js +214 -0
- package/dist/cc-orchestrator/external-agent-driver.d.ts +18 -0
- package/dist/cc-orchestrator/external-agent-driver.js +69 -0
- package/dist/cc-orchestrator/index.d.ts +8 -0
- package/dist/cc-orchestrator/index.js +8 -0
- package/dist/cc-orchestrator/relevance-judge.d.ts +15 -0
- package/dist/cc-orchestrator/relevance-judge.js +29 -0
- package/dist/cc-orchestrator/session-discovery.d.ts +46 -0
- package/dist/cc-orchestrator/session-discovery.js +125 -0
- package/dist/cc-orchestrator/session-history.d.ts +19 -0
- package/dist/cc-orchestrator/session-history.js +67 -0
- package/dist/cli/agent-server-stdio.d.ts +15 -1
- package/dist/cli/agent-server-stdio.js +115 -17
- package/dist/cli/agent-server-tcp.js +40 -26
- package/dist/context/compaction.d.ts +56 -0
- package/dist/context/compaction.js +186 -0
- package/dist/context/manager.js +25 -4
- package/dist/cost-tracker.js +5 -61
- package/dist/credentials/cipher.d.ts +49 -0
- package/dist/credentials/cipher.js +45 -0
- package/dist/credentials/cookie-jar.d.ts +24 -0
- package/dist/credentials/cookie-jar.js +40 -0
- package/dist/credentials/index.d.ts +7 -0
- package/dist/credentials/index.js +5 -0
- package/dist/credentials/inject-credential-tool.d.ts +20 -0
- package/dist/credentials/inject-credential-tool.js +130 -0
- package/dist/credentials/store.d.ts +72 -0
- package/dist/credentials/store.js +184 -0
- package/dist/credentials/types.d.ts +56 -0
- package/dist/credentials/use-credential-tool.d.ts +29 -0
- package/dist/credentials/use-credential-tool.js +205 -0
- package/dist/credentials/use-gate.d.ts +56 -0
- package/dist/credentials/use-gate.js +52 -0
- package/dist/data/model-metadata.d.ts +77 -0
- package/dist/data/model-metadata.js +56 -0
- package/dist/data/model-metadata.json +216 -0
- package/dist/data/openrouter-models.d.ts +18 -7
- package/dist/data/openrouter-models.js +35 -8
- package/dist/engine/aux-key.d.ts +10 -0
- package/dist/engine/aux-key.js +11 -0
- package/dist/engine/dynamic-tool-defs.d.ts +19 -0
- package/dist/engine/dynamic-tool-defs.js +36 -0
- package/dist/engine/engine.d.ts +267 -138
- package/dist/engine/engine.js +957 -191
- package/dist/engine/friendly-error.d.ts +18 -0
- package/dist/engine/friendly-error.js +63 -0
- package/dist/engine/goal.d.ts +145 -0
- package/dist/engine/goal.js +144 -0
- package/dist/engine/image-policy.d.ts +13 -0
- package/dist/engine/image-policy.js +24 -0
- package/dist/engine/model-connections-pool.d.ts +17 -0
- package/dist/engine/model-connections-pool.js +66 -0
- package/dist/engine/model-facade.d.ts +10 -0
- package/dist/engine/model-facade.js +25 -0
- package/dist/engine/patch-orphaned-tools.js +3 -0
- package/dist/engine/resolve-llm-config.d.ts +16 -0
- package/dist/engine/resolve-llm-config.js +44 -0
- package/dist/engine/runtime.js +2 -1
- package/dist/engine/sandbox-cache-key.d.ts +10 -0
- package/dist/engine/sandbox-cache-key.js +9 -0
- package/dist/engine/sandbox-config.d.ts +31 -0
- package/dist/engine/sandbox-config.js +38 -0
- package/dist/engine/session-usage.d.ts +19 -0
- package/dist/engine/session-usage.js +25 -0
- package/dist/engine/steer-queue.d.ts +32 -0
- package/dist/engine/steer-queue.js +26 -0
- package/dist/engine/streaming-tool-queue.d.ts +12 -0
- package/dist/engine/streaming-tool-queue.js +50 -10
- package/dist/engine/turn-loop.d.ts +67 -3
- package/dist/engine/turn-loop.js +292 -41
- package/dist/engine/types.d.ts +175 -0
- package/dist/engine/types.js +13 -0
- package/dist/external-agents/config.d.ts +2 -0
- package/dist/external-agents/config.js +15 -0
- package/dist/external-agents/types.d.ts +31 -0
- package/dist/external-agents/types.js +1 -0
- package/dist/git/utils.js +11 -6
- package/dist/git/worktree.d.ts +48 -0
- package/dist/git/worktree.js +86 -10
- package/dist/hooks/goal-stop-hook.d.ts +28 -0
- package/dist/hooks/goal-stop-hook.js +186 -9
- package/dist/hooks/registry.d.ts +8 -0
- package/dist/hooks/registry.js +19 -0
- package/dist/hooks/shell-runner.js +10 -21
- package/dist/index.d.ts +49 -6
- package/dist/index.js +60 -4
- package/dist/llm/capabilities/param-specs.d.ts +14 -0
- package/dist/llm/capabilities/param-specs.js +62 -0
- package/dist/llm/capabilities/rules.js +4 -0
- package/dist/llm/capabilities/types.d.ts +10 -0
- package/dist/llm/client-base.d.ts +28 -1
- package/dist/llm/client-base.js +119 -13
- package/dist/llm/model-cache.js +4 -2
- package/dist/llm/model-pool.d.ts +14 -0
- package/dist/llm/model-pool.js +13 -0
- package/dist/llm/provider-auth.d.ts +41 -0
- package/dist/llm/provider-auth.js +76 -0
- package/dist/llm/provider-catalog.d.ts +4 -0
- package/dist/llm/providers/anthropic.js +64 -14
- package/dist/llm/providers/openai.d.ts +36 -0
- package/dist/llm/providers/openai.js +257 -51
- package/dist/llm/reasoning-setting.d.ts +3 -3
- package/dist/llm/reasoning-setting.js +9 -1
- package/dist/llm/stream-watchdog.js +5 -1
- package/dist/llm/token-counter.js +9 -2
- package/dist/llm/types.d.ts +4 -0
- package/dist/logging/sanitize-messages.js +22 -0
- package/dist/lsp/manager.d.ts +1 -1
- package/dist/lsp/manager.js +40 -10
- package/dist/model-catalog/builtin.d.ts +12 -0
- package/dist/model-catalog/builtin.js +407 -0
- package/dist/model-catalog/gen-connections.d.ts +20 -0
- package/dist/model-catalog/gen-connections.js +28 -0
- package/dist/model-catalog/index.d.ts +41 -0
- package/dist/model-catalog/index.js +90 -0
- package/dist/model-catalog/params.d.ts +20 -0
- package/dist/model-catalog/params.js +45 -0
- package/dist/model-catalog/resolve.d.ts +48 -0
- package/dist/model-catalog/resolve.js +33 -0
- package/dist/model-catalog/save-entry.d.ts +32 -0
- package/dist/model-catalog/save-entry.js +104 -0
- package/dist/model-catalog/types.d.ts +561 -0
- package/dist/model-catalog/types.js +93 -0
- package/dist/model-catalog/upsert.d.ts +9 -0
- package/dist/model-catalog/upsert.js +8 -0
- package/dist/onboarding.d.ts +12 -82
- package/dist/onboarding.js +61 -326
- package/dist/plugins/gitOps.d.ts +19 -0
- package/dist/plugins/gitOps.js +73 -4
- package/dist/plugins/installer/checkUpdate.d.ts +16 -0
- package/dist/plugins/installer/checkUpdate.js +32 -0
- package/dist/plugins/installer/codex/convertCommands.d.ts +19 -0
- package/dist/plugins/installer/codex/convertCommands.js +46 -0
- package/dist/plugins/installer/codex/convertMcp.d.ts +5 -2
- package/dist/plugins/installer/codex/convertMcp.js +45 -5
- package/dist/plugins/installer/install.js +25 -1
- package/dist/plugins/installer/installFromArchive.d.ts +43 -0
- package/dist/plugins/installer/installFromArchive.js +134 -0
- package/dist/plugins/installer/installFromSource.js +8 -2
- package/dist/plugins/installer/loadPluginAgents.js +6 -2
- package/dist/plugins/installer/loadPluginMcp.d.ts +9 -2
- package/dist/plugins/installer/loadPluginMcp.js +35 -2
- package/dist/plugins/installer/pruneDisabled.d.ts +24 -0
- package/dist/plugins/installer/pruneDisabled.js +73 -0
- package/dist/plugins/installer/types.d.ts +5 -2
- package/dist/plugins/installer/types.js +1 -0
- package/dist/plugins/installer/uninstall.js +4 -0
- package/dist/plugins/installer/unzip.d.ts +14 -0
- package/dist/plugins/installer/unzip.js +82 -0
- package/dist/plugins/installer/update.d.ts +14 -0
- package/dist/plugins/installer/update.js +53 -21
- package/dist/plugins/loadPluginHooks.d.ts +47 -1
- package/dist/plugins/loadPluginHooks.js +73 -1
- package/dist/plugins/marketplaceManager.d.ts +7 -0
- package/dist/plugins/marketplaceManager.js +20 -0
- package/dist/plugins/pluginCommandHook.js +4 -18
- package/dist/plugins/pluginContent.d.ts +30 -0
- package/dist/plugins/pluginContent.js +83 -0
- package/dist/plugins/pluginInstaller.d.ts +13 -0
- package/dist/plugins/pluginInstaller.js +42 -8
- package/dist/plugins/schemas.js +1 -0
- package/dist/plugins/types.d.ts +6 -0
- package/dist/preset/index.d.ts +7 -1
- package/dist/preset/index.js +96 -6
- package/dist/product/types.d.ts +1 -1
- package/dist/prompt/composer.d.ts +27 -0
- package/dist/prompt/composer.js +67 -21
- package/dist/prompt/instruction-scanner.js +5 -3
- package/dist/prompt/section-loader.js +1 -0
- package/dist/prompt/sections/base.md +1 -0
- package/dist/prompt/sections/browser.md +10 -0
- package/dist/prompt/sections/coding.md +4 -0
- package/dist/protocol/chat-session-manager.d.ts +8 -2
- package/dist/protocol/chat-session-manager.js +36 -0
- package/dist/protocol/chat-session.d.ts +65 -0
- package/dist/protocol/chat-session.js +94 -2
- package/dist/protocol/client.d.ts +27 -0
- package/dist/protocol/client.js +38 -0
- package/dist/protocol/server.d.ts +112 -0
- package/dist/protocol/server.js +586 -16
- package/dist/protocol/transport.js +3 -2
- package/dist/protocol/types.d.ts +46 -0
- package/dist/protocol/types.js +16 -0
- package/dist/quota/credentials.d.ts +3 -0
- package/dist/quota/credentials.js +80 -0
- package/dist/quota/index.d.ts +36 -0
- package/dist/quota/index.js +155 -0
- package/dist/quota/types.d.ts +48 -0
- package/dist/quota/types.js +13 -0
- package/dist/review/review-prompt.d.ts +28 -0
- package/dist/review/review-prompt.js +81 -0
- package/dist/run/FileRunStore.js +8 -3
- package/dist/run/RunApprovalBackend.js +25 -5
- package/dist/run/RunManager.d.ts +12 -0
- package/dist/run/RunManager.js +35 -0
- package/dist/run/factory.d.ts +1 -1
- package/dist/runtime/background-shell.d.ts +139 -0
- package/dist/runtime/background-shell.js +495 -0
- package/dist/runtime/output-clean.d.ts +24 -0
- package/dist/runtime/output-clean.js +41 -0
- package/dist/runtime/ring-file.d.ts +64 -0
- package/dist/runtime/ring-file.js +174 -0
- package/dist/runtime/safe-spawn.js +15 -34
- package/dist/runtime/spawn-common.d.ts +148 -0
- package/dist/runtime/spawn-common.js +303 -0
- package/dist/runtime/truncate-output.d.ts +22 -0
- package/dist/runtime/truncate-output.js +49 -0
- package/dist/runtime/utf8-cut.d.ts +11 -0
- package/dist/runtime/utf8-cut.js +23 -0
- package/dist/services/auto-dream.d.ts +4 -0
- package/dist/services/auto-dream.js +26 -26
- package/dist/services/diagnostics.d.ts +1 -2
- package/dist/services/diagnostics.js +12 -7
- package/dist/services/extract-memories.d.ts +14 -1
- package/dist/services/extract-memories.js +45 -6
- package/dist/services/memory-orchestrator.d.ts +21 -0
- package/dist/services/memory-orchestrator.js +125 -50
- package/dist/services/session-memory.js +24 -12
- package/dist/session/file-history.d.ts +124 -1
- package/dist/session/file-history.js +222 -6
- package/dist/session/memory.d.ts +116 -2
- package/dist/session/memory.js +250 -28
- package/dist/session/session-manager.d.ts +40 -0
- package/dist/session/session-manager.js +113 -3
- package/dist/session/simple-diff.d.ts +23 -0
- package/dist/session/simple-diff.js +84 -0
- package/dist/session/transcript.d.ts +25 -1
- package/dist/session/transcript.js +35 -2
- package/dist/session/undo-target.d.ts +67 -0
- package/dist/session/undo-target.js +144 -0
- package/dist/settings/disk-defaults.d.ts +9 -2
- package/dist/settings/disk-defaults.js +11 -2
- package/dist/settings/feature-flags.d.ts +64 -0
- package/dist/settings/feature-flags.js +61 -0
- package/dist/settings/manager.d.ts +68 -1
- package/dist/settings/manager.js +266 -29
- package/dist/settings/migrate-config.d.ts +45 -0
- package/dist/settings/migrate-config.js +125 -0
- package/dist/settings/schema-export.d.ts +25 -0
- package/dist/settings/schema-export.js +38 -0
- package/dist/settings/schema.d.ts +1186 -771
- package/dist/settings/schema.js +268 -97
- package/dist/skills/scanner.d.ts +9 -0
- package/dist/skills/scanner.js +30 -2
- package/dist/stt/resolve-transcribe.d.ts +31 -0
- package/dist/stt/resolve-transcribe.js +108 -0
- package/dist/stt/transcribe.d.ts +51 -0
- package/dist/stt/transcribe.js +65 -0
- package/dist/tool-system/browser-bridge.d.ts +226 -0
- package/dist/tool-system/browser-bridge.js +163 -0
- package/dist/tool-system/builtin/agent-heartbeat.d.ts +49 -0
- package/dist/tool-system/builtin/agent-heartbeat.js +89 -0
- package/dist/tool-system/builtin/agent-notifications.d.ts +12 -3
- package/dist/tool-system/builtin/agent-notifications.js +9 -3
- package/dist/tool-system/builtin/agent-output-file.d.ts +38 -0
- package/dist/tool-system/builtin/agent-output-file.js +72 -0
- package/dist/tool-system/builtin/agent-registry.d.ts +12 -0
- package/dist/tool-system/builtin/agent-registry.js +8 -0
- package/dist/tool-system/builtin/agent.d.ts +21 -1
- package/dist/tool-system/builtin/agent.js +489 -42
- package/dist/tool-system/builtin/apply-patch/applier.js +66 -8
- package/dist/tool-system/builtin/apply-patch/backup-targets.d.ts +10 -0
- package/dist/tool-system/builtin/apply-patch/backup-targets.js +30 -0
- package/dist/tool-system/builtin/apply-patch/index.js +0 -15
- package/dist/tool-system/builtin/background-jobs.d.ts +76 -0
- package/dist/tool-system/builtin/background-jobs.js +124 -0
- package/dist/tool-system/builtin/background-shell-tools.d.ts +20 -0
- package/dist/tool-system/builtin/background-shell-tools.js +108 -0
- package/dist/tool-system/builtin/background-work.d.ts +67 -0
- package/dist/tool-system/builtin/background-work.js +86 -0
- package/dist/tool-system/builtin/bash-output-style.d.ts +32 -0
- package/dist/tool-system/builtin/bash-output-style.js +40 -0
- package/dist/tool-system/builtin/bash.d.ts +5 -2
- package/dist/tool-system/builtin/bash.js +98 -62
- package/dist/tool-system/builtin/browser-tools.d.ts +33 -0
- package/dist/tool-system/builtin/browser-tools.js +312 -0
- package/dist/tool-system/builtin/cancel-goal.d.ts +31 -0
- package/dist/tool-system/builtin/cancel-goal.js +64 -0
- package/dist/tool-system/builtin/check-quota.d.ts +15 -0
- package/dist/tool-system/builtin/check-quota.js +34 -0
- package/dist/tool-system/builtin/config.js +7 -0
- package/dist/tool-system/builtin/cron.d.ts +7 -0
- package/dist/tool-system/builtin/cron.js +55 -3
- package/dist/tool-system/builtin/drive-claude-code.d.ts +30 -0
- package/dist/tool-system/builtin/drive-claude-code.js +157 -0
- package/dist/tool-system/builtin/edit-model-catalog.d.ts +3 -0
- package/dist/tool-system/builtin/edit-model-catalog.js +94 -0
- package/dist/tool-system/builtin/edit.js +20 -16
- package/dist/tool-system/builtin/eol.d.ts +29 -0
- package/dist/tool-system/builtin/eol.js +37 -0
- package/dist/tool-system/builtin/file-cache.d.ts +6 -0
- package/dist/tool-system/builtin/file-cache.js +8 -0
- package/dist/tool-system/builtin/generate-image.d.ts +35 -0
- package/dist/tool-system/builtin/generate-image.js +278 -50
- package/dist/tool-system/builtin/generate-video.d.ts +55 -0
- package/dist/tool-system/builtin/generate-video.js +361 -0
- package/dist/tool-system/builtin/glob.js +0 -7
- package/dist/tool-system/builtin/grep.js +6 -8
- package/dist/tool-system/builtin/image-providers.d.ts +86 -0
- package/dist/tool-system/builtin/image-providers.js +190 -0
- package/dist/tool-system/builtin/image-uploader.d.ts +33 -0
- package/dist/tool-system/builtin/image-uploader.js +74 -0
- package/dist/tool-system/builtin/index.d.ts +7 -1
- package/dist/tool-system/builtin/index.js +249 -25
- package/dist/tool-system/builtin/mcp-tools.js +23 -3
- package/dist/tool-system/builtin/memory.js +45 -7
- package/dist/tool-system/builtin/notebook-edit.js +0 -7
- package/dist/tool-system/builtin/powershell.js +4 -1
- package/dist/tool-system/builtin/read.js +10 -10
- package/dist/tool-system/builtin/repl.js +4 -1
- package/dist/tool-system/builtin/skill.js +9 -0
- package/dist/tool-system/builtin/sleep.js +3 -2
- package/dist/tool-system/builtin/tool-search.js +25 -7
- package/dist/tool-system/builtin/video-providers.d.ts +154 -0
- package/dist/tool-system/builtin/video-providers.js +235 -0
- package/dist/tool-system/builtin/web-fetch.js +12 -2
- package/dist/tool-system/builtin/web-search.js +21 -7
- package/dist/tool-system/builtin/worktree.d.ts +2 -1
- package/dist/tool-system/builtin/worktree.js +31 -4
- package/dist/tool-system/builtin/write.js +0 -6
- package/dist/tool-system/context.d.ts +154 -6
- package/dist/tool-system/executor.d.ts +3 -1
- package/dist/tool-system/executor.js +172 -88
- package/dist/tool-system/investigation-guard.js +1 -1
- package/dist/tool-system/mcp-manager.d.ts +42 -4
- package/dist/tool-system/mcp-manager.js +186 -20
- package/dist/tool-system/path-policy.d.ts +5 -0
- package/dist/tool-system/path-policy.js +307 -8
- package/dist/tool-system/permission.d.ts +36 -1
- package/dist/tool-system/permission.js +281 -69
- package/dist/tool-system/plan-mode-allowlist.d.ts +13 -2
- package/dist/tool-system/plan-mode-allowlist.js +24 -2
- package/dist/tool-system/registry.d.ts +1 -0
- package/dist/tool-system/registry.js +16 -2
- package/dist/tool-system/sandbox/index.d.ts +8 -0
- package/dist/tool-system/sandbox/index.js +7 -2
- package/dist/tool-system/validate-tool-metadata.d.ts +36 -0
- package/dist/tool-system/validate-tool-metadata.js +63 -0
- package/dist/types.d.ts +217 -6
- package/dist/updater.js +20 -9
- package/dist/utils/envUtils.d.ts +0 -9
- package/dist/utils/envUtils.js +3 -28
- package/dist/utils/exec.d.ts +40 -0
- package/dist/utils/exec.js +144 -0
- package/dist/utils/json.d.ts +12 -0
- package/dist/utils/json.js +92 -0
- package/dist/utils/theme.d.ts +1 -1
- package/dist/utils/theme.js +1 -1
- package/dist/utils/toolDisplay.js +0 -1
- package/package.json +13 -7
- package/dist/agent/coordinator.d.ts +0 -49
- package/dist/agent/coordinator.js +0 -77
- package/dist/settings/manager.test.js +0 -73
- package/dist/tool-system/builtin/remote-trigger.d.ts +0 -6
- package/dist/tool-system/builtin/remote-trigger.js +0 -54
- package/dist/tool-system/builtin/send-message.d.ts +0 -6
- package/dist/tool-system/builtin/send-message.js +0 -47
- /package/dist/{settings/manager.test.d.ts → credentials/types.js} +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { pathWithCommonBins } from "./cc-capability.js";
|
|
4
|
+
/** Pure: reduce collected output lines + exit code to a result. Unit-testable. */
|
|
5
|
+
export function runWithLines(adapter, lines, exitCode) {
|
|
6
|
+
const parsed = adapter.parseResult(lines);
|
|
7
|
+
return { ...parsed, exitCode, lines };
|
|
8
|
+
}
|
|
9
|
+
/** Spawn ONE headless agent run, collect stream-json to exit, return result.
|
|
10
|
+
* No time concept — a single turn. Honors AbortSignal (kills the child). */
|
|
11
|
+
export function runAgentOnce(adapter, opts, signal) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const args = adapter.buildArgs({
|
|
14
|
+
prompt: opts.prompt,
|
|
15
|
+
resumeSessionId: opts.resumeSessionId,
|
|
16
|
+
permissionMode: opts.permissionMode ?? "default",
|
|
17
|
+
cwd: opts.cwd,
|
|
18
|
+
});
|
|
19
|
+
// claude takes the prompt in argv (`-p <prompt>`) and wants stdin closed
|
|
20
|
+
// (verified: avoids a 3s wait). codex `exec` reads the prompt from stdin
|
|
21
|
+
// (argv ends with `-`), so adapters that set promptViaStdin get a piped
|
|
22
|
+
// stdin we write the prompt to.
|
|
23
|
+
const viaStdin = adapter.promptViaStdin === true;
|
|
24
|
+
const child = spawn(opts.command, args, {
|
|
25
|
+
cwd: opts.cwd,
|
|
26
|
+
env: { ...process.env, PATH: pathWithCommonBins() },
|
|
27
|
+
// NOT detached: this child is owned by the (long-lived) worker that reads
|
|
28
|
+
// its stdout. Detaching orphaned it across a worker/app restart — the
|
|
29
|
+
// reader promise then never resolved and the completion notification never
|
|
30
|
+
// fired (the "后台任务没返回" bug). Bound to the worker, it lives or dies
|
|
31
|
+
// with the process that's actually listening for its result.
|
|
32
|
+
detached: false,
|
|
33
|
+
stdio: [viaStdin ? "pipe" : "ignore", "pipe", "pipe"],
|
|
34
|
+
});
|
|
35
|
+
if (viaStdin && child.stdin) {
|
|
36
|
+
child.stdin.end(opts.prompt);
|
|
37
|
+
}
|
|
38
|
+
const lines = [];
|
|
39
|
+
if (child.stdout) {
|
|
40
|
+
const rl = createInterface({ input: child.stdout });
|
|
41
|
+
rl.on("line", (line) => lines.push(line));
|
|
42
|
+
}
|
|
43
|
+
// Not detached → no own process group, so kill the child directly (a
|
|
44
|
+
// negative-pid group kill would target the worker's group). claude has no
|
|
45
|
+
// long-lived child tree of its own here, so a direct SIGTERM is sufficient.
|
|
46
|
+
const onAbort = () => {
|
|
47
|
+
try {
|
|
48
|
+
child.kill("SIGTERM");
|
|
49
|
+
}
|
|
50
|
+
catch { /* already gone */ }
|
|
51
|
+
};
|
|
52
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
53
|
+
child.on("error", (err) => {
|
|
54
|
+
signal?.removeEventListener("abort", onAbort);
|
|
55
|
+
// A missing binary is the most common failure (user hasn't installed the
|
|
56
|
+
// CLI, or GUI-launched Electron's PATH misses it). Turn the cryptic
|
|
57
|
+
// "spawn codex ENOENT" into something actionable that names the command.
|
|
58
|
+
if (err.code === "ENOENT") {
|
|
59
|
+
reject(new Error(`未找到命令 "${opts.command}"。请先安装该 CLI 并确保它在 PATH 中(${adapter.kind === "codex" ? "Codex CLI" : "Claude Code CLI"})。`));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
reject(err);
|
|
63
|
+
});
|
|
64
|
+
child.on("exit", (code) => {
|
|
65
|
+
signal?.removeEventListener("abort", onAbort);
|
|
66
|
+
resolve(runWithLines(adapter, lines, code));
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./cc-capability.js";
|
|
2
|
+
export * from "./agent-adapter.js";
|
|
3
|
+
export * from "./external-agent-driver.js";
|
|
4
|
+
export * from "./session-discovery.js";
|
|
5
|
+
export * from "./codex-session-discovery.js";
|
|
6
|
+
export * from "./relevance-judge.js";
|
|
7
|
+
export * from "./session-history.js";
|
|
8
|
+
export * from "./codex-session-history.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./cc-capability.js";
|
|
2
|
+
export * from "./agent-adapter.js";
|
|
3
|
+
export * from "./external-agent-driver.js";
|
|
4
|
+
export * from "./session-discovery.js";
|
|
5
|
+
export * from "./codex-session-discovery.js";
|
|
6
|
+
export * from "./relevance-judge.js";
|
|
7
|
+
export * from "./session-history.js";
|
|
8
|
+
export * from "./codex-session-history.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface JudgeDecision {
|
|
2
|
+
action: "continue-same" | "continue-fresh" | "stop";
|
|
3
|
+
handoffSummary?: string;
|
|
4
|
+
reason: string;
|
|
5
|
+
}
|
|
6
|
+
export interface JudgeInput {
|
|
7
|
+
goal?: string;
|
|
8
|
+
lastResult: string;
|
|
9
|
+
nextPrompt: string;
|
|
10
|
+
}
|
|
11
|
+
/** Injected aux-model call: takes a prompt, returns raw text. */
|
|
12
|
+
export type AuxLlm = (prompt: string) => Promise<string>;
|
|
13
|
+
export declare function buildJudgePrompt(input: JudgeInput): string;
|
|
14
|
+
export declare function parseJudgeResponse(raw: string): JudgeDecision;
|
|
15
|
+
export declare function judgeContinuation(input: JudgeInput, llm: AuxLlm): Promise<JudgeDecision>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const SYSTEM = `You decide how a scheduled task loop should continue after one run.
|
|
2
|
+
Reply with ONLY a JSON object: {"action": "continue-same"|"continue-fresh"|"stop", "handoffSummary"?: string, "reason": string}.
|
|
3
|
+
- "stop": the goal is met; no more runs needed.
|
|
4
|
+
- "continue-same": next run should resume the SAME session (work is related, keep context).
|
|
5
|
+
- "continue-fresh": next run should start a FRESH session because the next step is unrelated to what was just done; put a short context summary in handoffSummary.`;
|
|
6
|
+
export function buildJudgePrompt(input) {
|
|
7
|
+
return [
|
|
8
|
+
SYSTEM,
|
|
9
|
+
input.goal ? `\nOverall goal: ${input.goal}` : "",
|
|
10
|
+
`\nWhat the last run produced:\n${input.lastResult.slice(0, 4000)}`,
|
|
11
|
+
`\nThe next scheduled prompt:\n${input.nextPrompt}`,
|
|
12
|
+
`\nDecision JSON:`,
|
|
13
|
+
].join("\n");
|
|
14
|
+
}
|
|
15
|
+
export function parseJudgeResponse(raw) {
|
|
16
|
+
try {
|
|
17
|
+
const m = raw.match(/\{[\s\S]*\}/);
|
|
18
|
+
const d = JSON.parse(m ? m[0] : raw);
|
|
19
|
+
if (d.action === "stop" || d.action === "continue-fresh" || d.action === "continue-same") {
|
|
20
|
+
return { action: d.action, handoffSummary: typeof d.handoffSummary === "string" ? d.handoffSummary : undefined, reason: typeof d.reason === "string" ? d.reason : "" };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch { /* fall through */ }
|
|
24
|
+
return { action: "continue-same", reason: "unparseable judge output; defaulting to continue-same" };
|
|
25
|
+
}
|
|
26
|
+
export async function judgeContinuation(input, llm) {
|
|
27
|
+
const raw = await llm(buildJudgePrompt(input));
|
|
28
|
+
return parseJudgeResponse(raw);
|
|
29
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Encode a cwd to claude's project dir name: every non-[A-Za-z0-9] char → '-'.
|
|
2
|
+
* Mirrors `~/.claude/projects/<encoded>` (verified against real layout). */
|
|
3
|
+
export declare function encodeCwd(cwd: string): string;
|
|
4
|
+
export interface DiscoveredSession {
|
|
5
|
+
sessionId: string;
|
|
6
|
+
firstMessage: string;
|
|
7
|
+
lastModified: number;
|
|
8
|
+
messageCount: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Bound how many sessions a discovery scan deep-reads + returns. Without this,
|
|
12
|
+
* `discoverSessions` read EVERY `.jsonl` file fully (title + message count) on
|
|
13
|
+
* every list open — O(all history) work that makes the CC/Codex room list slow
|
|
14
|
+
* and unwieldy once a project accumulates sessions. The default surfaces only
|
|
15
|
+
* the recent, useful slice; the UI fetches older ones on demand by passing a
|
|
16
|
+
* larger `limit` (or `sinceMs: 0`).
|
|
17
|
+
*/
|
|
18
|
+
export interface DiscoverOptions {
|
|
19
|
+
/** Max sessions to return (after the recency filter), newest first.
|
|
20
|
+
* undefined or <= 0 = no cap. */
|
|
21
|
+
limit?: number;
|
|
22
|
+
/** Only include sessions modified within this many ms of `now`.
|
|
23
|
+
* undefined or <= 0 = no window. */
|
|
24
|
+
sinceMs?: number;
|
|
25
|
+
/** Injectable clock for tests. Defaults to Date.now(). */
|
|
26
|
+
now?: number;
|
|
27
|
+
}
|
|
28
|
+
/** Default room-list window: last 2 weeks AND at most 20 — the intersection. */
|
|
29
|
+
export declare const DEFAULT_DISCOVER_LIMIT = 20;
|
|
30
|
+
export declare const DEFAULT_DISCOVER_SINCE_MS: number;
|
|
31
|
+
/**
|
|
32
|
+
* Apply the recency window + limit to mtime-sorted stats, returning the slice to
|
|
33
|
+
* deep-read. Shared by the claude + codex discovery paths so they bound work
|
|
34
|
+
* identically. `total` (caller-side) lets the UI show a "load more" affordance.
|
|
35
|
+
*/
|
|
36
|
+
export declare function selectRecentStats<T extends {
|
|
37
|
+
mtimeMs: number;
|
|
38
|
+
}>(stats: T[], opts?: DiscoverOptions): T[];
|
|
39
|
+
/** List claude sessions for `cwd`. `claudeHome` defaults to ~/.claude (override
|
|
40
|
+
* for tests). Read-only, on-demand scan; no index. Stats every file cheaply,
|
|
41
|
+
* then deep-reads (title + count) ONLY the recent slice (opts) so an old,
|
|
42
|
+
* large history doesn't get fully read on every list open. */
|
|
43
|
+
export declare function discoverSessions(cwd: string, claudeHome?: string, opts?: DiscoverOptions): DiscoveredSession[];
|
|
44
|
+
/** Count of all session files for `cwd`, cheaply (stat only). Lets the UI know
|
|
45
|
+
* whether a bounded `discoverSessions` left older sessions unshown. */
|
|
46
|
+
export declare function countSessions(cwd: string, claudeHome?: string): number;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, statSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
/** Encode a cwd to claude's project dir name: every non-[A-Za-z0-9] char → '-'.
|
|
5
|
+
* Mirrors `~/.claude/projects/<encoded>` (verified against real layout). */
|
|
6
|
+
export function encodeCwd(cwd) {
|
|
7
|
+
return cwd.replace(/[^A-Za-z0-9]/g, "-");
|
|
8
|
+
}
|
|
9
|
+
/** Default room-list window: last 2 weeks AND at most 20 — the intersection. */
|
|
10
|
+
export const DEFAULT_DISCOVER_LIMIT = 20;
|
|
11
|
+
export const DEFAULT_DISCOVER_SINCE_MS = 14 * 24 * 60 * 60 * 1000;
|
|
12
|
+
function claudeProjectsDir(claudeHome) {
|
|
13
|
+
return join(claudeHome, "projects");
|
|
14
|
+
}
|
|
15
|
+
/** Extract first *real* user message text, skipping caveat/command noise. */
|
|
16
|
+
function firstUserMessage(lines) {
|
|
17
|
+
for (const line of lines) {
|
|
18
|
+
if (!line.trim())
|
|
19
|
+
continue;
|
|
20
|
+
let d;
|
|
21
|
+
try {
|
|
22
|
+
d = JSON.parse(line);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (d.type !== "user")
|
|
28
|
+
continue;
|
|
29
|
+
const c = d.message?.content;
|
|
30
|
+
const text = typeof c === "string"
|
|
31
|
+
? c
|
|
32
|
+
: Array.isArray(c)
|
|
33
|
+
? c.map((p) => (typeof p?.text === "string" ? p.text : "")).join("")
|
|
34
|
+
: "";
|
|
35
|
+
const t = text.trim();
|
|
36
|
+
if (!t)
|
|
37
|
+
continue;
|
|
38
|
+
if (t.startsWith("<local-command-caveat>") || t.startsWith("<command-name>"))
|
|
39
|
+
continue;
|
|
40
|
+
return t.slice(0, 200);
|
|
41
|
+
}
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
function countUserMessages(lines) {
|
|
45
|
+
let n = 0;
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
if (!line.trim())
|
|
48
|
+
continue;
|
|
49
|
+
try {
|
|
50
|
+
if (JSON.parse(line).type === "user")
|
|
51
|
+
n++;
|
|
52
|
+
}
|
|
53
|
+
catch { /* skip */ }
|
|
54
|
+
}
|
|
55
|
+
return n;
|
|
56
|
+
}
|
|
57
|
+
/** Cheap (sessionId, mtime) pairs for every session file — stat only, no read. */
|
|
58
|
+
function listSessionStats(dir) {
|
|
59
|
+
const stats = [];
|
|
60
|
+
for (const name of readdirSync(dir)) {
|
|
61
|
+
if (!name.endsWith(".jsonl"))
|
|
62
|
+
continue;
|
|
63
|
+
const file = join(dir, name);
|
|
64
|
+
let st;
|
|
65
|
+
try {
|
|
66
|
+
st = statSync(file);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
stats.push({ sessionId: name.replace(/\.jsonl$/, ""), file, mtimeMs: st.mtimeMs });
|
|
72
|
+
}
|
|
73
|
+
return stats;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Apply the recency window + limit to mtime-sorted stats, returning the slice to
|
|
77
|
+
* deep-read. Shared by the claude + codex discovery paths so they bound work
|
|
78
|
+
* identically. `total` (caller-side) lets the UI show a "load more" affordance.
|
|
79
|
+
*/
|
|
80
|
+
export function selectRecentStats(stats, opts = {}) {
|
|
81
|
+
const now = opts.now ?? Date.now();
|
|
82
|
+
let sel = stats.slice().sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
83
|
+
if (opts.sinceMs && opts.sinceMs > 0) {
|
|
84
|
+
const cutoff = now - opts.sinceMs;
|
|
85
|
+
sel = sel.filter((s) => s.mtimeMs >= cutoff);
|
|
86
|
+
}
|
|
87
|
+
if (opts.limit && opts.limit > 0)
|
|
88
|
+
sel = sel.slice(0, opts.limit);
|
|
89
|
+
return sel;
|
|
90
|
+
}
|
|
91
|
+
/** List claude sessions for `cwd`. `claudeHome` defaults to ~/.claude (override
|
|
92
|
+
* for tests). Read-only, on-demand scan; no index. Stats every file cheaply,
|
|
93
|
+
* then deep-reads (title + count) ONLY the recent slice (opts) so an old,
|
|
94
|
+
* large history doesn't get fully read on every list open. */
|
|
95
|
+
export function discoverSessions(cwd, claudeHome = join(homedir(), ".claude"), opts = {}) {
|
|
96
|
+
const dir = join(claudeProjectsDir(claudeHome), encodeCwd(cwd));
|
|
97
|
+
if (!existsSync(dir))
|
|
98
|
+
return [];
|
|
99
|
+
const selected = selectRecentStats(listSessionStats(dir), opts);
|
|
100
|
+
const out = [];
|
|
101
|
+
for (const s of selected) {
|
|
102
|
+
let lines;
|
|
103
|
+
try {
|
|
104
|
+
lines = readFileSync(s.file, "utf-8").split("\n");
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
out.push({
|
|
110
|
+
sessionId: s.sessionId,
|
|
111
|
+
firstMessage: firstUserMessage(lines),
|
|
112
|
+
lastModified: s.mtimeMs,
|
|
113
|
+
messageCount: countUserMessages(lines),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
/** Count of all session files for `cwd`, cheaply (stat only). Lets the UI know
|
|
119
|
+
* whether a bounded `discoverSessions` left older sessions unshown. */
|
|
120
|
+
export function countSessions(cwd, claudeHome = join(homedir(), ".claude")) {
|
|
121
|
+
const dir = join(claudeProjectsDir(claudeHome), encodeCwd(cwd));
|
|
122
|
+
if (!existsSync(dir))
|
|
123
|
+
return 0;
|
|
124
|
+
return listSessionStats(dir).length;
|
|
125
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface HistoryMessage {
|
|
2
|
+
role: "user" | "assistant";
|
|
3
|
+
text: string;
|
|
4
|
+
/** `summary` is a lossy one-field preview; `args` is the full tool_use input
|
|
5
|
+
* (e.g. a sub-agent's `prompt`) so a replayed tool card can show the real
|
|
6
|
+
* parameters, not just the whitelisted field. */
|
|
7
|
+
tools?: {
|
|
8
|
+
name: string;
|
|
9
|
+
summary: string;
|
|
10
|
+
args?: Record<string, unknown>;
|
|
11
|
+
}[];
|
|
12
|
+
ts?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Read the last `limit` user/assistant messages from a claude session jsonl. */
|
|
15
|
+
export declare function readRecentHistory(cwd: string, sessionId: string, limit: number, claudeHome?: string): {
|
|
16
|
+
messages: HistoryMessage[];
|
|
17
|
+
hasMore: boolean;
|
|
18
|
+
totalCount: number;
|
|
19
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { encodeCwd } from "./session-discovery.js";
|
|
5
|
+
const NOISE = ["<local-command-caveat>", "<command-name>"];
|
|
6
|
+
function textOf(content) {
|
|
7
|
+
if (typeof content === "string")
|
|
8
|
+
return content;
|
|
9
|
+
if (Array.isArray(content))
|
|
10
|
+
return content.map((p) => (typeof p?.text === "string" ? p.text : "")).join("");
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
function toolsOf(content) {
|
|
14
|
+
if (!Array.isArray(content))
|
|
15
|
+
return [];
|
|
16
|
+
const out = [];
|
|
17
|
+
for (const p of content) {
|
|
18
|
+
if (p?.type === "tool_use") {
|
|
19
|
+
const inp = p.input ?? {};
|
|
20
|
+
const summary = inp.command ?? inp.file_path ?? inp.path ?? inp.url ?? inp.pattern ?? inp.query ?? "";
|
|
21
|
+
const args = inp && typeof inp === "object" && Object.keys(inp).length > 0 ? inp : undefined;
|
|
22
|
+
out.push({ name: typeof p.name === "string" ? p.name : "tool", summary: String(summary).slice(0, 120), args });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
/** Read the last `limit` user/assistant messages from a claude session jsonl. */
|
|
28
|
+
export function readRecentHistory(cwd, sessionId, limit, claudeHome = join(homedir(), ".claude")) {
|
|
29
|
+
const file = join(claudeHome, "projects", encodeCwd(cwd), `${sessionId}.jsonl`);
|
|
30
|
+
if (!existsSync(file))
|
|
31
|
+
return { messages: [], hasMore: false, totalCount: 0 };
|
|
32
|
+
let raw;
|
|
33
|
+
try {
|
|
34
|
+
raw = readFileSync(file, "utf-8");
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return { messages: [], hasMore: false, totalCount: 0 };
|
|
38
|
+
}
|
|
39
|
+
const all = [];
|
|
40
|
+
for (const line of raw.split("\n")) {
|
|
41
|
+
if (!line.trim())
|
|
42
|
+
continue;
|
|
43
|
+
let d;
|
|
44
|
+
try {
|
|
45
|
+
d = JSON.parse(line);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (d.type === "user") {
|
|
51
|
+
const t = textOf(d.message?.content).trim();
|
|
52
|
+
if (!t || NOISE.some((n) => t.startsWith(n)))
|
|
53
|
+
continue;
|
|
54
|
+
all.push({ role: "user", text: t.slice(0, 4000) });
|
|
55
|
+
}
|
|
56
|
+
else if (d.type === "assistant") {
|
|
57
|
+
const t = textOf(d.message?.content).trim();
|
|
58
|
+
const tools = toolsOf(d.message?.content);
|
|
59
|
+
if (!t && tools.length === 0)
|
|
60
|
+
continue;
|
|
61
|
+
all.push({ role: "assistant", text: t.slice(0, 4000), tools: tools.length ? tools : undefined });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const lim = limit > 0 ? limit : 20;
|
|
65
|
+
const start = Math.max(0, all.length - lim);
|
|
66
|
+
return { messages: all.slice(start), hasMore: start > 0, totalCount: all.length };
|
|
67
|
+
}
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* bootstrap is invisible to them.
|
|
31
31
|
*/
|
|
32
32
|
import type { EngineConfigSlice } from "../protocol/chat-session-manager.js";
|
|
33
|
-
import type
|
|
33
|
+
import { type ValidatedSettings } from "../settings/schema.js";
|
|
34
34
|
/**
|
|
35
35
|
* Resolve per-session agent config: protocol slice overrides win, else fall
|
|
36
36
|
* back to disk settings.agent.*. Fixes the bug where settings.agent.* never
|
|
@@ -41,3 +41,17 @@ export declare function resolveSessionAgentConfig(slice: EngineConfigSlice, sett
|
|
|
41
41
|
customSystemPrompt: string | undefined;
|
|
42
42
|
appendSystemPrompt: string | undefined;
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a session's effective cwd. A protocol slice with an explicit cwd
|
|
46
|
+
* points the session at that project; a slice WITHOUT a cwd is a no-repo
|
|
47
|
+
* "纯聊天" session (the renderer omits cwd when no project is selected) and must
|
|
48
|
+
* land in the no-repo sandbox (~/.code-shell/no-repo) — NOT the worker's boot
|
|
49
|
+
* cwd.
|
|
50
|
+
*
|
|
51
|
+
* Why not the boot cwd: this stdio worker is long-lived and reused across
|
|
52
|
+
* sessions/projects, so its boot cwd is whatever project first spawned it.
|
|
53
|
+
* Inheriting it for a no-repo chat would (a) silently run the chat against a
|
|
54
|
+
* stale, unrelated project's files and (b) defeat the no-repo skill/plugin
|
|
55
|
+
* whitelist inversion (which only fires when cwd === noRepoDir).
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveSessionCwd(slice: EngineConfigSlice): string;
|
|
@@ -32,16 +32,24 @@
|
|
|
32
32
|
import { Engine } from "../engine/engine.js";
|
|
33
33
|
import { EngineRuntime } from "../engine/runtime.js";
|
|
34
34
|
import { ChatSessionManager } from "../protocol/chat-session-manager.js";
|
|
35
|
+
import { SessionManager } from "../session/session-manager.js";
|
|
36
|
+
import { validateSettings } from "../settings/schema.js";
|
|
35
37
|
import { AgentServer } from "../protocol/server.js";
|
|
36
38
|
import { StdioTransport } from "../protocol/transport.js";
|
|
37
|
-
import {
|
|
39
|
+
import { createNotification } from "../protocol/types.js";
|
|
40
|
+
import { setCronChangedSink } from "../tool-system/builtin/cron.js";
|
|
41
|
+
import { SettingsManager, noRepoDir } from "../settings/manager.js";
|
|
38
42
|
import { personalizationFrom } from "../settings/personalization.js";
|
|
39
43
|
import { MCPManager } from "../tool-system/mcp-manager.js";
|
|
40
44
|
import { mergePluginMcpServers } from "../plugins/installer/loadPluginMcp.js";
|
|
45
|
+
import { computeEffectiveDisabledLists } from "../capability-control/disabled-lists.js";
|
|
41
46
|
import { CostTracker } from "../cost-tracker.js";
|
|
42
47
|
import { installGracefulShutdown } from "./graceful-shutdown.js";
|
|
48
|
+
import { backgroundShellManager } from "../runtime/background-shell.js";
|
|
49
|
+
import { logger } from "../logging/logger.js";
|
|
43
50
|
import { cronScheduler } from "../automation/scheduler.js";
|
|
44
51
|
import { CronStore, defaultCronStorePath } from "../automation/store.js";
|
|
52
|
+
import { resolveLLMConfigForTag } from "../engine/resolve-llm-config.js";
|
|
45
53
|
/**
|
|
46
54
|
* Resolve per-session agent config: protocol slice overrides win, else fall
|
|
47
55
|
* back to disk settings.agent.*. Fixes the bug where settings.agent.* never
|
|
@@ -54,24 +62,55 @@ export function resolveSessionAgentConfig(slice, settings) {
|
|
|
54
62
|
appendSystemPrompt: slice.appendSystemPrompt ?? settings.agent.appendSystemPrompt,
|
|
55
63
|
};
|
|
56
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve a session's effective cwd. A protocol slice with an explicit cwd
|
|
67
|
+
* points the session at that project; a slice WITHOUT a cwd is a no-repo
|
|
68
|
+
* "纯聊天" session (the renderer omits cwd when no project is selected) and must
|
|
69
|
+
* land in the no-repo sandbox (~/.code-shell/no-repo) — NOT the worker's boot
|
|
70
|
+
* cwd.
|
|
71
|
+
*
|
|
72
|
+
* Why not the boot cwd: this stdio worker is long-lived and reused across
|
|
73
|
+
* sessions/projects, so its boot cwd is whatever project first spawned it.
|
|
74
|
+
* Inheriting it for a no-repo chat would (a) silently run the chat against a
|
|
75
|
+
* stale, unrelated project's files and (b) defeat the no-repo skill/plugin
|
|
76
|
+
* whitelist inversion (which only fires when cwd === noRepoDir).
|
|
77
|
+
*/
|
|
78
|
+
export function resolveSessionCwd(slice) {
|
|
79
|
+
return slice.cwd ?? noRepoDir();
|
|
80
|
+
}
|
|
57
81
|
// ─── Read base config from environment / settings ─────────────────
|
|
58
82
|
const cwd = process.env.AGENT_CWD ?? process.cwd();
|
|
59
83
|
// Load settings once to derive llm config for the seed engine.
|
|
60
84
|
// Desktop is a host application: read the full disk hierarchy (incl. the
|
|
61
85
|
// user's ~/.code-shell). The SDK default 'project' would skip user config.
|
|
62
86
|
const settingsManager = new SettingsManager(cwd, "full");
|
|
63
|
-
|
|
87
|
+
// Bootstrap load must NOT crash the worker on a schema-invalid (but JSON-valid)
|
|
88
|
+
// settings file: a hand-edited settings.json with e.g. `permissions.defaultMode:
|
|
89
|
+
// "ask"` or `env.FOO: 123` makes SettingsSchema.parse throw a ZodError at module
|
|
90
|
+
// top level, which the desktop/mobile host sees as "connection lost" / a
|
|
91
|
+
// nonzero-exit worker. Fall back to schema defaults and print a readable,
|
|
92
|
+
// truncated reason so the user can fix the file (freshSettings() applies the
|
|
93
|
+
// same best-effort contract per session once the worker is up).
|
|
94
|
+
let settings;
|
|
95
|
+
try {
|
|
96
|
+
settings = settingsManager.get();
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
const reason = (err instanceof Error ? err.message : String(err)).slice(0, 2000);
|
|
100
|
+
console.error(`[agent-server] settings.json 校验失败,已回退到默认配置(请修正后重启):\n${reason}`);
|
|
101
|
+
settings = validateSettings({});
|
|
102
|
+
}
|
|
64
103
|
// LLMConfig is pure model identity now — only the bare identity fields go here.
|
|
65
104
|
// temperature/imageDetail/timeout/retryMaxAttempts are ClientDefaults, derived
|
|
66
|
-
// inside Engine.populateModelPoolFromSettings
|
|
67
|
-
//
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
105
|
+
// inside Engine.populateModelPoolFromSettings (temperature is a ClientDefaults
|
|
106
|
+
// knob; imageDetail from settings.images.detail).
|
|
107
|
+
const seedLlm = resolveLLMConfigForTag(settings, "text", settings.defaults?.text);
|
|
108
|
+
if (!seedLlm) {
|
|
109
|
+
console.error(`[agent-server] 没有可用的文本模型连接(defaults.text=${settings.defaults?.text ?? "未设置"})。` +
|
|
110
|
+
`请在「连接」页添加并填写凭证。`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
const llmConfig = seedLlm;
|
|
75
114
|
// ─── Step 1: seed engine — populates model pool from settings ─────
|
|
76
115
|
const seedEngine = new Engine({
|
|
77
116
|
llm: llmConfig,
|
|
@@ -84,7 +123,7 @@ const modelPool = seedEngine.getModelPool();
|
|
|
84
123
|
const toolRegistry = seedEngine.getToolRegistry();
|
|
85
124
|
// Capture the seed engine's resolved llmConfig + clientDefaults
|
|
86
125
|
// (post-populateModelPoolFromSettings). Session engines inherit defaults so
|
|
87
|
-
//
|
|
126
|
+
// the temperature ClientDefault / images.detail apply uniformly without each
|
|
88
127
|
// factory call re-reading settings.
|
|
89
128
|
const resolvedLlmConfig = seedEngine.getConfig().llm;
|
|
90
129
|
const resolvedClientDefaults = seedEngine.getConfig().clientDefaults;
|
|
@@ -138,14 +177,42 @@ const chatManager = new ChatSessionManager({
|
|
|
138
177
|
// each time the factory fires; fall back to the snapshot only when the
|
|
139
178
|
// pool can't resolve (no active key — shouldn't happen in practice).
|
|
140
179
|
engineFactory: (slice) => {
|
|
141
|
-
|
|
180
|
+
// Effective cwd for THIS session: explicit slice.cwd → that project; absent
|
|
181
|
+
// → the no-repo sandbox (NOT the worker's stale boot cwd). See
|
|
182
|
+
// resolveSessionCwd for the full rationale.
|
|
183
|
+
const sessionCwd = resolveSessionCwd(slice);
|
|
184
|
+
// Read settings for THIS session's cwd, not the worker's boot cwd. A single
|
|
185
|
+
// worker serves many projects; the boot-cwd `freshSettings()` snapshot pins
|
|
186
|
+
// every session to the first project's project-layer settings, so project
|
|
187
|
+
// `mcpServers` / `agent.*` / permissions from the boot project would leak
|
|
188
|
+
// into (and shadow) other projects' sessions. A per-session SettingsManager
|
|
189
|
+
// still reads the full hierarchy (user ~/.code-shell + THIS project's
|
|
190
|
+
// .code-shell), keeping user-global settings while picking up the correct
|
|
191
|
+
// project overlay. Falls back to the boot snapshot if this project's file is
|
|
192
|
+
// malformed (same best-effort contract as freshSettings()).
|
|
193
|
+
const sessionSettingsManager = new SettingsManager(sessionCwd, "full");
|
|
194
|
+
let live;
|
|
195
|
+
try {
|
|
196
|
+
live = sessionSettingsManager.load();
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
live = freshSettings();
|
|
200
|
+
}
|
|
201
|
+
// Fold project capabilityOverrides over the global disabledPlugins before
|
|
202
|
+
// the MCP merge — a plugin force-enabled in 能力总览 (project "on") must
|
|
203
|
+
// contribute its MCP servers even while globally disabled, and vice versa.
|
|
204
|
+
// Raw `live.disabledPlugins` alone ignored the project layer.
|
|
205
|
+
const { disabledPlugins } = computeEffectiveDisabledLists(sessionSettingsManager, sessionCwd);
|
|
142
206
|
return new Engine({
|
|
143
207
|
llm: runtime.modelPool.resolveLLMConfig() ?? resolvedLlmConfig,
|
|
144
208
|
// Inherit the seed engine's resolved clientDefaults so every session
|
|
145
209
|
// engine sees the user's temperature/imageDetail without each factory
|
|
146
210
|
// call re-running populateModelPoolFromSettings.
|
|
147
211
|
clientDefaults: resolvedClientDefaults,
|
|
148
|
-
cwd
|
|
212
|
+
// sessionCwd resolves slice.cwd → no-repo sandbox (never the stale boot
|
|
213
|
+
// cwd). The slice.cwd spread below is now redundant with this but kept
|
|
214
|
+
// for clarity / explicitness.
|
|
215
|
+
cwd: sessionCwd,
|
|
149
216
|
runtime,
|
|
150
217
|
// This stdio worker exists only to serve the desktop app, so every
|
|
151
218
|
// session it creates is a desktop-origin session.
|
|
@@ -160,7 +227,7 @@ const chatManager = new ChatSessionManager({
|
|
|
160
227
|
// the first session to connect populates connections for the worker.
|
|
161
228
|
// Plugin-provided MCP servers (mcp-servers.json in installed plugins)
|
|
162
229
|
// are merged in here so the model can actually call them.
|
|
163
|
-
mcpServers: mergePluginMcpServers(live.mcpServers ?? {}, live.
|
|
230
|
+
mcpServers: mergePluginMcpServers(live.mcpServers ?? {}, disabledPlugins, live.mcpServerOverrides ?? {}),
|
|
164
231
|
// Per-session overrides from the protocol request; fall back to
|
|
165
232
|
// settings.agent.* so the user's 个性化 settings actually apply
|
|
166
233
|
// (previously slice arrived with only permissionMode+cwd, so these
|
|
@@ -168,6 +235,10 @@ const chatManager = new ChatSessionManager({
|
|
|
168
235
|
// `live` is re-read from disk per session, so settings edits take effect
|
|
169
236
|
// on the next session without a worker restart.
|
|
170
237
|
permissionMode: slice.permissionMode,
|
|
238
|
+
// Workspace trust for this project (asserted by desktop main's
|
|
239
|
+
// trust-store, threaded via RunParams). When false, core strips the
|
|
240
|
+
// project's dangerous .code-shell settings (permissions/env/hooks/…).
|
|
241
|
+
projectTrusted: slice.projectTrusted,
|
|
171
242
|
...resolveSessionAgentConfig(slice, live),
|
|
172
243
|
// Personalization + instruction compat come from disk settings only
|
|
173
244
|
// (not per-request protocol overrides), so they read straight from
|
|
@@ -176,7 +247,7 @@ const chatManager = new ChatSessionManager({
|
|
|
176
247
|
...personalizationFrom(live.agent),
|
|
177
248
|
maxTurns: slice.maxTurns,
|
|
178
249
|
maxContextTokens: slice.maxContextTokens,
|
|
179
|
-
|
|
250
|
+
// cwd already set to sessionCwd above (slice.cwd → no-repo fallback).
|
|
180
251
|
});
|
|
181
252
|
},
|
|
182
253
|
maxSessions: 16,
|
|
@@ -199,6 +270,19 @@ cronScheduler.setExecutionEnabled(false);
|
|
|
199
270
|
cronScheduler.loadJobs();
|
|
200
271
|
// ─── Step 5: AgentServer over stdio ──────────────────────────────
|
|
201
272
|
const stdioTransport = new StdioTransport(process.stdin, process.stdout);
|
|
273
|
+
// Cron jobs are persisted by this worker but only main arms/executes their
|
|
274
|
+
// timers (this worker keeps setExecutionEnabled(false) above). When an AI tool
|
|
275
|
+
// creates/deletes a cron job here, notify main over stdio so it reloads the
|
|
276
|
+
// store and (re)arms its scheduler — otherwise AI-created jobs never fire.
|
|
277
|
+
setCronChangedSink(() => {
|
|
278
|
+
stdioTransport.send(createNotification("agent/cronChanged", {}));
|
|
279
|
+
});
|
|
280
|
+
// Disk-only active-goal reader for agent/goalGet. In worker mode there is no
|
|
281
|
+
// legacyEngine, and reopening a session after restart doesn't create a live
|
|
282
|
+
// ChatSession (that only happens on a send), so chatManager.get() misses. This
|
|
283
|
+
// reads the same ~/.code-shell/sessions/<id>/state.json every Engine writes, so
|
|
284
|
+
// the goal block re-surfaces on load ("goal 还在但页面不显示" fix).
|
|
285
|
+
const goalDiskReader = new SessionManager();
|
|
202
286
|
const agentServer = new AgentServer({
|
|
203
287
|
chatManager,
|
|
204
288
|
transport: stdioTransport,
|
|
@@ -206,11 +290,25 @@ const agentServer = new AgentServer({
|
|
|
206
290
|
// engineFactory uses for new sessions, so a reloaded running session and a
|
|
207
291
|
// newly-created session converge on identical disk config (no divergence).
|
|
208
292
|
settingsReader: freshSettings,
|
|
293
|
+
readActiveGoalFromDisk: (sessionId) => goalDiskReader.readActiveGoal(sessionId),
|
|
209
294
|
});
|
|
210
295
|
// Clean up on termination signals. Without this, SIGTERM (parent kill),
|
|
211
296
|
// SIGINT (Ctrl+C), or SIGHUP would drop the process without closing sessions,
|
|
212
297
|
// clearing the idle sweeper, or terminating child MCP/tool processes.
|
|
213
|
-
// AgentServer.close() also
|
|
298
|
+
// AgentServer.close() → chatManager.closeAll() also reaps background shells.
|
|
214
299
|
installGracefulShutdown(agentServer);
|
|
300
|
+
// Reap orphaned background shells left by a previously-crashed worker
|
|
301
|
+
// (design §难点1): a worker crash detaches its `npm run dev` children, which
|
|
302
|
+
// keep holding ports. Scanning pidfiles on boot lists still-alive groups as
|
|
303
|
+
// `orphaned` (so ListShells/KillShell can clean them) and deletes stale ones.
|
|
304
|
+
try {
|
|
305
|
+
const orphans = backgroundShellManager.reapOrphansFromPidfiles();
|
|
306
|
+
if (orphans.length > 0) {
|
|
307
|
+
logger.info("bg_shell.orphans_found", { count: orphans.length });
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
/* best-effort — never block worker startup on this */
|
|
312
|
+
}
|
|
215
313
|
// Keep the process alive — readline in StdioTransport holds the event loop.
|
|
216
314
|
// On parent close / stdin EOF the process will exit naturally.
|