@cjhyy/code-shell-core 0.5.0-rc.0 → 0.5.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/dist/agent/agent-definition-registry.d.ts +29 -0
- package/dist/agent/agent-definition-registry.js +74 -0
- package/dist/agent/agent-definition.d.ts +37 -0
- package/dist/agent/agent-definition.js +59 -0
- package/dist/arena/arena.js +11 -12
- package/dist/arena/context/context-tools.js +2 -4
- package/dist/arena/context/within-root.d.ts +7 -0
- package/dist/arena/context/within-root.js +15 -0
- package/dist/arena/iterate/phases/argue.js +8 -2
- package/dist/arena/iterate/phases/revise.js +1 -1
- package/dist/arena/iterate/phases/tournament.js +3 -3
- package/dist/arena/phases/adjudication.js +1 -4
- package/dist/arena/phases/build-consensus.js +1 -4
- package/dist/arena/phases/cross-review.js +2 -8
- package/dist/arena/phases/debate-rounds.js +1 -4
- package/dist/arena/phases/participant-research.js +1 -4
- package/dist/arena/phases/planning-detail-expansion.js +8 -10
- package/dist/arena/planner.js +0 -1
- package/dist/arena/providers/docs.d.ts +1 -0
- package/dist/arena/providers/docs.js +5 -2
- package/dist/arena/providers/repo.d.ts +1 -0
- package/dist/arena/providers/repo.js +5 -2
- package/dist/arena/strategies/utils.js +36 -3
- package/dist/arena/transitions.d.ts +7 -2
- package/dist/arena/transitions.js +9 -4
- package/dist/arena/types.d.ts +8 -2
- package/dist/automation/cron-expr.d.ts +31 -0
- package/dist/automation/cron-expr.js +151 -0
- package/dist/automation/index.d.ts +41 -0
- package/dist/automation/index.js +39 -0
- package/dist/automation/runner.d.ts +67 -0
- package/dist/automation/runner.js +56 -0
- package/dist/automation/scheduler.d.ts +156 -0
- package/dist/automation/scheduler.js +546 -0
- package/dist/automation/store.d.ts +39 -0
- package/dist/automation/store.js +119 -0
- package/dist/automation/write-policy.d.ts +34 -0
- package/dist/automation/write-policy.js +113 -0
- package/dist/automation/write-run.d.ts +51 -0
- package/dist/automation/write-run.js +38 -0
- package/dist/capability-control/index.d.ts +5 -0
- package/dist/capability-control/index.js +3 -0
- package/dist/capability-control/overlay.d.ts +49 -0
- package/dist/capability-control/overlay.js +89 -0
- package/dist/capability-control/project.d.ts +65 -0
- package/dist/capability-control/project.js +147 -0
- package/dist/capability-control/service.d.ts +70 -0
- package/dist/capability-control/service.js +147 -0
- package/dist/capability-control/types.d.ts +62 -0
- package/dist/capability-control/types.js +17 -0
- package/dist/cli/agent-server-stdio.d.ts +12 -1
- package/dist/cli/agent-server-stdio.js +118 -18
- package/dist/cli/agent-server-tcp.d.ts +20 -0
- package/dist/cli/agent-server-tcp.js +120 -0
- package/dist/cli/graceful-shutdown.d.ts +27 -0
- package/dist/cli/graceful-shutdown.js +30 -0
- package/dist/context/compaction.js +23 -15
- package/dist/context/manager.d.ts +0 -20
- package/dist/context/manager.js +0 -37
- package/dist/cron/cron-runtime.d.ts +2 -0
- package/dist/cron/cron-runtime.js +2 -0
- package/dist/cron/cron-store.d.ts +2 -0
- package/dist/cron/cron-store.js +2 -0
- package/dist/cron/scheduler.d.ts +5 -32
- package/dist/cron/scheduler.js +5 -113
- package/dist/data/openrouter-sync.js +1 -1
- package/dist/engine/engine.d.ts +256 -16
- package/dist/engine/engine.js +856 -200
- package/dist/engine/goal.d.ts +42 -0
- package/dist/engine/goal.js +52 -0
- package/dist/engine/image-compression.d.ts +81 -0
- package/dist/engine/image-compression.js +186 -0
- package/dist/engine/image-policy.d.ts +152 -0
- package/dist/engine/image-policy.js +165 -0
- package/dist/engine/reactive-threshold.d.ts +13 -0
- package/dist/engine/reactive-threshold.js +17 -0
- package/dist/engine/runtime.js +7 -0
- package/dist/engine/session-title.d.ts +13 -0
- package/dist/engine/session-title.js +34 -0
- package/dist/engine/turn-loop.d.ts +29 -8
- package/dist/engine/turn-loop.js +498 -316
- package/dist/git/parse-log.d.ts +13 -0
- package/dist/git/parse-log.js +21 -0
- package/dist/git/utils.d.ts +8 -6
- package/dist/git/utils.js +57 -26
- package/dist/git/worktree.js +33 -18
- package/dist/hooks/events.d.ts +23 -1
- package/dist/hooks/goal-stop-hook.d.ts +30 -0
- package/dist/hooks/goal-stop-hook.js +102 -0
- package/dist/hooks/hook-output.d.ts +33 -0
- package/dist/hooks/hook-output.js +79 -0
- package/dist/hooks/registry.d.ts +7 -0
- package/dist/hooks/registry.js +38 -1
- package/dist/hooks/shell-runner.d.ts +2 -0
- package/dist/hooks/shell-runner.js +43 -2
- package/dist/index.d.ts +32 -7
- package/dist/index.js +30 -3
- package/dist/llm/capabilities/reasoning-control.d.ts +24 -0
- package/dist/llm/capabilities/reasoning-control.js +38 -0
- package/dist/llm/capabilities/rules.js +33 -3
- package/dist/llm/capabilities/types.d.ts +22 -2
- package/dist/llm/clamp-max-tokens.d.ts +11 -0
- package/dist/llm/clamp-max-tokens.js +17 -0
- package/dist/llm/client-base.d.ts +41 -4
- package/dist/llm/client-base.js +67 -13
- package/dist/llm/client-factory.d.ts +3 -3
- package/dist/llm/client-factory.js +2 -2
- package/dist/llm/model-pool.d.ts +15 -11
- package/dist/llm/model-pool.js +23 -20
- package/dist/llm/provider-catalog.d.ts +2 -2
- package/dist/llm/providers/anthropic.d.ts +32 -2
- package/dist/llm/providers/anthropic.js +147 -8
- package/dist/llm/providers/openai.d.ts +11 -2
- package/dist/llm/providers/openai.js +253 -52
- package/dist/llm/reasoning-setting.d.ts +51 -0
- package/dist/llm/reasoning-setting.js +32 -0
- package/dist/llm/stop-reason.d.ts +13 -0
- package/dist/llm/stop-reason.js +21 -0
- package/dist/llm/strip-vision.d.ts +32 -0
- package/dist/llm/strip-vision.js +94 -0
- package/dist/llm/types.d.ts +5 -6
- package/dist/logging/logger.js +18 -4
- package/dist/logging/sanitize-messages.d.ts +10 -0
- package/dist/logging/sanitize-messages.js +90 -0
- package/dist/lsp/client.js +28 -8
- package/dist/lsp/manager.js +2 -1
- package/dist/lsp/root-path.d.ts +9 -0
- package/dist/lsp/root-path.js +12 -0
- package/dist/onboarding.js +18 -11
- package/dist/plugins/gitOps.d.ts +34 -0
- package/dist/plugins/gitOps.js +59 -3
- package/dist/plugins/installer/codex/convertAgents.d.ts +7 -0
- package/dist/plugins/installer/codex/convertAgents.js +55 -0
- package/dist/plugins/installer/codex/convertMcp.d.ts +8 -0
- package/dist/plugins/installer/codex/convertMcp.js +45 -0
- package/dist/plugins/installer/codex/convertSkills.d.ts +12 -0
- package/dist/plugins/installer/codex/convertSkills.js +33 -0
- package/dist/plugins/installer/detectFormat.d.ts +2 -0
- package/dist/plugins/installer/detectFormat.js +6 -0
- package/dist/plugins/installer/install.d.ts +7 -0
- package/dist/plugins/installer/install.js +99 -0
- package/dist/plugins/installer/installFromSource.d.ts +13 -0
- package/dist/plugins/installer/installFromSource.js +45 -0
- package/dist/plugins/installer/list.d.ts +9 -0
- package/dist/plugins/installer/list.js +30 -0
- package/dist/plugins/installer/loadPluginAgents.d.ts +3 -0
- package/dist/plugins/installer/loadPluginAgents.js +23 -0
- package/dist/plugins/installer/loadPluginMcp.d.ts +7 -0
- package/dist/plugins/installer/loadPluginMcp.js +74 -0
- package/dist/plugins/installer/parseSource.d.ts +20 -0
- package/dist/plugins/installer/parseSource.js +74 -0
- package/dist/plugins/installer/paths.d.ts +5 -0
- package/dist/plugins/installer/paths.js +25 -0
- package/dist/plugins/installer/types.d.ts +49 -0
- package/dist/plugins/installer/types.js +27 -0
- package/dist/plugins/installer/uninstall.d.ts +2 -0
- package/dist/plugins/installer/uninstall.js +14 -0
- package/dist/plugins/installer/update.d.ts +14 -0
- package/dist/plugins/installer/update.js +65 -0
- package/dist/plugins/loadPluginHooks.d.ts +8 -1
- package/dist/plugins/loadPluginHooks.js +11 -1
- package/dist/plugins/marketplaceManager.d.ts +8 -1
- package/dist/plugins/marketplaceManager.js +38 -4
- package/dist/plugins/pluginCommandHook.js +34 -0
- package/dist/plugins/pluginInstaller.d.ts +21 -0
- package/dist/plugins/pluginInstaller.js +128 -29
- package/dist/plugins/schemas.js +40 -6
- package/dist/plugins/types.d.ts +4 -0
- package/dist/preset/index.js +16 -0
- package/dist/prompt/composer.d.ts +10 -0
- package/dist/prompt/composer.js +29 -4
- package/dist/prompt/sections/orchestration.md +8 -0
- package/dist/protocol/chat-session-manager.d.ts +8 -1
- package/dist/protocol/chat-session-manager.js +10 -0
- package/dist/protocol/chat-session.d.ts +17 -0
- package/dist/protocol/chat-session.js +28 -0
- package/dist/protocol/redact.d.ts +50 -0
- package/dist/protocol/redact.js +71 -0
- package/dist/protocol/server.d.ts +41 -2
- package/dist/protocol/server.js +231 -33
- package/dist/protocol/tcp-transport.d.ts +45 -0
- package/dist/protocol/tcp-transport.js +74 -0
- package/dist/protocol/types.d.ts +23 -0
- package/dist/protocol/types.js +6 -0
- package/dist/remote/bridge.d.ts +11 -2
- package/dist/remote/bridge.js +61 -33
- package/dist/run/ArtifactTracker.js +5 -4
- package/dist/run/EngineRunner.d.ts +27 -1
- package/dist/run/EngineRunner.js +46 -8
- package/dist/run/FileRunStore.js +32 -8
- package/dist/run/RunLock.d.ts +28 -2
- package/dist/run/RunLock.js +49 -7
- package/dist/run/RunManager.js +51 -3
- package/dist/run/RunQueue.d.ts +1 -0
- package/dist/run/RunQueue.js +8 -2
- package/dist/run/factory.d.ts +7 -0
- package/dist/run/factory.js +1 -0
- package/dist/run/index.d.ts +2 -2
- package/dist/run/index.js +1 -1
- package/dist/run/redirect-target.d.ts +7 -0
- package/dist/run/redirect-target.js +13 -0
- package/dist/run/types.js +1 -1
- package/dist/services/browser-open.d.ts +13 -0
- package/dist/services/browser-open.js +16 -0
- package/dist/services/dream-consolidation.d.ts +57 -0
- package/dist/services/dream-consolidation.js +151 -0
- package/dist/services/memory-orchestrator.js +18 -0
- package/dist/services/notifier.d.ts +8 -0
- package/dist/services/notifier.js +39 -25
- package/dist/services/oauth.js +5 -7
- package/dist/services/session-memory-sort.d.ts +8 -0
- package/dist/services/session-memory-sort.js +9 -0
- package/dist/services/session-memory.js +8 -9
- package/dist/session/file-history.js +10 -3
- package/dist/session/session-manager.d.ts +23 -1
- package/dist/session/session-manager.js +73 -2
- package/dist/settings/disk-defaults.d.ts +35 -0
- package/dist/settings/disk-defaults.js +24 -0
- package/dist/settings/manager.d.ts +45 -1
- package/dist/settings/manager.js +121 -17
- package/dist/settings/manager.test.d.ts +1 -0
- package/dist/settings/manager.test.js +73 -0
- package/dist/settings/personalization.d.ts +19 -0
- package/dist/settings/personalization.js +7 -0
- package/dist/settings/schema.d.ts +1163 -126
- package/dist/settings/schema.js +126 -10
- package/dist/skills/scanner.d.ts +13 -6
- package/dist/skills/scanner.js +23 -4
- package/dist/tool-system/builtin/add-marketplace.d.ts +12 -0
- package/dist/tool-system/builtin/add-marketplace.js +76 -0
- package/dist/tool-system/builtin/agent-registry.d.ts +15 -0
- package/dist/tool-system/builtin/agent-registry.js +14 -4
- package/dist/tool-system/builtin/agent.d.ts +55 -0
- package/dist/tool-system/builtin/agent.js +165 -17
- package/dist/tool-system/builtin/apply-patch/index.js +22 -3
- package/dist/tool-system/builtin/arena.js +6 -7
- package/dist/tool-system/builtin/complete-goal.d.ts +25 -0
- package/dist/tool-system/builtin/complete-goal.js +45 -0
- package/dist/tool-system/builtin/cron.js +53 -9
- package/dist/tool-system/builtin/edit.d.ts +2 -1
- package/dist/tool-system/builtin/edit.js +6 -1
- package/dist/tool-system/builtin/generate-image.d.ts +25 -0
- package/dist/tool-system/builtin/generate-image.js +146 -0
- package/dist/tool-system/builtin/glob.js +7 -0
- package/dist/tool-system/builtin/grep.js +6 -0
- package/dist/tool-system/builtin/index.d.ts +18 -1
- package/dist/tool-system/builtin/index.js +62 -1
- package/dist/tool-system/builtin/notebook-edit.d.ts +2 -1
- package/dist/tool-system/builtin/notebook-edit.js +8 -1
- package/dist/tool-system/builtin/read.d.ts +2 -1
- package/dist/tool-system/builtin/read.js +9 -1
- package/dist/tool-system/builtin/skill.js +23 -5
- package/dist/tool-system/builtin/sleep.js +9 -3
- package/dist/tool-system/builtin/task.js +4 -1
- package/dist/tool-system/builtin/update-automation-memory.d.ts +24 -0
- package/dist/tool-system/builtin/update-automation-memory.js +60 -0
- package/dist/tool-system/builtin/view-image.d.ts +18 -0
- package/dist/tool-system/builtin/view-image.js +96 -0
- package/dist/tool-system/builtin/web-search.d.ts +4 -1
- package/dist/tool-system/builtin/web-search.js +36 -3
- package/dist/tool-system/builtin/write.d.ts +2 -1
- package/dist/tool-system/builtin/write.js +7 -1
- package/dist/tool-system/context.d.ts +43 -0
- package/dist/tool-system/executor.d.ts +0 -1
- package/dist/tool-system/executor.js +34 -40
- package/dist/tool-system/investigation-guard.d.ts +4 -1
- package/dist/tool-system/investigation-guard.js +19 -3
- package/dist/tool-system/mcp-manager.d.ts +36 -12
- package/dist/tool-system/mcp-manager.js +173 -10
- package/dist/tool-system/path-policy.d.ts +94 -0
- package/dist/tool-system/path-policy.js +279 -0
- package/dist/tool-system/permission.js +43 -50
- package/dist/tool-system/plan-mode-allowlist.d.ts +21 -0
- package/dist/tool-system/plan-mode-allowlist.js +43 -0
- package/dist/tool-system/registry.js +12 -1
- package/dist/tool-system/validation.d.ts +10 -0
- package/dist/tool-system/validation.js +16 -3
- package/dist/types.d.ts +90 -15
- package/dist/utils/format.js +4 -2
- package/dist/utils/lockfile.js +9 -2
- package/dist/utils/theme.d.ts +6 -0
- package/dist/utils/theme.js +4 -2
- package/package.json +2 -1
|
@@ -7,6 +7,28 @@ export interface SessionBundle {
|
|
|
7
7
|
state: SessionState;
|
|
8
8
|
transcript: Transcript;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Validate a session ID before it is joined into a filesystem path.
|
|
12
|
+
*
|
|
13
|
+
* Internally generated IDs use `nanoid(16)` and are trusted by construction.
|
|
14
|
+
* But every public entry point (`create`'s explicitSessionId, `resume`,
|
|
15
|
+
* `exists`, `saveState`'s state.sessionId, `fork`'s sourceSessionId) accepts
|
|
16
|
+
* an ID from an outside caller — protocol clients, ChatSessionManager-driven
|
|
17
|
+
* cold starts, persisted state files — and join()'s it into `sessionsDir`.
|
|
18
|
+
* Without this check a value like "../etc/passwd" or "/tmp/x" would let the
|
|
19
|
+
* caller escape the sessions directory.
|
|
20
|
+
*
|
|
21
|
+
* Exported for direct unit testing.
|
|
22
|
+
*/
|
|
23
|
+
export declare function assertSafeSessionId(sessionId: unknown): asserts sessionId is string;
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the `.code-shell` home dir. `CODE_SHELL_HOME` overrides the default
|
|
26
|
+
* `~/.code-shell` — mirrors Codex's `CODEX_HOME`. Tests set it to a temp dir
|
|
27
|
+
* (see bunfig.toml preload) so a `new Engine()` / `new SessionManager()` with
|
|
28
|
+
* no explicit storageDir doesn't pollute the user's real ~/.code-shell/sessions
|
|
29
|
+
* with throwaway test sessions (the rm-usage/test-model sidebar junk).
|
|
30
|
+
*/
|
|
31
|
+
export declare function codeShellHome(): string;
|
|
10
32
|
export declare class SessionManager {
|
|
11
33
|
private readonly sessionsDir;
|
|
12
34
|
constructor(storageDir?: string);
|
|
@@ -17,7 +39,7 @@ export declare class SessionManager {
|
|
|
17
39
|
* nanoid. Either way the on-disk directory is materialized and the
|
|
18
40
|
* state.json + transcript.jsonl files are written before return.
|
|
19
41
|
*/
|
|
20
|
-
create(cwd: string, model: string, provider: string, explicitSessionId?: string): SessionBundle;
|
|
42
|
+
create(cwd: string, model: string, provider: string, explicitSessionId?: string, parentSessionId?: string | null, origin?: import("../types.js").SessionOrigin): SessionBundle;
|
|
21
43
|
/**
|
|
22
44
|
* Whether a session directory exists on disk. Used by ChatSession-driven
|
|
23
45
|
* cold starts to decide between resume vs create-with-explicit-sid
|
|
@@ -7,10 +7,57 @@ import { homedir } from "node:os";
|
|
|
7
7
|
import { nanoid } from "nanoid";
|
|
8
8
|
import { Transcript } from "./transcript.js";
|
|
9
9
|
import { SessionError } from "../exceptions.js";
|
|
10
|
+
/**
|
|
11
|
+
* Validate a session ID before it is joined into a filesystem path.
|
|
12
|
+
*
|
|
13
|
+
* Internally generated IDs use `nanoid(16)` and are trusted by construction.
|
|
14
|
+
* But every public entry point (`create`'s explicitSessionId, `resume`,
|
|
15
|
+
* `exists`, `saveState`'s state.sessionId, `fork`'s sourceSessionId) accepts
|
|
16
|
+
* an ID from an outside caller — protocol clients, ChatSessionManager-driven
|
|
17
|
+
* cold starts, persisted state files — and join()'s it into `sessionsDir`.
|
|
18
|
+
* Without this check a value like "../etc/passwd" or "/tmp/x" would let the
|
|
19
|
+
* caller escape the sessions directory.
|
|
20
|
+
*
|
|
21
|
+
* Exported for direct unit testing.
|
|
22
|
+
*/
|
|
23
|
+
export function assertSafeSessionId(sessionId) {
|
|
24
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
25
|
+
throw new SessionError(`invalid session id: must be a non-empty string`);
|
|
26
|
+
}
|
|
27
|
+
// basename check: reject any path-shaped value. Covers absolute paths,
|
|
28
|
+
// POSIX and Windows separators, parent-dir tokens, and the lone "..".
|
|
29
|
+
if (sessionId.includes("/") || sessionId.includes("\\")) {
|
|
30
|
+
throw new SessionError(`invalid session id: contains path separator: ${sessionId}`);
|
|
31
|
+
}
|
|
32
|
+
if (sessionId === "." || sessionId === ".." || sessionId.includes("..")) {
|
|
33
|
+
throw new SessionError(`invalid session id: contains parent-dir token: ${sessionId}`);
|
|
34
|
+
}
|
|
35
|
+
// Conservative character allow-list: letters, digits, and `-_.` only.
|
|
36
|
+
// This matches what nanoid emits plus the dotted variants in-house code
|
|
37
|
+
// already uses (e.g. "tui-main", "agent.foo"). Anything else (NUL,
|
|
38
|
+
// newline, control chars, shell metacharacters, glob chars) is rejected.
|
|
39
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(sessionId)) {
|
|
40
|
+
throw new SessionError(`invalid session id: unexpected characters: ${sessionId}`);
|
|
41
|
+
}
|
|
42
|
+
// Cap the length to keep filesystem APIs happy and avoid disk-name DoS.
|
|
43
|
+
if (sessionId.length > 128) {
|
|
44
|
+
throw new SessionError(`invalid session id: too long (max 128 chars)`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Resolve the `.code-shell` home dir. `CODE_SHELL_HOME` overrides the default
|
|
49
|
+
* `~/.code-shell` — mirrors Codex's `CODEX_HOME`. Tests set it to a temp dir
|
|
50
|
+
* (see bunfig.toml preload) so a `new Engine()` / `new SessionManager()` with
|
|
51
|
+
* no explicit storageDir doesn't pollute the user's real ~/.code-shell/sessions
|
|
52
|
+
* with throwaway test sessions (the rm-usage/test-model sidebar junk).
|
|
53
|
+
*/
|
|
54
|
+
export function codeShellHome() {
|
|
55
|
+
return process.env.CODE_SHELL_HOME || join(homedir(), ".code-shell");
|
|
56
|
+
}
|
|
10
57
|
export class SessionManager {
|
|
11
58
|
sessionsDir;
|
|
12
59
|
constructor(storageDir) {
|
|
13
|
-
this.sessionsDir = storageDir ?? join(
|
|
60
|
+
this.sessionsDir = storageDir ?? join(codeShellHome(), "sessions");
|
|
14
61
|
mkdirSync(this.sessionsDir, { recursive: true });
|
|
15
62
|
}
|
|
16
63
|
/**
|
|
@@ -20,7 +67,12 @@ export class SessionManager {
|
|
|
20
67
|
* nanoid. Either way the on-disk directory is materialized and the
|
|
21
68
|
* state.json + transcript.jsonl files are written before return.
|
|
22
69
|
*/
|
|
23
|
-
create(cwd, model, provider, explicitSessionId) {
|
|
70
|
+
create(cwd, model, provider, explicitSessionId, parentSessionId, origin) {
|
|
71
|
+
// External callers may pass any string; nanoid output is trusted. Either
|
|
72
|
+
// way the ID gets joined into a filesystem path, so the public entry
|
|
73
|
+
// point validates before that join.
|
|
74
|
+
if (explicitSessionId !== undefined)
|
|
75
|
+
assertSafeSessionId(explicitSessionId);
|
|
24
76
|
const sessionId = explicitSessionId ?? nanoid(16);
|
|
25
77
|
const sessionDir = join(this.sessionsDir, sessionId);
|
|
26
78
|
mkdirSync(sessionDir, { recursive: true });
|
|
@@ -34,6 +86,12 @@ export class SessionManager {
|
|
|
34
86
|
turnCount: 0,
|
|
35
87
|
invokedSkills: [],
|
|
36
88
|
status: "active",
|
|
89
|
+
// Always write the key: a sub-agent gets its parent sid; a top-level
|
|
90
|
+
// session gets explicit null. This lets the desktop disk-rebuild tell a
|
|
91
|
+
// new top-level session (key present, null) apart from a legacy session
|
|
92
|
+
// (key absent) and from a sub-agent (key present, non-empty string).
|
|
93
|
+
parentSessionId: parentSessionId ?? null,
|
|
94
|
+
...(origin ? { origin } : {}),
|
|
37
95
|
};
|
|
38
96
|
writeFileSync(join(sessionDir, "state.json"), JSON.stringify(state, null, 2), "utf-8");
|
|
39
97
|
const transcript = new Transcript(join(sessionDir, "transcript.jsonl"));
|
|
@@ -52,9 +110,19 @@ export class SessionManager {
|
|
|
52
110
|
* without catching SessionError.
|
|
53
111
|
*/
|
|
54
112
|
exists(sessionId) {
|
|
113
|
+
// exists() is a probe — callers use it to decide between resume and
|
|
114
|
+
// create-with-explicit-sid. Treat an invalid id as "not present"
|
|
115
|
+
// rather than letting the traversal-shaped string reach existsSync.
|
|
116
|
+
try {
|
|
117
|
+
assertSafeSessionId(sessionId);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
55
122
|
return existsSync(join(this.sessionsDir, sessionId));
|
|
56
123
|
}
|
|
57
124
|
resume(sessionId) {
|
|
125
|
+
assertSafeSessionId(sessionId);
|
|
58
126
|
const sessionDir = join(this.sessionsDir, sessionId);
|
|
59
127
|
if (!existsSync(sessionDir)) {
|
|
60
128
|
throw new SessionError(`Session not found: ${sessionId}`);
|
|
@@ -70,6 +138,9 @@ export class SessionManager {
|
|
|
70
138
|
return { state, transcript };
|
|
71
139
|
}
|
|
72
140
|
saveState(state) {
|
|
141
|
+
// state.sessionId could come from a deserialized state.json that was
|
|
142
|
+
// tampered with on disk. Validate before joining.
|
|
143
|
+
assertSafeSessionId(state.sessionId);
|
|
73
144
|
const sessionDir = join(this.sessionsDir, state.sessionId);
|
|
74
145
|
mkdirSync(sessionDir, { recursive: true });
|
|
75
146
|
// Atomic write: stage to .tmp, then rename. Protects against two processes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ValidatedSettings } from "./schema.js";
|
|
2
|
+
import type { EngineConfig } from "../engine/engine.js";
|
|
3
|
+
/**
|
|
4
|
+
* The subset of EngineConfig the config hot-reload ("layer 2") pushes onto an
|
|
5
|
+
* already-running session's `this.config`. These are the "disk-default" fields:
|
|
6
|
+
* read from settings.json at construction, otherwise frozen for the session's
|
|
7
|
+
* life. Request-override fields (permissionMode / goal / maxTurns /
|
|
8
|
+
* maxContextTokens / cwd) are intentionally excluded — those are per-request
|
|
9
|
+
* and handled by handleConfigure / run() options, not by disk hot-push.
|
|
10
|
+
*
|
|
11
|
+
* Reload semantics per field (see Engine.refreshRuntimeConfig):
|
|
12
|
+
* - preset: system-prompt / behavior hot-reloads (next-turn PromptComposer
|
|
13
|
+
* re-resolves it); the builtin TOOL SET it implies needs a session restart
|
|
14
|
+
* (registry is ctor-frozen, possibly shared via runtime).
|
|
15
|
+
* - customSystemPrompt / appendSystemPrompt / responseLanguage / userProfile:
|
|
16
|
+
* hot — re-read by the next-turn PromptComposer.
|
|
17
|
+
*
|
|
18
|
+
* IMPORTANT (#8): every field here is pushed as a PURE DISK value and will
|
|
19
|
+
* OVERRIDE any per-request slice override of the same field on the running
|
|
20
|
+
* session. Safe for the desktop host today (its slice carries only
|
|
21
|
+
* permissionMode+cwd, both excluded above). A future host that sets any of
|
|
22
|
+
* these per-request MUST exclude that field from the reload patch — or track
|
|
23
|
+
* per-request overrides separately — to avoid the reload clobbering it.
|
|
24
|
+
*/
|
|
25
|
+
export type DiskDefaultPatch = Pick<EngineConfig, "preset" | "customSystemPrompt" | "appendSystemPrompt" | "responseLanguage" | "userProfile" | "instructions" | "mcpServers">;
|
|
26
|
+
/**
|
|
27
|
+
* Derive the disk-default config patch from freshly-read settings.
|
|
28
|
+
*
|
|
29
|
+
* This MUST mirror how `agent-server-stdio.ts`'s engineFactory builds these
|
|
30
|
+
* same fields from `settings.agent` + `mergePluginMcpServers(...)`, so a
|
|
31
|
+
* reloaded running session and a newly-created session converge on identical
|
|
32
|
+
* config (no divergence). It reuses `personalizationFrom` for the three
|
|
33
|
+
* personalization fields rather than duplicating the mapping.
|
|
34
|
+
*/
|
|
35
|
+
export declare function diskDefaultsFrom(settings: ValidatedSettings): DiskDefaultPatch;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { personalizationFrom } from "./personalization.js";
|
|
2
|
+
import { mergePluginMcpServers } from "../plugins/installer/loadPluginMcp.js";
|
|
3
|
+
/**
|
|
4
|
+
* Derive the disk-default config patch from freshly-read settings.
|
|
5
|
+
*
|
|
6
|
+
* This MUST mirror how `agent-server-stdio.ts`'s engineFactory builds these
|
|
7
|
+
* same fields from `settings.agent` + `mergePluginMcpServers(...)`, so a
|
|
8
|
+
* reloaded running session and a newly-created session converge on identical
|
|
9
|
+
* config (no divergence). It reuses `personalizationFrom` for the three
|
|
10
|
+
* personalization fields rather than duplicating the mapping.
|
|
11
|
+
*/
|
|
12
|
+
export function diskDefaultsFrom(settings) {
|
|
13
|
+
const agent = settings.agent ?? {};
|
|
14
|
+
return {
|
|
15
|
+
preset: agent.preset,
|
|
16
|
+
customSystemPrompt: agent.customSystemPrompt,
|
|
17
|
+
appendSystemPrompt: agent.appendSystemPrompt,
|
|
18
|
+
...personalizationFrom(agent),
|
|
19
|
+
// Same merge engineFactory uses: user-configured servers + enabled
|
|
20
|
+
// plugins' servers. Plugin servers a disabled plugin would contribute are
|
|
21
|
+
// skipped, matching new-session construction.
|
|
22
|
+
mcpServers: mergePluginMcpServers(settings.mcpServers ?? {}, settings.disabledPlugins ?? []),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -4,12 +4,31 @@
|
|
|
4
4
|
* Priority: CLI flags > local > project > user > managed
|
|
5
5
|
*/
|
|
6
6
|
import { type ValidatedSettings } from "./schema.js";
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the user's home directory. Prefers `process.env.HOME` so that
|
|
9
|
+
* runtime env overrides (set after process start, e.g. in tests) actually
|
|
10
|
+
* take effect — on some runtimes (e.g. Bun on macOS) `os.homedir()` is
|
|
11
|
+
* cached from the user database at process startup and ignores later
|
|
12
|
+
* `process.env.HOME` mutations.
|
|
13
|
+
*/
|
|
14
|
+
export declare function userHome(): string;
|
|
7
15
|
export type SettingsSourceName = "managed" | "user" | "project" | "local" | "flag";
|
|
16
|
+
/**
|
|
17
|
+
* Which disk layers a SettingsManager is allowed to read.
|
|
18
|
+
* 'full' — managed + user (~/.code-shell) + project + local (host terminal entrypoints)
|
|
19
|
+
* 'project' — project + local only (${cwd}/.code-shell); never the host user dir. [default]
|
|
20
|
+
* 'isolated' — no disk layers at all; only explicit flag overrides.
|
|
21
|
+
* Flag overrides always apply regardless of scope. Default is 'project' so a
|
|
22
|
+
* codeshell library/SDK embedding never silently inherits the host user's
|
|
23
|
+
* personal ~/.code-shell config (keys, models, MCP servers, hooks).
|
|
24
|
+
*/
|
|
25
|
+
export type SettingsScope = "isolated" | "project" | "full";
|
|
8
26
|
export declare class SettingsManager {
|
|
9
27
|
private readonly cwd;
|
|
28
|
+
private readonly scope;
|
|
10
29
|
private sources;
|
|
11
30
|
private merged;
|
|
12
|
-
constructor(cwd?: string);
|
|
31
|
+
constructor(cwd?: string, scope?: SettingsScope);
|
|
13
32
|
/**
|
|
14
33
|
* Load settings from all sources.
|
|
15
34
|
*/
|
|
@@ -31,6 +50,31 @@ export declare class SettingsManager {
|
|
|
31
50
|
* The merged cache is invalidated so the next get() picks up the change.
|
|
32
51
|
*/
|
|
33
52
|
saveUserSetting(key: string, value: unknown): void;
|
|
53
|
+
/**
|
|
54
|
+
* Persist a single setting (dotted key path) to the PROJECT-level config
|
|
55
|
+
* file at ${cwd}/.code-shell/settings.json. This is where capabilityOverrides
|
|
56
|
+
* live — project overlays never touch the global user file. Atomic write +
|
|
57
|
+
* cache invalidation mirror saveUserSetting.
|
|
58
|
+
*/
|
|
59
|
+
saveProjectSetting(key: string, value: unknown, cwd: string): void;
|
|
60
|
+
/**
|
|
61
|
+
* Delete a single dotted key from the PROJECT-level config file. Used to
|
|
62
|
+
* express "inherit" — we don't persist the literal "inherit"; we remove the
|
|
63
|
+
* override key. No-ops if the file or any intermediate segment is absent.
|
|
64
|
+
*/
|
|
65
|
+
deleteProjectSetting(key: string, cwd: string): void;
|
|
66
|
+
/**
|
|
67
|
+
* Read ONE scope's raw settings file, validated but UNMERGED. Capability
|
|
68
|
+
* overlay math needs the project overlay and the user/global baseline
|
|
69
|
+
* separately — the merged get() collapses provenance and can't express
|
|
70
|
+
* tri-state inheritance. user → ~/.code-shell/settings.json, project →
|
|
71
|
+
* ${cwd}/.code-shell/settings.json. Only keys actually present in the file
|
|
72
|
+
* are returned (defaults are not synthesized), so an absent file → {}.
|
|
73
|
+
*/
|
|
74
|
+
getForScope(scope: "user" | "project", cwd?: string): Partial<ValidatedSettings>;
|
|
75
|
+
private projectSettingsPath;
|
|
76
|
+
private readJsonObject;
|
|
77
|
+
private atomicWriteJson;
|
|
34
78
|
private loadJsonFile;
|
|
35
79
|
private deepMerge;
|
|
36
80
|
}
|
package/dist/settings/manager.js
CHANGED
|
@@ -15,34 +15,44 @@ import { migrateModels } from "../migrate-models.js";
|
|
|
15
15
|
* cached from the user database at process startup and ignores later
|
|
16
16
|
* `process.env.HOME` mutations.
|
|
17
17
|
*/
|
|
18
|
-
function userHome() {
|
|
18
|
+
export function userHome() {
|
|
19
19
|
return process.env.HOME ?? homedir();
|
|
20
20
|
}
|
|
21
21
|
export class SettingsManager {
|
|
22
22
|
cwd;
|
|
23
|
+
scope;
|
|
23
24
|
sources = [];
|
|
24
25
|
merged = null;
|
|
25
|
-
constructor(cwd = process.cwd()) {
|
|
26
|
+
constructor(cwd = process.cwd(), scope = "project") {
|
|
26
27
|
this.cwd = cwd;
|
|
28
|
+
this.scope = scope;
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
29
31
|
* Load settings from all sources.
|
|
30
32
|
*/
|
|
31
33
|
load(flagOverrides) {
|
|
32
34
|
this.sources = [];
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
35
|
+
// Scope gates which disk layers we read. 'full' reads the host user dir
|
|
36
|
+
// (~/.code-shell); 'project' and 'isolated' never do. See SettingsScope.
|
|
37
|
+
const readUser = this.scope === "full";
|
|
38
|
+
const readProject = this.scope !== "isolated";
|
|
39
|
+
if (readUser) {
|
|
40
|
+
// 1. Managed (lowest priority)
|
|
41
|
+
this.loadJsonFile(join(userHome(), ".code-shell", "settings.managed.json"), "managed", 0);
|
|
42
|
+
// 2. User — only ~/.code-shell/. We used to also read ~/.claude/settings.json
|
|
43
|
+
// for "zero-migration from Claude Code", but Claude Code's schema diverges
|
|
44
|
+
// (e.g. `model` is a string there, an object here). Merging caused boot
|
|
45
|
+
// crashes on machines that had Claude Code installed but never ran us.
|
|
46
|
+
// File-level compat (CLAUDE.md, .claude/skills/) is kept elsewhere — only
|
|
47
|
+
// the settings.json read is dropped.
|
|
48
|
+
this.loadJsonFile(join(userHome(), ".code-shell", "settings.json"), "user", 1);
|
|
49
|
+
}
|
|
50
|
+
if (readProject) {
|
|
51
|
+
// 3. Project
|
|
52
|
+
this.loadJsonFile(join(this.cwd, ".code-shell", "settings.json"), "project", 2);
|
|
53
|
+
// 4. Local
|
|
54
|
+
this.loadJsonFile(join(this.cwd, ".code-shell", "settings.local.json"), "local", 3);
|
|
55
|
+
}
|
|
46
56
|
// 5. CLI flags (highest priority)
|
|
47
57
|
if (flagOverrides && Object.keys(flagOverrides).length > 0) {
|
|
48
58
|
this.sources.push({ name: "flag", priority: 4, data: flagOverrides });
|
|
@@ -54,9 +64,10 @@ export class SettingsManager {
|
|
|
54
64
|
// Auto-migrate legacy models[] in the user settings file. Runs directly
|
|
55
65
|
// on the user-scope file (not the merged result), because the merge
|
|
56
66
|
// collapses provenance and the migration needs to write back to a
|
|
57
|
-
// single physical file.
|
|
67
|
+
// single physical file. Gated on readUser: under non-full scope we must
|
|
68
|
+
// not read — let alone rewrite — the host's ~/.code-shell/settings.json.
|
|
58
69
|
const userPath = join(userHome(), ".code-shell", "settings.json");
|
|
59
|
-
if (existsSync(userPath)) {
|
|
70
|
+
if (readUser && existsSync(userPath)) {
|
|
60
71
|
try {
|
|
61
72
|
const userRaw = JSON.parse(readFileSync(userPath, "utf-8"));
|
|
62
73
|
const result = migrateModels({
|
|
@@ -144,6 +155,99 @@ export class SettingsManager {
|
|
|
144
155
|
renameSync(tmp, path);
|
|
145
156
|
this.invalidate();
|
|
146
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Persist a single setting (dotted key path) to the PROJECT-level config
|
|
160
|
+
* file at ${cwd}/.code-shell/settings.json. This is where capabilityOverrides
|
|
161
|
+
* live — project overlays never touch the global user file. Atomic write +
|
|
162
|
+
* cache invalidation mirror saveUserSetting.
|
|
163
|
+
*/
|
|
164
|
+
saveProjectSetting(key, value, cwd) {
|
|
165
|
+
const path = this.projectSettingsPath(cwd);
|
|
166
|
+
const current = this.readJsonObject(path);
|
|
167
|
+
const parts = key.split(".");
|
|
168
|
+
let target = current;
|
|
169
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
170
|
+
const seg = parts[i];
|
|
171
|
+
const next = target[seg];
|
|
172
|
+
if (!next || typeof next !== "object" || Array.isArray(next)) {
|
|
173
|
+
target[seg] = {};
|
|
174
|
+
}
|
|
175
|
+
target = target[seg];
|
|
176
|
+
}
|
|
177
|
+
target[parts[parts.length - 1]] = value;
|
|
178
|
+
this.atomicWriteJson(path, current);
|
|
179
|
+
this.invalidate();
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Delete a single dotted key from the PROJECT-level config file. Used to
|
|
183
|
+
* express "inherit" — we don't persist the literal "inherit"; we remove the
|
|
184
|
+
* override key. No-ops if the file or any intermediate segment is absent.
|
|
185
|
+
*/
|
|
186
|
+
deleteProjectSetting(key, cwd) {
|
|
187
|
+
const path = this.projectSettingsPath(cwd);
|
|
188
|
+
if (!existsSync(path))
|
|
189
|
+
return;
|
|
190
|
+
const current = this.readJsonObject(path);
|
|
191
|
+
const parts = key.split(".");
|
|
192
|
+
let target = current;
|
|
193
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
194
|
+
const next = target?.[parts[i]];
|
|
195
|
+
if (!next || typeof next !== "object" || Array.isArray(next))
|
|
196
|
+
return;
|
|
197
|
+
target = next;
|
|
198
|
+
}
|
|
199
|
+
if (target)
|
|
200
|
+
delete target[parts[parts.length - 1]];
|
|
201
|
+
this.atomicWriteJson(path, current);
|
|
202
|
+
this.invalidate();
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Read ONE scope's raw settings file, validated but UNMERGED. Capability
|
|
206
|
+
* overlay math needs the project overlay and the user/global baseline
|
|
207
|
+
* separately — the merged get() collapses provenance and can't express
|
|
208
|
+
* tri-state inheritance. user → ~/.code-shell/settings.json, project →
|
|
209
|
+
* ${cwd}/.code-shell/settings.json. Only keys actually present in the file
|
|
210
|
+
* are returned (defaults are not synthesized), so an absent file → {}.
|
|
211
|
+
*/
|
|
212
|
+
getForScope(scope, cwd) {
|
|
213
|
+
const path = scope === "user"
|
|
214
|
+
? join(userHome(), ".code-shell", "settings.json")
|
|
215
|
+
: this.projectSettingsPath(cwd ?? this.cwd);
|
|
216
|
+
const raw = this.readJsonObject(path);
|
|
217
|
+
// validateSettings applies defaults; for a scope view we want only the
|
|
218
|
+
// file's own keys, so validate then project back the present keys.
|
|
219
|
+
const validated = validateSettings(raw);
|
|
220
|
+
const out = {};
|
|
221
|
+
for (const k of Object.keys(raw))
|
|
222
|
+
out[k] = validated[k];
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
projectSettingsPath(cwd) {
|
|
226
|
+
if (!cwd || cwd.trim().length === 0) {
|
|
227
|
+
throw new Error("project setting write requires a non-empty cwd");
|
|
228
|
+
}
|
|
229
|
+
return join(cwd, ".code-shell", "settings.json");
|
|
230
|
+
}
|
|
231
|
+
readJsonObject(path) {
|
|
232
|
+
if (!existsSync(path))
|
|
233
|
+
return {};
|
|
234
|
+
try {
|
|
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 {};
|
|
244
|
+
}
|
|
245
|
+
atomicWriteJson(path, data) {
|
|
246
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
247
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
248
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
249
|
+
renameSync(tmp, path);
|
|
250
|
+
}
|
|
147
251
|
loadJsonFile(path, name, priority) {
|
|
148
252
|
if (!existsSync(path))
|
|
149
253
|
return;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { SettingsManager } from "./manager.js";
|
|
6
|
+
/**
|
|
7
|
+
* Scope isolation tests. We point HOME at a temp dir (userHome() reads
|
|
8
|
+
* process.env.HOME first) and use a separate temp cwd, then seed each of the
|
|
9
|
+
* four disk layers with a uniquely-named mcpServers entry. mcpServers is a
|
|
10
|
+
* record merged key-by-key, so every layer that was actually read leaves its
|
|
11
|
+
* own key in the merged result — letting us detect which layers were read
|
|
12
|
+
* (unlike arrays, which deep-merge replaces wholesale).
|
|
13
|
+
*/
|
|
14
|
+
describe("SettingsManager scope", () => {
|
|
15
|
+
let home;
|
|
16
|
+
let cwd;
|
|
17
|
+
let prevHome;
|
|
18
|
+
function serverFor(tag) {
|
|
19
|
+
return { [tag]: { name: tag, command: "echo", transport: "stdio" } };
|
|
20
|
+
}
|
|
21
|
+
function writeSettings(dir, file, tag) {
|
|
22
|
+
mkdirSync(join(dir, ".code-shell"), { recursive: true });
|
|
23
|
+
writeFileSync(join(dir, ".code-shell", file), JSON.stringify({ mcpServers: serverFor(tag) }), "utf-8");
|
|
24
|
+
}
|
|
25
|
+
function layersIn(sm) {
|
|
26
|
+
return Object.keys(sm.get().mcpServers ?? {}).sort();
|
|
27
|
+
}
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
prevHome = process.env.HOME;
|
|
30
|
+
home = mkdtempSync(join(tmpdir(), "cs-home-"));
|
|
31
|
+
cwd = mkdtempSync(join(tmpdir(), "cs-cwd-"));
|
|
32
|
+
process.env.HOME = home;
|
|
33
|
+
writeSettings(home, "settings.managed.json", "MANAGED");
|
|
34
|
+
writeSettings(home, "settings.json", "USER");
|
|
35
|
+
writeSettings(cwd, "settings.json", "PROJECT");
|
|
36
|
+
writeSettings(cwd, "settings.local.json", "LOCAL");
|
|
37
|
+
});
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
process.env.HOME = prevHome;
|
|
40
|
+
rmSync(home, { recursive: true, force: true });
|
|
41
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
42
|
+
});
|
|
43
|
+
test("full reads all four disk layers", () => {
|
|
44
|
+
expect(layersIn(new SettingsManager(cwd, "full"))).toEqual([
|
|
45
|
+
"LOCAL",
|
|
46
|
+
"MANAGED",
|
|
47
|
+
"PROJECT",
|
|
48
|
+
"USER",
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
test("project (default) reads project+local, never user/managed", () => {
|
|
52
|
+
expect(layersIn(new SettingsManager(cwd, "project"))).toEqual(["LOCAL", "PROJECT"]);
|
|
53
|
+
});
|
|
54
|
+
test("default scope is project", () => {
|
|
55
|
+
expect(layersIn(new SettingsManager(cwd))).toEqual(["LOCAL", "PROJECT"]);
|
|
56
|
+
});
|
|
57
|
+
test("isolated reads no disk layers", () => {
|
|
58
|
+
expect(layersIn(new SettingsManager(cwd, "isolated"))).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
test("flag overrides apply even under isolated", () => {
|
|
61
|
+
const sm = new SettingsManager(cwd, "isolated");
|
|
62
|
+
sm.load({ mcpServers: serverFor("FLAG") });
|
|
63
|
+
expect(layersIn(sm)).toEqual(["FLAG"]);
|
|
64
|
+
});
|
|
65
|
+
test("non-full scope never triggers user-file model migration write-back", () => {
|
|
66
|
+
// Legacy models[] in the user file would normally be migrated (and the
|
|
67
|
+
// file rewritten with a .bak). Under project/isolated we must not touch
|
|
68
|
+
// the user file at all.
|
|
69
|
+
writeFileSync(join(home, ".code-shell", "settings.json"), JSON.stringify({ models: [{ id: "legacy", provider: "x" }] }), "utf-8");
|
|
70
|
+
new SettingsManager(cwd, "project").get();
|
|
71
|
+
expect(existsSync(join(home, ".code-shell", "settings.json.bak"))).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** The agent.* subset of settings the Engine needs for personalization +
|
|
2
|
+
* instruction-file compat. Hosts spread this into EngineConfig so the three
|
|
3
|
+
* fields stay wired identically across desktop / TUI / TCP (avoids per-host drift). */
|
|
4
|
+
export interface PersonalizationConfig {
|
|
5
|
+
responseLanguage?: string;
|
|
6
|
+
userProfile?: string;
|
|
7
|
+
instructions?: {
|
|
8
|
+
compatClaude?: boolean;
|
|
9
|
+
compatCodex?: boolean;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export declare function personalizationFrom(agent: {
|
|
13
|
+
responseLanguage?: string;
|
|
14
|
+
userProfile?: string;
|
|
15
|
+
instructions?: {
|
|
16
|
+
compatClaude?: boolean;
|
|
17
|
+
compatCodex?: boolean;
|
|
18
|
+
};
|
|
19
|
+
}): PersonalizationConfig;
|