@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.
Files changed (283) hide show
  1. package/dist/agent/agent-definition-registry.d.ts +29 -0
  2. package/dist/agent/agent-definition-registry.js +74 -0
  3. package/dist/agent/agent-definition.d.ts +37 -0
  4. package/dist/agent/agent-definition.js +59 -0
  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 +118 -18
  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 +256 -16
  68. package/dist/engine/engine.js +856 -200
  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 +32 -7
  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 +10 -0
  171. package/dist/prompt/composer.js +29 -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 +45 -1
  220. package/dist/settings/manager.js +121 -17
  221. package/dist/settings/manager.test.d.ts +1 -0
  222. package/dist/settings/manager.test.js +73 -0
  223. package/dist/settings/personalization.d.ts +19 -0
  224. package/dist/settings/personalization.js +7 -0
  225. package/dist/settings/schema.d.ts +1163 -126
  226. package/dist/settings/schema.js +126 -10
  227. package/dist/skills/scanner.d.ts +13 -6
  228. package/dist/skills/scanner.js +23 -4
  229. package/dist/tool-system/builtin/add-marketplace.d.ts +12 -0
  230. package/dist/tool-system/builtin/add-marketplace.js +76 -0
  231. package/dist/tool-system/builtin/agent-registry.d.ts +15 -0
  232. package/dist/tool-system/builtin/agent-registry.js +14 -4
  233. package/dist/tool-system/builtin/agent.d.ts +55 -0
  234. package/dist/tool-system/builtin/agent.js +165 -17
  235. package/dist/tool-system/builtin/apply-patch/index.js +22 -3
  236. package/dist/tool-system/builtin/arena.js +6 -7
  237. package/dist/tool-system/builtin/complete-goal.d.ts +25 -0
  238. package/dist/tool-system/builtin/complete-goal.js +45 -0
  239. package/dist/tool-system/builtin/cron.js +53 -9
  240. package/dist/tool-system/builtin/edit.d.ts +2 -1
  241. package/dist/tool-system/builtin/edit.js +6 -1
  242. package/dist/tool-system/builtin/generate-image.d.ts +25 -0
  243. package/dist/tool-system/builtin/generate-image.js +146 -0
  244. package/dist/tool-system/builtin/glob.js +7 -0
  245. package/dist/tool-system/builtin/grep.js +6 -0
  246. package/dist/tool-system/builtin/index.d.ts +18 -1
  247. package/dist/tool-system/builtin/index.js +62 -1
  248. package/dist/tool-system/builtin/notebook-edit.d.ts +2 -1
  249. package/dist/tool-system/builtin/notebook-edit.js +8 -1
  250. package/dist/tool-system/builtin/read.d.ts +2 -1
  251. package/dist/tool-system/builtin/read.js +9 -1
  252. package/dist/tool-system/builtin/skill.js +23 -5
  253. package/dist/tool-system/builtin/sleep.js +9 -3
  254. package/dist/tool-system/builtin/task.js +4 -1
  255. package/dist/tool-system/builtin/update-automation-memory.d.ts +24 -0
  256. package/dist/tool-system/builtin/update-automation-memory.js +60 -0
  257. package/dist/tool-system/builtin/view-image.d.ts +18 -0
  258. package/dist/tool-system/builtin/view-image.js +96 -0
  259. package/dist/tool-system/builtin/web-search.d.ts +4 -1
  260. package/dist/tool-system/builtin/web-search.js +36 -3
  261. package/dist/tool-system/builtin/write.d.ts +2 -1
  262. package/dist/tool-system/builtin/write.js +7 -1
  263. package/dist/tool-system/context.d.ts +43 -0
  264. package/dist/tool-system/executor.d.ts +0 -1
  265. package/dist/tool-system/executor.js +34 -40
  266. package/dist/tool-system/investigation-guard.d.ts +4 -1
  267. package/dist/tool-system/investigation-guard.js +19 -3
  268. package/dist/tool-system/mcp-manager.d.ts +36 -12
  269. package/dist/tool-system/mcp-manager.js +173 -10
  270. package/dist/tool-system/path-policy.d.ts +94 -0
  271. package/dist/tool-system/path-policy.js +279 -0
  272. package/dist/tool-system/permission.js +43 -50
  273. package/dist/tool-system/plan-mode-allowlist.d.ts +21 -0
  274. package/dist/tool-system/plan-mode-allowlist.js +43 -0
  275. package/dist/tool-system/registry.js +12 -1
  276. package/dist/tool-system/validation.d.ts +10 -0
  277. package/dist/tool-system/validation.js +16 -3
  278. package/dist/types.d.ts +90 -15
  279. package/dist/utils/format.js +4 -2
  280. package/dist/utils/lockfile.js +9 -2
  281. package/dist/utils/theme.d.ts +6 -0
  282. package/dist/utils/theme.js +4 -2
  283. package/package.json +2 -1
@@ -11,6 +11,7 @@ export type RunQueueExecutor = (runId: string) => Promise<void>;
11
11
  export declare class RunQueue {
12
12
  private readonly concurrency;
13
13
  private readonly pending;
14
+ private readonly pendingSet;
14
15
  private readonly active;
15
16
  private executor;
16
17
  private draining;
@@ -6,7 +6,10 @@
6
6
  */
7
7
  export class RunQueue {
8
8
  concurrency;
9
+ // `pending` keeps FIFO order; `pendingSet` mirrors its membership for O(1)
10
+ // dedup/lookup (the array's includes() was O(n)).
9
11
  pending = [];
12
+ pendingSet = new Set();
10
13
  active = new Set();
11
14
  executor = null;
12
15
  draining = false;
@@ -17,15 +20,17 @@ export class RunQueue {
17
20
  this.executor = fn;
18
21
  }
19
22
  enqueue(runId) {
20
- if (this.pending.includes(runId) || this.active.has(runId))
23
+ if (this.pendingSet.has(runId) || this.active.has(runId))
21
24
  return;
22
25
  this.pending.push(runId);
26
+ this.pendingSet.add(runId);
23
27
  this.drain();
24
28
  }
25
29
  cancel(runId) {
26
30
  const idx = this.pending.indexOf(runId);
27
31
  if (idx !== -1) {
28
32
  this.pending.splice(idx, 1);
33
+ this.pendingSet.delete(runId);
29
34
  return true;
30
35
  }
31
36
  return false;
@@ -34,7 +39,7 @@ export class RunQueue {
34
39
  return this.active.has(runId);
35
40
  }
36
41
  isPending(runId) {
37
- return this.pending.includes(runId);
42
+ return this.pendingSet.has(runId);
38
43
  }
39
44
  get activeCount() {
40
45
  return this.active.size;
@@ -57,6 +62,7 @@ export class RunQueue {
57
62
  return;
58
63
  while (this.active.size < this.concurrency && this.pending.length > 0) {
59
64
  const runId = this.pending.shift();
65
+ this.pendingSet.delete(runId);
60
66
  this.active.add(runId);
61
67
  this.executor(runId)
62
68
  .catch((err) => {
@@ -56,6 +56,13 @@ export interface CreateRunManagerOptions {
56
56
  defaultTags?: string[];
57
57
  /** Metadata merged into every submitted run. Submit input wins on conflicts. */
58
58
  defaultMetadata?: Record<string, unknown>;
59
+ /**
60
+ * Override the approval backend for every run. Used by unattended hosts
61
+ * (automation/cron) that must auto-decide without a UI — e.g.
62
+ * `new HeadlessApprovalBackend("approve-read-only")`. When unset, runs use
63
+ * the interactive run-aware backend.
64
+ */
65
+ approvalBackend?: import("../tool-system/permission.js").ApprovalBackend;
59
66
  }
60
67
  /**
61
68
  * Create a fully configured RunManager with one call.
@@ -41,6 +41,7 @@ export function createRunManager(options) {
41
41
  customSystemPrompt: options.customSystemPrompt,
42
42
  appendSystemPrompt: options.appendSystemPrompt,
43
43
  hooks: options.hooks,
44
+ ...(options.approvalBackend ? { approvalBackend: options.approvalBackend } : {}),
44
45
  },
45
46
  concurrency: options.concurrency ?? 1,
46
47
  runsDir,
@@ -9,9 +9,9 @@ export { RunQueue } from "./RunQueue.js";
9
9
  export { RunApprovalBackend, createRunAskUserFn, type RunLifecycleHooks, type PendingApproval, type PendingInput, } from "./RunApprovalBackend.js";
10
10
  export { CheckpointWriter, type CheckpointWriterConfig } from "./CheckpointWriter.js";
11
11
  export { ArtifactTracker, type ArtifactTrackerConfig } from "./ArtifactTracker.js";
12
- export { RunLock, type RunLockConfig } from "./RunLock.js";
12
+ export { RunLock, type RunLockConfig, type RunLockAcquireResult } from "./RunLock.js";
13
13
  export { Heartbeat, type HeartbeatConfig, type HeartbeatData } from "./Heartbeat.js";
14
14
  export { NoopEvaluator, CompositeEvaluator, type Evaluator, type EvaluatorResult, type EvaluatorVerdict, type EvaluatorContext, } from "./Evaluator.js";
15
- export { EngineRunner, type EngineRunnerConfig, type RunExecutionHandle, type RunExecutor, type CustomToolEntry, } from "./EngineRunner.js";
15
+ export { EngineRunner, AUTOMATION_PROMPT_NOTE, AUTOMATION_RUN_SOURCE, type EngineRunnerConfig, type RunExecutionHandle, type RunExecutor, type CustomToolEntry, } from "./EngineRunner.js";
16
16
  export { RunManager, type RunManagerConfig } from "./RunManager.js";
17
17
  export { createRunManager, type CreateRunManagerOptions } from "./factory.js";
package/dist/run/index.js CHANGED
@@ -16,7 +16,7 @@ export { Heartbeat } from "./Heartbeat.js";
16
16
  // Evaluator
17
17
  export { NoopEvaluator, CompositeEvaluator, } from "./Evaluator.js";
18
18
  // Runner
19
- export { EngineRunner, } from "./EngineRunner.js";
19
+ export { EngineRunner, AUTOMATION_PROMPT_NOTE, AUTOMATION_RUN_SOURCE, } from "./EngineRunner.js";
20
20
  // Manager
21
21
  export { RunManager } from "./RunManager.js";
22
22
  // Factory
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Extract the file target of a trailing shell redirect (`> file` / `>> file`).
3
+ * Handles quoted paths with spaces — the old /> \s*(\S+)\s*$/ matched only the
4
+ * first non-whitespace run, so `> "my file.txt"` yielded `"my`
5
+ * (review-2026-05-30). Returns undefined when there's no trailing redirect.
6
+ */
7
+ export declare function parseRedirectTarget(command: string): string | undefined;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Extract the file target of a trailing shell redirect (`> file` / `>> file`).
3
+ * Handles quoted paths with spaces — the old /> \s*(\S+)\s*$/ matched only the
4
+ * first non-whitespace run, so `> "my file.txt"` yielded `"my`
5
+ * (review-2026-05-30). Returns undefined when there's no trailing redirect.
6
+ */
7
+ export function parseRedirectTarget(command) {
8
+ // Optional quote captured in group 1; body in group 2 (or unquoted in 3).
9
+ const m = command.match(/>>?\s*(?:(['"])(.*?)\1|(\S+))\s*$/);
10
+ if (!m)
11
+ return undefined;
12
+ return m[2] !== undefined ? m[2] : m[3];
13
+ }
package/dist/run/types.js CHANGED
@@ -10,7 +10,7 @@
10
10
  * Key = current status, Value = allowed next statuses.
11
11
  */
12
12
  export const VALID_TRANSITIONS = {
13
- queued: ["running", "cancelled"],
13
+ queued: ["running", "blocked", "cancelled"],
14
14
  running: [
15
15
  "waiting_input",
16
16
  "waiting_approval",
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Build the argv to open a URL in the system browser, per platform. Returned
3
+ * as { cmd, args } for execFile (no shell), so the URL is a single literal
4
+ * argument and can't be shell-interpreted — the old code interpolated it into
5
+ * an exec() shell string (review-2026-05-30, security).
6
+ *
7
+ * Windows uses `cmd /c start "" <url>`: `start`'s first quoted arg is the
8
+ * window title, so an empty title slot keeps the URL from being read as one.
9
+ */
10
+ export declare function browserOpenCommand(platform: NodeJS.Platform, url: string): {
11
+ cmd: string;
12
+ args: string[];
13
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Build the argv to open a URL in the system browser, per platform. Returned
3
+ * as { cmd, args } for execFile (no shell), so the URL is a single literal
4
+ * argument and can't be shell-interpreted — the old code interpolated it into
5
+ * an exec() shell string (review-2026-05-30, security).
6
+ *
7
+ * Windows uses `cmd /c start "" <url>`: `start`'s first quoted arg is the
8
+ * window title, so an empty title slot keeps the URL from being read as one.
9
+ */
10
+ export function browserOpenCommand(platform, url) {
11
+ if (platform === "darwin")
12
+ return { cmd: "open", args: [url] };
13
+ if (platform === "win32")
14
+ return { cmd: "cmd", args: ["/c", "start", "", url] };
15
+ return { cmd: "xdg-open", args: [url] };
16
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Dream consolidation — the LLM tool-call loop that cleans up the `dream`
3
+ * memory scope (deduplicate, merge, drop stale, improve descriptions).
4
+ *
5
+ * This logic used to live as two private methods on Engine
6
+ * (`runDreamLoop` + `dispatchDreamTool`). It was extracted here so it can be
7
+ * driven from two places:
8
+ * - the end-of-session auto-dream pipeline (Engine.runDreamLoop delegates here)
9
+ * - a manual "整理 / Dream" trigger from the desktop host, which constructs a
10
+ * seed Engine purely to obtain a toolRegistry + LLM client and then calls
11
+ * this directly (it never runs a turn).
12
+ *
13
+ * The loop is intentionally small and offline:
14
+ * - No streaming, no UI events — it runs in the background.
15
+ * - No permission prompts — there is no interactive backend on this path, so
16
+ * we hard-reject any attempt to Save/Delete in the "user" scope before
17
+ * dispatching. The "dream" scope is the LLM's workspace and goes through
18
+ * freely.
19
+ * - Capped at MAX_TURNS LLM round-trips and MAX_WRITES total mutations to
20
+ * bound damage on misbehavior.
21
+ */
22
+ import type { LLMClientBase } from "../llm/client-base.js";
23
+ import type { ToolRegistry } from "../tool-system/registry.js";
24
+ import type { ToolContext } from "../tool-system/context.js";
25
+ export interface DreamConsolidationInput {
26
+ /** LLM client for the consolidation calls. */
27
+ llmClient: LLMClientBase;
28
+ /** Tool registry that must contain the four Memory* tools. */
29
+ toolRegistry: ToolRegistry;
30
+ /**
31
+ * Base tool context. `cwd` is overridden with `projectDir` (or process.cwd())
32
+ * so the memory tools resolve the right memory directory. Callers with an
33
+ * Engine pass `engine.buildToolContext()`.
34
+ */
35
+ toolContext: ToolContext;
36
+ /** Project root; when set, memories are scoped per-project. */
37
+ projectDir?: string;
38
+ /** For log attribution. */
39
+ sessionId?: string;
40
+ }
41
+ export interface DreamConsolidationResult {
42
+ /** True if the loop ran (with or without writes); false if it bailed early. */
43
+ ran: boolean;
44
+ /** The LLM's final one-paragraph summary of what it changed (may be empty). */
45
+ summary: string;
46
+ }
47
+ /**
48
+ * Drive the dream-scope consolidation tool-call loop.
49
+ *
50
+ * Loads both scopes (user is read-only context; dream is the workspace),
51
+ * builds the dream prompts, and runs the LLM with a whitelisted subset of
52
+ * memory tools until it stops calling tools or hits the turn cap.
53
+ *
54
+ * Returns `ran: false` if the registry is missing the memory tools (nothing
55
+ * happened); otherwise `ran: true` with the final summary text.
56
+ */
57
+ export declare function runDreamConsolidation(input: DreamConsolidationInput): Promise<DreamConsolidationResult>;
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Dream consolidation — the LLM tool-call loop that cleans up the `dream`
3
+ * memory scope (deduplicate, merge, drop stale, improve descriptions).
4
+ *
5
+ * This logic used to live as two private methods on Engine
6
+ * (`runDreamLoop` + `dispatchDreamTool`). It was extracted here so it can be
7
+ * driven from two places:
8
+ * - the end-of-session auto-dream pipeline (Engine.runDreamLoop delegates here)
9
+ * - a manual "整理 / Dream" trigger from the desktop host, which constructs a
10
+ * seed Engine purely to obtain a toolRegistry + LLM client and then calls
11
+ * this directly (it never runs a turn).
12
+ *
13
+ * The loop is intentionally small and offline:
14
+ * - No streaming, no UI events — it runs in the background.
15
+ * - No permission prompts — there is no interactive backend on this path, so
16
+ * we hard-reject any attempt to Save/Delete in the "user" scope before
17
+ * dispatching. The "dream" scope is the LLM's workspace and goes through
18
+ * freely.
19
+ * - Capped at MAX_TURNS LLM round-trips and MAX_WRITES total mutations to
20
+ * bound damage on misbehavior.
21
+ */
22
+ import { MemoryManager } from "../session/memory.js";
23
+ import { buildDreamSystemPrompt, buildDreamUserPrompt, } from "./auto-dream.js";
24
+ import { logger } from "../logging/logger.js";
25
+ const MAX_TURNS = 8;
26
+ const MAX_WRITES = 10;
27
+ const MEMORY_TOOL_NAMES = ["MemoryList", "MemoryRead", "MemorySave", "MemoryDelete"];
28
+ /**
29
+ * Drive the dream-scope consolidation tool-call loop.
30
+ *
31
+ * Loads both scopes (user is read-only context; dream is the workspace),
32
+ * builds the dream prompts, and runs the LLM with a whitelisted subset of
33
+ * memory tools until it stops calling tools or hits the turn cap.
34
+ *
35
+ * Returns `ran: false` if the registry is missing the memory tools (nothing
36
+ * happened); otherwise `ran: true` with the final summary text.
37
+ */
38
+ export async function runDreamConsolidation(input) {
39
+ const { llmClient, toolRegistry, projectDir, sessionId } = input;
40
+ const memoryTools = MEMORY_TOOL_NAMES
41
+ .map((n) => toolRegistry.getTool(n))
42
+ .filter((t) => t != null);
43
+ if (memoryTools.length < MEMORY_TOOL_NAMES.length) {
44
+ logger.warn("memory.dream_missing_tools", {
45
+ sessionId,
46
+ found: memoryTools.map((t) => t.name),
47
+ });
48
+ return { ran: false, summary: "" };
49
+ }
50
+ // Dream sees BOTH scopes — user/ is read-only context so it can spot
51
+ // duplicates spanning scopes, dream/ is the workspace it edits.
52
+ const mm = new MemoryManager({ projectDir });
53
+ const userMems = mm.loadScope("user");
54
+ const dreamMems = mm.loadScope("dream");
55
+ const systemPrompt = buildDreamSystemPrompt();
56
+ const userPrompt = buildDreamUserPrompt(userMems, dreamMems);
57
+ // Strip RegisteredTool down to the shape createMessage expects.
58
+ const toolDefs = memoryTools.map((t) => ({
59
+ name: t.name,
60
+ description: t.description,
61
+ inputSchema: t.inputSchema,
62
+ }));
63
+ const toolCtx = {
64
+ ...input.toolContext,
65
+ cwd: projectDir ?? process.cwd(),
66
+ };
67
+ const messages = [{ role: "user", content: userPrompt }];
68
+ let writeBudget = MAX_WRITES;
69
+ for (let turn = 0; turn < MAX_TURNS; turn++) {
70
+ const resp = await llmClient.createMessage({
71
+ systemPrompt,
72
+ messages,
73
+ tools: toolDefs,
74
+ maxTokens: 2048,
75
+ recordUsage: false,
76
+ reasoning: { mode: "off" },
77
+ });
78
+ if (resp.toolCalls.length === 0) {
79
+ logger.info("memory.dream_finished", {
80
+ sessionId,
81
+ turn,
82
+ finalText: resp.text.slice(0, 500),
83
+ });
84
+ return { ran: true, summary: resp.text };
85
+ }
86
+ // Echo the assistant turn back so subsequent turns see the tool_use ids.
87
+ const assistantContent = [];
88
+ if (resp.text)
89
+ assistantContent.push({ type: "text", text: resp.text });
90
+ for (const tc of resp.toolCalls) {
91
+ assistantContent.push({
92
+ type: "tool_use",
93
+ id: tc.id,
94
+ name: tc.toolName,
95
+ input: tc.args,
96
+ });
97
+ }
98
+ messages.push({ role: "assistant", content: assistantContent });
99
+ // Dispatch every tool call requested in this turn.
100
+ const toolResults = [];
101
+ for (const tc of resp.toolCalls) {
102
+ const result = await dispatchDreamTool(tc, toolRegistry, toolCtx, () => {
103
+ if (writeBudget <= 0)
104
+ return false;
105
+ writeBudget--;
106
+ return true;
107
+ });
108
+ toolResults.push({
109
+ type: "tool_result",
110
+ tool_use_id: tc.id,
111
+ content: result,
112
+ });
113
+ }
114
+ messages.push({ role: "user", content: toolResults });
115
+ }
116
+ logger.warn("memory.dream_hit_turn_cap", { sessionId, maxTurns: MAX_TURNS });
117
+ return { ran: true, summary: "" };
118
+ }
119
+ /**
120
+ * Execute one memory tool call inside the dream loop. Enforces the two
121
+ * dream-loop invariants the prompt also states:
122
+ * - Only the 4 memory tools are dispatchable.
123
+ * - Save/Delete in "user" scope is refused (returned as a tool error)
124
+ * because dream runs without an interactive permission backend.
125
+ */
126
+ async function dispatchDreamTool(tc, toolRegistry, ctx, consumeWriteBudget) {
127
+ const allowed = new Set(MEMORY_TOOL_NAMES);
128
+ if (!allowed.has(tc.toolName)) {
129
+ return `Error: tool "${tc.toolName}" is not available in the dream loop`;
130
+ }
131
+ const isWrite = tc.toolName === "MemorySave" || tc.toolName === "MemoryDelete";
132
+ if (isWrite) {
133
+ const scope = tc.args?.scope;
134
+ if (scope !== "dream") {
135
+ return (`Error: dream loop may only write to scope "dream", got "${scope}". ` +
136
+ `User-scope changes require interactive permission, which is not available here.`);
137
+ }
138
+ if (!consumeWriteBudget()) {
139
+ return "Error: dream write budget exhausted — stop calling write tools and summarize instead.";
140
+ }
141
+ }
142
+ try {
143
+ const result = await toolRegistry.executeTool(tc.toolName, tc.args, { ctx });
144
+ if (result.isError)
145
+ return result.error ?? `Error executing ${tc.toolName}`;
146
+ return result.result ?? "";
147
+ }
148
+ catch (err) {
149
+ return `Error executing ${tc.toolName}: ${err.message}`;
150
+ }
151
+ }
@@ -36,10 +36,19 @@ export class MemoryOrchestrator {
36
36
  // --------------- 1. Extract durable memories ---------------
37
37
  let extracted = 0;
38
38
  try {
39
+ let t = Date.now();
39
40
  const existing = mm.loadAll();
41
+ const loadMs = Date.now() - t;
42
+ t = Date.now();
40
43
  const extractionPrompt = buildExtractionPrompt(transcript, existing);
44
+ const promptMs = Date.now() - t;
45
+ t = Date.now();
41
46
  const response = await this.options.callLLM("You are a memory extraction assistant. Extract only durable, reusable information worth carrying into future sessions.", extractionPrompt);
47
+ const llmMs = Date.now() - t;
48
+ t = Date.now();
42
49
  const entries = parseExtractionResponse(response);
50
+ const parseMs = Date.now() - t;
51
+ t = Date.now();
43
52
  for (const entry of entries) {
44
53
  mm.save({
45
54
  type: entry.type,
@@ -48,11 +57,20 @@ export class MemoryOrchestrator {
48
57
  content: entry.content,
49
58
  });
50
59
  }
60
+ const saveMs = Date.now() - t;
51
61
  extracted = entries.length;
52
62
  logger.info("memory.extraction_done", {
53
63
  sessionId,
54
64
  extracted,
55
65
  elapsedMs: Date.now() - startTime,
66
+ loadMs,
67
+ promptMs,
68
+ llmMs,
69
+ parseMs,
70
+ saveMs,
71
+ existingCount: existing.length,
72
+ transcriptMessages: transcript.length,
73
+ responseChars: response.length,
56
74
  });
57
75
  }
58
76
  catch (err) {
@@ -15,6 +15,14 @@ export interface NotificationOptions {
15
15
  * Send a desktop notification.
16
16
  */
17
17
  export declare function notify(options: NotificationOptions): void;
18
+ /** Escape a string for embedding inside an AppleScript double-quoted literal. */
19
+ export declare function escapeAppleScriptString(str: string): string;
20
+ /** Build the osascript argv (a single `-e <script>` pair). */
21
+ export declare function buildOsascriptArgs(title: string, message: string, sound: boolean): string[];
22
+ /** Build the notify-send argv with title/message as separate tokens. */
23
+ export declare function buildNotifySendArgs(title: string, message: string, urgency: "low" | "normal" | "critical"): string[];
24
+ /** Build the powershell.exe argv (a single `-Command <script>` element). */
25
+ export declare function buildPowershellArgs(title: string, message: string): string[];
18
26
  /**
19
27
  * Send a notification that a task/agent has completed.
20
28
  */
@@ -4,7 +4,7 @@
4
4
  * Sends notifications when tasks complete, agents finish, or errors occur.
5
5
  * Falls back gracefully when no notification system is available.
6
6
  */
7
- import { execSync } from "node:child_process";
7
+ import { execFileSync } from "node:child_process";
8
8
  /**
9
9
  * Send a desktop notification.
10
10
  */
@@ -12,37 +12,54 @@ export function notify(options) {
12
12
  const { title, message, sound = false, urgency = "normal" } = options;
13
13
  try {
14
14
  if (process.platform === "darwin") {
15
- // macOS: osascript
16
- const soundFlag = sound ? 'sound name "default"' : "";
17
- execSync(`osascript -e 'display notification "${escape(message)}" with title "${escape(title)}" ${soundFlag}'`, { timeout: 5000 });
15
+ // macOS: osascript. Pass the script as a single -e argv element via
16
+ // execFileSync (no shell), so title/message are never seen by the shell.
17
+ execFileSync("osascript", buildOsascriptArgs(title, message, sound), { timeout: 5000 });
18
18
  }
19
19
  else if (process.platform === "linux") {
20
- // Linux: notify-send
21
- const urgencyFlag = urgency === "critical" ? "-u critical" : urgency === "low" ? "-u low" : "-u normal";
22
- execSync(`notify-send ${urgencyFlag} "${escape(title)}" "${escape(message)}"`, {
23
- timeout: 5000,
24
- });
20
+ // Linux: notify-send. argv keeps title/message as separate tokens.
21
+ execFileSync("notify-send", buildNotifySendArgs(title, message, urgency), { timeout: 5000 });
25
22
  }
26
23
  else if (process.platform === "win32") {
27
- // Windows: PowerShell toast
28
- const ps = `
29
- [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
30
- $xml = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)
31
- $text = $xml.GetElementsByTagName('text')
32
- $text[0].AppendChild($xml.CreateTextNode('${escape(title)}')) | Out-Null
33
- $text[1].AppendChild($xml.CreateTextNode('${escape(message)}')) | Out-Null
34
- $toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
35
- [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('CodeShell').Show($toast)
36
- `.trim();
37
- execSync(`powershell.exe -NoProfile -Command "${ps.replace(/\n/g, "; ")}"`, {
38
- timeout: 5000,
39
- });
24
+ // Windows: PowerShell toast. The script is one -Command argv element
25
+ // (no outer shell); title/message are escaped for PowerShell single
26
+ // quotes (' '').
27
+ execFileSync("powershell.exe", buildPowershellArgs(title, message), { timeout: 5000 });
40
28
  }
41
29
  }
42
30
  catch {
43
31
  // Silently fail — notifications are best-effort
44
32
  }
45
33
  }
34
+ /** Escape a string for embedding inside an AppleScript double-quoted literal. */
35
+ export function escapeAppleScriptString(str) {
36
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, " ");
37
+ }
38
+ /** Build the osascript argv (a single `-e <script>` pair). */
39
+ export function buildOsascriptArgs(title, message, sound) {
40
+ const soundClause = sound ? ' sound name "default"' : "";
41
+ const script = `display notification "${escapeAppleScriptString(message)}"` +
42
+ ` with title "${escapeAppleScriptString(title)}"${soundClause}`;
43
+ return ["-e", script];
44
+ }
45
+ /** Build the notify-send argv with title/message as separate tokens. */
46
+ export function buildNotifySendArgs(title, message, urgency) {
47
+ return ["-u", urgency, title, message];
48
+ }
49
+ /** Build the powershell.exe argv (a single `-Command <script>` element). */
50
+ export function buildPowershellArgs(title, message) {
51
+ const esc = (s) => s.replace(/'/g, "''").replace(/\n/g, " ");
52
+ const ps = [
53
+ "[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null",
54
+ "$xml = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)",
55
+ "$text = $xml.GetElementsByTagName('text')",
56
+ `$text[0].AppendChild($xml.CreateTextNode('${esc(title)}')) | Out-Null`,
57
+ `$text[1].AppendChild($xml.CreateTextNode('${esc(message)}')) | Out-Null`,
58
+ "$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)",
59
+ "[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('CodeShell').Show($toast)",
60
+ ].join("; ");
61
+ return ["-NoProfile", "-Command", ps];
62
+ }
46
63
  /**
47
64
  * Send a notification that a task/agent has completed.
48
65
  */
@@ -64,6 +81,3 @@ export function notifyError(context, error) {
64
81
  urgency: "critical",
65
82
  });
66
83
  }
67
- function escape(str) {
68
- return str.replace(/['"\\]/g, "\\$&").replace(/\n/g, " ");
69
- }
@@ -5,7 +5,8 @@
5
5
  */
6
6
  import { createServer } from "node:http";
7
7
  import { randomBytes, createHash } from "node:crypto";
8
- import { exec } from "node:child_process";
8
+ import { execFile } from "node:child_process";
9
+ import { browserOpenCommand } from "./browser-open.js";
9
10
  /**
10
11
  * Generate PKCE code verifier and challenge.
11
12
  */
@@ -18,12 +19,9 @@ function generatePKCE() {
18
19
  * Open a URL in the system browser.
19
20
  */
20
21
  function openBrowser(url) {
21
- const cmd = process.platform === "darwin"
22
- ? `open "${url}"`
23
- : process.platform === "win32"
24
- ? `start "${url}"`
25
- : `xdg-open "${url}"`;
26
- exec(cmd, () => { });
22
+ // execFile with an argv array — no shell, so the URL can't be interpreted.
23
+ const { cmd, args } = browserOpenCommand(process.platform, url);
24
+ execFile(cmd, args, () => { });
27
25
  }
28
26
  /**
29
27
  * Run the OAuth authorization code flow.
@@ -0,0 +1,8 @@
1
+ import type { SessionMemoryEntry } from "./session-memory.js";
2
+ /**
3
+ * Order session memories most-recent-first by their `createdAt` field. The
4
+ * caller previously sorted by filename (= sessionId), which has no
5
+ * chronological meaning (review-2026-05-30). Returns a new array; input is not
6
+ * mutated.
7
+ */
8
+ export declare function sortSessionMemoriesByRecency(entries: SessionMemoryEntry[]): SessionMemoryEntry[];
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Order session memories most-recent-first by their `createdAt` field. The
3
+ * caller previously sorted by filename (= sessionId), which has no
4
+ * chronological meaning (review-2026-05-30). Returns a new array; input is not
5
+ * mutated.
6
+ */
7
+ export function sortSessionMemoriesByRecency(entries) {
8
+ return [...entries].sort((a, b) => (a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0));
9
+ }
@@ -7,6 +7,7 @@
7
7
  import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs";
8
8
  import { join } from "node:path";
9
9
  import { homedir } from "node:os";
10
+ import { sortSessionMemoriesByRecency } from "./session-memory-sort.js";
10
11
  const MEMORY_DIR = join(homedir(), ".code-shell", "session-memories");
11
12
  /**
12
13
  * Save a session memory entry.
@@ -36,20 +37,18 @@ export function loadSessionMemory(sessionId) {
36
37
  export function listSessionMemories(limit = 50) {
37
38
  if (!existsSync(MEMORY_DIR))
38
39
  return [];
39
- const files = readdirSync(MEMORY_DIR)
40
- .filter((f) => f.endsWith(".json"))
41
- .sort()
42
- .reverse()
43
- .slice(0, limit);
40
+ // Read all entries, then order by createdAt (not by filename — the filename
41
+ // is the sessionId, which has no chronological meaning), then take `limit`.
44
42
  const entries = [];
45
- for (const file of files) {
43
+ for (const file of readdirSync(MEMORY_DIR)) {
44
+ if (!file.endsWith(".json"))
45
+ continue;
46
46
  try {
47
- const entry = JSON.parse(readFileSync(join(MEMORY_DIR, file), "utf-8"));
48
- entries.push(entry);
47
+ entries.push(JSON.parse(readFileSync(join(MEMORY_DIR, file), "utf-8")));
49
48
  }
50
49
  catch { }
51
50
  }
52
- return entries;
51
+ return sortSessionMemoriesByRecency(entries).slice(0, limit);
53
52
  }
54
53
  /**
55
54
  * Search session memories by keyword.
@@ -29,10 +29,17 @@ export class FileHistory {
29
29
  const existing = this.snapshots.find((s) => s.filePath === absPath && s.hash === hash);
30
30
  if (existing)
31
31
  return existing;
32
- // Create backup
32
+ // Create backup. The filename must uniquely identify (path, content): a
33
+ // bare 100-char tail of the path can collide for two different files
34
+ // whose tails coincide, and with the same ms timestamp the second
35
+ // copyFileSync would silently overwrite the first backup while both
36
+ // index entries point to it (restore would then return the wrong file's
37
+ // content). Fold a full-path hash and the content hash into the name so
38
+ // distinct (path, content) pairs never share a backup file.
33
39
  const timestamp = Date.now();
34
- const safeName = absPath.replace(/[/\\:]/g, "_").slice(-100);
35
- const backupPath = join(this.historyDir, `${timestamp}_${safeName}`);
40
+ const pathHash = createHash("md5").update(absPath).digest("hex").slice(0, 8);
41
+ const safeName = absPath.replace(/[/\\:]/g, "_").slice(-80);
42
+ const backupPath = join(this.historyDir, `${timestamp}_${pathHash}_${hash.slice(0, 8)}_${safeName}`);
36
43
  copyFileSync(absPath, backupPath);
37
44
  const snapshot = {
38
45
  filePath: absPath,