@chatcode/chatcode-cli 3.0.4-rc.7 → 3.0.5-rc.1

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 (143) hide show
  1. package/README.i18n.yaml +6 -6
  2. package/README.md +10 -4
  3. package/README.zh.md +10 -4
  4. package/lib/bin.js +23 -9
  5. package/lib/{command-errors-Ca64Lgba.js → command-errors-CsRCh0gk.js} +12 -12
  6. package/lib/{command-trees-CVc4Xu_z.js → command-trees-CD_43rxX.js} +2 -2
  7. package/lib/{component-identity-6aEut5Yt.js → component-identity-DGA2-TeB.js} +3 -3
  8. package/lib/{dump-config-ZzrfvtnX.js → dump-config-D8kXfdTs.js} +1 -1
  9. package/lib/dump-config-DuCc-QB7.js +2 -0
  10. package/lib/{dump-config-schema-BlO-eZ-J.js → dump-config-schema-CtZbroi6.js} +2 -2
  11. package/lib/{extension-events-C7MUpqVE.js → extension-events-D0BjYRqT.js} +21 -21
  12. package/lib/{host-access-CLbyAGT5.js → host-access-DLJjuR07.js} +1 -1
  13. package/lib/{keymap-82JwLLHe.js → keymap-C4cs2IBN.js} +2 -2
  14. package/lib/{plugin-BlQlWIR5.js → plugin-CGtTByud.js} +801 -456
  15. package/lib/{plugin-BZdazFXS.js → plugin-CYw-MBRn.js} +1 -1
  16. package/lib/{profile-boot-B0aEEQIR.js → profile-boot-CffsubnV.js} +7 -7
  17. package/lib/profile-boot.js +1 -1
  18. package/lib/{scenes-BNS0ZFyQ.js → scenes-CbAGK5Uy.js} +3 -3
  19. package/lib/{settings-sections-CXH24cu5.js → settings-sections-DYKDa04e.js} +3 -3
  20. package/lib/{statusBarSettings-BAMzn8fX.js → statusBarSettings-T1m8xad4.js} +3 -3
  21. package/lib/{themes-DXUIvP7m.js → themes-BY2ui1Dv.js} +12 -12
  22. package/lib/tui-compat-command-trees.js +1 -1
  23. package/lib/tui-compat-extensions.js +3 -3
  24. package/lib/tui-compat-plugin-host.js +5 -5
  25. package/lib/tui-compat-scenes.js +1 -1
  26. package/lib/tui-compat-settings-sections.js +1 -1
  27. package/lib/tui-compat-working-activity.js +1 -1
  28. package/lib/tui-compat-workspaces.js +1 -1
  29. package/lib/tui-compat.js +7 -7
  30. package/lib/types/bin.d.ts +9 -0
  31. package/lib/types/tui-compat.d.ts +1 -1
  32. package/lib/types/web-instance-process.d.ts +20 -0
  33. package/lib/{web-instance-DbP7IHPv.js → web-instance-D_Ppi2Zo.js} +176 -14
  34. package/lib/{web-instance-B0jec7cq.js → web-instance-amvCJWnq.js} +1 -1
  35. package/lib/{workspaces-CUJXNU5W.js → workspaces-D5VKbESy.js} +2 -2
  36. package/package.json +28 -29
  37. package/runtime/manifest.json +2 -2
  38. package/runtime/node_modules/@deepseek-ai/dsh-agent-default-model/lib/index.js +7 -3
  39. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/client.js +11 -3
  40. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/client/contract/snapshot.d.ts +1 -0
  41. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/client/sessions/assistant-stream.d.ts +2 -0
  42. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/client/sessions/assistant-stream.js +4 -0
  43. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/client/sessions/session.js +10 -3
  44. package/runtime/node_modules/@deepseek-ai/dsh-app-boot/lib/index.js +32 -11
  45. package/runtime/node_modules/@deepseek-ai/dsh-atomic-write/lib/index.js +62 -11
  46. package/runtime/node_modules/@deepseek-ai/dsh-atomic-write/lib/types/darwin-lock.d.ts +7 -0
  47. package/runtime/node_modules/@deepseek-ai/dsh-atomic-write/lib/types/index.d.ts +7 -2
  48. package/runtime/node_modules/@deepseek-ai/dsh-atomic-write/lib/types/linux-lock.d.ts +1 -1
  49. package/runtime/node_modules/@deepseek-ai/dsh-client-locale/lib/client.js +7 -7
  50. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-agent-preset/lib/client.js +62 -62
  51. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-approval/lib/client.js +10 -10
  52. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-attachment/lib/client.js +49 -49
  53. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-chat/lib/client.js +204 -204
  54. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-commands/lib/client.js +16 -16
  55. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-conversation/lib/client.js +159 -157
  56. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-conversation/lib/types/client/service.d.ts +4 -2
  57. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-cordis/lib/client.js +74 -74
  58. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/client.js +129 -123
  59. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/index.js +19 -14
  60. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/types/changes.d.ts +2 -2
  61. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/types/client/Deliverables.d.ts +1 -2
  62. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/types/client/locales.d.ts +2 -1
  63. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/types/client/present-open.d.ts +1 -1
  64. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-deliverables/package.json +2 -4
  65. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-directory-picker-browse/lib/client.js +39 -39
  66. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-goal/lib/client.js +16 -16
  67. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-input-trigger/lib/client.js +23 -23
  68. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-jobs/lib/client.js +37 -37
  69. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-layout/lib/client.js +9 -9
  70. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-message-feedback/lib/client.js +12 -12
  71. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-model-selection/lib/client.js +119 -32
  72. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-model-selection/lib/types/client/catalog.d.ts +13 -0
  73. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-model-selection/lib/types/client/directory.d.ts +2 -0
  74. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-model-selection/lib/types/client/effort-preferences.d.ts +20 -0
  75. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-model-selection/lib/types/client/slots.d.ts +2 -1
  76. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-open-in-app/lib/client.js +8 -8
  77. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-permission-presets/lib/client.js +18 -18
  78. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-plan/lib/client.js +22 -22
  79. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-plugin-manager/lib/client.js +128 -128
  80. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-schedule/lib/client.js +182 -182
  81. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-account/lib/client.js +130 -130
  82. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-general/lib/client.js +50 -50
  83. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-models/lib/client.js +88 -88
  84. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-plugin-inventory/lib/client.js +36 -36
  85. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-plugins/lib/client.js +13 -13
  86. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-subagent/lib/client.js +27 -27
  87. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-shortcuts/lib/client.js +42 -42
  88. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-sidebar/lib/client.js +44 -46
  89. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-sidebar-browser/lib/client.js +25 -25
  90. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-sidebar-documentpreview/lib/client.excel.js +9 -9
  91. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-sidebar-documentpreview/lib/client.js +70 -70
  92. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-sidebar-documentpreview/lib/client.pdf.js +9 -9
  93. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-sidebar-files/lib/client.js +18 -18
  94. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-sidebar-right/lib/client.js +26 -26
  95. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-sidebar-terminal/lib/client.js +16 -16
  96. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-sidebar-terminal/lib/client.terminal.js +11 -11
  97. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-skill/lib/client.js +20 -20
  98. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-subagent/lib/client.js +37 -37
  99. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-theme/lib/client.js +27 -27
  100. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-tool/lib/client.js +95 -95
  101. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-trajectory/lib/client.js +197 -197
  102. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-user-questions/lib/client.js +49 -49
  103. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workflow-run/lib/client.js +24 -24
  104. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/client.js +656 -152
  105. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/types/client/ArchivedSessions.d.ts +9 -2
  106. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/types/client/locales.d.ts +46 -0
  107. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/types/client/session-actions/DeleteSession.d.ts +9 -10
  108. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/types/client/session-actions/deletion.d.ts +57 -0
  109. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/package.json +2 -0
  110. package/runtime/node_modules/@deepseek-ai/dsh-experimental-client-ui-agent-team/lib/client.js +32 -32
  111. package/runtime/node_modules/@deepseek-ai/dsh-experimental-client-ui-voice-input/lib/client.js +24 -24
  112. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek/lib/index.js +899 -453
  113. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek/lib/types/adapter.d.ts +4 -20
  114. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek/lib/types/chat-completions/adapter.d.ts +8 -0
  115. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek/lib/types/chat-completions/serialize.d.ts +10 -0
  116. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek/lib/types/chat-completions/sse.d.ts +24 -0
  117. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek/lib/types/chat-completions/translate.d.ts +37 -0
  118. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek/lib/types/chat-completions/types.d.ts +159 -0
  119. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek/lib/types/file-store.d.ts +3 -0
  120. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek/lib/types/files-api.d.ts +4 -0
  121. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek/lib/types/index.d.ts +1 -0
  122. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek/lib/types/protocol-adapter.d.ts +41 -0
  123. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek/lib/types/protocol.d.ts +35 -0
  124. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek/lib/types/transport.d.ts +2 -1
  125. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek/lib/types/types.d.ts +2 -2
  126. package/runtime/node_modules/@deepseek-ai/dsh-mcp-client/lib/index.js +5 -4
  127. package/runtime/node_modules/@deepseek-ai/dsh-session-log-export/lib/client.js +4 -4
  128. package/runtime/node_modules/@deepseek-ai/dsh-session-log-export/lib/index.js +1 -1
  129. package/runtime/node_modules/@deepseek-ai/dsh-session-persistence-jsonl/lib/index.js +2 -2
  130. package/runtime/node_modules/@deepseek-ai/dsh-tool-cordis/lib/types/api-catalog.js +12 -10
  131. package/runtime/node_modules/@deepseek-ai/dsh-workspace-changes/lib/index.js +208 -39
  132. package/runtime/node_modules/@deepseek-ai/dsh-workspace-changes/lib/types/archive.d.ts +47 -0
  133. package/runtime/node_modules/@deepseek-ai/dsh-workspace-changes/lib/types/archive.js +129 -0
  134. package/runtime/node_modules/@deepseek-ai/dsh-workspace-changes/lib/types/index.d.ts +2 -0
  135. package/runtime/node_modules/@deepseek-ai/dsh-workspace-changes/lib/types/index.js +31 -8
  136. package/runtime/node_modules/@deepseek-ai/dsh-workspace-changes/lib/types/recorder.d.ts +7 -2
  137. package/runtime/node_modules/@deepseek-ai/dsh-workspace-changes/lib/types/recorder.js +35 -19
  138. package/runtime/node_modules/@deepseek-ai/dsh-workspace-changes/lib/types/types.d.ts +11 -10
  139. package/runtime/node_modules/@deepseek-ai/dsh-workspace-changes/package.json +1 -0
  140. package/scripts/link-embedded-runtime.mjs +1 -1
  141. package/lib/dump-config-CDixiJNt.js +0 -2
  142. package/lib/{home-migration-BjDmRwBB.js → home-migration-lRjtl2fP.js} +1 -1
  143. package/lib/{startup-diagnostics-C_hRCp-q.js → startup-diagnostics-4mLHYTFc.js} +1 -1
@@ -1,9 +1,146 @@
1
1
  import { homedir, tmpdir } from "node:os";
2
- import z from "@deepseek-ai/schemastery";
3
- import { copyFile, mkdir, mkdtemp, open, readFile, realpath, rm, writeFile } from "node:fs/promises";
4
2
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
3
+ import z from "@deepseek-ai/schemastery";
4
+ import { chatCodeCliHomePath } from "@deepseek-ai/dsh-home-paths";
5
5
  import { createHash } from "node:crypto";
6
+ import { copyFile, mkdir, mkdtemp, open, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
7
+ import { promisify } from "node:util";
8
+ import { gunzip, gzip } from "node:zlib";
6
9
  import { structuredPatch } from "diff";
10
+ //#region lib/types/archive.js
11
+ /** Durable change summaries and compressed file comparisons, independent of temporary git snapshots. */
12
+ const compress = promisify(gzip);
13
+ const decompress = promisify(gunzip);
14
+ /** Stores completed turn records until their Session is permanently deleted. */
15
+ var ChangesArchive = class {
16
+ directory;
17
+ constructor(directory) {
18
+ this.directory = directory;
19
+ }
20
+ sessionDirectory(sessionId) {
21
+ return join(this.directory, createHash("sha256").update(sessionId).digest("hex"));
22
+ }
23
+ /**
24
+ * Save the summary and each listed comparison before publishing its event.
25
+ * @param sessionId - owning Session.
26
+ * @param summary - complete summary, including the Host working directory.
27
+ * @param diff - resolves one comparison from the turn's immutable snapshots.
28
+ * @param signal - cancels staging before the event is appended.
29
+ * @returns a staged record to commit with the event's sequence and then dispose.
30
+ */
31
+ async stage(sessionId, summary, diff, signal) {
32
+ const parent = this.sessionDirectory(sessionId);
33
+ await mkdir(parent, {
34
+ recursive: true,
35
+ mode: 448
36
+ });
37
+ const staging = await mkdtemp(join(parent, ".pending-"));
38
+ try {
39
+ await writeFile(join(staging, "summary.json"), JSON.stringify({
40
+ version: 1,
41
+ summary
42
+ }), {
43
+ mode: 384,
44
+ signal
45
+ });
46
+ for (let index = 0; index < summary.files.length; index += 1) {
47
+ signal.throwIfAborted();
48
+ const bytes = await compress(JSON.stringify(await diff(index)));
49
+ await writeFile(join(staging, `${index}.json.gz`), bytes, {
50
+ mode: 384,
51
+ signal
52
+ });
53
+ }
54
+ signal.throwIfAborted();
55
+ return {
56
+ commit: (seq) => rename(staging, join(parent, String(seq))),
57
+ dispose: () => rm(staging, {
58
+ recursive: true,
59
+ force: true
60
+ })
61
+ };
62
+ } catch (error) {
63
+ await rm(staging, {
64
+ recursive: true,
65
+ force: true
66
+ });
67
+ throw error;
68
+ }
69
+ }
70
+ /**
71
+ * Read an archived summary without opening a live Session or its repository.
72
+ * @param sessionId - owning Session.
73
+ * @param seq - announcing event sequence.
74
+ * @returns the validated summary, or undefined when no archive exists.
75
+ */
76
+ async summary(sessionId, seq) {
77
+ const bytes = await readMissing(join(this.sessionDirectory(sessionId), String(seq), "summary.json"));
78
+ if (bytes === void 0) return void 0;
79
+ const value = JSON.parse(bytes.toString("utf8"));
80
+ if (!record(value) || value.version !== 1 || !isSummary(value.summary)) throw new Error("Invalid workspace change archive summary");
81
+ return value.summary;
82
+ }
83
+ /**
84
+ * Read a stored comparison independently of later workspace edits or git cleanup.
85
+ * @param sessionId - owning Session.
86
+ * @param seq - announcing event sequence.
87
+ * @param index - listed file index.
88
+ * @param signal - cancellation.
89
+ * @returns the validated comparison, or undefined for a missing record or index.
90
+ */
91
+ async diff(sessionId, seq, index, signal) {
92
+ const bytes = await readMissing(join(this.sessionDirectory(sessionId), String(seq), `${index}.json.gz`), signal);
93
+ if (bytes === void 0) return void 0;
94
+ const value = JSON.parse((await decompress(bytes)).toString("utf8"));
95
+ signal.throwIfAborted();
96
+ if (!isDiff(value)) throw new Error("Invalid workspace change archive comparison");
97
+ return value;
98
+ }
99
+ /**
100
+ * Remove secondary records when the Session is permanently deleted.
101
+ * @param sessionId - deleted Session.
102
+ * @returns after its archive directory has been removed.
103
+ */
104
+ remove(sessionId) {
105
+ return rm(this.sessionDirectory(sessionId), {
106
+ recursive: true,
107
+ force: true
108
+ });
109
+ }
110
+ };
111
+ async function readMissing(file, signal) {
112
+ try {
113
+ return await readFile(file, signal === void 0 ? {} : { signal });
114
+ } catch (error) {
115
+ if (record(error) && error.code === "ENOENT") return void 0;
116
+ throw error;
117
+ }
118
+ }
119
+ function record(value) {
120
+ return typeof value === "object" && value !== null && !Array.isArray(value);
121
+ }
122
+ function count(value) {
123
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
124
+ }
125
+ function pathFields(value) {
126
+ return typeof value.path === "string" && value.path.length > 0 && typeof value.display === "string" && value.display.length > 0;
127
+ }
128
+ function isSummary(value) {
129
+ if (!record(value)) return false;
130
+ const { turn, cwd, files, total, added, deleted, snapshot } = value;
131
+ return count(turn) && turn > 0 && typeof cwd === "string" && isAbsolute(cwd) && count(total) && count(added) && count(deleted) && Array.isArray(files) && files.length <= total && files.every((file) => record(file) && pathFields(file) && count(file.added) && count(file.deleted) && (file.binary === void 0 || file.binary === true) && (file.oversized === void 0 || file.oversized === true)) && (snapshot === void 0 || record(snapshot) && typeof snapshot.before === "string" && typeof snapshot.after === "string");
132
+ }
133
+ function isDiff(value) {
134
+ if (!record(value) || !pathFields(value)) return false;
135
+ if (value.kind === "binary" || value.kind === "oversized") return true;
136
+ return value.kind === "text" && typeof value.before === "boolean" && typeof value.after === "boolean" && typeof value.coarse === "boolean" && Array.isArray(value.hunks) && value.hunks.every((hunk) => record(hunk) && [
137
+ hunk.oldStart,
138
+ hunk.oldLines,
139
+ hunk.newStart,
140
+ hunk.newLines
141
+ ].every(count) && Array.isArray(hunk.lines) && hunk.lines.every((line) => typeof line === "string" && /^[+ -]/.test(line)));
142
+ }
143
+ //#endregion
7
144
  //#region lib/types/numstat.js
8
145
  /** Parsing of `git diff-tree --numstat -z` output. */
9
146
  /**
@@ -620,8 +757,9 @@ const OVERSIZED = Symbol("oversized");
620
757
  * with its diff, and the appended `workspace/changes` event whose summary and
621
758
  * comparisons this recorder keeps. Snapshot objects and captured copies live in
622
759
  * a temporary directory owned by the recorder; disposal removes it together
623
- * with the summaries. Tool execution waits for pending work so a snapshot or
624
- * capture never races a mutation. A working directory outside any repository,
760
+ * with the live records; the durable archive retains the summaries and comparisons.
761
+ * Tool execution waits for pending work so a snapshot or capture never races a mutation.
762
+ * A working directory outside any repository,
625
763
  * or a Host without git, gets no snapshot; its summary lists the files the file
626
764
  * tools changed.
627
765
  */
@@ -742,9 +880,17 @@ var TurnRecorder = class {
742
880
  */
743
881
  async diff(seq, index, signal) {
744
882
  const record = this.records.get(seq);
745
- const file = record?.summary.files[index];
746
- const sources = record?.sources[index];
747
- if (file === void 0 || sources === void 0) return void 0;
883
+ if (record === void 0 || record.summary.files[index] === void 0) return void 0;
884
+ try {
885
+ return await this.compare(record, index, signal);
886
+ } catch (error) {
887
+ if (this.lifetime.signal.aborted) return void 0;
888
+ throw error;
889
+ }
890
+ }
891
+ async compare(record, index, signal) {
892
+ const file = record.summary.files[index];
893
+ const sources = record.sources[index];
748
894
  const { path, display } = file;
749
895
  if (sources.refusal !== void 0) return {
750
896
  kind: sources.refusal,
@@ -752,27 +898,22 @@ var TurnRecorder = class {
752
898
  display
753
899
  };
754
900
  const combined = AbortSignal.any([signal, this.lifetime.signal]);
755
- try {
756
- const [before, after] = await Promise.all([this.readSide(sources.before, combined), this.readSide(sources.after, combined)]);
757
- if (before === OVERSIZED || after === OVERSIZED) return {
758
- kind: "oversized",
759
- path,
760
- display
761
- };
762
- const { hunks, coarse } = compareText(before, after, this.env.diffTimeoutMs);
763
- return {
764
- kind: "text",
765
- path,
766
- display,
767
- before: before !== null,
768
- after: after !== null,
769
- hunks,
770
- coarse
771
- };
772
- } catch (error) {
773
- if (this.lifetime.signal.aborted) return void 0;
774
- throw error;
775
- }
901
+ const [before, after] = await Promise.all([this.readSide(sources.before, combined), this.readSide(sources.after, combined)]);
902
+ if (before === OVERSIZED || after === OVERSIZED) return {
903
+ kind: "oversized",
904
+ path,
905
+ display
906
+ };
907
+ const { hunks, coarse } = compareText(before, after, this.env.diffTimeoutMs);
908
+ return {
909
+ kind: "text",
910
+ path,
911
+ display,
912
+ before: before !== null,
913
+ after: after !== null,
914
+ hunks,
915
+ coarse
916
+ };
776
917
  }
777
918
  /**
778
919
  * Abort queued work, forget every record, and remove the temporary directory.
@@ -815,6 +956,8 @@ var TurnRecorder = class {
815
956
  if (git === null) return null;
816
957
  const workspace = await locateGitWorkspace(git, cwd, () => this.scratchDir(), signal);
817
958
  if (workspace === null) return null;
959
+ const archive = await canonicalPath(this.env.archive.directory);
960
+ if (isInside(workspace.root, archive)) workspace.excludes = [...workspace.excludes, toPosix(relative(workspace.root, archive))];
818
961
  this.repository = {
819
962
  git,
820
963
  workspace
@@ -894,9 +1037,8 @@ var TurnRecorder = class {
894
1037
  }
895
1038
  const sorted = [...listed.values()].sort((a, b) => compareDisplay(a.file, b.file));
896
1039
  if (sorted.length === 0 && state.recordedAfterSeq < 0) return;
897
- const event = this.session.append("workspace/changes", { turn: state.turn });
898
1040
  const kept = sorted.slice(0, this.env.maxFiles);
899
- this.records.set(event.seq, {
1041
+ const record = {
900
1042
  summary: {
901
1043
  turn: state.turn,
902
1044
  cwd: this.cwd,
@@ -907,8 +1049,17 @@ var TurnRecorder = class {
907
1049
  ...snapshot === void 0 ? {} : { snapshot }
908
1050
  },
909
1051
  sources: kept.map((entry) => entry.sources)
910
- });
911
- state.recordedAfterSeq = event.seq;
1052
+ };
1053
+ const staged = await this.env.archive.stage(this.session.id, record.summary, (index) => this.compare(record, index, signal), signal);
1054
+ try {
1055
+ signal.throwIfAborted();
1056
+ const event = this.session.append("workspace/changes", { turn: state.turn });
1057
+ this.records.set(event.seq, record);
1058
+ state.recordedAfterSeq = event.seq;
1059
+ await staged.commit(event.seq);
1060
+ } finally {
1061
+ await staged.dispose();
1062
+ }
912
1063
  }
913
1064
  /**
914
1065
  * The listing of a captured pair: an oversized side lists the file without
@@ -966,8 +1117,8 @@ function changedFile({ cwd, home }, root, absolute, counts) {
966
1117
  * listed file's before-and-after comparison on demand. Each summary is
967
1118
  * announced by a `workspace/changes` Session event that carries only the turn
968
1119
  * number; summaries and comparisons are served through the `workspaceChanges`
969
- * service until the Session is disposed. Outside a git repository, or without
970
- * git, the summary lists file-tool edits only.
1120
+ * service across Host restarts until the Session is permanently deleted.
1121
+ * Outside a git repository, or without git, the summary lists file-tool edits only.
971
1122
  */
972
1123
  /** Stable Loader identity. */
973
1124
  const name = "workspace-changes";
@@ -975,6 +1126,7 @@ const name = "workspace-changes";
975
1126
  const inject = ["subprocess"];
976
1127
  /** Schemastery validation for {@link Config}. */
977
1128
  const Config = z.object({
1129
+ directory: z.string().default(chatCodeCliHomePath("workspace-changes")),
978
1130
  timeoutMs: z.number().default(3e4),
979
1131
  outputMaxBytes: z.number().default(8 * 1024 * 1024),
980
1132
  maxFiles: z.number().default(500),
@@ -1021,6 +1173,7 @@ async function resolveGit(ctx, signal) {
1021
1173
  * @param config - validated bounds.
1022
1174
  */
1023
1175
  function apply(ctx, config) {
1176
+ if (!isAbsolute(config.directory)) throw new Error("workspace-changes requires an absolute directory");
1024
1177
  for (const [field, value] of [
1025
1178
  ["timeoutMs", config.timeoutMs],
1026
1179
  ["outputMaxBytes", config.outputMaxBytes],
@@ -1029,21 +1182,27 @@ function apply(ctx, config) {
1029
1182
  ["diffTimeoutMs", config.diffTimeoutMs]
1030
1183
  ]) if (!Number.isSafeInteger(value) || value < 1) throw new Error(`workspace-changes requires a positive integer ${field}`);
1031
1184
  const lifetime = new AbortController();
1185
+ const archive = new ChangesArchive(config.directory);
1032
1186
  const recorders = /* @__PURE__ */ new Map();
1033
1187
  const byId = /* @__PURE__ */ new Map();
1188
+ const retiring = /* @__PURE__ */ new Set();
1034
1189
  const forget = (session) => {
1035
1190
  const recorder = recorders.get(session);
1036
1191
  recorders.delete(session);
1037
1192
  byId.delete(session.id);
1038
- return recorder?.dispose() ?? Promise.resolve();
1193
+ if (recorder === void 0) return Promise.resolve();
1194
+ const disposal = recorder.dispose();
1195
+ retiring.add(disposal);
1196
+ disposal.then(() => retiring.delete(disposal), () => retiring.delete(disposal));
1197
+ return disposal;
1039
1198
  };
1040
1199
  ctx.effect(() => async () => {
1041
1200
  lifetime.abort();
1042
- await Promise.all([...recorders.keys()].map(forget));
1201
+ await Promise.all([...recorders.keys()].map(forget).concat([...retiring]));
1043
1202
  });
1044
1203
  ctx.provide("workspaceChanges", {
1045
- summary: (sessionId, seq) => byId.get(sessionId)?.summary(seq),
1046
- diff: (sessionId, seq, index, signal) => byId.get(sessionId)?.diff(seq, index, signal) ?? Promise.resolve(void 0)
1204
+ summary: async (sessionId, seq) => byId.get(sessionId)?.summary(seq) ?? archive.summary(sessionId, seq),
1205
+ diff: async (sessionId, seq, index, signal) => await archive.diff(sessionId, seq, index, signal) ?? byId.get(sessionId)?.diff(seq, index, signal)
1047
1206
  });
1048
1207
  let runner;
1049
1208
  const gitRunner = () => {
@@ -1063,6 +1222,7 @@ function apply(ctx, config) {
1063
1222
  let recorder = recorders.get(session);
1064
1223
  if (recorder === void 0) {
1065
1224
  recorder = new TurnRecorder(session, cwd, {
1225
+ archive,
1066
1226
  git: gitRunner(),
1067
1227
  tempRoot: tmpdir(),
1068
1228
  maxFiles: config.maxFiles,
@@ -1087,7 +1247,16 @@ function apply(ctx, config) {
1087
1247
  else if (event.type === "turn/end") recorders.get(session)?.end(event.data.turn);
1088
1248
  });
1089
1249
  ctx.on("session/disposed", (session) => {
1090
- forget(session);
1250
+ forget(session).catch((error) => {
1251
+ ctx.logger.warn(`workspace-changes: ${String(error)}`);
1252
+ });
1253
+ });
1254
+ ctx.on("session/deleting", async ({ sessionIds }) => {
1255
+ for (const sessionId of sessionIds) {
1256
+ const session = [...recorders.keys()].find((candidate) => candidate.id === sessionId);
1257
+ if (session !== void 0) await forget(session);
1258
+ await archive.remove(sessionId);
1259
+ }
1091
1260
  });
1092
1261
  ctx.on("agent/turn-stopping", async ({ agent, turn }) => {
1093
1262
  await recorders.get(agent.session)?.stopping(turn);
@@ -0,0 +1,47 @@
1
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
2
+ import type { WorkspaceChangesSummary, WorkspaceFileDiff } from './types.ts';
3
+ /** A complete comparison set awaiting its announcing event's sequence. */
4
+ export interface StagedChanges {
5
+ /** Publish the directory atomically under the announcing event's sequence. */
6
+ commit(seq: number): Promise<void>;
7
+ /** Remove any unpublished staging directory. */
8
+ dispose(): Promise<void>;
9
+ }
10
+ /** Stores completed turn records until their Session is permanently deleted. */
11
+ export declare class ChangesArchive {
12
+ readonly directory: string;
13
+ constructor(directory: string);
14
+ private sessionDirectory;
15
+ /**
16
+ * Save the summary and each listed comparison before publishing its event.
17
+ * @param sessionId - owning Session.
18
+ * @param summary - complete summary, including the Host working directory.
19
+ * @param diff - resolves one comparison from the turn's immutable snapshots.
20
+ * @param signal - cancels staging before the event is appended.
21
+ * @returns a staged record to commit with the event's sequence and then dispose.
22
+ */
23
+ stage(sessionId: SessionId, summary: WorkspaceChangesSummary, diff: (index: number) => Promise<WorkspaceFileDiff>, signal: AbortSignal): Promise<StagedChanges>;
24
+ /**
25
+ * Read an archived summary without opening a live Session or its repository.
26
+ * @param sessionId - owning Session.
27
+ * @param seq - announcing event sequence.
28
+ * @returns the validated summary, or undefined when no archive exists.
29
+ */
30
+ summary(sessionId: SessionId, seq: number): Promise<WorkspaceChangesSummary | undefined>;
31
+ /**
32
+ * Read a stored comparison independently of later workspace edits or git cleanup.
33
+ * @param sessionId - owning Session.
34
+ * @param seq - announcing event sequence.
35
+ * @param index - listed file index.
36
+ * @param signal - cancellation.
37
+ * @returns the validated comparison, or undefined for a missing record or index.
38
+ */
39
+ diff(sessionId: SessionId, seq: number, index: number, signal: AbortSignal): Promise<WorkspaceFileDiff | undefined>;
40
+ /**
41
+ * Remove secondary records when the Session is permanently deleted.
42
+ * @param sessionId - deleted Session.
43
+ * @returns after its archive directory has been removed.
44
+ */
45
+ remove(sessionId: SessionId): Promise<void>;
46
+ }
47
+ //# sourceMappingURL=archive.d.ts.map
@@ -0,0 +1,129 @@
1
+ /** Durable change summaries and compressed file comparisons, independent of temporary git snapshots. */
2
+ import { createHash } from 'node:crypto';
3
+ import { mkdir, mkdtemp, readFile, rename, rm, writeFile } from 'node:fs/promises';
4
+ import { isAbsolute, join } from 'node:path';
5
+ import { promisify } from 'node:util';
6
+ import { gzip, gunzip } from 'node:zlib';
7
+ const compress = promisify(gzip);
8
+ const decompress = promisify(gunzip);
9
+ /** Stores completed turn records until their Session is permanently deleted. */
10
+ export class ChangesArchive {
11
+ directory;
12
+ constructor(directory) {
13
+ this.directory = directory;
14
+ }
15
+ sessionDirectory(sessionId) {
16
+ return join(this.directory, createHash('sha256').update(sessionId).digest('hex'));
17
+ }
18
+ /**
19
+ * Save the summary and each listed comparison before publishing its event.
20
+ * @param sessionId - owning Session.
21
+ * @param summary - complete summary, including the Host working directory.
22
+ * @param diff - resolves one comparison from the turn's immutable snapshots.
23
+ * @param signal - cancels staging before the event is appended.
24
+ * @returns a staged record to commit with the event's sequence and then dispose.
25
+ */
26
+ async stage(sessionId, summary, diff, signal) {
27
+ const parent = this.sessionDirectory(sessionId);
28
+ await mkdir(parent, { recursive: true, mode: 0o700 });
29
+ const staging = await mkdtemp(join(parent, '.pending-'));
30
+ try {
31
+ await writeFile(join(staging, 'summary.json'), JSON.stringify({ version: 1, summary }), { mode: 0o600, signal });
32
+ for (let index = 0; index < summary.files.length; index += 1) {
33
+ signal.throwIfAborted();
34
+ const bytes = await compress(JSON.stringify(await diff(index)));
35
+ await writeFile(join(staging, `${index}.json.gz`), bytes, { mode: 0o600, signal });
36
+ }
37
+ signal.throwIfAborted();
38
+ return {
39
+ commit: seq => rename(staging, join(parent, String(seq))),
40
+ dispose: () => rm(staging, { recursive: true, force: true }),
41
+ };
42
+ }
43
+ catch (error) {
44
+ await rm(staging, { recursive: true, force: true });
45
+ throw error;
46
+ }
47
+ }
48
+ /**
49
+ * Read an archived summary without opening a live Session or its repository.
50
+ * @param sessionId - owning Session.
51
+ * @param seq - announcing event sequence.
52
+ * @returns the validated summary, or undefined when no archive exists.
53
+ */
54
+ async summary(sessionId, seq) {
55
+ const bytes = await readMissing(join(this.sessionDirectory(sessionId), String(seq), 'summary.json'));
56
+ if (bytes === undefined)
57
+ return undefined;
58
+ const value = JSON.parse(bytes.toString('utf8'));
59
+ if (!record(value) || value.version !== 1 || !isSummary(value.summary))
60
+ throw new Error('Invalid workspace change archive summary');
61
+ return value.summary;
62
+ }
63
+ /**
64
+ * Read a stored comparison independently of later workspace edits or git cleanup.
65
+ * @param sessionId - owning Session.
66
+ * @param seq - announcing event sequence.
67
+ * @param index - listed file index.
68
+ * @param signal - cancellation.
69
+ * @returns the validated comparison, or undefined for a missing record or index.
70
+ */
71
+ async diff(sessionId, seq, index, signal) {
72
+ const bytes = await readMissing(join(this.sessionDirectory(sessionId), String(seq), `${index}.json.gz`), signal);
73
+ if (bytes === undefined)
74
+ return undefined;
75
+ const value = JSON.parse((await decompress(bytes)).toString('utf8'));
76
+ signal.throwIfAborted();
77
+ if (!isDiff(value))
78
+ throw new Error('Invalid workspace change archive comparison');
79
+ return value;
80
+ }
81
+ /**
82
+ * Remove secondary records when the Session is permanently deleted.
83
+ * @param sessionId - deleted Session.
84
+ * @returns after its archive directory has been removed.
85
+ */
86
+ remove(sessionId) {
87
+ return rm(this.sessionDirectory(sessionId), { recursive: true, force: true });
88
+ }
89
+ }
90
+ async function readMissing(file, signal) {
91
+ try {
92
+ return await readFile(file, signal === undefined ? {} : { signal });
93
+ }
94
+ catch (error) {
95
+ if (record(error) && error.code === 'ENOENT')
96
+ return undefined;
97
+ throw error;
98
+ }
99
+ }
100
+ function record(value) {
101
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
102
+ }
103
+ function count(value) {
104
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
105
+ }
106
+ function pathFields(value) {
107
+ return typeof value.path === 'string' && value.path.length > 0 && typeof value.display === 'string' && value.display.length > 0;
108
+ }
109
+ function isSummary(value) {
110
+ if (!record(value))
111
+ return false;
112
+ const { turn, cwd, files, total, added, deleted, snapshot } = value;
113
+ return count(turn) && turn > 0 && typeof cwd === 'string' && isAbsolute(cwd) && count(total) && count(added) && count(deleted)
114
+ && Array.isArray(files) && files.length <= total && files.every(file => record(file) && pathFields(file)
115
+ && count(file.added) && count(file.deleted) && (file.binary === undefined || file.binary === true)
116
+ && (file.oversized === undefined || file.oversized === true))
117
+ && (snapshot === undefined || (record(snapshot) && typeof snapshot.before === 'string' && typeof snapshot.after === 'string'));
118
+ }
119
+ function isDiff(value) {
120
+ if (!record(value) || !pathFields(value))
121
+ return false;
122
+ if (value.kind === 'binary' || value.kind === 'oversized')
123
+ return true;
124
+ return value.kind === 'text' && typeof value.before === 'boolean' && typeof value.after === 'boolean'
125
+ && typeof value.coarse === 'boolean' && Array.isArray(value.hunks) && value.hunks.every(hunk => record(hunk)
126
+ && [hunk.oldStart, hunk.oldLines, hunk.newStart, hunk.newLines].every(count)
127
+ && Array.isArray(hunk.lines) && hunk.lines.every(line => typeof line === 'string' && /^[+ -]/.test(line)));
128
+ }
129
+ //# sourceMappingURL=archive.js.map
@@ -7,6 +7,8 @@ export declare const name = "workspace-changes";
7
7
  export declare const inject: string[];
8
8
  /** Snapshot, capture, and comparison bounds. Invalid values fail plugin load. */
9
9
  export interface Config {
10
+ /** Absolute directory retaining summaries and comparisons across Host restarts. */
11
+ directory: string;
10
12
  /** Milliseconds one git command may run before the turn's record is abandoned. */
11
13
  timeoutMs: number;
12
14
  /** Bytes of git output retained per command; a larger diff listing abandons the record. */
@@ -5,11 +5,14 @@
5
5
  * listed file's before-and-after comparison on demand. Each summary is
6
6
  * announced by a `workspace/changes` Session event that carries only the turn
7
7
  * number; summaries and comparisons are served through the `workspaceChanges`
8
- * service until the Session is disposed. Outside a git repository, or without
9
- * git, the summary lists file-tool edits only.
8
+ * service across Host restarts until the Session is permanently deleted.
9
+ * Outside a git repository, or without git, the summary lists file-tool edits only.
10
10
  */
11
11
  import { homedir, tmpdir } from 'node:os';
12
+ import { isAbsolute } from 'node:path';
12
13
  import z from '@deepseek-ai/schemastery';
14
+ import { chatCodeCliHomePath } from '@deepseek-ai/dsh-home-paths';
15
+ import { ChangesArchive } from "./archive.js";
13
16
  import { GitRunner } from "./git.js";
14
17
  import { TurnRecorder } from "./recorder.js";
15
18
  /** Stable Loader identity. */
@@ -18,6 +21,7 @@ export const name = 'workspace-changes';
18
21
  export const inject = ['subprocess'];
19
22
  /** Schemastery validation for {@link Config}. */
20
23
  export const Config = z.object({
24
+ directory: z.string().default(chatCodeCliHomePath('workspace-changes')),
21
25
  timeoutMs: z.number().default(30_000),
22
26
  outputMaxBytes: z.number().default(8 * 1024 * 1024),
23
27
  maxFiles: z.number().default(500),
@@ -61,6 +65,8 @@ async function resolveGit(ctx, signal) {
61
65
  * @param config - validated bounds.
62
66
  */
63
67
  export function apply(ctx, config) {
68
+ if (!isAbsolute(config.directory))
69
+ throw new Error('workspace-changes requires an absolute directory');
64
70
  for (const [field, value] of [
65
71
  ['timeoutMs', config.timeoutMs], ['outputMaxBytes', config.outputMaxBytes], ['maxFiles', config.maxFiles],
66
72
  ['maxFileBytes', config.maxFileBytes], ['diffTimeoutMs', config.diffTimeoutMs],
@@ -69,21 +75,28 @@ export function apply(ctx, config) {
69
75
  throw new Error(`workspace-changes requires a positive integer ${field}`);
70
76
  }
71
77
  const lifetime = new AbortController();
78
+ const archive = new ChangesArchive(config.directory);
72
79
  const recorders = new Map();
73
80
  const byId = new Map();
81
+ const retiring = new Set();
74
82
  const forget = (session) => {
75
83
  const recorder = recorders.get(session);
76
84
  recorders.delete(session);
77
85
  byId.delete(session.id);
78
- return recorder?.dispose() ?? Promise.resolve();
86
+ if (recorder === undefined)
87
+ return Promise.resolve();
88
+ const disposal = recorder.dispose();
89
+ retiring.add(disposal);
90
+ void disposal.then(() => retiring.delete(disposal), () => retiring.delete(disposal));
91
+ return disposal;
79
92
  };
80
93
  ctx.effect(() => async () => {
81
94
  lifetime.abort();
82
- await Promise.all([...recorders.keys()].map(forget));
95
+ await Promise.all([...recorders.keys()].map(forget).concat([...retiring]));
83
96
  });
84
97
  const service = {
85
- summary: (sessionId, seq) => byId.get(sessionId)?.summary(seq),
86
- diff: (sessionId, seq, index, signal) => byId.get(sessionId)?.diff(seq, index, signal) ?? Promise.resolve(undefined),
98
+ summary: async (sessionId, seq) => byId.get(sessionId)?.summary(seq) ?? archive.summary(sessionId, seq),
99
+ diff: async (sessionId, seq, index, signal) => await archive.diff(sessionId, seq, index, signal) ?? byId.get(sessionId)?.diff(seq, index, signal),
87
100
  };
88
101
  ctx.provide('workspaceChanges', service);
89
102
  let runner;
@@ -101,7 +114,7 @@ export function apply(ctx, config) {
101
114
  let recorder = recorders.get(session);
102
115
  if (recorder === undefined) {
103
116
  recorder = new TurnRecorder(session, cwd, {
104
- git: gitRunner(), tempRoot: tmpdir(), maxFiles: config.maxFiles,
117
+ archive, git: gitRunner(), tempRoot: tmpdir(), maxFiles: config.maxFiles,
105
118
  maxFileBytes: config.maxFileBytes, diffTimeoutMs: config.diffTimeoutMs,
106
119
  warn: (message) => { ctx.logger.warn(message); },
107
120
  });
@@ -122,7 +135,17 @@ export function apply(ctx, config) {
122
135
  else if (event.type === 'turn/end')
123
136
  recorders.get(session)?.end(event.data.turn);
124
137
  });
125
- ctx.on('session/disposed', (session) => { void forget(session); });
138
+ ctx.on('session/disposed', (session) => {
139
+ void forget(session).catch((error) => { ctx.logger.warn(`workspace-changes: ${String(error)}`); });
140
+ });
141
+ ctx.on('session/deleting', async ({ sessionIds }) => {
142
+ for (const sessionId of sessionIds) {
143
+ const session = [...recorders.keys()].find(candidate => candidate.id === sessionId);
144
+ if (session !== undefined)
145
+ await forget(session);
146
+ await archive.remove(sessionId);
147
+ }
148
+ });
126
149
  ctx.on('agent/turn-stopping', async ({ agent, turn }) => {
127
150
  await recorders.get(agent.session)?.stopping(turn);
128
151
  });
@@ -1,8 +1,11 @@
1
1
  import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
2
+ import type { ChangesArchive } from './archive.ts';
2
3
  import { type GitRunner } from './git.ts';
3
4
  import type { WorkspaceChangesSummary, WorkspaceFileDiff } from './types.ts';
4
5
  /** Facts shared by every recorder of one plugin instance. */
5
6
  export interface RecorderEnvironment {
7
+ /** Durable summaries and comparisons, retained after recorder disposal. */
8
+ archive: ChangesArchive;
6
9
  /** Resolves to the runner, or null when git is unavailable and no snapshot is taken. */
7
10
  git: Promise<GitRunner | null>;
8
11
  /** Directory that receives each Session's temporary directory. */
@@ -22,8 +25,9 @@ export interface RecorderEnvironment {
22
25
  * with its diff, and the appended `workspace/changes` event whose summary and
23
26
  * comparisons this recorder keeps. Snapshot objects and captured copies live in
24
27
  * a temporary directory owned by the recorder; disposal removes it together
25
- * with the summaries. Tool execution waits for pending work so a snapshot or
26
- * capture never races a mutation. A working directory outside any repository,
28
+ * with the live records; the durable archive retains the summaries and comparisons.
29
+ * Tool execution waits for pending work so a snapshot or capture never races a mutation.
30
+ * A working directory outside any repository,
27
31
  * or a Host without git, gets no snapshot; its summary lists the files the file
28
32
  * tools changed.
29
33
  */
@@ -90,6 +94,7 @@ export declare class TurnRecorder {
90
94
  * @throws when a read fails while the recorder lives.
91
95
  */
92
96
  diff(seq: number, index: number, signal: AbortSignal): Promise<WorkspaceFileDiff | undefined>;
97
+ private compare;
93
98
  /**
94
99
  * Abort queued work, forget every record, and remove the temporary directory.
95
100
  * @returns once the temporary directory is gone.