@mycadet/assistant 0.12.11 → 0.13.0

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 (176) hide show
  1. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
  2. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
  3. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
  4. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
  5. package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +0 -20
  6. package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
  7. package/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
  8. package/openapi.yaml +2 -708
  9. package/package.json +2 -3
  10. package/scripts/sync-web-search-catalog.ts +1 -2
  11. package/src/__tests__/cloud-profile-guard.test.ts +2 -2
  12. package/src/__tests__/config-schema.test.ts +6 -38
  13. package/src/__tests__/conversation-queue.test.ts +45 -6
  14. package/src/__tests__/db-readiness-http-gate.test.ts +0 -6
  15. package/src/__tests__/persist-onboarding-artifacts.test.ts +0 -1
  16. package/src/__tests__/persona-resolver.test.ts +0 -51
  17. package/src/__tests__/provider-cloud-proxy-integration.test.ts +2 -110
  18. package/src/__tests__/run-due-schedules.test.ts +0 -4
  19. package/src/__tests__/schedule-store.test.ts +0 -12
  20. package/src/__tests__/stt-stream-session.test.ts +4 -4
  21. package/src/__tests__/telephony-tts-guard.test.ts +2 -50
  22. package/src/__tests__/voice-config-update.test.ts +12 -167
  23. package/src/__tests__/web-search-catalog-parity.test.ts +0 -2
  24. package/src/__tests__/workspace-migration-130-speech-mode-to-provider.test.ts +9 -7
  25. package/src/__tests__/workspace-migration-132-web-search-mode-to-provider.test.ts +6 -6
  26. package/src/calls/__tests__/voice-session-bridge.test.ts +1 -4
  27. package/src/calls/resolve-call-tts-provider.ts +6 -25
  28. package/src/calls/telephony-credential-preflight.ts +0 -10
  29. package/src/calls/telephony-tts-capability.ts +1 -17
  30. package/src/cli/commands/tts.ts +1 -2
  31. package/src/cli/index.help.ts +0 -2
  32. package/src/cli/program.ts +0 -2
  33. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +4 -42
  34. package/src/config/schemas/__tests__/stt.test.ts +10 -14
  35. package/src/config/schemas/services.ts +5 -6
  36. package/src/config/schemas/stt.ts +1 -3
  37. package/src/config/schemas/tts.ts +1 -30
  38. package/src/daemon/daemon-readiness.ts +4 -29
  39. package/src/daemon/lifecycle.ts +0 -31
  40. package/src/daemon/providers-setup.ts +0 -8
  41. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  42. package/src/heartbeat/heartbeat-service.ts +1 -66
  43. package/src/ipc/__tests__/db-readiness-ipc-gate.test.ts +1 -9
  44. package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +0 -44
  45. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +4 -6
  46. package/src/live-voice/__tests__/live-voice-stt.test.ts +1 -1
  47. package/src/live-voice/live-voice-credential-preflight.ts +9 -33
  48. package/src/live-voice/live-voice-session.ts +10 -6
  49. package/src/live-voice/live-voice-tts.ts +1 -8
  50. package/src/plugins/defaults/memory/worker.ts +3 -3
  51. package/src/prompts/persona-resolver.ts +0 -35
  52. package/src/providers/__tests__/search-provider-catalog.test.ts +5 -8
  53. package/src/providers/__tests__/voice-error-copy.test.ts +9 -10
  54. package/src/providers/search-provider-catalog.ts +7 -39
  55. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +3 -47
  56. package/src/providers/speech-to-text/__tests__/resolve.test.ts +6 -372
  57. package/src/providers/speech-to-text/provider-catalog.ts +2 -73
  58. package/src/providers/speech-to-text/resolve.ts +3 -120
  59. package/src/runtime/http-router-types.ts +1 -3
  60. package/src/runtime/http-server.ts +1 -1
  61. package/src/runtime/routes/__tests__/tts-routes.test.ts +2 -6
  62. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +7 -7
  63. package/src/runtime/routes/index.ts +0 -4
  64. package/src/runtime/routes/live-voice-routes.test.ts +4 -11
  65. package/src/runtime/routes/live-voice-routes.ts +8 -11
  66. package/src/runtime/routes/secret-routes.ts +0 -6
  67. package/src/runtime/routes/tts-routes.ts +0 -38
  68. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1 -7
  69. package/src/schedule/__tests__/worker-mcp-readiness.test.ts +0 -4
  70. package/src/schedule/schedule-store.ts +0 -7
  71. package/src/schedule/scheduler.ts +0 -5
  72. package/src/schedule/worker.ts +1 -1
  73. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +2 -10
  74. package/src/stt/daemon-batch-transcriber.ts +4 -38
  75. package/src/stt/types.ts +1 -9
  76. package/src/tools/network/__tests__/web-search.test.ts +3 -250
  77. package/src/tools/network/web-search.ts +4 -165
  78. package/src/tts/__tests__/provider-resolution.test.ts +0 -5
  79. package/src/tts/provider-catalog.ts +0 -4
  80. package/src/tts/types.ts +0 -1
  81. package/src/__tests__/cloud-speech-defaults.test.ts +0 -330
  82. package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1708
  83. package/src/__tests__/migration-export-http.test.ts +0 -557
  84. package/src/__tests__/migration-export-streaming.test.ts +0 -350
  85. package/src/__tests__/migration-export-to-gcs.test.ts +0 -706
  86. package/src/__tests__/migration-import-commit-http.test.ts +0 -1176
  87. package/src/__tests__/migration-import-from-gcs.test.ts +0 -534
  88. package/src/__tests__/migration-import-from-path.test.ts +0 -324
  89. package/src/__tests__/migration-import-from-url.test.ts +0 -588
  90. package/src/__tests__/migration-import-preflight-http.test.ts +0 -739
  91. package/src/__tests__/migration-jobs-status.test.ts +0 -139
  92. package/src/__tests__/migration-parity-persistence.test.ts +0 -1891
  93. package/src/__tests__/migration-transport.test.ts +0 -1089
  94. package/src/__tests__/migration-validate-http.test.ts +0 -634
  95. package/src/__tests__/migration-wizard.test.ts +0 -1395
  96. package/src/__tests__/rebind-secrets-screen.test.ts +0 -876
  97. package/src/__tests__/sanitize-config-for-transfer.test.ts +0 -259
  98. package/src/__tests__/transfer-progress-screen.test.ts +0 -1217
  99. package/src/__tests__/validation-results-screen.test.ts +0 -1130
  100. package/src/__tests__/vbundle-pax-and-symlink.test.ts +0 -147
  101. package/src/background-wake/background-wake-routes.test.ts +0 -906
  102. package/src/background-wake/cloud-client.test.ts +0 -328
  103. package/src/background-wake/cloud-client.ts +0 -196
  104. package/src/background-wake/next-wake.test.ts +0 -335
  105. package/src/background-wake/next-wake.ts +0 -188
  106. package/src/background-wake/publisher.ts +0 -93
  107. package/src/background-wake/wake-intent-hooks.test.ts +0 -234
  108. package/src/cli/commands/__tests__/backup.test.ts +0 -363
  109. package/src/cli/commands/backup.help.ts +0 -180
  110. package/src/cli/commands/backup.ts +0 -398
  111. package/src/cloud/cloud-speech.test.ts +0 -335
  112. package/src/cloud/cloud-speech.ts +0 -300
  113. package/src/config/cloud-speech-defaults.ts +0 -300
  114. package/src/config/sanitize-for-transfer.ts +0 -111
  115. package/src/live-voice/__tests__/live-voice-cloud-speech-fallback.test.ts +0 -250
  116. package/src/providers/speech-to-text/__tests__/cadet-cloud-realtime.test.ts +0 -457
  117. package/src/providers/speech-to-text/__tests__/cadet-cloud.test.ts +0 -271
  118. package/src/providers/speech-to-text/__tests__/cadet-speech-relay-connection.test.ts +0 -126
  119. package/src/providers/speech-to-text/cadet-cloud-flux-realtime.ts +0 -309
  120. package/src/providers/speech-to-text/cadet-cloud-realtime.ts +0 -315
  121. package/src/providers/speech-to-text/cadet-cloud.ts +0 -108
  122. package/src/providers/speech-to-text/cadet-speech-relay-connection.ts +0 -176
  123. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +0 -254
  124. package/src/runtime/migrations/__tests__/job-registry.test.ts +0 -346
  125. package/src/runtime/migrations/__tests__/origin-mode.test.ts +0 -62
  126. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +0 -184
  127. package/src/runtime/migrations/__tests__/staged-import-path.test.ts +0 -110
  128. package/src/runtime/migrations/__tests__/v1-test-helpers.ts +0 -112
  129. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +0 -285
  130. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +0 -126
  131. package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +0 -253
  132. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +0 -208
  133. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +0 -485
  134. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +0 -349
  135. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +0 -196
  136. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +0 -551
  137. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +0 -428
  138. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +0 -220
  139. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +0 -1823
  140. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +0 -598
  141. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +0 -463
  142. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -538
  143. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +0 -515
  144. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +0 -453
  145. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +0 -319
  146. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +0 -240
  147. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +0 -473
  148. package/src/runtime/migrations/gcs-signed-url.ts +0 -190
  149. package/src/runtime/migrations/job-registry.ts +0 -297
  150. package/src/runtime/migrations/migration-transport.ts +0 -612
  151. package/src/runtime/migrations/migration-wizard.ts +0 -747
  152. package/src/runtime/migrations/origin-mode.ts +0 -42
  153. package/src/runtime/migrations/rebind-secrets-screen.ts +0 -412
  154. package/src/runtime/migrations/staged-import-path.ts +0 -116
  155. package/src/runtime/migrations/transfer-progress-screen.ts +0 -323
  156. package/src/runtime/migrations/validation-results-screen.ts +0 -469
  157. package/src/runtime/migrations/vbundle-builder.ts +0 -1382
  158. package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -405
  159. package/src/runtime/migrations/vbundle-import-policy.ts +0 -241
  160. package/src/runtime/migrations/vbundle-importer.ts +0 -826
  161. package/src/runtime/migrations/vbundle-metadata-merge.ts +0 -113
  162. package/src/runtime/migrations/vbundle-streaming-importer.ts +0 -2844
  163. package/src/runtime/migrations/vbundle-streaming-validator.ts +0 -426
  164. package/src/runtime/migrations/vbundle-tar-stream.ts +0 -244
  165. package/src/runtime/migrations/vbundle-validator.ts +0 -724
  166. package/src/runtime/routes/__tests__/migration-cadet-metadata-reconcile.test.ts +0 -227
  167. package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +0 -54
  168. package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +0 -70
  169. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +0 -257
  170. package/src/runtime/routes/background-wake-routes.ts +0 -387
  171. package/src/runtime/routes/migration-routes.ts +0 -2584
  172. package/src/tools/network/__tests__/cloud-search-proxy.test.ts +0 -282
  173. package/src/tools/network/cloud-search-proxy.ts +0 -211
  174. package/src/tts/__tests__/cadet-provider.test.ts +0 -253
  175. package/src/tts/providers/cadet-provider.ts +0 -244
  176. package/src/types/tar-stream.d.ts +0 -66
@@ -1,485 +0,0 @@
1
- /**
2
- * Buffer-vs-streaming `.vbundle` import parity suite.
3
- *
4
- * Pins the existing disk-outcome equivalence between `commitImport`
5
- * (buffer-based) and `streamCommitImport` (streaming) as a regression net
6
- * BEFORE any production code is migrated to a shared policy module.
7
- *
8
- * Each test builds one archive, mkdtemps two sibling workspaces, seeds them
9
- * identically, runs each importer against its own workspace, and asserts the
10
- * post-import disk trees are byte-for-byte identical:
11
- *
12
- * expect(walkDiskTree(streamWs)).toEqual(walkDiskTree(bufferWs))
13
- *
14
- * Per-case invariants (carry-forward markers survive, persona lands at the
15
- * right disk path, traversal entries do not erase the workspace, etc.) are
16
- * asserted on top of disk-tree equality.
17
- */
18
-
19
- import { createHash } from "node:crypto";
20
- import {
21
- existsSync,
22
- mkdirSync,
23
- mkdtempSync,
24
- readdirSync,
25
- readFileSync,
26
- realpathSync,
27
- rmSync,
28
- writeFileSync,
29
- } from "node:fs";
30
- import { tmpdir } from "node:os";
31
- import { dirname, join, relative } from "node:path";
32
- import { Readable } from "node:stream";
33
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
34
-
35
- import { buildVBundle } from "../vbundle-builder.js";
36
- import {
37
- analyzeImport,
38
- DefaultPathResolver,
39
- } from "../vbundle-import-analyzer.js";
40
- import { commitImport } from "../vbundle-importer.js";
41
- import { streamCommitImport } from "../vbundle-streaming-importer.js";
42
- import { defaultV1Options } from "./v1-test-helpers.js";
43
-
44
- // ---------------------------------------------------------------------------
45
- // Shared helpers
46
- // ---------------------------------------------------------------------------
47
-
48
- /**
49
- * Allocate a fresh workspace path under a temp parent dir we own. The parent
50
- * is realpath-resolved so macOS `/var` → `/private/var` symlink mismatches
51
- * don't trip the streaming importer's `rebaseOntoTempWorkspace` containment
52
- * check (which compares `resolve(diskPath)` against `resolve(workspaceDir)`).
53
- *
54
- * Returns `<parent>/workspace`. The directory itself is NOT created — tests
55
- * that need it pre-existing call `mkdirSync` themselves.
56
- */
57
- function freshWorkspace(): string {
58
- const parent = realpathSync(
59
- mkdtempSync(join(tmpdir(), "vbundle-import-parity-")),
60
- );
61
- return join(parent, "workspace");
62
- }
63
-
64
- /** Best-effort cleanup of a workspace's parent dir. */
65
- function cleanupWorkspaceParent(workspaceDir: string): void {
66
- try {
67
- rmSync(join(workspaceDir, ".."), { recursive: true, force: true });
68
- } catch {
69
- // best-effort
70
- }
71
- }
72
-
73
- function sha256Hex(data: Uint8Array): string {
74
- return createHash("sha256").update(data).digest("hex");
75
- }
76
-
77
- /**
78
- * Recursively walk `root` and return a `Map<relPath, sha256Hex>` for every
79
- * regular file. Skips dot-prefixed scratch dirs the streaming importer may
80
- * leave behind on failure paths (`.import-*`, `.pre-import-*`) plus the
81
- * import marker file — the buffer importer never produces these, so they'd
82
- * spuriously break parity if included.
83
- *
84
- * Two importers are parity-equivalent for a given input iff the maps they
85
- * produce on identically-seeded sibling workspaces are equal.
86
- */
87
- function walkDiskTree(root: string): Map<string, string> {
88
- const out = new Map<string, string>();
89
- if (!existsSync(root)) {
90
- return out;
91
- }
92
-
93
- const stack: string[] = [root];
94
- while (stack.length > 0) {
95
- const dir = stack.pop()!;
96
- let entries;
97
- try {
98
- entries = readdirSync(dir, { withFileTypes: true });
99
- } catch {
100
- continue;
101
- }
102
- for (const entry of entries) {
103
- // Skip streaming-importer scratch artifacts so they don't show up as
104
- // false negatives in the parity comparison.
105
- if (
106
- entry.name.startsWith(".import-") ||
107
- entry.name.startsWith(".pre-import-") ||
108
- entry.name === ".import-marker.json"
109
- ) {
110
- continue;
111
- }
112
- const abs = join(dir, entry.name);
113
- if (entry.isDirectory()) {
114
- stack.push(abs);
115
- } else if (entry.isFile()) {
116
- const rel = relative(root, abs);
117
- out.set(rel, sha256Hex(readFileSync(abs)));
118
- }
119
- }
120
- }
121
- return out;
122
- }
123
-
124
- interface SeedFile {
125
- relPath: string;
126
- content: string | Uint8Array;
127
- }
128
-
129
- /** Mkdir parents and write each file to `workspaceDir`. */
130
- function seedLiveWorkspace(workspaceDir: string, files: SeedFile[]): void {
131
- mkdirSync(workspaceDir, { recursive: true });
132
- for (const { relPath, content } of files) {
133
- const abs = join(workspaceDir, relPath);
134
- mkdirSync(dirname(abs), { recursive: true });
135
- writeFileSync(abs, content);
136
- }
137
- }
138
-
139
- function runBufferImport(workspaceDir: string, archive: Uint8Array): void {
140
- const result = commitImport({
141
- archiveData: archive,
142
- pathResolver: new DefaultPathResolver(workspaceDir),
143
- workspaceDir,
144
- });
145
- if (!result.ok) {
146
- throw new Error(
147
- `buffer commitImport unexpectedly failed: ${JSON.stringify(result)}`,
148
- );
149
- }
150
- }
151
-
152
- async function runStreamImport(
153
- workspaceDir: string,
154
- archive: Uint8Array,
155
- importCredentials?: (
156
- credentials: Array<{ account: string; value: string }>,
157
- ) => Promise<void>,
158
- ): Promise<void> {
159
- const result = await streamCommitImport({
160
- source: Readable.from([Buffer.from(archive)]),
161
- pathResolver: new DefaultPathResolver(workspaceDir),
162
- workspaceDir,
163
- importCredentials,
164
- });
165
- if (!result.ok) {
166
- throw new Error(
167
- `streamCommitImport unexpectedly failed: ${JSON.stringify(result)}`,
168
- );
169
- }
170
- }
171
-
172
- // ---------------------------------------------------------------------------
173
- // Parity tests
174
- // ---------------------------------------------------------------------------
175
-
176
- describe("vbundle import parity (buffer vs streaming)", () => {
177
- let bufferWs: string;
178
- let streamWs: string;
179
-
180
- beforeEach(() => {
181
- bufferWs = freshWorkspace();
182
- streamWs = freshWorkspace();
183
- });
184
-
185
- afterEach(() => {
186
- cleanupWorkspaceParent(bufferWs);
187
- cleanupWorkspaceParent(streamWs);
188
- });
189
-
190
- test("A — full workspace + credentials: identical disk outcome", async () => {
191
- const dbBytes = new Uint8Array(16);
192
- for (let i = 0; i < dbBytes.length; i++) {
193
- dbBytes[i] = (i * 17) & 0xff;
194
- }
195
-
196
- const configJson = JSON.stringify({ version: 1 });
197
- const metadataJson = JSON.stringify({
198
- version: 5,
199
- credentials: [
200
- {
201
- credentialId: "id-openai-api_key",
202
- service: "openai",
203
- field: "api_key",
204
- allowedTools: [],
205
- allowedDomains: [],
206
- createdAt: 1700000000000,
207
- updatedAt: 1700000000000,
208
- },
209
- ],
210
- });
211
-
212
- const openaiKey = new Uint8Array(16);
213
- for (let i = 0; i < openaiKey.length; i++) {
214
- openaiKey[i] = (i + 5) & 0xff;
215
- }
216
- const anthropicKey = new TextEncoder().encode("sk-ant-test");
217
-
218
- const { archive } = buildVBundle({
219
- files: [
220
- { path: "workspace/data/db/assistant.db", data: dbBytes },
221
- {
222
- path: "workspace/config.json",
223
- data: new TextEncoder().encode(configJson),
224
- },
225
- {
226
- path: "workspace/data/credentials/metadata.json",
227
- data: new TextEncoder().encode(metadataJson),
228
- },
229
- { path: "credentials/openai-key", data: openaiKey },
230
- { path: "credentials/anthropic-key", data: anthropicKey },
231
- ],
232
- ...defaultV1Options(),
233
- });
234
-
235
- // Pre-create both workspaces (streaming importer expects to operate
236
- // against an existing dir and the atomic-swap path requires it).
237
- mkdirSync(bufferWs, { recursive: true });
238
- mkdirSync(streamWs, { recursive: true });
239
-
240
- runBufferImport(bufferWs, archive);
241
- await runStreamImport(streamWs, archive, async () => {
242
- // parity test: credentials are intercepted but not persisted
243
- });
244
-
245
- const bufferMap = walkDiskTree(bufferWs);
246
- const streamMap = walkDiskTree(streamWs);
247
-
248
- expect(streamMap).toEqual(bufferMap);
249
-
250
- // Sanity: the three workspace-bound files we expect both importers to
251
- // land are present in the parity map.
252
- expect(bufferMap.has("data/db/assistant.db")).toBe(true);
253
- expect(bufferMap.has("config.json")).toBe(true);
254
- expect(bufferMap.has("data/credentials/metadata.json")).toBe(true);
255
- });
256
-
257
- test("B — config-only partial bundle: live preserved paths survive on both sides", async () => {
258
- const seeds: SeedFile[] = [
259
- { relPath: "data/db/marker", content: "db-marker" },
260
- { relPath: "data/qdrant/marker", content: "qdrant-marker" },
261
- { relPath: "embedding-models/marker", content: "embedding-marker" },
262
- { relPath: "deprecated/marker", content: "deprecated-marker" },
263
- ];
264
-
265
- seedLiveWorkspace(bufferWs, seeds);
266
- seedLiveWorkspace(streamWs, seeds);
267
-
268
- const configJson = JSON.stringify({ version: 1 });
269
- const { archive } = buildVBundle({
270
- files: [
271
- // Validator requires the DB entry (legacy or workspace-prefixed).
272
- // It's not the focus of this case — config.json is — but it must
273
- // be present for the bundle to validate.
274
- { path: "data/db/assistant.db", data: new Uint8Array() },
275
- {
276
- path: "workspace/config.json",
277
- data: new TextEncoder().encode(configJson),
278
- },
279
- ],
280
- ...defaultV1Options(),
281
- });
282
-
283
- runBufferImport(bufferWs, archive);
284
- await runStreamImport(streamWs, archive);
285
-
286
- const bufferMap = walkDiskTree(bufferWs);
287
- const streamMap = walkDiskTree(streamWs);
288
-
289
- expect(streamMap).toEqual(bufferMap);
290
-
291
- // Each carry-forward marker must still be on disk.
292
- for (const seed of seeds) {
293
- expect(bufferMap.has(seed.relPath)).toBe(true);
294
- expect(streamMap.has(seed.relPath)).toBe(true);
295
- }
296
- });
297
-
298
- test("C — legacy prompts/USER.md: both importers land it at users/<slug>.md", async () => {
299
- const personaBody = `_ Lines starting with _ are comments - they won't appear in the system prompt
300
-
301
- # User Profile
302
-
303
- - Preferred name/reference: Captain Parity
304
- - Pronouns: they/them
305
- - Locale: en-US
306
- - Work role: Quartermaster
307
- - Goals: Verify importer parity
308
- - Hobbies/fun: Diff'ing trees
309
- - Daily tools: Terminal
310
- `;
311
-
312
- // Pre-create users/ in both workspaces so the resolver's containment
313
- // check has a real on-disk parent to anchor against.
314
- mkdirSync(join(bufferWs, "users"), { recursive: true });
315
- mkdirSync(join(streamWs, "users"), { recursive: true });
316
-
317
- const { archive } = buildVBundle({
318
- files: [
319
- { path: "data/db/assistant.db", data: new Uint8Array() },
320
- {
321
- path: "prompts/USER.md",
322
- data: new TextEncoder().encode(personaBody),
323
- },
324
- ],
325
- ...defaultV1Options(),
326
- });
327
-
328
- const bufferGuardianPath = join(bufferWs, "users", "captain.md");
329
- const streamGuardianPath = join(streamWs, "users", "captain.md");
330
-
331
- const bufferResolver = new DefaultPathResolver(
332
- bufferWs,
333
- undefined,
334
- () => bufferGuardianPath,
335
- );
336
- const streamResolver = new DefaultPathResolver(
337
- streamWs,
338
- undefined,
339
- () => streamGuardianPath,
340
- );
341
-
342
- const bufferResult = commitImport({
343
- archiveData: archive,
344
- pathResolver: bufferResolver,
345
- workspaceDir: bufferWs,
346
- });
347
- expect(bufferResult.ok).toBe(true);
348
-
349
- const streamResult = await streamCommitImport({
350
- source: Readable.from([Buffer.from(archive)]),
351
- pathResolver: streamResolver,
352
- workspaceDir: streamWs,
353
- });
354
- expect(streamResult.ok).toBe(true);
355
-
356
- const bufferMap = walkDiskTree(bufferWs);
357
- const streamMap = walkDiskTree(streamWs);
358
-
359
- expect(streamMap).toEqual(bufferMap);
360
-
361
- expect(existsSync(bufferGuardianPath)).toBe(true);
362
- expect(existsSync(streamGuardianPath)).toBe(true);
363
- expect(readFileSync(bufferGuardianPath, "utf-8")).toBe(personaBody);
364
- expect(readFileSync(streamGuardianPath, "utf-8")).toBe(personaBody);
365
- });
366
-
367
- test("D — no workspace entries at all: legacy bundle leaves seeded files in place", async () => {
368
- const seeds: SeedFile[] = [
369
- { relPath: "unrelated.txt", content: "stay" },
370
- { relPath: "custom-dir/note.md", content: "# note" },
371
- ];
372
-
373
- seedLiveWorkspace(bufferWs, seeds);
374
- seedLiveWorkspace(streamWs, seeds);
375
-
376
- const dbBytes = new TextEncoder().encode("legacy-db-payload");
377
- const { archive } = buildVBundle({
378
- files: [{ path: "data/db/assistant.db", data: dbBytes }],
379
- ...defaultV1Options(),
380
- });
381
-
382
- runBufferImport(bufferWs, archive);
383
- await runStreamImport(streamWs, archive);
384
-
385
- const bufferMap = walkDiskTree(bufferWs);
386
- const streamMap = walkDiskTree(streamWs);
387
-
388
- expect(streamMap).toEqual(bufferMap);
389
-
390
- for (const seed of seeds) {
391
- expect(bufferMap.has(seed.relPath)).toBe(true);
392
- expect(streamMap.has(seed.relPath)).toBe(true);
393
- }
394
- });
395
-
396
- test("E — path-traversal workspace entry: both importers refuse to clear", async () => {
397
- seedLiveWorkspace(bufferWs, [{ relPath: "marker.txt", content: "keep" }]);
398
- seedLiveWorkspace(streamWs, [{ relPath: "marker.txt", content: "keep" }]);
399
-
400
- const { archive } = buildVBundle({
401
- files: [
402
- { path: "data/db/assistant.db", data: new Uint8Array() },
403
- {
404
- path: "workspace/../../etc/passwd",
405
- data: new TextEncoder().encode("nope"),
406
- },
407
- ],
408
- ...defaultV1Options(),
409
- });
410
-
411
- runBufferImport(bufferWs, archive);
412
- await runStreamImport(streamWs, archive);
413
-
414
- const bufferMap = walkDiskTree(bufferWs);
415
- const streamMap = walkDiskTree(streamWs);
416
-
417
- expect(streamMap).toEqual(bufferMap);
418
-
419
- expect(bufferMap.has("marker.txt")).toBe(true);
420
- expect(streamMap.has("marker.txt")).toBe(true);
421
- });
422
-
423
- test("F — debug-profile gateway entry: both importers drain it and write nothing", async () => {
424
- const configJson = JSON.stringify({ version: 1 });
425
- const { archive, manifest } = buildVBundle({
426
- files: [
427
- { path: "workspace/data/db/assistant.db", data: new Uint8Array(8) },
428
- {
429
- path: "workspace/config.json",
430
- data: new TextEncoder().encode(configJson),
431
- },
432
- {
433
- path: "gateway/export.tar.gz",
434
- data: new TextEncoder().encode("not-a-real-tarball"),
435
- },
436
- ],
437
- ...defaultV1Options(),
438
- });
439
-
440
- // Preflight must not block on the gateway entry, or the CLI would
441
- // refuse the restore before either importer runs.
442
- const preflight = analyzeImport({
443
- manifest,
444
- pathResolver: new DefaultPathResolver(bufferWs),
445
- });
446
- expect(preflight.can_import).toBe(true);
447
- expect(preflight.conflicts).toEqual([]);
448
-
449
- mkdirSync(bufferWs, { recursive: true });
450
- mkdirSync(streamWs, { recursive: true });
451
-
452
- const bufferResult = commitImport({
453
- archiveData: archive,
454
- pathResolver: new DefaultPathResolver(bufferWs),
455
- workspaceDir: bufferWs,
456
- });
457
- const streamResult = await streamCommitImport({
458
- source: Readable.from([Buffer.from(archive)]),
459
- pathResolver: new DefaultPathResolver(streamWs),
460
- workspaceDir: streamWs,
461
- });
462
- if (!bufferResult.ok || !streamResult.ok) {
463
- throw new Error("import unexpectedly failed");
464
- }
465
-
466
- const bufferMap = walkDiskTree(bufferWs);
467
- const streamMap = walkDiskTree(streamWs);
468
-
469
- expect(streamMap).toEqual(bufferMap);
470
- expect(bufferMap.has("config.json")).toBe(true);
471
- expect(bufferMap.has("data/db/assistant.db")).toBe(true);
472
- for (const relPath of bufferMap.keys()) {
473
- expect(relPath.startsWith("gateway")).toBe(false);
474
- }
475
- // Skipped silently, like a retired path: counted, but no
476
- // "no known disk target" warning.
477
- for (const { report } of [bufferResult, streamResult]) {
478
- expect(report.warnings).toEqual([]);
479
- const gateway = report.files.find(
480
- (f) => f.path === "gateway/export.tar.gz",
481
- );
482
- expect(gateway?.action).toBe("skipped");
483
- }
484
- });
485
- });