@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
package/dist/settings/manager.js
CHANGED
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
* Priority: CLI flags > local > project > user > managed
|
|
5
5
|
*/
|
|
6
6
|
import { readFileSync, existsSync, mkdirSync, writeFileSync, renameSync, copyFileSync, } from "node:fs";
|
|
7
|
-
import { join, dirname } from "node:path";
|
|
7
|
+
import { join, dirname, extname } from "node:path";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
|
+
import { parse as parseYaml } from "yaml";
|
|
9
10
|
import { validateSettings } from "./schema.js";
|
|
10
11
|
import { migrateModels } from "../migrate-models.js";
|
|
12
|
+
import { migrateConfig, CONFIG_VERSION_KEY } from "./migrate-config.js";
|
|
11
13
|
/**
|
|
12
14
|
* Resolve the user's home directory. Prefers `process.env.HOME` so that
|
|
13
15
|
* runtime env overrides (set after process start, e.g. in tests) actually
|
|
@@ -18,14 +20,92 @@ import { migrateModels } from "../migrate-models.js";
|
|
|
18
20
|
export function userHome() {
|
|
19
21
|
return process.env.HOME ?? homedir();
|
|
20
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Settings keys that form the trust/permission root and therefore must NOT be
|
|
25
|
+
* writable through the generic `config_set` / provider-agnostic write path.
|
|
26
|
+
* A protocol peer (external driver, paired phone, or a compromised renderer)
|
|
27
|
+
* can send arbitrary config writes; letting it set these would be equivalent to
|
|
28
|
+
* remotely disabling the workspace-trust / permission model:
|
|
29
|
+
* - permissions → self-authorize any tool (permissions.rules / defaultMode)
|
|
30
|
+
* - env / localEnvironment → inject BASH_ENV / NODE_OPTIONS / LD_PRELOAD / PATH
|
|
31
|
+
* - hooks → run arbitrary commands on tool events
|
|
32
|
+
* - mcpServers / mcpServerOverrides → point tools at attacker-controlled servers
|
|
33
|
+
* These are only meant to be changed by the local settings UI (a trusted write
|
|
34
|
+
* path) or by hand-editing the file, never by a generic remote config write.
|
|
35
|
+
*/
|
|
36
|
+
const PROTECTED_SETTING_ROOTS = new Set([
|
|
37
|
+
"permissions",
|
|
38
|
+
"env",
|
|
39
|
+
"localEnvironment",
|
|
40
|
+
"hooks",
|
|
41
|
+
"mcpServers",
|
|
42
|
+
"mcpServerOverrides",
|
|
43
|
+
]);
|
|
44
|
+
/**
|
|
45
|
+
* True if `key` (a dotted path like "permissions.rules" or just "env") targets a
|
|
46
|
+
* protected trust-root field. Matches on the FIRST segment so nested writes
|
|
47
|
+
* ("permissions.defaultMode", "env.FOO") are caught too.
|
|
48
|
+
*/
|
|
49
|
+
export function isProtectedSettingKey(key) {
|
|
50
|
+
const root = key.split(".")[0] ?? "";
|
|
51
|
+
return PROTECTED_SETTING_ROOTS.has(root);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The fixed cwd used for "no-repo" pure-chat conversations (a chat not bound to
|
|
55
|
+
* any code project). Same location as desktop's `resolveNoRepoCwd`
|
|
56
|
+
* (`join(homedir(), ".code-shell", "no-repo")`), but built from {@link userHome}
|
|
57
|
+
* so tests can isolate it by overriding `$HOME`. The engine compares
|
|
58
|
+
* `config.cwd === noRepoDir()` to flip skill/plugin filtering to whitelist mode.
|
|
59
|
+
*/
|
|
60
|
+
export function noRepoDir() {
|
|
61
|
+
return join(userHome(), ".code-shell", "no-repo");
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Top-level settings fields that can influence code execution and are therefore
|
|
65
|
+
* stripped from an UNTRUSTED project's disk layers (project + local) before
|
|
66
|
+
* merge. A repo commits these into its `.code-shell/settings.json`, so an
|
|
67
|
+
* untrusted clone must not have them take effect:
|
|
68
|
+
* - permissions — `rules` can self-authorize (allow Bash …); `defaultMode`
|
|
69
|
+
* can set bypassPermissions.
|
|
70
|
+
* - env — injected into Bash child env unfiltered (BASH_ENV,
|
|
71
|
+
* LD_PRELOAD, PATH → arbitrary code on next command).
|
|
72
|
+
* - localEnvironment — same, as the env floor.
|
|
73
|
+
* - hooks — arbitrary commands on lifecycle events.
|
|
74
|
+
* - mcpServers — auto-connect to attacker-controlled MCP servers.
|
|
75
|
+
* Only project-scoped layers are filtered; user/managed/flag are trusted.
|
|
76
|
+
* Mirrors Claude Code's TRUSTED_SETTING_SOURCES model (project sources excluded
|
|
77
|
+
* from dangerous env application until trust is granted).
|
|
78
|
+
*/
|
|
79
|
+
export const DANGEROUS_PROJECT_FIELDS = [
|
|
80
|
+
"permissions",
|
|
81
|
+
"env",
|
|
82
|
+
"localEnvironment",
|
|
83
|
+
"hooks",
|
|
84
|
+
"mcpServers",
|
|
85
|
+
];
|
|
21
86
|
export class SettingsManager {
|
|
22
87
|
cwd;
|
|
23
88
|
scope;
|
|
89
|
+
projectTrusted;
|
|
24
90
|
sources = [];
|
|
25
91
|
merged = null;
|
|
26
|
-
constructor(cwd = process.cwd(), scope = "project"
|
|
92
|
+
constructor(cwd = process.cwd(), scope = "project",
|
|
93
|
+
/**
|
|
94
|
+
* Workspace trust for the project directory. When false, dangerous fields
|
|
95
|
+
* committed into the project's own `.code-shell/settings.{json,local.json}`
|
|
96
|
+
* ({@link DANGEROUS_PROJECT_FIELDS}) are stripped before merge, so a cloned
|
|
97
|
+
* malicious repo can't self-authorize permission rules, inject `env`
|
|
98
|
+
* (BASH_ENV/LD_PRELOAD/…), register hooks, or connect MCP servers. Safe
|
|
99
|
+
* fields (model choice, UI prefs, …) still merge. The user/managed/flag
|
|
100
|
+
* layers are never gated — the user put those there deliberately.
|
|
101
|
+
*
|
|
102
|
+
* Defaults to `true` so existing embedders/tests keep their behavior; the
|
|
103
|
+
* host (desktop) passes the real trust decision from its trust-store.
|
|
104
|
+
*/
|
|
105
|
+
projectTrusted = true) {
|
|
27
106
|
this.cwd = cwd;
|
|
28
107
|
this.scope = scope;
|
|
108
|
+
this.projectTrusted = projectTrusted;
|
|
29
109
|
}
|
|
30
110
|
/**
|
|
31
111
|
* Load settings from all sources.
|
|
@@ -59,6 +139,33 @@ export class SettingsManager {
|
|
|
59
139
|
}
|
|
60
140
|
// Sort by priority ascending (merge in order, later wins)
|
|
61
141
|
this.sources.sort((a, b) => a.priority - b.priority);
|
|
142
|
+
// Version-based config migration (migrate-config.ts), applied per physical
|
|
143
|
+
// file (user + project settings.json) so the write-back lands in the file
|
|
144
|
+
// the data came from. A file is only rewritten (with a .bak) when a step
|
|
145
|
+
// actually changed its content — a version-stamp-only diff isn't worth
|
|
146
|
+
// dirtying the user's (or a repo-tracked project) file for; steps are
|
|
147
|
+
// idempotent, so re-running on unstamped files each load is fine.
|
|
148
|
+
if (readUser) {
|
|
149
|
+
this.applyConfigMigration(join(userHome(), ".code-shell", "settings.json"), "user");
|
|
150
|
+
}
|
|
151
|
+
if (readProject) {
|
|
152
|
+
this.applyConfigMigration(join(this.cwd, ".code-shell", "settings.json"), "project");
|
|
153
|
+
}
|
|
154
|
+
// Workspace-trust gate: an untrusted project must not influence execution
|
|
155
|
+
// through dangerous fields committed into its own .code-shell/settings.*.
|
|
156
|
+
// Strip them from the project/local layers (after migration re-read the
|
|
157
|
+
// files, so we filter the final data) before merge. See
|
|
158
|
+
// DANGEROUS_PROJECT_FIELDS and the `projectTrusted` ctor arg.
|
|
159
|
+
if (!this.projectTrusted) {
|
|
160
|
+
for (const source of this.sources) {
|
|
161
|
+
if (source.name !== "project" && source.name !== "local")
|
|
162
|
+
continue;
|
|
163
|
+
for (const field of DANGEROUS_PROJECT_FIELDS) {
|
|
164
|
+
if (field in source.data)
|
|
165
|
+
delete source.data[field];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
62
169
|
// Deep merge
|
|
63
170
|
const raw = this.deepMerge();
|
|
64
171
|
// Auto-migrate legacy models[] in the user settings file. Runs directly
|
|
@@ -81,7 +188,9 @@ export class SettingsManager {
|
|
|
81
188
|
providers: result.providers,
|
|
82
189
|
models: result.models,
|
|
83
190
|
};
|
|
84
|
-
|
|
191
|
+
// Atomic write (tmp+rename) — a concurrent load must not see a
|
|
192
|
+
// half-written file. File exists here (existsSync guard above).
|
|
193
|
+
this.atomicWriteJson(userPath, migrated);
|
|
85
194
|
// Re-deep-merge with the migrated user data so the validate
|
|
86
195
|
// call sees the new shape rather than the legacy one.
|
|
87
196
|
const userSource = this.sources.find((s) => s.name === "user");
|
|
@@ -99,6 +208,47 @@ export class SettingsManager {
|
|
|
99
208
|
this.merged = validateSettings(raw);
|
|
100
209
|
return this.merged;
|
|
101
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Run the version-based migrations (migrate-config.ts MIGRATIONS) against a
|
|
213
|
+
* single physical settings file. Best-effort: any read/parse/write error
|
|
214
|
+
* falls through silently and the original source data is used as-is.
|
|
215
|
+
* Writes back (with a .bak, like the models[] migration above) ONLY when a
|
|
216
|
+
* step changed actual content — the configVersion stamp alone doesn't
|
|
217
|
+
* justify touching the file. On write-back the in-memory source is updated
|
|
218
|
+
* so this load() already sees the migrated shape.
|
|
219
|
+
*/
|
|
220
|
+
applyConfigMigration(path, sourceName) {
|
|
221
|
+
if (!existsSync(path))
|
|
222
|
+
return;
|
|
223
|
+
try {
|
|
224
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
225
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
226
|
+
return;
|
|
227
|
+
const raw = parsed;
|
|
228
|
+
const result = migrateConfig(raw);
|
|
229
|
+
if (!result.changed)
|
|
230
|
+
return;
|
|
231
|
+
// Compare content with the version stamp normalized away — stamp-only
|
|
232
|
+
// changes are not persisted.
|
|
233
|
+
const stripStamp = (c) => {
|
|
234
|
+
const { [CONFIG_VERSION_KEY]: _v, ...rest } = c;
|
|
235
|
+
return rest;
|
|
236
|
+
};
|
|
237
|
+
if (JSON.stringify(stripStamp(raw)) === JSON.stringify(stripStamp(result.config)))
|
|
238
|
+
return;
|
|
239
|
+
copyFileSync(path, `${path}.bak`);
|
|
240
|
+
// Atomic write (tmp+rename) so a concurrent load can't read a half-written
|
|
241
|
+
// migrated file — matches the normal save path (atomicWriteJson). The file
|
|
242
|
+
// exists here (existsSync guard above), so the recursive mkdir is a no-op.
|
|
243
|
+
this.atomicWriteJson(path, result.config);
|
|
244
|
+
const source = this.sources.find((s) => s.name === sourceName);
|
|
245
|
+
if (source)
|
|
246
|
+
source.data = result.config;
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
// Best-effort — fall through to normal merge/validate.
|
|
250
|
+
}
|
|
251
|
+
}
|
|
102
252
|
/**
|
|
103
253
|
* Get current effective settings.
|
|
104
254
|
*/
|
|
@@ -149,9 +299,11 @@ export class SettingsManager {
|
|
|
149
299
|
target[parts[parts.length - 1]] = value;
|
|
150
300
|
mkdirSync(dirname(path), { recursive: true });
|
|
151
301
|
// Atomic write: stage to .tmp, then rename, so a concurrent read can't
|
|
152
|
-
// catch a half-written file.
|
|
302
|
+
// catch a half-written file. mode 0o600 — settings.json can hold plaintext
|
|
303
|
+
// API keys, so it must be owner-only like credentials.json (store.ts:56),
|
|
304
|
+
// not world-readable (default umask leaves 0o644 otherwise).
|
|
153
305
|
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
154
|
-
writeFileSync(tmp, JSON.stringify(current, null, 2), "utf-8");
|
|
306
|
+
writeFileSync(tmp, JSON.stringify(current, null, 2), { encoding: "utf-8", mode: 0o600 });
|
|
155
307
|
renameSync(tmp, path);
|
|
156
308
|
this.invalidate();
|
|
157
309
|
}
|
|
@@ -162,7 +314,14 @@ export class SettingsManager {
|
|
|
162
314
|
* cache invalidation mirror saveUserSetting.
|
|
163
315
|
*/
|
|
164
316
|
saveProjectSetting(key, value, cwd) {
|
|
317
|
+
// projectSettingsPath throws on an empty cwd (boundary guard) — keep that.
|
|
165
318
|
const path = this.projectSettingsPath(cwd);
|
|
319
|
+
// Don't resurrect a deleted project root: atomicWriteJson's recursive mkdir
|
|
320
|
+
// of <cwd>/.code-shell recreates `cwd` itself as an empty shell when cwd is
|
|
321
|
+
// gone. A non-empty cwd that no longer exists means the project was deleted
|
|
322
|
+
// — skip the write rather than recreate it.
|
|
323
|
+
if (!existsSync(cwd))
|
|
324
|
+
return;
|
|
166
325
|
const current = this.readJsonObject(path);
|
|
167
326
|
const parts = key.split(".");
|
|
168
327
|
let target = current;
|
|
@@ -185,7 +344,13 @@ export class SettingsManager {
|
|
|
185
344
|
*/
|
|
186
345
|
deleteProjectSetting(key, cwd) {
|
|
187
346
|
const path = this.projectSettingsPath(cwd);
|
|
188
|
-
|
|
347
|
+
// Must be YAML-aware, symmetric with saveProjectSetting: a project with only
|
|
348
|
+
// settings.yaml has no .json, so the old `existsSync(path)` guard returned
|
|
349
|
+
// here and the override survived (read/merge ARE yaml-aware → UI shows
|
|
350
|
+
// "inherited" but the key still applies). readJsonObject resolves the sibling
|
|
351
|
+
// YAML; the cleaned object is written back as JSON (JSON is the write-back
|
|
352
|
+
// format and wins over YAML, exactly as save does).
|
|
353
|
+
if (!resolveConfigPath(path))
|
|
189
354
|
return;
|
|
190
355
|
const current = this.readJsonObject(path);
|
|
191
356
|
const parts = key.split(".");
|
|
@@ -220,6 +385,14 @@ export class SettingsManager {
|
|
|
220
385
|
const out = {};
|
|
221
386
|
for (const k of Object.keys(raw))
|
|
222
387
|
out[k] = validated[k];
|
|
388
|
+
// Same workspace-trust gate as load(): getForScope("project") is a direct
|
|
389
|
+
// file read that bypasses the merge, so an untrusted project's dangerous
|
|
390
|
+
// fields (e.g. localEnvironment.setupScripts — shell run at worktree setup)
|
|
391
|
+
// must be stripped here too. See DANGEROUS_PROJECT_FIELDS.
|
|
392
|
+
if (scope === "project" && !this.projectTrusted) {
|
|
393
|
+
for (const field of DANGEROUS_PROJECT_FIELDS)
|
|
394
|
+
delete out[field];
|
|
395
|
+
}
|
|
223
396
|
return out;
|
|
224
397
|
}
|
|
225
398
|
projectSettingsPath(cwd) {
|
|
@@ -229,47 +402,111 @@ export class SettingsManager {
|
|
|
229
402
|
return join(cwd, ".code-shell", "settings.json");
|
|
230
403
|
}
|
|
231
404
|
readJsonObject(path) {
|
|
232
|
-
|
|
405
|
+
// Resolve to a sibling .yaml/.yml when the .json layer is absent so
|
|
406
|
+
// scope views (getForScope) see hand-written YAML too. JSON still wins.
|
|
407
|
+
const resolved = resolveConfigPath(path);
|
|
408
|
+
if (!resolved)
|
|
233
409
|
return {};
|
|
234
|
-
|
|
235
|
-
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
236
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
237
|
-
return parsed;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
catch {
|
|
241
|
-
// Corrupt file — overwrite rather than crash.
|
|
242
|
-
}
|
|
243
|
-
return {};
|
|
410
|
+
return parseConfigFile(resolved) ?? {};
|
|
244
411
|
}
|
|
245
412
|
atomicWriteJson(path, data) {
|
|
246
413
|
mkdirSync(dirname(path), { recursive: true });
|
|
414
|
+
// mode 0o600: settings.json may hold plaintext API keys — owner-only, see
|
|
415
|
+
// saveUserSetting above and credentials/store.ts.
|
|
247
416
|
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
248
|
-
writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
417
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 0o600 });
|
|
249
418
|
renameSync(tmp, path);
|
|
250
419
|
}
|
|
251
420
|
loadJsonFile(path, name, priority) {
|
|
252
|
-
|
|
421
|
+
// `path` is the canonical .json path for this layer. When it's absent but
|
|
422
|
+
// a sibling settings.yaml/.yml exists, read the YAML instead (JSON wins
|
|
423
|
+
// when both exist — JSON is the write-back format, YAML is hand-written).
|
|
424
|
+
const resolved = resolveConfigPath(path);
|
|
425
|
+
if (!resolved)
|
|
253
426
|
return;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if (typeof data === "object" && data !== null) {
|
|
258
|
-
this.sources.push({ name, priority, data });
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
catch {
|
|
262
|
-
// Skip invalid files
|
|
263
|
-
}
|
|
427
|
+
const data = parseConfigFile(resolved);
|
|
428
|
+
if (data)
|
|
429
|
+
this.sources.push({ name, priority, data });
|
|
264
430
|
}
|
|
265
431
|
deepMerge() {
|
|
266
432
|
let result = {};
|
|
267
433
|
for (const source of this.sources) {
|
|
268
434
|
result = merge(result, source.data);
|
|
269
435
|
}
|
|
436
|
+
// Top-level `hooks` is the one array that CONCATENATES across layers
|
|
437
|
+
// instead of being replaced wholesale: a user-level (global) hook and a
|
|
438
|
+
// project-level hook should BOTH run, mirroring how Claude Code merges
|
|
439
|
+
// hooks from all settings files. Order follows layer priority (user
|
|
440
|
+
// first, project after). An explicit `"hooks": null` in a layer still
|
|
441
|
+
// resets everything below it (the escape hatch merge() already gives
|
|
442
|
+
// every other key); per-entry opt-out is the `disabled` field.
|
|
443
|
+
let hooks;
|
|
444
|
+
let sawHooks = false;
|
|
445
|
+
for (const source of this.sources) {
|
|
446
|
+
if (!("hooks" in source.data))
|
|
447
|
+
continue;
|
|
448
|
+
const v = source.data.hooks;
|
|
449
|
+
if (v === null) {
|
|
450
|
+
hooks = undefined;
|
|
451
|
+
sawHooks = true;
|
|
452
|
+
}
|
|
453
|
+
else if (Array.isArray(v)) {
|
|
454
|
+
hooks = [...(hooks ?? []), ...v];
|
|
455
|
+
sawHooks = true;
|
|
456
|
+
}
|
|
457
|
+
// Non-array garbage is left to merge()'s wholesale result so
|
|
458
|
+
// validateSettings still sees (and rejects) it unchanged.
|
|
459
|
+
}
|
|
460
|
+
if (sawHooks) {
|
|
461
|
+
if (hooks !== undefined)
|
|
462
|
+
result.hooks = hooks;
|
|
463
|
+
else
|
|
464
|
+
delete result.hooks;
|
|
465
|
+
}
|
|
270
466
|
return result;
|
|
271
467
|
}
|
|
272
468
|
}
|
|
469
|
+
/**
|
|
470
|
+
* Parse a config file by extension: .yaml/.yml go through the YAML parser,
|
|
471
|
+
* everything else through JSON.parse. Mirrors the loader's existing
|
|
472
|
+
* "corrupt file never crashes — silently skip" contract: on any read/parse
|
|
473
|
+
* error, or a non-object top-level value, returns null. The caller decides
|
|
474
|
+
* what an absent/empty layer means.
|
|
475
|
+
*/
|
|
476
|
+
function parseConfigFile(path) {
|
|
477
|
+
if (!existsSync(path))
|
|
478
|
+
return null;
|
|
479
|
+
try {
|
|
480
|
+
const content = readFileSync(path, "utf-8");
|
|
481
|
+
const ext = extname(path).toLowerCase();
|
|
482
|
+
const parsed = ext === ".yaml" || ext === ".yml" ? parseYaml(content) : JSON.parse(content);
|
|
483
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
484
|
+
return parsed;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
// Corrupt file — skip rather than crash.
|
|
489
|
+
}
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Given the JSON path for a settings layer (e.g. .../settings.json or
|
|
494
|
+
* .../settings.local.json), return the path that should actually be read:
|
|
495
|
+
* the .json file if it exists, otherwise a sibling .yaml/.yml if present.
|
|
496
|
+
* JSON is the write-back format and wins when both exist; YAML is a
|
|
497
|
+
* hand-written read-only alternative. Returns null when no layer file exists.
|
|
498
|
+
*/
|
|
499
|
+
function resolveConfigPath(jsonPath) {
|
|
500
|
+
if (existsSync(jsonPath))
|
|
501
|
+
return jsonPath;
|
|
502
|
+
const base = jsonPath.replace(/\.json$/, "");
|
|
503
|
+
for (const ext of [".yaml", ".yml"]) {
|
|
504
|
+
const candidate = `${base}${ext}`;
|
|
505
|
+
if (existsSync(candidate))
|
|
506
|
+
return candidate;
|
|
507
|
+
}
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
273
510
|
function merge(base, override) {
|
|
274
511
|
const result = { ...base };
|
|
275
512
|
for (const [key, value] of Object.entries(override)) {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version-based settings migration (TODO 8.5).
|
|
3
|
+
*
|
|
4
|
+
* A settings file carries an optional `configVersion` number. As the schema
|
|
5
|
+
* evolves, breaking shape changes register a migration step here; on load we
|
|
6
|
+
* apply every step whose `from` is >= the file's current version, in order,
|
|
7
|
+
* bringing the object up to CURRENT_CONFIG_VERSION. Pure + ordered so it's
|
|
8
|
+
* deterministic and unit-testable; the SettingsManager wires the result to a
|
|
9
|
+
* write-back (with a .bak) the same way the existing models[] migration does.
|
|
10
|
+
*
|
|
11
|
+
* Files with no `configVersion` are treated as version 0 (pre-versioning).
|
|
12
|
+
* A file already at CURRENT is a no-op (changed:false).
|
|
13
|
+
*/
|
|
14
|
+
export declare const CONFIG_VERSION_KEY = "configVersion";
|
|
15
|
+
export interface MigrationStep {
|
|
16
|
+
/** Apply this step to a config at exactly this version. */
|
|
17
|
+
from: number;
|
|
18
|
+
/** Version the config is at after this step runs. */
|
|
19
|
+
to: number;
|
|
20
|
+
/** Transform the raw config object (must not mutate the input). */
|
|
21
|
+
migrate: (config: Record<string, unknown>) => Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Registered migrations, in ascending `from` order. New breaking changes
|
|
25
|
+
* append a step here; CURRENT_CONFIG_VERSION follows automatically.
|
|
26
|
+
*/
|
|
27
|
+
export declare const MIGRATIONS: readonly MigrationStep[];
|
|
28
|
+
/** The version a freshly-written config is stamped with. */
|
|
29
|
+
export declare const CURRENT_CONFIG_VERSION: number;
|
|
30
|
+
/** Read the config's version, defaulting to 0 for pre-versioning files. */
|
|
31
|
+
export declare function configVersionOf(config: Record<string, unknown>): number;
|
|
32
|
+
export interface MigrateResult {
|
|
33
|
+
config: Record<string, unknown>;
|
|
34
|
+
changed: boolean;
|
|
35
|
+
fromVersion: number;
|
|
36
|
+
toVersion: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Bring a raw config up to CURRENT_CONFIG_VERSION by applying each registered
|
|
40
|
+
* step whose `from` matches the running version, in order. Always stamps the
|
|
41
|
+
* resulting (and an already-current) config with the current version key.
|
|
42
|
+
* `changed` is true when any step ran OR the version stamp was added/updated.
|
|
43
|
+
* Never mutates the input.
|
|
44
|
+
*/
|
|
45
|
+
export declare function migrateConfig(input: Record<string, unknown>, migrations?: readonly MigrationStep[]): MigrateResult;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version-based settings migration (TODO 8.5).
|
|
3
|
+
*
|
|
4
|
+
* A settings file carries an optional `configVersion` number. As the schema
|
|
5
|
+
* evolves, breaking shape changes register a migration step here; on load we
|
|
6
|
+
* apply every step whose `from` is >= the file's current version, in order,
|
|
7
|
+
* bringing the object up to CURRENT_CONFIG_VERSION. Pure + ordered so it's
|
|
8
|
+
* deterministic and unit-testable; the SettingsManager wires the result to a
|
|
9
|
+
* write-back (with a .bak) the same way the existing models[] migration does.
|
|
10
|
+
*
|
|
11
|
+
* Files with no `configVersion` are treated as version 0 (pre-versioning).
|
|
12
|
+
* A file already at CURRENT is a no-op (changed:false).
|
|
13
|
+
*/
|
|
14
|
+
import { BUILTIN_CATALOG } from "../model-catalog/builtin.js";
|
|
15
|
+
export const CONFIG_VERSION_KEY = "configVersion";
|
|
16
|
+
/**
|
|
17
|
+
* v0 → v1: backfill `catalogId` on imageGen/videoGen `providers[]` entries
|
|
18
|
+
* written before the model catalog existed (Catalog v1). Legacy entries only
|
|
19
|
+
* carry `kind`; the connections UI resolves its template by `catalogId`, so
|
|
20
|
+
* without it the model-preset dropdown degrades to an empty text box. The
|
|
21
|
+
* match mirrors the renderer's legacy fallback: builtin entry whose
|
|
22
|
+
* `adapterKind === kind` within the section's tag. Unmatched entries are left
|
|
23
|
+
* untouched (the UI shows a manual-input hint for those).
|
|
24
|
+
*/
|
|
25
|
+
function backfillGenCatalogIds(config) {
|
|
26
|
+
const next = { ...config };
|
|
27
|
+
for (const [key, tag] of [
|
|
28
|
+
["imageGen", "image"],
|
|
29
|
+
["videoGen", "video"],
|
|
30
|
+
]) {
|
|
31
|
+
const gen = next[key];
|
|
32
|
+
if (!gen || typeof gen !== "object" || Array.isArray(gen))
|
|
33
|
+
continue;
|
|
34
|
+
const providers = gen.providers;
|
|
35
|
+
if (!Array.isArray(providers))
|
|
36
|
+
continue;
|
|
37
|
+
let touched = false;
|
|
38
|
+
const out = providers.map((p) => {
|
|
39
|
+
if (!p || typeof p !== "object" || Array.isArray(p))
|
|
40
|
+
return p;
|
|
41
|
+
const rec = p;
|
|
42
|
+
if (typeof rec.catalogId === "string")
|
|
43
|
+
return p;
|
|
44
|
+
const entry = BUILTIN_CATALOG.find((e) => e.adapterKind === rec.kind && e.tag === tag);
|
|
45
|
+
if (!entry)
|
|
46
|
+
return p;
|
|
47
|
+
touched = true;
|
|
48
|
+
return { ...rec, catalogId: entry.id };
|
|
49
|
+
});
|
|
50
|
+
if (touched)
|
|
51
|
+
next[key] = { ...gen, providers: out };
|
|
52
|
+
}
|
|
53
|
+
return next;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* v1 → v2: drop the sandbox config the 设置页 mis-wrote. The local-env page
|
|
57
|
+
* used to write `sandbox:{mode:"auto", network:"allow", writableRoots:[],
|
|
58
|
+
* deniedReads:[]}` (its display default) on every save — opting users into a
|
|
59
|
+
* sandbox they never chose. That exact fingerprint = not a real choice, so we
|
|
60
|
+
* remove the whole sandbox field (→ follow/off). A user who actually configured
|
|
61
|
+
* it (changed mode/network or set roots/reads) is left untouched.
|
|
62
|
+
*/
|
|
63
|
+
function clearMisWrittenSandboxAuto(config) {
|
|
64
|
+
const sb = config.sandbox;
|
|
65
|
+
if (!sb || typeof sb !== "object" || Array.isArray(sb))
|
|
66
|
+
return config;
|
|
67
|
+
const s = sb;
|
|
68
|
+
const isMisWritten = s.mode === "auto" &&
|
|
69
|
+
s.network === "allow" &&
|
|
70
|
+
Array.isArray(s.writableRoots) && s.writableRoots.length === 0 &&
|
|
71
|
+
Array.isArray(s.deniedReads) && s.deniedReads.length === 0;
|
|
72
|
+
if (!isMisWritten)
|
|
73
|
+
return config;
|
|
74
|
+
const next = { ...config };
|
|
75
|
+
delete next.sandbox;
|
|
76
|
+
return next;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Registered migrations, in ascending `from` order. New breaking changes
|
|
80
|
+
* append a step here; CURRENT_CONFIG_VERSION follows automatically.
|
|
81
|
+
*/
|
|
82
|
+
export const MIGRATIONS = [
|
|
83
|
+
{ from: 0, to: 1, migrate: backfillGenCatalogIds },
|
|
84
|
+
{ from: 1, to: 2, migrate: clearMisWrittenSandboxAuto },
|
|
85
|
+
];
|
|
86
|
+
/** The version a freshly-written config is stamped with. */
|
|
87
|
+
export const CURRENT_CONFIG_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.to), 0);
|
|
88
|
+
/** Read the config's version, defaulting to 0 for pre-versioning files. */
|
|
89
|
+
export function configVersionOf(config) {
|
|
90
|
+
const v = config[CONFIG_VERSION_KEY];
|
|
91
|
+
return typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.floor(v) : 0;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Bring a raw config up to CURRENT_CONFIG_VERSION by applying each registered
|
|
95
|
+
* step whose `from` matches the running version, in order. Always stamps the
|
|
96
|
+
* resulting (and an already-current) config with the current version key.
|
|
97
|
+
* `changed` is true when any step ran OR the version stamp was added/updated.
|
|
98
|
+
* Never mutates the input.
|
|
99
|
+
*/
|
|
100
|
+
export function migrateConfig(input, migrations = MIGRATIONS) {
|
|
101
|
+
const target = migrations.reduce((max, m) => Math.max(max, m.to), 0);
|
|
102
|
+
const fromVersion = configVersionOf(input);
|
|
103
|
+
let config = { ...input };
|
|
104
|
+
let v = fromVersion;
|
|
105
|
+
let stepsRan = false;
|
|
106
|
+
// Apply steps deterministically: for the current version, find the matching
|
|
107
|
+
// step, run it, advance. A migration list with gaps simply stops when no
|
|
108
|
+
// step matches the running version.
|
|
109
|
+
for (;;) {
|
|
110
|
+
const step = migrations.find((m) => m.from === v);
|
|
111
|
+
if (!step)
|
|
112
|
+
break;
|
|
113
|
+
config = { ...step.migrate(config) };
|
|
114
|
+
v = step.to;
|
|
115
|
+
stepsRan = true;
|
|
116
|
+
}
|
|
117
|
+
const alreadyStamped = input[CONFIG_VERSION_KEY] === target;
|
|
118
|
+
config[CONFIG_VERSION_KEY] = target;
|
|
119
|
+
return {
|
|
120
|
+
config,
|
|
121
|
+
changed: stepsRan || !alreadyStamped,
|
|
122
|
+
fromVersion,
|
|
123
|
+
toVersion: target,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema export for the settings file.
|
|
3
|
+
*
|
|
4
|
+
* Editors (VS Code et al.) can point a `$schema` at the emitted
|
|
5
|
+
* `settings.schema.json` to get autocomplete + validation when hand-editing
|
|
6
|
+
* `~/.code-shell/settings.json` or `<project>/.code-shell/settings.json`.
|
|
7
|
+
*
|
|
8
|
+
* This module is side-effect free: it only EXPOSES the generator and a writer.
|
|
9
|
+
* `manager.load()` deliberately does NOT call these — emitting a file during
|
|
10
|
+
* load would pollute test HOMEs and add disk I/O to every boot. A host that
|
|
11
|
+
* wants the file on disk should call `writeSettingsSchemaFile()` explicitly
|
|
12
|
+
* (wiring TBD — see the task return note).
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Generate the JSON Schema for the settings object. The `name` option makes
|
|
16
|
+
* the root a `$ref` into `definitions.CodeShellSettings`, which is the shape
|
|
17
|
+
* editors expect when wiring up `$schema`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function settingsJsonSchema(): Record<string, unknown>;
|
|
20
|
+
/**
|
|
21
|
+
* Write the schema to `<dir>/settings.schema.json` using the same atomic
|
|
22
|
+
* tmp-file + rename dance as SettingsManager.atomicWriteJson, so a concurrent
|
|
23
|
+
* reader never sees a half-written file. Returns the absolute output path.
|
|
24
|
+
*/
|
|
25
|
+
export declare function writeSettingsSchemaFile(dir: string): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema export for the settings file.
|
|
3
|
+
*
|
|
4
|
+
* Editors (VS Code et al.) can point a `$schema` at the emitted
|
|
5
|
+
* `settings.schema.json` to get autocomplete + validation when hand-editing
|
|
6
|
+
* `~/.code-shell/settings.json` or `<project>/.code-shell/settings.json`.
|
|
7
|
+
*
|
|
8
|
+
* This module is side-effect free: it only EXPOSES the generator and a writer.
|
|
9
|
+
* `manager.load()` deliberately does NOT call these — emitting a file during
|
|
10
|
+
* load would pollute test HOMEs and add disk I/O to every boot. A host that
|
|
11
|
+
* wants the file on disk should call `writeSettingsSchemaFile()` explicitly
|
|
12
|
+
* (wiring TBD — see the task return note).
|
|
13
|
+
*/
|
|
14
|
+
import { mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { dirname, join } from "node:path";
|
|
16
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
17
|
+
import { SettingsSchema } from "./schema.js";
|
|
18
|
+
/**
|
|
19
|
+
* Generate the JSON Schema for the settings object. The `name` option makes
|
|
20
|
+
* the root a `$ref` into `definitions.CodeShellSettings`, which is the shape
|
|
21
|
+
* editors expect when wiring up `$schema`.
|
|
22
|
+
*/
|
|
23
|
+
export function settingsJsonSchema() {
|
|
24
|
+
return zodToJsonSchema(SettingsSchema, { name: "CodeShellSettings" });
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Write the schema to `<dir>/settings.schema.json` using the same atomic
|
|
28
|
+
* tmp-file + rename dance as SettingsManager.atomicWriteJson, so a concurrent
|
|
29
|
+
* reader never sees a half-written file. Returns the absolute output path.
|
|
30
|
+
*/
|
|
31
|
+
export function writeSettingsSchemaFile(dir) {
|
|
32
|
+
const out = join(dir, "settings.schema.json");
|
|
33
|
+
mkdirSync(dirname(out), { recursive: true });
|
|
34
|
+
const tmp = `${out}.${process.pid}.${Date.now()}.tmp`;
|
|
35
|
+
writeFileSync(tmp, JSON.stringify(settingsJsonSchema(), null, 2), "utf-8");
|
|
36
|
+
renameSync(tmp, out);
|
|
37
|
+
return out;
|
|
38
|
+
}
|