@cjhyy/code-shell-core 0.5.0-rc.1 → 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.
Files changed (278) hide show
  1. package/dist/agent/agent-definition-registry.d.ts +18 -3
  2. package/dist/agent/agent-definition-registry.js +47 -18
  3. package/dist/agent/agent-definition.d.ts +17 -0
  4. package/dist/agent/agent-definition.js +22 -1
  5. package/dist/arena/arena.js +11 -12
  6. package/dist/arena/context/context-tools.js +2 -4
  7. package/dist/arena/context/within-root.d.ts +7 -0
  8. package/dist/arena/context/within-root.js +15 -0
  9. package/dist/arena/iterate/phases/argue.js +8 -2
  10. package/dist/arena/iterate/phases/revise.js +1 -1
  11. package/dist/arena/iterate/phases/tournament.js +3 -3
  12. package/dist/arena/phases/adjudication.js +1 -4
  13. package/dist/arena/phases/build-consensus.js +1 -4
  14. package/dist/arena/phases/cross-review.js +2 -8
  15. package/dist/arena/phases/debate-rounds.js +1 -4
  16. package/dist/arena/phases/participant-research.js +1 -4
  17. package/dist/arena/phases/planning-detail-expansion.js +8 -10
  18. package/dist/arena/planner.js +0 -1
  19. package/dist/arena/providers/docs.d.ts +1 -0
  20. package/dist/arena/providers/docs.js +5 -2
  21. package/dist/arena/providers/repo.d.ts +1 -0
  22. package/dist/arena/providers/repo.js +5 -2
  23. package/dist/arena/strategies/utils.js +36 -3
  24. package/dist/arena/transitions.d.ts +7 -2
  25. package/dist/arena/transitions.js +9 -4
  26. package/dist/arena/types.d.ts +8 -2
  27. package/dist/automation/cron-expr.d.ts +31 -0
  28. package/dist/automation/cron-expr.js +151 -0
  29. package/dist/automation/index.d.ts +41 -0
  30. package/dist/automation/index.js +39 -0
  31. package/dist/automation/runner.d.ts +67 -0
  32. package/dist/automation/runner.js +56 -0
  33. package/dist/automation/scheduler.d.ts +156 -0
  34. package/dist/automation/scheduler.js +546 -0
  35. package/dist/automation/store.d.ts +39 -0
  36. package/dist/automation/store.js +119 -0
  37. package/dist/automation/write-policy.d.ts +34 -0
  38. package/dist/automation/write-policy.js +113 -0
  39. package/dist/automation/write-run.d.ts +51 -0
  40. package/dist/automation/write-run.js +38 -0
  41. package/dist/capability-control/index.d.ts +5 -0
  42. package/dist/capability-control/index.js +3 -0
  43. package/dist/capability-control/overlay.d.ts +49 -0
  44. package/dist/capability-control/overlay.js +89 -0
  45. package/dist/capability-control/project.d.ts +65 -0
  46. package/dist/capability-control/project.js +147 -0
  47. package/dist/capability-control/service.d.ts +70 -0
  48. package/dist/capability-control/service.js +147 -0
  49. package/dist/capability-control/types.d.ts +62 -0
  50. package/dist/capability-control/types.js +17 -0
  51. package/dist/cli/agent-server-stdio.d.ts +12 -1
  52. package/dist/cli/agent-server-stdio.js +114 -19
  53. package/dist/cli/agent-server-tcp.d.ts +20 -0
  54. package/dist/cli/agent-server-tcp.js +120 -0
  55. package/dist/cli/graceful-shutdown.d.ts +27 -0
  56. package/dist/cli/graceful-shutdown.js +30 -0
  57. package/dist/context/compaction.js +23 -15
  58. package/dist/context/manager.d.ts +0 -20
  59. package/dist/context/manager.js +0 -37
  60. package/dist/cron/cron-runtime.d.ts +2 -0
  61. package/dist/cron/cron-runtime.js +2 -0
  62. package/dist/cron/cron-store.d.ts +2 -0
  63. package/dist/cron/cron-store.js +2 -0
  64. package/dist/cron/scheduler.d.ts +5 -32
  65. package/dist/cron/scheduler.js +5 -113
  66. package/dist/data/openrouter-sync.js +1 -1
  67. package/dist/engine/engine.d.ts +208 -14
  68. package/dist/engine/engine.js +754 -169
  69. package/dist/engine/goal.d.ts +42 -0
  70. package/dist/engine/goal.js +52 -0
  71. package/dist/engine/image-compression.d.ts +81 -0
  72. package/dist/engine/image-compression.js +186 -0
  73. package/dist/engine/image-policy.d.ts +152 -0
  74. package/dist/engine/image-policy.js +165 -0
  75. package/dist/engine/reactive-threshold.d.ts +13 -0
  76. package/dist/engine/reactive-threshold.js +17 -0
  77. package/dist/engine/runtime.js +7 -0
  78. package/dist/engine/session-title.d.ts +13 -0
  79. package/dist/engine/session-title.js +34 -0
  80. package/dist/engine/turn-loop.d.ts +29 -8
  81. package/dist/engine/turn-loop.js +498 -316
  82. package/dist/git/parse-log.d.ts +13 -0
  83. package/dist/git/parse-log.js +21 -0
  84. package/dist/git/utils.d.ts +8 -6
  85. package/dist/git/utils.js +57 -26
  86. package/dist/git/worktree.js +33 -18
  87. package/dist/hooks/events.d.ts +23 -1
  88. package/dist/hooks/goal-stop-hook.d.ts +30 -0
  89. package/dist/hooks/goal-stop-hook.js +102 -0
  90. package/dist/hooks/hook-output.d.ts +33 -0
  91. package/dist/hooks/hook-output.js +79 -0
  92. package/dist/hooks/registry.d.ts +7 -0
  93. package/dist/hooks/registry.js +38 -1
  94. package/dist/hooks/shell-runner.d.ts +2 -0
  95. package/dist/hooks/shell-runner.js +43 -2
  96. package/dist/index.d.ts +31 -6
  97. package/dist/index.js +30 -3
  98. package/dist/llm/capabilities/reasoning-control.d.ts +24 -0
  99. package/dist/llm/capabilities/reasoning-control.js +38 -0
  100. package/dist/llm/capabilities/rules.js +33 -3
  101. package/dist/llm/capabilities/types.d.ts +22 -2
  102. package/dist/llm/clamp-max-tokens.d.ts +11 -0
  103. package/dist/llm/clamp-max-tokens.js +17 -0
  104. package/dist/llm/client-base.d.ts +41 -4
  105. package/dist/llm/client-base.js +67 -13
  106. package/dist/llm/client-factory.d.ts +3 -3
  107. package/dist/llm/client-factory.js +2 -2
  108. package/dist/llm/model-pool.d.ts +15 -11
  109. package/dist/llm/model-pool.js +23 -20
  110. package/dist/llm/provider-catalog.d.ts +2 -2
  111. package/dist/llm/providers/anthropic.d.ts +32 -2
  112. package/dist/llm/providers/anthropic.js +147 -8
  113. package/dist/llm/providers/openai.d.ts +11 -2
  114. package/dist/llm/providers/openai.js +253 -52
  115. package/dist/llm/reasoning-setting.d.ts +51 -0
  116. package/dist/llm/reasoning-setting.js +32 -0
  117. package/dist/llm/stop-reason.d.ts +13 -0
  118. package/dist/llm/stop-reason.js +21 -0
  119. package/dist/llm/strip-vision.d.ts +32 -0
  120. package/dist/llm/strip-vision.js +94 -0
  121. package/dist/llm/types.d.ts +5 -6
  122. package/dist/logging/logger.js +18 -4
  123. package/dist/logging/sanitize-messages.d.ts +10 -0
  124. package/dist/logging/sanitize-messages.js +90 -0
  125. package/dist/lsp/client.js +28 -8
  126. package/dist/lsp/manager.js +2 -1
  127. package/dist/lsp/root-path.d.ts +9 -0
  128. package/dist/lsp/root-path.js +12 -0
  129. package/dist/onboarding.js +18 -11
  130. package/dist/plugins/gitOps.d.ts +34 -0
  131. package/dist/plugins/gitOps.js +59 -3
  132. package/dist/plugins/installer/codex/convertAgents.d.ts +7 -0
  133. package/dist/plugins/installer/codex/convertAgents.js +55 -0
  134. package/dist/plugins/installer/codex/convertMcp.d.ts +8 -0
  135. package/dist/plugins/installer/codex/convertMcp.js +45 -0
  136. package/dist/plugins/installer/codex/convertSkills.d.ts +12 -0
  137. package/dist/plugins/installer/codex/convertSkills.js +33 -0
  138. package/dist/plugins/installer/detectFormat.d.ts +2 -0
  139. package/dist/plugins/installer/detectFormat.js +6 -0
  140. package/dist/plugins/installer/install.d.ts +7 -0
  141. package/dist/plugins/installer/install.js +99 -0
  142. package/dist/plugins/installer/installFromSource.d.ts +13 -0
  143. package/dist/plugins/installer/installFromSource.js +45 -0
  144. package/dist/plugins/installer/list.d.ts +9 -0
  145. package/dist/plugins/installer/list.js +30 -0
  146. package/dist/plugins/installer/loadPluginAgents.d.ts +3 -0
  147. package/dist/plugins/installer/loadPluginAgents.js +23 -0
  148. package/dist/plugins/installer/loadPluginMcp.d.ts +7 -0
  149. package/dist/plugins/installer/loadPluginMcp.js +74 -0
  150. package/dist/plugins/installer/parseSource.d.ts +20 -0
  151. package/dist/plugins/installer/parseSource.js +74 -0
  152. package/dist/plugins/installer/paths.d.ts +5 -0
  153. package/dist/plugins/installer/paths.js +25 -0
  154. package/dist/plugins/installer/types.d.ts +49 -0
  155. package/dist/plugins/installer/types.js +27 -0
  156. package/dist/plugins/installer/uninstall.d.ts +2 -0
  157. package/dist/plugins/installer/uninstall.js +14 -0
  158. package/dist/plugins/installer/update.d.ts +14 -0
  159. package/dist/plugins/installer/update.js +65 -0
  160. package/dist/plugins/loadPluginHooks.d.ts +8 -1
  161. package/dist/plugins/loadPluginHooks.js +11 -1
  162. package/dist/plugins/marketplaceManager.d.ts +8 -1
  163. package/dist/plugins/marketplaceManager.js +38 -4
  164. package/dist/plugins/pluginCommandHook.js +34 -0
  165. package/dist/plugins/pluginInstaller.d.ts +21 -0
  166. package/dist/plugins/pluginInstaller.js +128 -29
  167. package/dist/plugins/schemas.js +40 -6
  168. package/dist/plugins/types.d.ts +4 -0
  169. package/dist/preset/index.js +16 -0
  170. package/dist/prompt/composer.d.ts +4 -0
  171. package/dist/prompt/composer.js +28 -4
  172. package/dist/prompt/sections/orchestration.md +8 -0
  173. package/dist/protocol/chat-session-manager.d.ts +8 -1
  174. package/dist/protocol/chat-session-manager.js +10 -0
  175. package/dist/protocol/chat-session.d.ts +17 -0
  176. package/dist/protocol/chat-session.js +28 -0
  177. package/dist/protocol/redact.d.ts +50 -0
  178. package/dist/protocol/redact.js +71 -0
  179. package/dist/protocol/server.d.ts +41 -2
  180. package/dist/protocol/server.js +231 -33
  181. package/dist/protocol/tcp-transport.d.ts +45 -0
  182. package/dist/protocol/tcp-transport.js +74 -0
  183. package/dist/protocol/types.d.ts +23 -0
  184. package/dist/protocol/types.js +6 -0
  185. package/dist/remote/bridge.d.ts +11 -2
  186. package/dist/remote/bridge.js +61 -33
  187. package/dist/run/ArtifactTracker.js +5 -4
  188. package/dist/run/EngineRunner.d.ts +27 -1
  189. package/dist/run/EngineRunner.js +46 -8
  190. package/dist/run/FileRunStore.js +32 -8
  191. package/dist/run/RunLock.d.ts +28 -2
  192. package/dist/run/RunLock.js +49 -7
  193. package/dist/run/RunManager.js +51 -3
  194. package/dist/run/RunQueue.d.ts +1 -0
  195. package/dist/run/RunQueue.js +8 -2
  196. package/dist/run/factory.d.ts +7 -0
  197. package/dist/run/factory.js +1 -0
  198. package/dist/run/index.d.ts +2 -2
  199. package/dist/run/index.js +1 -1
  200. package/dist/run/redirect-target.d.ts +7 -0
  201. package/dist/run/redirect-target.js +13 -0
  202. package/dist/run/types.js +1 -1
  203. package/dist/services/browser-open.d.ts +13 -0
  204. package/dist/services/browser-open.js +16 -0
  205. package/dist/services/dream-consolidation.d.ts +57 -0
  206. package/dist/services/dream-consolidation.js +151 -0
  207. package/dist/services/memory-orchestrator.js +18 -0
  208. package/dist/services/notifier.d.ts +8 -0
  209. package/dist/services/notifier.js +39 -25
  210. package/dist/services/oauth.js +5 -7
  211. package/dist/services/session-memory-sort.d.ts +8 -0
  212. package/dist/services/session-memory-sort.js +9 -0
  213. package/dist/services/session-memory.js +8 -9
  214. package/dist/session/file-history.js +10 -3
  215. package/dist/session/session-manager.d.ts +23 -1
  216. package/dist/session/session-manager.js +73 -2
  217. package/dist/settings/disk-defaults.d.ts +35 -0
  218. package/dist/settings/disk-defaults.js +24 -0
  219. package/dist/settings/manager.d.ts +33 -0
  220. package/dist/settings/manager.js +94 -1
  221. package/dist/settings/personalization.d.ts +19 -0
  222. package/dist/settings/personalization.js +7 -0
  223. package/dist/settings/schema.d.ts +1142 -126
  224. package/dist/settings/schema.js +119 -10
  225. package/dist/tool-system/builtin/add-marketplace.d.ts +12 -0
  226. package/dist/tool-system/builtin/add-marketplace.js +76 -0
  227. package/dist/tool-system/builtin/agent-registry.d.ts +11 -0
  228. package/dist/tool-system/builtin/agent-registry.js +8 -4
  229. package/dist/tool-system/builtin/agent.d.ts +29 -10
  230. package/dist/tool-system/builtin/agent.js +108 -52
  231. package/dist/tool-system/builtin/apply-patch/index.js +22 -3
  232. package/dist/tool-system/builtin/arena.js +6 -7
  233. package/dist/tool-system/builtin/complete-goal.d.ts +25 -0
  234. package/dist/tool-system/builtin/complete-goal.js +45 -0
  235. package/dist/tool-system/builtin/cron.js +53 -9
  236. package/dist/tool-system/builtin/edit.d.ts +2 -1
  237. package/dist/tool-system/builtin/edit.js +6 -1
  238. package/dist/tool-system/builtin/generate-image.d.ts +25 -0
  239. package/dist/tool-system/builtin/generate-image.js +146 -0
  240. package/dist/tool-system/builtin/glob.js +7 -0
  241. package/dist/tool-system/builtin/grep.js +6 -0
  242. package/dist/tool-system/builtin/index.d.ts +18 -1
  243. package/dist/tool-system/builtin/index.js +62 -1
  244. package/dist/tool-system/builtin/notebook-edit.d.ts +2 -1
  245. package/dist/tool-system/builtin/notebook-edit.js +8 -1
  246. package/dist/tool-system/builtin/read.d.ts +2 -1
  247. package/dist/tool-system/builtin/read.js +9 -1
  248. package/dist/tool-system/builtin/sleep.js +9 -3
  249. package/dist/tool-system/builtin/task.js +4 -1
  250. package/dist/tool-system/builtin/update-automation-memory.d.ts +24 -0
  251. package/dist/tool-system/builtin/update-automation-memory.js +60 -0
  252. package/dist/tool-system/builtin/view-image.d.ts +18 -0
  253. package/dist/tool-system/builtin/view-image.js +96 -0
  254. package/dist/tool-system/builtin/web-search.d.ts +4 -1
  255. package/dist/tool-system/builtin/web-search.js +36 -3
  256. package/dist/tool-system/builtin/write.d.ts +2 -1
  257. package/dist/tool-system/builtin/write.js +7 -1
  258. package/dist/tool-system/context.d.ts +12 -0
  259. package/dist/tool-system/executor.d.ts +0 -1
  260. package/dist/tool-system/executor.js +34 -40
  261. package/dist/tool-system/investigation-guard.d.ts +4 -1
  262. package/dist/tool-system/investigation-guard.js +19 -3
  263. package/dist/tool-system/mcp-manager.d.ts +36 -12
  264. package/dist/tool-system/mcp-manager.js +173 -10
  265. package/dist/tool-system/path-policy.d.ts +94 -0
  266. package/dist/tool-system/path-policy.js +279 -0
  267. package/dist/tool-system/permission.js +43 -50
  268. package/dist/tool-system/plan-mode-allowlist.d.ts +21 -0
  269. package/dist/tool-system/plan-mode-allowlist.js +43 -0
  270. package/dist/tool-system/registry.js +12 -1
  271. package/dist/tool-system/validation.d.ts +10 -0
  272. package/dist/tool-system/validation.js +16 -3
  273. package/dist/types.d.ts +90 -15
  274. package/dist/utils/format.js +4 -2
  275. package/dist/utils/lockfile.js +9 -2
  276. package/dist/utils/theme.d.ts +6 -0
  277. package/dist/utils/theme.js +4 -2
  278. 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(homedir(), ".code-shell", "sessions");
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,6 +4,14 @@
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";
8
16
  /**
9
17
  * Which disk layers a SettingsManager is allowed to read.
@@ -42,6 +50,31 @@ export declare class SettingsManager {
42
50
  * The merged cache is invalidated so the next get() picks up the change.
43
51
  */
44
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;
45
78
  private loadJsonFile;
46
79
  private deepMerge;
47
80
  }
@@ -15,7 +15,7 @@ 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 {
@@ -155,6 +155,99 @@ export class SettingsManager {
155
155
  renameSync(tmp, path);
156
156
  this.invalidate();
157
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
+ }
158
251
  loadJsonFile(path, name, priority) {
159
252
  if (!existsSync(path))
160
253
  return;
@@ -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;
@@ -0,0 +1,7 @@
1
+ export function personalizationFrom(agent) {
2
+ return {
3
+ responseLanguage: agent.responseLanguage,
4
+ userProfile: agent.userProfile,
5
+ instructions: agent.instructions,
6
+ };
7
+ }