@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,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map a raw error into a user-friendly message with a concrete next step
|
|
3
|
+
* (TODO 3.4). The turn loop emits `{type:"error", error}` to the UI; routing
|
|
4
|
+
* the message through here turns opaque provider strings ("401", "ETIMEDOUT")
|
|
5
|
+
* into something a user can act on. Pure + testable — no I/O.
|
|
6
|
+
*
|
|
7
|
+
* Heuristic, message-pattern based: we don't have typed error codes from every
|
|
8
|
+
* provider, so we match on substrings. Unknown errors pass through unchanged
|
|
9
|
+
* (with no suggestion) rather than getting a misleading canned message.
|
|
10
|
+
*/
|
|
11
|
+
export interface FriendlyError {
|
|
12
|
+
message: string;
|
|
13
|
+
/** A concrete next step, or undefined when we have nothing useful to add. */
|
|
14
|
+
suggestion?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function friendlyError(err: unknown): FriendlyError;
|
|
17
|
+
/** One-line form: "<message> (<suggestion>)" — what the UI shows. */
|
|
18
|
+
export declare function formatFriendlyError(err: unknown): string;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map a raw error into a user-friendly message with a concrete next step
|
|
3
|
+
* (TODO 3.4). The turn loop emits `{type:"error", error}` to the UI; routing
|
|
4
|
+
* the message through here turns opaque provider strings ("401", "ETIMEDOUT")
|
|
5
|
+
* into something a user can act on. Pure + testable — no I/O.
|
|
6
|
+
*
|
|
7
|
+
* Heuristic, message-pattern based: we don't have typed error codes from every
|
|
8
|
+
* provider, so we match on substrings. Unknown errors pass through unchanged
|
|
9
|
+
* (with no suggestion) rather than getting a misleading canned message.
|
|
10
|
+
*/
|
|
11
|
+
const RULES = [
|
|
12
|
+
{
|
|
13
|
+
test: /\b(401|403|invalid api key|unauthorized|authentication)\b/i,
|
|
14
|
+
message: () => "Authentication failed — the API key was rejected.",
|
|
15
|
+
suggestion: "Check the provider's apiKey/authCommand in settings (run /login to reconfigure), then retry.",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
test: /\b(429|rate.?limit|too many requests)\b/i,
|
|
19
|
+
message: () => "Rate limited by the provider.",
|
|
20
|
+
suggestion: "Wait a moment and retry, lower request frequency, or switch to a model with more headroom.",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
test: /\b(timed? ?out|etimedout|esockettimedout|deadline)\b/i,
|
|
24
|
+
suggestion: "The request timed out — check your network, then retry. Long tasks can run in the background (run_in_background).",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
test: /\b(econnrefused|enotfound|econnreset|network|fetch failed|socket hang up)\b/i,
|
|
28
|
+
message: () => "Network error reaching the provider.",
|
|
29
|
+
suggestion: "Check your connection and the provider baseUrl, then retry. Transient errors are retried automatically.",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
test: /\bcontext (limit|length|window)|too long|maximum context|prompt is too long\b/i,
|
|
33
|
+
message: () => "The conversation exceeded the model's context window.",
|
|
34
|
+
suggestion: "Start a new session, /compact the history, or switch to a model with a larger context window.",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
test: /\b(insufficient_quota|quota|billing|payment|credit)\b/i,
|
|
38
|
+
message: () => "The provider rejected the request for billing/quota reasons.",
|
|
39
|
+
suggestion: "Check the provider account's quota/billing, then retry.",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
test: /\b(500|502|503|504|overloaded|server error|service unavailable)\b/i,
|
|
43
|
+
message: () => "The provider had a server-side error.",
|
|
44
|
+
suggestion: "This is usually transient — retry shortly, or switch models if it persists.",
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
export function friendlyError(err) {
|
|
48
|
+
const raw = err instanceof Error ? err.message : typeof err === "string" ? err : String(err);
|
|
49
|
+
for (const rule of RULES) {
|
|
50
|
+
if (rule.test.test(raw)) {
|
|
51
|
+
return {
|
|
52
|
+
message: rule.message ? rule.message(raw) : raw,
|
|
53
|
+
suggestion: rule.suggestion,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { message: raw };
|
|
58
|
+
}
|
|
59
|
+
/** One-line form: "<message> (<suggestion>)" — what the UI shows. */
|
|
60
|
+
export function formatFriendlyError(err) {
|
|
61
|
+
const f = friendlyError(err);
|
|
62
|
+
return f.suggestion ? `${f.message}\n→ ${f.suggestion}` : f.message;
|
|
63
|
+
}
|
package/dist/engine/goal.d.ts
CHANGED
|
@@ -17,13 +17,158 @@ export interface GoalConfig {
|
|
|
17
17
|
tokenBudget?: number;
|
|
18
18
|
/** Hard cap on wall-clock duration of the whole goal run, in ms. */
|
|
19
19
|
timeBudgetMs?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Hard cap on total turns for this goal run. When set, it overrides the
|
|
22
|
+
* engine's interactive maxTurns default — a goal ("keep going until done")
|
|
23
|
+
* routinely needs more turns than a single interactive prompt, and the
|
|
24
|
+
* stop-hook keeps re-blocking completion, so the 100-turn interactive
|
|
25
|
+
* ceiling would silently truncate a long unattended goal. Drops
|
|
26
|
+
* non-positive values to "no override". See GOAL_DEFAULT_MAX_TURNS for the
|
|
27
|
+
* fallback applied when a goal is active but no explicit cap was given.
|
|
28
|
+
*/
|
|
29
|
+
maxTurns?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Max CONSECUTIVE times the on_stop judge may block termination before the
|
|
32
|
+
* loop forces a stop (the "stuck on an unsatisfiable goal" backstop). A goal
|
|
33
|
+
* that keeps legitimately making progress resets this every accepted turn, so
|
|
34
|
+
* this only bites when the judge re-blocks over and over with no completion.
|
|
35
|
+
* Overrides GOAL_DEFAULT_MAX_STOP_BLOCKS. Non-positive values are dropped.
|
|
36
|
+
*/
|
|
37
|
+
maxStopBlocks?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Epoch-ms timestamp of when the user last set/replaced this goal. The judge
|
|
40
|
+
* uses it to anchor RELATIVE deadlines written in the objective ("做到3点"、
|
|
41
|
+
* "until 15:00"): a bare "3点" is read as the first such time AFTER the goal
|
|
42
|
+
* was set, not after "now" — otherwise, once the clock passes the deadline,
|
|
43
|
+
* the judge could misread it as tomorrow's 3点 and never stop. Stamped at the
|
|
44
|
+
* override site (Engine), preserved through normalize so a resumed/inherited
|
|
45
|
+
* goal keeps its original anchor. Absent for goals created before this field
|
|
46
|
+
* existed → the judge falls back to reasoning from current time alone.
|
|
47
|
+
*/
|
|
48
|
+
setAtMs?: number;
|
|
20
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Default consecutive-stop-block cap for goal runs. The real safety net for an
|
|
52
|
+
* unattended goal is the token/time budget + maxTurns; this cap only stops a
|
|
53
|
+
* goal that the judge keeps re-blocking with NO progress between blocks. 8 was
|
|
54
|
+
* too tight — a complex goal can legitimately get "not yet" several times while
|
|
55
|
+
* advancing — so we allow a generous streak before declaring it stuck.
|
|
56
|
+
*/
|
|
57
|
+
export declare const GOAL_DEFAULT_MAX_STOP_BLOCKS = 25;
|
|
58
|
+
/**
|
|
59
|
+
* Consecutive-stop-block cap for NON-goal (interactive) runs. The 25-block goal
|
|
60
|
+
* default is far too loose for a plain interactive session: a plugin `on_stop`
|
|
61
|
+
* hook that keeps returning continueSession would loop 25× before the backstop
|
|
62
|
+
* bites (~3× the model calls). Non-goal runs fall back to this tighter cap —
|
|
63
|
+
* the historical value before the goal feature raised the goal-mode default.
|
|
64
|
+
*/
|
|
65
|
+
export declare const INTERACTIVE_DEFAULT_MAX_STOP_BLOCKS = 8;
|
|
66
|
+
/**
|
|
67
|
+
* Resolve the consecutive-stop-block cap for a run. Precedence:
|
|
68
|
+
* 1. explicit `configMaxStopBlocks` (engine/caller override)
|
|
69
|
+
* 2. `goal.maxStopBlocks`
|
|
70
|
+
* 3. GOAL_DEFAULT_MAX_STOP_BLOCKS when a goal is active,
|
|
71
|
+
* INTERACTIVE_DEFAULT_MAX_STOP_BLOCKS otherwise (don't leak the loose goal
|
|
72
|
+
* cap onto plain interactive runs — see B3 in the redesign doc).
|
|
73
|
+
* Pure + injectable so engine and tests agree.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveMaxStopBlocks(configMaxStopBlocks: number | undefined, goal: GoalConfig | undefined): number;
|
|
76
|
+
/**
|
|
77
|
+
* Turn ceiling applied when a goal is active and neither the GoalConfig nor
|
|
78
|
+
* the engine config set an explicit maxTurns. Higher than the interactive
|
|
79
|
+
* default (100) because goal runs are unattended and the stop-hook keeps the
|
|
80
|
+
* loop going until the objective is met — the real safety backstops are the
|
|
81
|
+
* token/time budgets and maxStopBlocks, not this ceiling. A round 300 gives
|
|
82
|
+
* long goals room while still bounding a pathological loop.
|
|
83
|
+
*/
|
|
84
|
+
export declare const GOAL_DEFAULT_MAX_TURNS = 300;
|
|
21
85
|
/**
|
|
22
86
|
* Coerce a raw goal input into a normalized GoalConfig, or undefined when
|
|
23
87
|
* there is effectively no goal (empty objective). Drops non-positive
|
|
24
88
|
* budgets (0 / negative → "no limit", same as absent).
|
|
25
89
|
*/
|
|
26
90
|
export declare function normalizeGoal(raw: string | GoalConfig | undefined): GoalConfig | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* Decide the goal-set timestamp (setAtMs) to persist when a send supplies an
|
|
93
|
+
* explicit goal. Pure so the engine and tests agree on the anchor rule:
|
|
94
|
+
* - A NEW or CHANGED objective → stamp `nowMs` (the user just expressed a
|
|
95
|
+
* fresh goal; any relative deadline in it is anchored to now).
|
|
96
|
+
* - The SAME objective continuing (bare re-send inheriting the stored goal) →
|
|
97
|
+
* keep the stored anchor, so a re-send doesn't silently move the deadline.
|
|
98
|
+
* Falls back to `nowMs` if the stored goal predates this field.
|
|
99
|
+
* `stored` is the previously-persisted active goal (or undefined for the first
|
|
100
|
+
* goal on a session).
|
|
101
|
+
*/
|
|
102
|
+
export declare function resolveGoalSetAt(explicitObjective: string, stored: GoalConfig | undefined, nowMs: number): number;
|
|
103
|
+
/** Interactive (no-goal) turn ceiling — a single prompt rarely needs more. */
|
|
104
|
+
export declare const INTERACTIVE_DEFAULT_MAX_TURNS = 100;
|
|
105
|
+
/**
|
|
106
|
+
* Resolve the turn ceiling for a run, honoring goal mode.
|
|
107
|
+
*
|
|
108
|
+
* Precedence (first defined wins):
|
|
109
|
+
* 1. `configMaxTurns` — an explicit engine/caller override (always wins).
|
|
110
|
+
* 2. `goal.maxTurns` — a per-goal cap from the GoalConfig.
|
|
111
|
+
* 3. `GOAL_DEFAULT_MAX_TURNS` when a goal is active (raise the unattended
|
|
112
|
+
* ceiling above the interactive default).
|
|
113
|
+
* 4. `INTERACTIVE_DEFAULT_MAX_TURNS` otherwise.
|
|
114
|
+
*
|
|
115
|
+
* Pure + injectable so the engine and tests agree on the rule.
|
|
116
|
+
*/
|
|
117
|
+
export declare function resolveMaxTurns(configMaxTurns: number | undefined, goal: GoalConfig | undefined): number;
|
|
118
|
+
/**
|
|
119
|
+
* How close a run is to either hard ceiling. Both maxTurns and maxStopBlocks
|
|
120
|
+
* can force-stop a goal, so the "approaching limit" UX must watch BOTH — the
|
|
121
|
+
* old code only watched maxTurns (turnsRemaining===2), but a re-blocked goal
|
|
122
|
+
* almost always hits the much-tighter stop-block cap first. See redesign doc.
|
|
123
|
+
*/
|
|
124
|
+
export interface LimitProximity {
|
|
125
|
+
/** Turns left before maxTurns (maxTurns - turnCount). */
|
|
126
|
+
turnsRemaining: number;
|
|
127
|
+
/** Consecutive blocks left before maxStopBlocks (cap - stopBlockCount). */
|
|
128
|
+
stopBlocksRemaining: number;
|
|
129
|
+
/** True when EITHER dimension is within its approach threshold. */
|
|
130
|
+
approaching: boolean;
|
|
131
|
+
/** Which ceiling is closest — drives UI copy and the extend default. */
|
|
132
|
+
nearest: "turns" | "stopBlocks";
|
|
133
|
+
}
|
|
134
|
+
/** Warn when this many turns (or fewer) remain before maxTurns. */
|
|
135
|
+
export declare const APPROACH_TURNS = 5;
|
|
136
|
+
/** Warn when this many consecutive blocks (or fewer) remain before the cap. */
|
|
137
|
+
export declare const APPROACH_STOP_BLOCKS = 3;
|
|
138
|
+
/**
|
|
139
|
+
* Compute proximity to the two stop ceilings. Pure so turn-loop and tests
|
|
140
|
+
* agree. `nearest` favors stopBlocks when both are near, because the stop-block
|
|
141
|
+
* cap (default 25) is reached far sooner than maxTurns (default 300) for a
|
|
142
|
+
* goal the judge keeps re-blocking — it's the limit that actually bites.
|
|
143
|
+
*/
|
|
144
|
+
export declare function limitProximity(turnCount: number, maxTurns: number, stopBlockCount: number, maxStopBlocks: number): LimitProximity;
|
|
145
|
+
export interface GoalExtension {
|
|
146
|
+
addTurns?: number;
|
|
147
|
+
addTokenBudget?: number;
|
|
148
|
+
addTimeBudgetMs?: number;
|
|
149
|
+
/** Raise the consecutive-stop-block cap — the limit that usually bites. */
|
|
150
|
+
addStopBlocks?: number;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Compute the new turn ceiling + goal budgets after a mid-run extension
|
|
154
|
+
* (TODO 3.1). Pure so the TurnLoop and tests agree on the arithmetic:
|
|
155
|
+
* - addTurns bumps the maxTurns ceiling (floored, positive only).
|
|
156
|
+
* - addTokenBudget/addTimeBudgetMs raise the goal's caps. When a cap was
|
|
157
|
+
* previously unset ("unlimited"), we seed it from CURRENT USAGE before
|
|
158
|
+
* adding — `tokensUsed` for tokens, `elapsedMs` for time — so extending an
|
|
159
|
+
* unbounded run sets a fresh cap ABOVE current usage rather than below it.
|
|
160
|
+
* (Seeding time from 0 was bug B1: a long-running unbounded goal would get
|
|
161
|
+
* a cap below its elapsed time and force-stop immediately on extend.)
|
|
162
|
+
* - addStopBlocks is handled by the caller (TurnLoop.extend) against the live
|
|
163
|
+
* config, not here, since it isn't part of the goal budget shape.
|
|
164
|
+
* `tokensUsed`/`elapsedMs` are the tracker's running totals (used only to seed
|
|
165
|
+
* unset caps). Returns the resulting limits; never mutates its inputs.
|
|
166
|
+
*/
|
|
167
|
+
export declare function applyGoalExtension(currentMaxTurns: number, goal: GoalConfig | undefined, tokensUsed: number, elapsedMs: number, ext: GoalExtension): {
|
|
168
|
+
maxTurns: number;
|
|
169
|
+
tokenBudget?: number;
|
|
170
|
+
timeBudgetMs?: number;
|
|
171
|
+
};
|
|
27
172
|
export interface GoalBudgetTracker {
|
|
28
173
|
goal: GoalConfig;
|
|
29
174
|
/** Wall-clock start (ms epoch), captured at run start. */
|
package/dist/engine/goal.js
CHANGED
|
@@ -10,6 +10,48 @@
|
|
|
10
10
|
* it accumulates total tokens across turns and stamps a wall-clock start,
|
|
11
11
|
* so an unattended goal run can't burn tokens or wall time without bound.
|
|
12
12
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* Default consecutive-stop-block cap for goal runs. The real safety net for an
|
|
15
|
+
* unattended goal is the token/time budget + maxTurns; this cap only stops a
|
|
16
|
+
* goal that the judge keeps re-blocking with NO progress between blocks. 8 was
|
|
17
|
+
* too tight — a complex goal can legitimately get "not yet" several times while
|
|
18
|
+
* advancing — so we allow a generous streak before declaring it stuck.
|
|
19
|
+
*/
|
|
20
|
+
export const GOAL_DEFAULT_MAX_STOP_BLOCKS = 25;
|
|
21
|
+
/**
|
|
22
|
+
* Consecutive-stop-block cap for NON-goal (interactive) runs. The 25-block goal
|
|
23
|
+
* default is far too loose for a plain interactive session: a plugin `on_stop`
|
|
24
|
+
* hook that keeps returning continueSession would loop 25× before the backstop
|
|
25
|
+
* bites (~3× the model calls). Non-goal runs fall back to this tighter cap —
|
|
26
|
+
* the historical value before the goal feature raised the goal-mode default.
|
|
27
|
+
*/
|
|
28
|
+
export const INTERACTIVE_DEFAULT_MAX_STOP_BLOCKS = 8;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the consecutive-stop-block cap for a run. Precedence:
|
|
31
|
+
* 1. explicit `configMaxStopBlocks` (engine/caller override)
|
|
32
|
+
* 2. `goal.maxStopBlocks`
|
|
33
|
+
* 3. GOAL_DEFAULT_MAX_STOP_BLOCKS when a goal is active,
|
|
34
|
+
* INTERACTIVE_DEFAULT_MAX_STOP_BLOCKS otherwise (don't leak the loose goal
|
|
35
|
+
* cap onto plain interactive runs — see B3 in the redesign doc).
|
|
36
|
+
* Pure + injectable so engine and tests agree.
|
|
37
|
+
*/
|
|
38
|
+
export function resolveMaxStopBlocks(configMaxStopBlocks, goal) {
|
|
39
|
+
if (typeof configMaxStopBlocks === "number" && configMaxStopBlocks > 0) {
|
|
40
|
+
return Math.floor(configMaxStopBlocks);
|
|
41
|
+
}
|
|
42
|
+
if (goal?.maxStopBlocks && goal.maxStopBlocks > 0)
|
|
43
|
+
return goal.maxStopBlocks;
|
|
44
|
+
return goal ? GOAL_DEFAULT_MAX_STOP_BLOCKS : INTERACTIVE_DEFAULT_MAX_STOP_BLOCKS;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Turn ceiling applied when a goal is active and neither the GoalConfig nor
|
|
48
|
+
* the engine config set an explicit maxTurns. Higher than the interactive
|
|
49
|
+
* default (100) because goal runs are unattended and the stop-hook keeps the
|
|
50
|
+
* loop going until the objective is met — the real safety backstops are the
|
|
51
|
+
* token/time budgets and maxStopBlocks, not this ceiling. A round 300 gives
|
|
52
|
+
* long goals room while still bounding a pathological loop.
|
|
53
|
+
*/
|
|
54
|
+
export const GOAL_DEFAULT_MAX_TURNS = 300;
|
|
13
55
|
/**
|
|
14
56
|
* Coerce a raw goal input into a normalized GoalConfig, or undefined when
|
|
15
57
|
* there is effectively no goal (empty objective). Drops non-positive
|
|
@@ -27,8 +69,110 @@ export function normalizeGoal(raw) {
|
|
|
27
69
|
out.tokenBudget = obj.tokenBudget;
|
|
28
70
|
if (typeof obj.timeBudgetMs === "number" && obj.timeBudgetMs > 0)
|
|
29
71
|
out.timeBudgetMs = obj.timeBudgetMs;
|
|
72
|
+
if (typeof obj.maxTurns === "number" && obj.maxTurns > 0)
|
|
73
|
+
out.maxTurns = Math.floor(obj.maxTurns);
|
|
74
|
+
if (typeof obj.maxStopBlocks === "number" && obj.maxStopBlocks > 0) {
|
|
75
|
+
out.maxStopBlocks = Math.floor(obj.maxStopBlocks);
|
|
76
|
+
}
|
|
77
|
+
// Preserve a positive goal-set timestamp so a resumed/inherited goal keeps
|
|
78
|
+
// the anchor for relative deadlines. Non-positive/junk is dropped, matching
|
|
79
|
+
// the budget fields (a bogus anchor is worse than none).
|
|
80
|
+
if (typeof obj.setAtMs === "number" && obj.setAtMs > 0)
|
|
81
|
+
out.setAtMs = obj.setAtMs;
|
|
30
82
|
return out;
|
|
31
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Decide the goal-set timestamp (setAtMs) to persist when a send supplies an
|
|
86
|
+
* explicit goal. Pure so the engine and tests agree on the anchor rule:
|
|
87
|
+
* - A NEW or CHANGED objective → stamp `nowMs` (the user just expressed a
|
|
88
|
+
* fresh goal; any relative deadline in it is anchored to now).
|
|
89
|
+
* - The SAME objective continuing (bare re-send inheriting the stored goal) →
|
|
90
|
+
* keep the stored anchor, so a re-send doesn't silently move the deadline.
|
|
91
|
+
* Falls back to `nowMs` if the stored goal predates this field.
|
|
92
|
+
* `stored` is the previously-persisted active goal (or undefined for the first
|
|
93
|
+
* goal on a session).
|
|
94
|
+
*/
|
|
95
|
+
export function resolveGoalSetAt(explicitObjective, stored, nowMs) {
|
|
96
|
+
const sameGoalContinues = !!stored && stored.objective === explicitObjective;
|
|
97
|
+
return sameGoalContinues ? stored.setAtMs ?? nowMs : nowMs;
|
|
98
|
+
}
|
|
99
|
+
/** Interactive (no-goal) turn ceiling — a single prompt rarely needs more. */
|
|
100
|
+
export const INTERACTIVE_DEFAULT_MAX_TURNS = 100;
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the turn ceiling for a run, honoring goal mode.
|
|
103
|
+
*
|
|
104
|
+
* Precedence (first defined wins):
|
|
105
|
+
* 1. `configMaxTurns` — an explicit engine/caller override (always wins).
|
|
106
|
+
* 2. `goal.maxTurns` — a per-goal cap from the GoalConfig.
|
|
107
|
+
* 3. `GOAL_DEFAULT_MAX_TURNS` when a goal is active (raise the unattended
|
|
108
|
+
* ceiling above the interactive default).
|
|
109
|
+
* 4. `INTERACTIVE_DEFAULT_MAX_TURNS` otherwise.
|
|
110
|
+
*
|
|
111
|
+
* Pure + injectable so the engine and tests agree on the rule.
|
|
112
|
+
*/
|
|
113
|
+
export function resolveMaxTurns(configMaxTurns, goal) {
|
|
114
|
+
if (typeof configMaxTurns === "number" && configMaxTurns > 0)
|
|
115
|
+
return configMaxTurns;
|
|
116
|
+
if (goal)
|
|
117
|
+
return goal.maxTurns ?? GOAL_DEFAULT_MAX_TURNS;
|
|
118
|
+
return INTERACTIVE_DEFAULT_MAX_TURNS;
|
|
119
|
+
}
|
|
120
|
+
/** Warn when this many turns (or fewer) remain before maxTurns. */
|
|
121
|
+
export const APPROACH_TURNS = 5;
|
|
122
|
+
/** Warn when this many consecutive blocks (or fewer) remain before the cap. */
|
|
123
|
+
export const APPROACH_STOP_BLOCKS = 3;
|
|
124
|
+
/**
|
|
125
|
+
* Compute proximity to the two stop ceilings. Pure so turn-loop and tests
|
|
126
|
+
* agree. `nearest` favors stopBlocks when both are near, because the stop-block
|
|
127
|
+
* cap (default 25) is reached far sooner than maxTurns (default 300) for a
|
|
128
|
+
* goal the judge keeps re-blocking — it's the limit that actually bites.
|
|
129
|
+
*/
|
|
130
|
+
export function limitProximity(turnCount, maxTurns, stopBlockCount, maxStopBlocks) {
|
|
131
|
+
const turnsRemaining = maxTurns - turnCount;
|
|
132
|
+
const stopBlocksRemaining = maxStopBlocks - stopBlockCount;
|
|
133
|
+
const turnsNear = turnsRemaining <= APPROACH_TURNS;
|
|
134
|
+
const blocksNear = stopBlocksRemaining <= APPROACH_STOP_BLOCKS;
|
|
135
|
+
return {
|
|
136
|
+
turnsRemaining,
|
|
137
|
+
stopBlocksRemaining,
|
|
138
|
+
approaching: turnsNear || blocksNear,
|
|
139
|
+
nearest: blocksNear ? "stopBlocks" : "turns",
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Compute the new turn ceiling + goal budgets after a mid-run extension
|
|
144
|
+
* (TODO 3.1). Pure so the TurnLoop and tests agree on the arithmetic:
|
|
145
|
+
* - addTurns bumps the maxTurns ceiling (floored, positive only).
|
|
146
|
+
* - addTokenBudget/addTimeBudgetMs raise the goal's caps. When a cap was
|
|
147
|
+
* previously unset ("unlimited"), we seed it from CURRENT USAGE before
|
|
148
|
+
* adding — `tokensUsed` for tokens, `elapsedMs` for time — so extending an
|
|
149
|
+
* unbounded run sets a fresh cap ABOVE current usage rather than below it.
|
|
150
|
+
* (Seeding time from 0 was bug B1: a long-running unbounded goal would get
|
|
151
|
+
* a cap below its elapsed time and force-stop immediately on extend.)
|
|
152
|
+
* - addStopBlocks is handled by the caller (TurnLoop.extend) against the live
|
|
153
|
+
* config, not here, since it isn't part of the goal budget shape.
|
|
154
|
+
* `tokensUsed`/`elapsedMs` are the tracker's running totals (used only to seed
|
|
155
|
+
* unset caps). Returns the resulting limits; never mutates its inputs.
|
|
156
|
+
*/
|
|
157
|
+
export function applyGoalExtension(currentMaxTurns, goal, tokensUsed, elapsedMs, ext) {
|
|
158
|
+
let maxTurns = currentMaxTurns;
|
|
159
|
+
if (typeof ext.addTurns === "number" && ext.addTurns > 0) {
|
|
160
|
+
maxTurns += Math.floor(ext.addTurns);
|
|
161
|
+
}
|
|
162
|
+
let tokenBudget = goal?.tokenBudget;
|
|
163
|
+
let timeBudgetMs = goal?.timeBudgetMs;
|
|
164
|
+
if (goal) {
|
|
165
|
+
if (typeof ext.addTokenBudget === "number" && ext.addTokenBudget > 0) {
|
|
166
|
+
tokenBudget = (tokenBudget ?? tokensUsed) + Math.floor(ext.addTokenBudget);
|
|
167
|
+
}
|
|
168
|
+
if (typeof ext.addTimeBudgetMs === "number" && ext.addTimeBudgetMs > 0) {
|
|
169
|
+
// Seed an unset cap from elapsed time (not 0), mirroring the token branch,
|
|
170
|
+
// so the new cap lands above current usage. Fixes B1.
|
|
171
|
+
timeBudgetMs = (timeBudgetMs ?? elapsedMs) + Math.floor(ext.addTimeBudgetMs);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { maxTurns, tokenBudget, timeBudgetMs };
|
|
175
|
+
}
|
|
32
176
|
/** Create a run-scoped tracker. `nowMs` is injected for testability. */
|
|
33
177
|
export function createGoalBudgetTracker(goal, nowMs) {
|
|
34
178
|
return { goal, startedAtMs: nowMs, tokensUsed: 0 };
|
|
@@ -150,3 +150,16 @@ export interface DropOversizedResult {
|
|
|
150
150
|
}
|
|
151
151
|
export declare function dropOversizedImages(images: readonly ParsedImage[]): DropOversizedResult;
|
|
152
152
|
export declare function enforceImagePolicy(images: readonly ParsedImage[]): ImagePolicyVerdict;
|
|
153
|
+
/**
|
|
154
|
+
* Collect the workspace file paths of any attached images that came from a
|
|
155
|
+
* real file (the desktop composer's path-attach flow sets ParsedImage.name to
|
|
156
|
+
* the absolute path). Pasted screenshots whose name is a bare filename, or any
|
|
157
|
+
* name that doesn't resolve to an existing file, are excluded.
|
|
158
|
+
*
|
|
159
|
+
* Pure given an `exists` predicate + `resolve` joiner, so it's unit-testable
|
|
160
|
+
* without touching the real fs. The engine passes `existsSync` and a
|
|
161
|
+
* cwd-aware joiner; downstream this surfaces the paths to the model so tools
|
|
162
|
+
* like GenerateImage(referenceImages) can use them (the path the composer
|
|
163
|
+
* already knew was otherwise dropped on the way to the LLM).
|
|
164
|
+
*/
|
|
165
|
+
export declare function collectAttachedImagePaths(images: ReadonlyArray<ParsedImage>, resolve: (name: string) => string, exists: (absPath: string) => boolean): string[];
|
|
@@ -163,3 +163,27 @@ export function enforceImagePolicy(images) {
|
|
|
163
163
|
}
|
|
164
164
|
return { ok: true };
|
|
165
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Collect the workspace file paths of any attached images that came from a
|
|
168
|
+
* real file (the desktop composer's path-attach flow sets ParsedImage.name to
|
|
169
|
+
* the absolute path). Pasted screenshots whose name is a bare filename, or any
|
|
170
|
+
* name that doesn't resolve to an existing file, are excluded.
|
|
171
|
+
*
|
|
172
|
+
* Pure given an `exists` predicate + `resolve` joiner, so it's unit-testable
|
|
173
|
+
* without touching the real fs. The engine passes `existsSync` and a
|
|
174
|
+
* cwd-aware joiner; downstream this surfaces the paths to the model so tools
|
|
175
|
+
* like GenerateImage(referenceImages) can use them (the path the composer
|
|
176
|
+
* already knew was otherwise dropped on the way to the LLM).
|
|
177
|
+
*/
|
|
178
|
+
export function collectAttachedImagePaths(images, resolve, exists) {
|
|
179
|
+
const out = [];
|
|
180
|
+
for (const img of images) {
|
|
181
|
+
const name = img.name?.trim();
|
|
182
|
+
if (!name)
|
|
183
|
+
continue;
|
|
184
|
+
const abs = resolve(name);
|
|
185
|
+
if (exists(abs))
|
|
186
|
+
out.push(abs);
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge settings.modelConnections[] (the unified instance store) into
|
|
3
|
+
* ModelPool entries, so the engine sends requests using the new catalog-driven
|
|
4
|
+
* model selection. Pure: instances + catalog → ModelEntry[]. apiKeyRef is
|
|
5
|
+
* dereferenced via resolveInstance; protocol/baseUrl/token limits come from the
|
|
6
|
+
* resolved catalog entry + preset.
|
|
7
|
+
* See docs/superpowers/specs/2026-06-15-unified-model-catalog-design.md §6.
|
|
8
|
+
*/
|
|
9
|
+
import { type ModelInstance, type Credential } from "../model-catalog/resolve.js";
|
|
10
|
+
import type { CatalogEntry } from "../model-catalog/index.js";
|
|
11
|
+
import type { ModelEntry } from "../llm/model-pool.js";
|
|
12
|
+
export type { ModelInstance, Credential } from "../model-catalog/resolve.js";
|
|
13
|
+
/**
|
|
14
|
+
* Convert text model connections into ModelPool register entries. Non-text
|
|
15
|
+
* instances and ones whose catalogId doesn't resolve are skipped.
|
|
16
|
+
*/
|
|
17
|
+
export declare function modelEntriesFromConnections(connections: ModelInstance[], credentials: Credential[], catalog: CatalogEntry[]): ModelEntry[];
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge settings.modelConnections[] (the unified instance store) into
|
|
3
|
+
* ModelPool entries, so the engine sends requests using the new catalog-driven
|
|
4
|
+
* model selection. Pure: instances + catalog → ModelEntry[]. apiKeyRef is
|
|
5
|
+
* dereferenced via resolveInstance; protocol/baseUrl/token limits come from the
|
|
6
|
+
* resolved catalog entry + preset.
|
|
7
|
+
* See docs/superpowers/specs/2026-06-15-unified-model-catalog-design.md §6.
|
|
8
|
+
*/
|
|
9
|
+
import { resolveInstance } from "../model-catalog/resolve.js";
|
|
10
|
+
import { applyParams } from "../model-catalog/params.js";
|
|
11
|
+
/** Map a catalog entry's protocol to the ModelPool `provider` (LLM client). */
|
|
12
|
+
function clientProvider(entry) {
|
|
13
|
+
return entry.protocol === "anthropic-style" ? "anthropic" : "openai";
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Map a connection's `paramValues.reasoning` to the engine's ReasoningSetting,
|
|
17
|
+
* so the new param store flows through the existing reasoning request pipeline
|
|
18
|
+
* (no new request-layer plumbing). enum→effort, number→budget, boolean→on/off.
|
|
19
|
+
*/
|
|
20
|
+
function reasoningFromParamValues(paramValues) {
|
|
21
|
+
const v = paramValues?.reasoning;
|
|
22
|
+
if (typeof v === "string") {
|
|
23
|
+
return { mode: "effort", effort: v };
|
|
24
|
+
}
|
|
25
|
+
if (typeof v === "number")
|
|
26
|
+
return { mode: "budget", budgetTokens: v };
|
|
27
|
+
if (typeof v === "boolean")
|
|
28
|
+
return v ? { mode: "on" } : { mode: "off" };
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Convert text model connections into ModelPool register entries. Non-text
|
|
33
|
+
* instances and ones whose catalogId doesn't resolve are skipped.
|
|
34
|
+
*/
|
|
35
|
+
export function modelEntriesFromConnections(connections, credentials, catalog) {
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const inst of connections) {
|
|
38
|
+
if (inst.tag !== "text")
|
|
39
|
+
continue;
|
|
40
|
+
const resolved = resolveInstance(inst, credentials, catalog);
|
|
41
|
+
if (!resolved)
|
|
42
|
+
continue;
|
|
43
|
+
const { entry, preset } = resolved;
|
|
44
|
+
const reasoning = reasoningFromParamValues(inst.paramValues);
|
|
45
|
+
// Generic param passthrough: map the connection's paramValues to request-body
|
|
46
|
+
// fields via each ParamSpec's wire.field. Exclude `reasoning` — it rides the
|
|
47
|
+
// dedicated entry.reasoning path (per-model dynamic translation in the client);
|
|
48
|
+
// emitting it here too would double-send it.
|
|
49
|
+
const passthroughParams = (preset?.params ?? []).filter((p) => p.name !== "reasoning");
|
|
50
|
+
const extraBody = applyParams(inst.paramValues ?? {}, passthroughParams);
|
|
51
|
+
const e = {
|
|
52
|
+
key: inst.id,
|
|
53
|
+
provider: clientProvider(entry),
|
|
54
|
+
model: resolved.model,
|
|
55
|
+
baseUrl: resolved.baseUrl,
|
|
56
|
+
needsKey: resolved.needsKey,
|
|
57
|
+
...(resolved.apiKey !== undefined ? { apiKey: resolved.apiKey } : {}),
|
|
58
|
+
...(preset?.maxContextTokens !== undefined ? { maxContextTokens: preset.maxContextTokens } : {}),
|
|
59
|
+
...(preset?.maxOutputTokens !== undefined ? { maxOutputTokens: preset.maxOutputTokens } : {}),
|
|
60
|
+
...(reasoning !== undefined ? { reasoning } : {}),
|
|
61
|
+
...(Object.keys(extraBody).length > 0 ? { extraBody } : {}),
|
|
62
|
+
};
|
|
63
|
+
out.push(e);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
import type { LLMClientBase } from "../llm/client-base.js";
|
|
5
5
|
import type { Message, ToolDefinition, LLMResponse, StreamCallback } from "../types.js";
|
|
6
6
|
import { Transcript } from "../session/transcript.js";
|
|
7
|
+
/**
|
|
8
|
+
* Prompt-cache hit rate for one request, as CC computes it:
|
|
9
|
+
* cacheRead / (cacheRead + cacheCreation + uncachedInput)
|
|
10
|
+
* where uncachedInput = promptTokens − cacheRead − cacheCreation (promptTokens
|
|
11
|
+
* already includes the cached portion). Returns undefined when the provider
|
|
12
|
+
* reported no cache info at all, so `llm.request` only carries the field when
|
|
13
|
+
* it's meaningful. Makes hit-rate observable in logs — the prerequisite for
|
|
14
|
+
* any caching tuning (docs/todo/prompt-cache-optimization.md §四 step 1).
|
|
15
|
+
*/
|
|
16
|
+
export declare function cacheHitRate(usage: LLMResponse["usage"]): number | undefined;
|
|
7
17
|
export declare class ModelFacade {
|
|
8
18
|
private readonly client;
|
|
9
19
|
private readonly transcript;
|
|
@@ -10,6 +10,29 @@ function nextReqId() {
|
|
|
10
10
|
_reqSeq += 1;
|
|
11
11
|
return `r${_reqSeq.toString(36)}`;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Prompt-cache hit rate for one request, as CC computes it:
|
|
15
|
+
* cacheRead / (cacheRead + cacheCreation + uncachedInput)
|
|
16
|
+
* where uncachedInput = promptTokens − cacheRead − cacheCreation (promptTokens
|
|
17
|
+
* already includes the cached portion). Returns undefined when the provider
|
|
18
|
+
* reported no cache info at all, so `llm.request` only carries the field when
|
|
19
|
+
* it's meaningful. Makes hit-rate observable in logs — the prerequisite for
|
|
20
|
+
* any caching tuning (docs/todo/prompt-cache-optimization.md §四 step 1).
|
|
21
|
+
*/
|
|
22
|
+
export function cacheHitRate(usage) {
|
|
23
|
+
if (!usage)
|
|
24
|
+
return undefined;
|
|
25
|
+
const read = usage.cacheReadTokens ?? 0;
|
|
26
|
+
const creation = usage.cacheCreationTokens ?? 0;
|
|
27
|
+
if (read === 0 && creation === 0)
|
|
28
|
+
return undefined;
|
|
29
|
+
const prompt = usage.promptTokens ?? 0;
|
|
30
|
+
const uncached = Math.max(0, prompt - read - creation);
|
|
31
|
+
const denom = read + creation + uncached;
|
|
32
|
+
if (denom === 0)
|
|
33
|
+
return undefined;
|
|
34
|
+
return read / denom;
|
|
35
|
+
}
|
|
13
36
|
export class ModelFacade {
|
|
14
37
|
client;
|
|
15
38
|
transcript;
|
|
@@ -82,6 +105,7 @@ export class ModelFacade {
|
|
|
82
105
|
toolCalls: response.toolCalls.length,
|
|
83
106
|
textLen: response.text?.length ?? 0,
|
|
84
107
|
usage: response.usage,
|
|
108
|
+
cacheHitRate: cacheHitRate(response.usage),
|
|
85
109
|
});
|
|
86
110
|
recordLLMResponse(sid, {
|
|
87
111
|
text: response.text,
|
|
@@ -135,6 +159,7 @@ export class ModelFacade {
|
|
|
135
159
|
toolCalls: response.toolCalls.length,
|
|
136
160
|
textLen: response.text?.length ?? 0,
|
|
137
161
|
usage: response.usage,
|
|
162
|
+
cacheHitRate: cacheHitRate(response.usage),
|
|
138
163
|
});
|
|
139
164
|
recordLLMResponse(sid, {
|
|
140
165
|
text: response.text,
|
|
@@ -79,6 +79,9 @@ export function patchOrphanedToolUses(messages) {
|
|
|
79
79
|
type: "tool_result",
|
|
80
80
|
tool_use_id: id,
|
|
81
81
|
content: SYNTHETIC_ERROR_TEXT,
|
|
82
|
+
// Flag as an error so the Anthropic provider marks it is_error; otherwise
|
|
83
|
+
// the model reads the synthetic result as a successful tool output.
|
|
84
|
+
is_error: true,
|
|
82
85
|
}));
|
|
83
86
|
messages.splice(i + 1, 0, { role: "user", content: errorBlocks });
|
|
84
87
|
// Mark these ids answered so a later assistant message referencing
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resolveLLMConfigForTag — 单一入口:settings + tag + 可选偏好实例 id → LLMConfig。
|
|
3
|
+
* 所有非-engine 的 seed 场景(agent-server bootstrap / automation / dream / TUI
|
|
4
|
+
* 命令)都调它,避免各自手搓 { provider: settings.model.provider, ... }。
|
|
5
|
+
*
|
|
6
|
+
* 复用 modelEntriesFromConnections + 临时 ModelPool.toLLMConfig,与 engine 内部
|
|
7
|
+
* 长生命周期 pool 共享同一批零件,不逻辑分叉。
|
|
8
|
+
*
|
|
9
|
+
* 选择优先级:preferredId(命中才用)→ defaults[tag] → 首个可用连接。
|
|
10
|
+
* 返回 null = 该 tag 下没有任何可用连接,调用方据此抛明确错误。
|
|
11
|
+
*
|
|
12
|
+
* 本次只处理 text;image/video 已有独立 resolver(resolveImageProvider 等)。
|
|
13
|
+
*/
|
|
14
|
+
import type { LLMConfig } from "../types.js";
|
|
15
|
+
import type { ValidatedSettings } from "../settings/schema.js";
|
|
16
|
+
export declare function resolveLLMConfigForTag(settings: ValidatedSettings, tag: "text", preferredInstanceId?: string): LLMConfig | null;
|