@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,184 +0,0 @@
1
- /**
2
- * Tests for credential-aware rebind-secrets screen behavior.
3
- *
4
- * Covers:
5
- * - All credentials imported successfully -> re-enter-secrets auto-completed
6
- * - Partial import failure -> targeted list of failed credentials
7
- * - Legacy bundles without credentials -> original rebind-secrets behavior
8
- */
9
-
10
- import { describe, expect, test } from "bun:test";
11
-
12
- import type { MigrationWizardState } from "../migration-wizard.js";
13
- import { createWizardState } from "../migration-wizard.js";
14
- import {
15
- createTaskCompletionState,
16
- deriveRebindSecretsScreenState,
17
- } from "../rebind-secrets-screen.js";
18
-
19
- // ---------------------------------------------------------------------------
20
- // Helpers
21
- // ---------------------------------------------------------------------------
22
-
23
- /**
24
- * Create a wizard state that has progressed to the rebind-secrets step.
25
- */
26
- function wizardAtRebindSecrets(
27
- credentialsImported?: MigrationWizardState["credentialsImported"],
28
- ): MigrationWizardState {
29
- const base = createWizardState();
30
- return {
31
- ...base,
32
- currentStep: "rebind-secrets",
33
- direction: "managed-to-self-hosted",
34
- steps: {
35
- ...base.steps,
36
- "select-direction": { status: "success" },
37
- "upload-bundle": { status: "success" },
38
- validate: { status: "success" },
39
- "preflight-review": { status: "success" },
40
- transfer: { status: "success" },
41
- "rebind-secrets": { status: "idle" },
42
- complete: { status: "idle" },
43
- },
44
- hasBundleData: true,
45
- credentialsImported,
46
- };
47
- }
48
-
49
- // ---------------------------------------------------------------------------
50
- // Tests
51
- // ---------------------------------------------------------------------------
52
-
53
- describe("rebind-secrets-screen credential awareness", () => {
54
- test("all credentials imported successfully -> re-enter-secrets is auto-completed", () => {
55
- const wizard = wizardAtRebindSecrets({
56
- total: 3,
57
- succeeded: 3,
58
- failed: 0,
59
- failedAccounts: [],
60
- });
61
- const completion = createTaskCompletionState();
62
- const screen = deriveRebindSecretsScreenState(wizard, completion);
63
-
64
- expect(screen.phase).toBe("active");
65
- if (screen.phase !== "active") {
66
- return;
67
- }
68
-
69
- const secretsTask = screen.tasks.find((t) => t.id === "re-enter-secrets");
70
- expect(secretsTask).toBeDefined();
71
- expect(secretsTask!.status).toBe("complete");
72
- expect(secretsTask!.title).toBe("API keys and secrets transferred");
73
- expect(secretsTask!.description).toContain("3 credential(s)");
74
- expect(secretsTask!.description).toContain("automatically imported");
75
- });
76
-
77
- test("partial import failure -> shows only failed credentials", () => {
78
- const wizard = wizardAtRebindSecrets({
79
- total: 3,
80
- succeeded: 2,
81
- failed: 1,
82
- failedAccounts: ["openai-key"],
83
- });
84
- const completion = createTaskCompletionState();
85
- const screen = deriveRebindSecretsScreenState(wizard, completion);
86
-
87
- expect(screen.phase).toBe("active");
88
- if (screen.phase !== "active") {
89
- return;
90
- }
91
-
92
- const secretsTask = screen.tasks.find((t) => t.id === "re-enter-secrets");
93
- expect(secretsTask).toBeDefined();
94
- expect(secretsTask!.status).toBe("pending");
95
- expect(secretsTask!.title).toBe("Re-enter failed credentials");
96
- expect(secretsTask!.description).toContain("2 of 3");
97
- expect(secretsTask!.description).toContain('"openai-key"');
98
- expect(secretsTask!.description).toContain("manual re-entry");
99
- });
100
-
101
- test("legacy bundle without credentials -> original rebind-secrets behavior", () => {
102
- const wizard = wizardAtRebindSecrets(undefined);
103
- const completion = createTaskCompletionState();
104
- const screen = deriveRebindSecretsScreenState(wizard, completion);
105
-
106
- expect(screen.phase).toBe("active");
107
- if (screen.phase !== "active") {
108
- return;
109
- }
110
-
111
- const secretsTask = screen.tasks.find((t) => t.id === "re-enter-secrets");
112
- expect(secretsTask).toBeDefined();
113
- expect(secretsTask!.status).toBe("pending");
114
- expect(secretsTask!.title).toBe("Re-enter API keys and secrets");
115
- expect(secretsTask!.description).toContain("redacted in export bundles");
116
- });
117
-
118
- test("all credentials imported -> allRequiredComplete reflects auto-completion", () => {
119
- const wizard = wizardAtRebindSecrets({
120
- total: 2,
121
- succeeded: 2,
122
- failed: 0,
123
- failedAccounts: [],
124
- });
125
- // Mark all other required tasks as complete
126
- let completion = createTaskCompletionState();
127
- completion = {
128
- ...completion,
129
- "rebind-channels": true,
130
- "reconfigure-auth": true,
131
- };
132
- const screen = deriveRebindSecretsScreenState(wizard, completion);
133
-
134
- expect(screen.phase).toBe("active");
135
- if (screen.phase !== "active") {
136
- return;
137
- }
138
-
139
- // re-enter-secrets is auto-completed, rebind-channels and reconfigure-auth are manually done
140
- expect(screen.allRequiredComplete).toBe(true);
141
- });
142
-
143
- test("partial failure -> allRequiredComplete is false when failed creds not manually acknowledged", () => {
144
- const wizard = wizardAtRebindSecrets({
145
- total: 3,
146
- succeeded: 2,
147
- failed: 1,
148
- failedAccounts: ["anthropic-key"],
149
- });
150
- const completion = createTaskCompletionState();
151
- const screen = deriveRebindSecretsScreenState(wizard, completion);
152
-
153
- expect(screen.phase).toBe("active");
154
- if (screen.phase !== "active") {
155
- return;
156
- }
157
-
158
- // re-enter-secrets is still pending (partial failure), so not all required complete
159
- expect(screen.allRequiredComplete).toBe(false);
160
- });
161
-
162
- test("multiple failed credentials are listed in description", () => {
163
- const wizard = wizardAtRebindSecrets({
164
- total: 4,
165
- succeeded: 1,
166
- failed: 3,
167
- failedAccounts: ["openai-key", "anthropic-key", "github-token"],
168
- });
169
- const completion = createTaskCompletionState();
170
- const screen = deriveRebindSecretsScreenState(wizard, completion);
171
-
172
- expect(screen.phase).toBe("active");
173
- if (screen.phase !== "active") {
174
- return;
175
- }
176
-
177
- const secretsTask = screen.tasks.find((t) => t.id === "re-enter-secrets");
178
- expect(secretsTask).toBeDefined();
179
- expect(secretsTask!.description).toContain('"openai-key"');
180
- expect(secretsTask!.description).toContain('"anthropic-key"');
181
- expect(secretsTask!.description).toContain('"github-token"');
182
- expect(secretsTask!.description).toContain("1 of 4");
183
- });
184
- });
@@ -1,110 +0,0 @@
1
- import {
2
- mkdirSync,
3
- mkdtempSync,
4
- realpathSync,
5
- symlinkSync,
6
- writeFileSync,
7
- } from "node:fs";
8
- import { tmpdir } from "node:os";
9
- import { join } from "node:path";
10
- import { describe, expect, test } from "bun:test";
11
-
12
- import {
13
- resolveStagedImportPath,
14
- RESTORE_STAGING_DIRNAME,
15
- StagedImportPathError,
16
- } from "../staged-import-path.js";
17
-
18
- function stagingWorkspace(): { workspace: string; staging: string } {
19
- const workspace = realpathSync(mkdtempSync(join(tmpdir(), "staged-import-")));
20
- const staging = join(workspace, RESTORE_STAGING_DIRNAME);
21
- mkdirSync(staging, { recursive: true });
22
- return { workspace, staging };
23
- }
24
-
25
- describe("resolveStagedImportPath", () => {
26
- test("resolves a relative path inside the staging directory", () => {
27
- const { workspace, staging } = stagingWorkspace();
28
- const file = join(staging, "backup.vbundle");
29
- writeFileSync(file, "bundle");
30
-
31
- expect(
32
- resolveStagedImportPath(
33
- `${RESTORE_STAGING_DIRNAME}/backup.vbundle`,
34
- workspace,
35
- ),
36
- ).toBe(realpathSync(file));
37
- });
38
-
39
- test("resolves an absolute path inside the staging directory", () => {
40
- const { workspace, staging } = stagingWorkspace();
41
- const file = join(staging, "backup.vbundle");
42
- writeFileSync(file, "bundle");
43
-
44
- expect(resolveStagedImportPath(file, workspace)).toBe(realpathSync(file));
45
- });
46
-
47
- test("rejects a missing file", () => {
48
- const { workspace } = stagingWorkspace();
49
- expect(() =>
50
- resolveStagedImportPath(
51
- `${RESTORE_STAGING_DIRNAME}/missing.vbundle`,
52
- workspace,
53
- ),
54
- ).toThrow(StagedImportPathError);
55
- });
56
-
57
- test("rejects traversal out of the staging directory", () => {
58
- const { workspace } = stagingWorkspace();
59
- writeFileSync(join(workspace, "secret.vbundle"), "nope");
60
-
61
- expect(() =>
62
- resolveStagedImportPath(
63
- `${RESTORE_STAGING_DIRNAME}/../secret.vbundle`,
64
- workspace,
65
- ),
66
- ).toThrow(StagedImportPathError);
67
- });
68
-
69
- test("rejects a path outside the workspace", () => {
70
- const { workspace } = stagingWorkspace();
71
- const outside = join(tmpdir(), `outside-${Date.now()}.vbundle`);
72
- writeFileSync(outside, "nope");
73
-
74
- expect(() => resolveStagedImportPath(outside, workspace)).toThrow(
75
- StagedImportPathError,
76
- );
77
- });
78
-
79
- test("rejects a symlink even when it points at a staging file", () => {
80
- const { workspace, staging } = stagingWorkspace();
81
- const file = join(staging, "backup.vbundle");
82
- writeFileSync(file, "bundle");
83
- const link = join(staging, "alias.vbundle");
84
- symlinkSync(file, link);
85
-
86
- expect(() => resolveStagedImportPath(link, workspace)).toThrow(
87
- StagedImportPathError,
88
- );
89
- });
90
-
91
- test("rejects a non-.vbundle file in staging", () => {
92
- const { workspace, staging } = stagingWorkspace();
93
- const file = join(staging, "notes.txt");
94
- writeFileSync(file, "nope");
95
-
96
- expect(() =>
97
- resolveStagedImportPath(
98
- `${RESTORE_STAGING_DIRNAME}/notes.txt`,
99
- workspace,
100
- ),
101
- ).toThrow(StagedImportPathError);
102
- });
103
-
104
- test("rejects an empty path", () => {
105
- const { workspace } = stagingWorkspace();
106
- expect(() => resolveStagedImportPath(" ", workspace)).toThrow(
107
- StagedImportPathError,
108
- );
109
- });
110
- });
@@ -1,112 +0,0 @@
1
- /**
2
- * Shared test helpers for vbundle v1 manifest fixture builders.
3
- *
4
- * Most tests don't care about the specific values of the assistant identity,
5
- * origin, compatibility, or export-options blocks — they just need the
6
- * builder/validator to accept their fixtures. Centralizing the defaults
7
- * keeps every test from re-spelling the same six required option fields.
8
- */
9
-
10
- import { randomUUID } from "node:crypto";
11
-
12
- import type {
13
- BuildVBundleOptions,
14
- VBundleAssistantInfo,
15
- VBundleCompatibility,
16
- VBundleExportOptions,
17
- VBundleOriginInfo,
18
- } from "../vbundle-builder.js";
19
- import {
20
- computeManifestChecksum,
21
- type ManifestFileEntryType,
22
- type ManifestType,
23
- } from "../vbundle-validator.js";
24
-
25
- export interface DefaultV1Options {
26
- assistant: VBundleAssistantInfo;
27
- origin: VBundleOriginInfo;
28
- compatibility: VBundleCompatibility;
29
- exportOptions: VBundleExportOptions;
30
- secretsRedacted: boolean;
31
- }
32
-
33
- /**
34
- * Sensible defaults for the six caller-required v1 manifest options.
35
- *
36
- * `secretsRedacted` defaults to false to match the runtime's typical
37
- * "credentials included by design" path; tests that exercise the cloud
38
- * cross-field refine override `origin.mode` and `secretsRedacted` directly.
39
- */
40
- export function defaultV1Options(): DefaultV1Options {
41
- return {
42
- assistant: {
43
- id: "self",
44
- name: "Test",
45
- runtime_version: "0.0.0-test",
46
- },
47
- origin: {
48
- mode: "self-hosted-local",
49
- },
50
- compatibility: {
51
- min_runtime_version: "0.0.0-test",
52
- max_runtime_version: null,
53
- },
54
- exportOptions: {
55
- include_logs: false,
56
- include_browser_state: false,
57
- include_memory_vectors: false,
58
- },
59
- secretsRedacted: false,
60
- };
61
- }
62
-
63
- /**
64
- * Convenience: spread `defaultV1Options()` into a `BuildVBundleOptions`
65
- * with the supplied `files`. Saves repeating the spread at every call site.
66
- */
67
- export function buildVBundleTestOptions(
68
- files: BuildVBundleOptions["files"],
69
- overrides: Partial<DefaultV1Options> = {},
70
- ): BuildVBundleOptions {
71
- return {
72
- files,
73
- ...defaultV1Options(),
74
- ...overrides,
75
- };
76
- }
77
-
78
- /**
79
- * Build a v1 ManifestType for tests, mirroring buildManifestObject() in
80
- * vbundle-builder.ts. Use this in test fixtures that need a synthetic
81
- * manifest rather than calling buildVBundle (e.g. cross-version compat
82
- * tests that need to mutate fields between emit and validate).
83
- *
84
- * Pass `overrides` to override any field after the defaults are applied —
85
- * useful for negative-path tests that exercise specific schema rejections.
86
- * `schema_version` is widened to `number` so negative tests can write 0/2/etc.
87
- * The checksum is computed on the merged shape so overrides take effect.
88
- */
89
- export type BuildTestManifestOverrides = Partial<
90
- Omit<ManifestType, "schema_version">
91
- > & { schema_version?: number };
92
-
93
- export function buildTestManifest(input: {
94
- contents: ManifestFileEntryType[];
95
- overrides?: BuildTestManifestOverrides;
96
- }): ManifestType {
97
- const base = defaultV1Options();
98
- const merged = {
99
- schema_version: 1,
100
- bundle_id: randomUUID(),
101
- created_at: new Date().toISOString(),
102
- assistant: base.assistant,
103
- origin: base.origin,
104
- compatibility: base.compatibility,
105
- contents: input.contents,
106
- checksum: "",
107
- secrets_redacted: base.secretsRedacted,
108
- export_options: base.exportOptions,
109
- ...(input.overrides ?? {}),
110
- } as ManifestType;
111
- return { ...merged, checksum: computeManifestChecksum(merged) };
112
- }
@@ -1,285 +0,0 @@
1
- /**
2
- * Tests for credential inclusion in vbundle export.
3
- *
4
- * Covers:
5
- * - buildExportVBundle() with credentials includes credentials/* entries
6
- * - Credentials appear in the manifest with correct SHA-256 checksums
7
- * - Export with no credentials produces the same archive as before (backward compat)
8
- * - Streaming path (streamExportVBundle) includes credential entries
9
- */
10
-
11
- import { createHash } from "node:crypto";
12
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
13
- import { tmpdir } from "node:os";
14
- import { join } from "node:path";
15
- import { gunzipSync } from "node:zlib";
16
- import { describe, expect, test } from "bun:test";
17
-
18
- import { buildExportVBundle, streamExportVBundle } from "../vbundle-builder.js";
19
- import { defaultV1Options } from "./v1-test-helpers.js";
20
-
21
- // ---------------------------------------------------------------------------
22
- // Helpers
23
- // ---------------------------------------------------------------------------
24
-
25
- const BLOCK_SIZE = 512;
26
-
27
- function sha256Hex(data: Uint8Array | string): string {
28
- return createHash("sha256").update(data).digest("hex");
29
- }
30
-
31
- /** Parse a tar archive into its entries (name -> data). */
32
- function parseTar(tar: Uint8Array): Map<string, Uint8Array> {
33
- const entries = new Map<string, Uint8Array>();
34
- let offset = 0;
35
- let paxPath: string | undefined;
36
-
37
- while (offset + BLOCK_SIZE <= tar.length) {
38
- const header = tar.subarray(offset, offset + BLOCK_SIZE);
39
-
40
- // Check for end-of-archive (all zeros)
41
- if (header.every((b) => b === 0)) {
42
- break;
43
- }
44
-
45
- // Read file name
46
- let name = "";
47
- for (let i = 0; i < 100 && header[i] !== 0; i++) {
48
- name += String.fromCharCode(header[i]);
49
- }
50
-
51
- // Read size (octal at offset 124, 12 bytes)
52
- let sizeStr = "";
53
- for (let i = 124; i < 136 && header[i] !== 0; i++) {
54
- sizeStr += String.fromCharCode(header[i]);
55
- }
56
- const size = parseInt(sizeStr.trim(), 8) || 0;
57
-
58
- // Type flag
59
- const typeFlag = String.fromCharCode(header[156]);
60
-
61
- offset += BLOCK_SIZE;
62
-
63
- const data = tar.subarray(offset, offset + size);
64
- const paddedSize = Math.ceil(size / BLOCK_SIZE) * BLOCK_SIZE;
65
- offset += paddedSize;
66
-
67
- if (typeFlag === "x") {
68
- // PAX extended header — extract path
69
- const paxStr = new TextDecoder().decode(data);
70
- const match = paxStr.match(/\d+ path=(.+)\n/);
71
- if (match) {
72
- paxPath = match[1];
73
- }
74
- continue;
75
- }
76
-
77
- const entryName = paxPath ?? name;
78
- paxPath = undefined;
79
- entries.set(entryName, new Uint8Array(data));
80
- }
81
-
82
- return entries;
83
- }
84
-
85
- /** Create a minimal workspace directory with a config file for testing. */
86
- function createTestWorkspace(): { dir: string; cleanup: () => void } {
87
- const dir = join(
88
- tmpdir(),
89
- `vbundle-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
90
- );
91
- mkdirSync(dir, { recursive: true });
92
- writeFileSync(join(dir, "config.json"), JSON.stringify({ test: true }));
93
- // Create a minimal data/db directory with a fake database file
94
- const dbDir = join(dir, "data", "db");
95
- mkdirSync(dbDir, { recursive: true });
96
- writeFileSync(join(dbDir, "assistant.db"), "fake-db-content");
97
- return {
98
- dir,
99
- cleanup: () => rmSync(dir, { recursive: true, force: true }),
100
- };
101
- }
102
-
103
- // ---------------------------------------------------------------------------
104
- // Tests
105
- // ---------------------------------------------------------------------------
106
-
107
- describe("buildExportVBundle with credentials", () => {
108
- test("includes credential entries under credentials/ prefix", () => {
109
- const workspace = createTestWorkspace();
110
- try {
111
- const credentials = [
112
- { account: "openai-key", value: "sk-test-123" },
113
- { account: "anthropic-key", value: "sk-ant-456" },
114
- ];
115
-
116
- const result = buildExportVBundle({
117
- ...defaultV1Options(),
118
- workspaceDir: workspace.dir,
119
- credentials,
120
- });
121
-
122
- const tar = gunzipSync(result.archive);
123
- const entries = parseTar(tar);
124
-
125
- expect(entries.has("credentials/openai-key")).toBe(true);
126
- expect(entries.has("credentials/anthropic-key")).toBe(true);
127
-
128
- const decoder = new TextDecoder();
129
- expect(decoder.decode(entries.get("credentials/openai-key")!)).toBe(
130
- "sk-test-123",
131
- );
132
- expect(decoder.decode(entries.get("credentials/anthropic-key")!)).toBe(
133
- "sk-ant-456",
134
- );
135
- } finally {
136
- workspace.cleanup();
137
- }
138
- });
139
-
140
- test("credentials appear in manifest with correct SHA-256 checksums", () => {
141
- const workspace = createTestWorkspace();
142
- try {
143
- const credentials = [
144
- { account: "my-api-key", value: "secret-value-abc" },
145
- ];
146
-
147
- const result = buildExportVBundle({
148
- ...defaultV1Options(),
149
- workspaceDir: workspace.dir,
150
- credentials,
151
- });
152
-
153
- const expectedHash = sha256Hex(
154
- new TextEncoder().encode("secret-value-abc"),
155
- );
156
-
157
- const credEntry = result.manifest.contents.find(
158
- (f) => f.path === "credentials/my-api-key",
159
- );
160
- expect(credEntry).toBeDefined();
161
- expect(credEntry!.sha256).toBe(expectedHash);
162
- expect(credEntry!.size_bytes).toBe(
163
- new TextEncoder().encode("secret-value-abc").length,
164
- );
165
- } finally {
166
- workspace.cleanup();
167
- }
168
- });
169
-
170
- test("export with no credentials produces archive without credentials/ entries", () => {
171
- const workspace = createTestWorkspace();
172
- try {
173
- const result = buildExportVBundle({
174
- ...defaultV1Options(),
175
- workspaceDir: workspace.dir,
176
- });
177
-
178
- const tar = gunzipSync(result.archive);
179
- const entries = parseTar(tar);
180
-
181
- const credentialEntries = [...entries.keys()].filter((k) =>
182
- k.startsWith("credentials/"),
183
- );
184
- expect(credentialEntries).toHaveLength(0);
185
-
186
- // Manifest should have no credential entries
187
- const credManifestEntries = result.manifest.contents.filter((f) =>
188
- f.path.startsWith("credentials/"),
189
- );
190
- expect(credManifestEntries).toHaveLength(0);
191
- } finally {
192
- workspace.cleanup();
193
- }
194
- });
195
-
196
- test("export with empty credentials array produces archive without credentials/ entries", () => {
197
- const workspace = createTestWorkspace();
198
- try {
199
- const result = buildExportVBundle({
200
- ...defaultV1Options(),
201
- workspaceDir: workspace.dir,
202
- credentials: [],
203
- });
204
-
205
- const tar = gunzipSync(result.archive);
206
- const entries = parseTar(tar);
207
-
208
- const credentialEntries = [...entries.keys()].filter((k) =>
209
- k.startsWith("credentials/"),
210
- );
211
- expect(credentialEntries).toHaveLength(0);
212
- } finally {
213
- workspace.cleanup();
214
- }
215
- });
216
- });
217
-
218
- describe("streamExportVBundle with credentials", () => {
219
- test("includes credential entries in the streaming archive", async () => {
220
- const workspace = createTestWorkspace();
221
- let result: Awaited<ReturnType<typeof streamExportVBundle>> | undefined;
222
- try {
223
- const credentials = [
224
- { account: "stream-key", value: "stream-secret-789" },
225
- ];
226
-
227
- result = await streamExportVBundle({
228
- ...defaultV1Options(),
229
- workspaceDir: workspace.dir,
230
- credentials,
231
- });
232
-
233
- // Read the temp file and decompress
234
- const archiveData = new Uint8Array(
235
- await Bun.file(result.tempPath).arrayBuffer(),
236
- );
237
- const tar = gunzipSync(archiveData);
238
- const entries = parseTar(tar);
239
-
240
- expect(entries.has("credentials/stream-key")).toBe(true);
241
-
242
- const decoder = new TextDecoder();
243
- expect(decoder.decode(entries.get("credentials/stream-key")!)).toBe(
244
- "stream-secret-789",
245
- );
246
-
247
- // Verify manifest entry
248
- const credEntry = result.manifest.contents.find(
249
- (f) => f.path === "credentials/stream-key",
250
- );
251
- expect(credEntry).toBeDefined();
252
- expect(credEntry!.sha256).toBe(
253
- sha256Hex(new TextEncoder().encode("stream-secret-789")),
254
- );
255
- } finally {
256
- await result?.cleanup();
257
- workspace.cleanup();
258
- }
259
- });
260
-
261
- test("streaming export without credentials has no credentials/ entries", async () => {
262
- const workspace = createTestWorkspace();
263
- let result: Awaited<ReturnType<typeof streamExportVBundle>> | undefined;
264
- try {
265
- result = await streamExportVBundle({
266
- ...defaultV1Options(),
267
- workspaceDir: workspace.dir,
268
- });
269
-
270
- const archiveData = new Uint8Array(
271
- await Bun.file(result.tempPath).arrayBuffer(),
272
- );
273
- const tar = gunzipSync(archiveData);
274
- const entries = parseTar(tar);
275
-
276
- const credentialEntries = [...entries.keys()].filter((k) =>
277
- k.startsWith("credentials/"),
278
- );
279
- expect(credentialEntries).toHaveLength(0);
280
- } finally {
281
- await result?.cleanup();
282
- workspace.cleanup();
283
- }
284
- });
285
- });