@mycadet/assistant 0.12.10 → 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 (180) hide show
  1. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  2. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  3. package/node_modules/@mycadet/ces-client/src/http-credentials.ts +18 -1
  4. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  5. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  6. package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +0 -20
  7. package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  8. package/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  9. package/openapi.yaml +2 -708
  10. package/package.json +2 -3
  11. package/scripts/sync-web-search-catalog.ts +1 -2
  12. package/src/__tests__/cloud-profile-guard.test.ts +41 -5
  13. package/src/__tests__/config-schema.test.ts +6 -38
  14. package/src/__tests__/conversation-queue.test.ts +45 -6
  15. package/src/__tests__/db-readiness-http-gate.test.ts +0 -6
  16. package/src/__tests__/persist-onboarding-artifacts.test.ts +0 -1
  17. package/src/__tests__/persona-resolver.test.ts +0 -51
  18. package/src/__tests__/provider-cloud-proxy-integration.test.ts +2 -110
  19. package/src/__tests__/run-due-schedules.test.ts +0 -4
  20. package/src/__tests__/schedule-store.test.ts +0 -12
  21. package/src/__tests__/stt-stream-session.test.ts +4 -4
  22. package/src/__tests__/telephony-tts-guard.test.ts +2 -50
  23. package/src/__tests__/voice-config-update.test.ts +12 -167
  24. package/src/__tests__/web-search-catalog-parity.test.ts +0 -2
  25. package/src/__tests__/workspace-migration-130-speech-mode-to-provider.test.ts +9 -7
  26. package/src/__tests__/workspace-migration-132-web-search-mode-to-provider.test.ts +6 -6
  27. package/src/calls/__tests__/voice-session-bridge.test.ts +1 -4
  28. package/src/calls/resolve-call-tts-provider.ts +6 -25
  29. package/src/calls/telephony-credential-preflight.ts +0 -10
  30. package/src/calls/telephony-tts-capability.ts +1 -17
  31. package/src/cli/commands/tts.ts +1 -2
  32. package/src/cli/index.help.ts +0 -2
  33. package/src/cli/program.ts +0 -2
  34. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +4 -42
  35. package/src/config/schemas/__tests__/stt.test.ts +10 -14
  36. package/src/config/schemas/llm.ts +3 -5
  37. package/src/config/schemas/services.ts +5 -6
  38. package/src/config/schemas/stt.ts +1 -3
  39. package/src/config/schemas/tts.ts +1 -30
  40. package/src/daemon/daemon-readiness.ts +4 -29
  41. package/src/daemon/lifecycle.ts +0 -31
  42. package/src/daemon/providers-setup.ts +0 -8
  43. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  44. package/src/heartbeat/heartbeat-service.ts +1 -66
  45. package/src/ipc/__tests__/db-readiness-ipc-gate.test.ts +1 -9
  46. package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +0 -44
  47. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +4 -6
  48. package/src/live-voice/__tests__/live-voice-stt.test.ts +1 -1
  49. package/src/live-voice/live-voice-credential-preflight.ts +9 -33
  50. package/src/live-voice/live-voice-session.ts +10 -6
  51. package/src/live-voice/live-voice-tts.ts +1 -8
  52. package/src/plugins/defaults/memory/worker.ts +3 -3
  53. package/src/prompts/persona-resolver.ts +0 -35
  54. package/src/providers/__tests__/search-provider-catalog.test.ts +5 -8
  55. package/src/providers/__tests__/voice-error-copy.test.ts +9 -10
  56. package/src/providers/search-provider-catalog.ts +7 -39
  57. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +3 -47
  58. package/src/providers/speech-to-text/__tests__/resolve.test.ts +6 -372
  59. package/src/providers/speech-to-text/provider-catalog.ts +2 -73
  60. package/src/providers/speech-to-text/resolve.ts +3 -120
  61. package/src/runtime/http-router-types.ts +1 -3
  62. package/src/runtime/http-server.ts +1 -1
  63. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +13 -11
  64. package/src/runtime/routes/__tests__/tts-routes.test.ts +2 -6
  65. package/src/runtime/routes/conversation-query-routes.ts +0 -96
  66. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +7 -7
  67. package/src/runtime/routes/index.ts +0 -4
  68. package/src/runtime/routes/live-voice-routes.test.ts +4 -11
  69. package/src/runtime/routes/live-voice-routes.ts +8 -11
  70. package/src/runtime/routes/secret-routes.ts +0 -6
  71. package/src/runtime/routes/tts-routes.ts +0 -38
  72. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1 -7
  73. package/src/schedule/__tests__/worker-mcp-readiness.test.ts +0 -4
  74. package/src/schedule/schedule-store.ts +0 -7
  75. package/src/schedule/scheduler.ts +0 -5
  76. package/src/schedule/worker.ts +1 -1
  77. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +2 -10
  78. package/src/stt/daemon-batch-transcriber.ts +4 -38
  79. package/src/stt/types.ts +1 -9
  80. package/src/tools/network/__tests__/web-search.test.ts +3 -250
  81. package/src/tools/network/web-search.ts +4 -165
  82. package/src/tts/__tests__/provider-resolution.test.ts +0 -5
  83. package/src/tts/provider-catalog.ts +0 -4
  84. package/src/tts/types.ts +0 -1
  85. package/src/__tests__/cloud-speech-defaults.test.ts +0 -330
  86. package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1708
  87. package/src/__tests__/migration-export-http.test.ts +0 -557
  88. package/src/__tests__/migration-export-streaming.test.ts +0 -350
  89. package/src/__tests__/migration-export-to-gcs.test.ts +0 -706
  90. package/src/__tests__/migration-import-commit-http.test.ts +0 -1176
  91. package/src/__tests__/migration-import-from-gcs.test.ts +0 -534
  92. package/src/__tests__/migration-import-from-path.test.ts +0 -324
  93. package/src/__tests__/migration-import-from-url.test.ts +0 -588
  94. package/src/__tests__/migration-import-preflight-http.test.ts +0 -739
  95. package/src/__tests__/migration-jobs-status.test.ts +0 -139
  96. package/src/__tests__/migration-parity-persistence.test.ts +0 -1891
  97. package/src/__tests__/migration-transport.test.ts +0 -1089
  98. package/src/__tests__/migration-validate-http.test.ts +0 -634
  99. package/src/__tests__/migration-wizard.test.ts +0 -1395
  100. package/src/__tests__/rebind-secrets-screen.test.ts +0 -876
  101. package/src/__tests__/sanitize-config-for-transfer.test.ts +0 -259
  102. package/src/__tests__/transfer-progress-screen.test.ts +0 -1217
  103. package/src/__tests__/validation-results-screen.test.ts +0 -1130
  104. package/src/__tests__/vbundle-pax-and-symlink.test.ts +0 -147
  105. package/src/background-wake/background-wake-routes.test.ts +0 -906
  106. package/src/background-wake/cloud-client.test.ts +0 -328
  107. package/src/background-wake/cloud-client.ts +0 -196
  108. package/src/background-wake/next-wake.test.ts +0 -335
  109. package/src/background-wake/next-wake.ts +0 -188
  110. package/src/background-wake/publisher.ts +0 -93
  111. package/src/background-wake/wake-intent-hooks.test.ts +0 -234
  112. package/src/cli/commands/__tests__/backup.test.ts +0 -363
  113. package/src/cli/commands/backup.help.ts +0 -180
  114. package/src/cli/commands/backup.ts +0 -398
  115. package/src/cloud/cloud-speech.test.ts +0 -335
  116. package/src/cloud/cloud-speech.ts +0 -300
  117. package/src/config/cloud-speech-defaults.ts +0 -300
  118. package/src/config/sanitize-for-transfer.ts +0 -111
  119. package/src/live-voice/__tests__/live-voice-cloud-speech-fallback.test.ts +0 -250
  120. package/src/providers/speech-to-text/__tests__/cadet-cloud-realtime.test.ts +0 -457
  121. package/src/providers/speech-to-text/__tests__/cadet-cloud.test.ts +0 -271
  122. package/src/providers/speech-to-text/__tests__/cadet-speech-relay-connection.test.ts +0 -126
  123. package/src/providers/speech-to-text/cadet-cloud-flux-realtime.ts +0 -309
  124. package/src/providers/speech-to-text/cadet-cloud-realtime.ts +0 -315
  125. package/src/providers/speech-to-text/cadet-cloud.ts +0 -108
  126. package/src/providers/speech-to-text/cadet-speech-relay-connection.ts +0 -176
  127. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +0 -254
  128. package/src/runtime/migrations/__tests__/job-registry.test.ts +0 -346
  129. package/src/runtime/migrations/__tests__/origin-mode.test.ts +0 -62
  130. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +0 -184
  131. package/src/runtime/migrations/__tests__/staged-import-path.test.ts +0 -110
  132. package/src/runtime/migrations/__tests__/v1-test-helpers.ts +0 -112
  133. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +0 -285
  134. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +0 -126
  135. package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +0 -253
  136. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +0 -208
  137. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +0 -485
  138. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +0 -349
  139. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +0 -196
  140. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +0 -551
  141. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +0 -428
  142. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +0 -220
  143. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +0 -1823
  144. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +0 -598
  145. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +0 -463
  146. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -538
  147. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +0 -515
  148. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +0 -453
  149. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +0 -319
  150. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +0 -240
  151. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +0 -473
  152. package/src/runtime/migrations/gcs-signed-url.ts +0 -190
  153. package/src/runtime/migrations/job-registry.ts +0 -297
  154. package/src/runtime/migrations/migration-transport.ts +0 -612
  155. package/src/runtime/migrations/migration-wizard.ts +0 -747
  156. package/src/runtime/migrations/origin-mode.ts +0 -42
  157. package/src/runtime/migrations/rebind-secrets-screen.ts +0 -412
  158. package/src/runtime/migrations/staged-import-path.ts +0 -116
  159. package/src/runtime/migrations/transfer-progress-screen.ts +0 -323
  160. package/src/runtime/migrations/validation-results-screen.ts +0 -469
  161. package/src/runtime/migrations/vbundle-builder.ts +0 -1382
  162. package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -405
  163. package/src/runtime/migrations/vbundle-import-policy.ts +0 -241
  164. package/src/runtime/migrations/vbundle-importer.ts +0 -826
  165. package/src/runtime/migrations/vbundle-metadata-merge.ts +0 -113
  166. package/src/runtime/migrations/vbundle-streaming-importer.ts +0 -2844
  167. package/src/runtime/migrations/vbundle-streaming-validator.ts +0 -426
  168. package/src/runtime/migrations/vbundle-tar-stream.ts +0 -244
  169. package/src/runtime/migrations/vbundle-validator.ts +0 -724
  170. package/src/runtime/routes/__tests__/migration-cadet-metadata-reconcile.test.ts +0 -227
  171. package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +0 -54
  172. package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +0 -70
  173. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +0 -257
  174. package/src/runtime/routes/background-wake-routes.ts +0 -387
  175. package/src/runtime/routes/migration-routes.ts +0 -2584
  176. package/src/tools/network/__tests__/cloud-search-proxy.test.ts +0 -282
  177. package/src/tools/network/cloud-search-proxy.ts +0 -211
  178. package/src/tts/__tests__/cadet-provider.test.ts +0 -253
  179. package/src/tts/providers/cadet-provider.ts +0 -244
  180. 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
- });