@vellumai/assistant 0.4.31 → 0.4.33

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 (193) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/docs/architecture/memory.md +1 -1
  3. package/package.json +1 -1
  4. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -7
  5. package/src/__tests__/access-request-decision.test.ts +83 -1
  6. package/src/__tests__/actor-token-service.test.ts +0 -1
  7. package/src/__tests__/anthropic-provider.test.ts +86 -1
  8. package/src/__tests__/approval-routes-http.test.ts +0 -1
  9. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  10. package/src/__tests__/call-controller.test.ts +0 -1
  11. package/src/__tests__/call-routes-http.test.ts +0 -1
  12. package/src/__tests__/channel-guardian.test.ts +0 -1
  13. package/src/__tests__/channel-invite-transport.test.ts +52 -40
  14. package/src/__tests__/checker.test.ts +37 -98
  15. package/src/__tests__/commit-message-enrichment-service.test.ts +4 -23
  16. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -1
  17. package/src/__tests__/config-schema.test.ts +6 -5
  18. package/src/__tests__/credential-security-invariants.test.ts +2 -0
  19. package/src/__tests__/daemon-server-session-init.test.ts +1 -19
  20. package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
  21. package/src/__tests__/followup-tools.test.ts +0 -30
  22. package/src/__tests__/gemini-provider.test.ts +79 -1
  23. package/src/__tests__/guardian-action-followup-executor.test.ts +0 -1
  24. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  25. package/src/__tests__/guardian-outbound-http.test.ts +0 -1
  26. package/src/__tests__/handlers-telegram-config.test.ts +0 -1
  27. package/src/__tests__/inbound-invite-redemption.test.ts +1 -4
  28. package/src/__tests__/ingress-reconcile.test.ts +3 -36
  29. package/src/__tests__/ipc-snapshot.test.ts +0 -4
  30. package/src/__tests__/managed-proxy-context.test.ts +163 -0
  31. package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
  32. package/src/__tests__/memory-regressions.test.ts +6 -6
  33. package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1
  34. package/src/__tests__/migration-export-http.test.ts +0 -1
  35. package/src/__tests__/migration-import-commit-http.test.ts +0 -1
  36. package/src/__tests__/migration-import-preflight-http.test.ts +0 -1
  37. package/src/__tests__/migration-validate-http.test.ts +0 -1
  38. package/src/__tests__/non-member-access-request.test.ts +0 -1
  39. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  40. package/src/__tests__/notification-telegram-adapter.test.ts +0 -4
  41. package/src/__tests__/openai-provider.test.ts +82 -0
  42. package/src/__tests__/provider-fail-open-selection.test.ts +134 -1
  43. package/src/__tests__/provider-managed-proxy-integration.test.ts +269 -0
  44. package/src/__tests__/recurrence-types.test.ts +0 -15
  45. package/src/__tests__/relay-server.test.ts +145 -2
  46. package/src/__tests__/sandbox-host-parity.test.ts +5 -2
  47. package/src/__tests__/schedule-tools.test.ts +28 -44
  48. package/src/__tests__/session-init.benchmark.test.ts +0 -2
  49. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  50. package/src/__tests__/slack-channel-config.test.ts +0 -1
  51. package/src/__tests__/slack-inbound-verification.test.ts +0 -1
  52. package/src/__tests__/sms-messaging-provider.test.ts +0 -4
  53. package/src/__tests__/task-management-tools.test.ts +111 -0
  54. package/src/__tests__/terminal-tools.test.ts +5 -2
  55. package/src/__tests__/trusted-contact-approval-notifier.test.ts +66 -74
  56. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  57. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -1
  58. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
  59. package/src/__tests__/trusted-contact-verification.test.ts +0 -1
  60. package/src/__tests__/twilio-config.test.ts +0 -3
  61. package/src/__tests__/twilio-routes.test.ts +0 -1
  62. package/src/__tests__/update-bulletin.test.ts +0 -2
  63. package/src/__tests__/user-reference.test.ts +47 -1
  64. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  65. package/src/__tests__/workspace-git-service.test.ts +2 -2
  66. package/src/amazon/session.ts +30 -91
  67. package/src/calls/call-controller.ts +423 -571
  68. package/src/calls/finalize-call.ts +20 -0
  69. package/src/calls/relay-access-wait.ts +340 -0
  70. package/src/calls/relay-server.ts +271 -956
  71. package/src/calls/relay-setup-router.ts +307 -0
  72. package/src/calls/relay-verification.ts +280 -0
  73. package/src/calls/twilio-config.ts +1 -8
  74. package/src/calls/voice-control-protocol.ts +184 -0
  75. package/src/calls/voice-session-bridge.ts +1 -8
  76. package/src/channels/config.ts +41 -2
  77. package/src/config/agent-schema.ts +1 -1
  78. package/src/config/bundled-skills/followups/TOOLS.json +0 -4
  79. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  80. package/src/config/bundled-skills/schedule/TOOLS.json +2 -10
  81. package/src/config/bundled-skills/slack/SKILL.md +2 -0
  82. package/src/config/bundled-skills/slack-digest-setup/SKILL.md +164 -0
  83. package/src/config/core-schema.ts +1 -1
  84. package/src/config/env.ts +0 -14
  85. package/src/config/feature-flag-registry.json +5 -5
  86. package/src/config/loader.ts +19 -0
  87. package/src/config/schema.ts +2 -2
  88. package/src/config/user-reference.ts +47 -9
  89. package/src/daemon/handlers/config-channels.ts +11 -10
  90. package/src/daemon/handlers/contacts.ts +5 -1
  91. package/src/daemon/handlers/session-history.ts +398 -0
  92. package/src/daemon/handlers/session-user-message.ts +982 -0
  93. package/src/daemon/handlers/sessions.ts +9 -1338
  94. package/src/daemon/ipc-contract/sessions.ts +0 -6
  95. package/src/daemon/ipc-contract-inventory.json +0 -1
  96. package/src/daemon/lifecycle.ts +18 -55
  97. package/src/home-base/app-link-store.ts +0 -7
  98. package/src/memory/channel-delivery-store.ts +1 -0
  99. package/src/memory/conversation-attention-store.ts +1 -1
  100. package/src/memory/conversation-store.ts +0 -51
  101. package/src/memory/db-init.ts +9 -1
  102. package/src/memory/delivery-crud.ts +13 -0
  103. package/src/memory/invite-store.ts +71 -1
  104. package/src/memory/job-handlers/conflict.ts +24 -0
  105. package/src/memory/migrations/040-invite-code-hash-column.ts +16 -0
  106. package/src/memory/migrations/105-contacts-and-triage.ts +4 -7
  107. package/src/memory/migrations/134-contacts-notes-column.ts +50 -33
  108. package/src/memory/migrations/index.ts +1 -0
  109. package/src/memory/migrations/registry.ts +6 -0
  110. package/src/memory/recall-cache.ts +0 -5
  111. package/src/memory/schema/calls.ts +274 -0
  112. package/src/memory/schema/contacts.ts +127 -0
  113. package/src/memory/schema/conversations.ts +129 -0
  114. package/src/memory/schema/guardian.ts +172 -0
  115. package/src/memory/schema/index.ts +8 -0
  116. package/src/memory/schema/infrastructure.ts +205 -0
  117. package/src/memory/schema/memory-core.ts +196 -0
  118. package/src/memory/schema/notifications.ts +191 -0
  119. package/src/memory/schema/tasks.ts +78 -0
  120. package/src/memory/schema.ts +1 -1385
  121. package/src/memory/slack-thread-store.ts +0 -69
  122. package/src/notifications/decisions-store.ts +2 -105
  123. package/src/notifications/deliveries-store.ts +0 -11
  124. package/src/notifications/preferences-store.ts +1 -58
  125. package/src/permissions/checker.ts +6 -17
  126. package/src/providers/anthropic/client.ts +6 -2
  127. package/src/providers/gemini/client.ts +13 -2
  128. package/src/providers/managed-proxy/constants.ts +55 -0
  129. package/src/providers/managed-proxy/context.ts +77 -0
  130. package/src/providers/registry.ts +112 -0
  131. package/src/runtime/auth/__tests__/guard-tests.test.ts +52 -26
  132. package/src/runtime/auth/token-service.ts +50 -0
  133. package/src/runtime/channel-guardian-service.ts +1 -3
  134. package/src/runtime/channel-invite-transport.ts +121 -34
  135. package/src/runtime/channel-invite-transports/email.ts +50 -0
  136. package/src/runtime/channel-invite-transports/slack.ts +81 -0
  137. package/src/runtime/channel-invite-transports/sms.ts +70 -0
  138. package/src/runtime/channel-invite-transports/telegram.ts +29 -11
  139. package/src/runtime/channel-invite-transports/voice.ts +12 -12
  140. package/src/runtime/http-server.ts +83 -722
  141. package/src/runtime/http-types.ts +0 -16
  142. package/src/runtime/invite-redemption-service.ts +193 -0
  143. package/src/runtime/invite-redemption-templates.ts +6 -6
  144. package/src/runtime/invite-service.ts +81 -11
  145. package/src/runtime/middleware/auth.ts +0 -12
  146. package/src/runtime/routes/access-request-decision.ts +52 -6
  147. package/src/runtime/routes/app-routes.ts +33 -0
  148. package/src/runtime/routes/approval-routes.ts +32 -0
  149. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +6 -0
  150. package/src/runtime/routes/attachment-routes.ts +32 -0
  151. package/src/runtime/routes/brain-graph-routes.ts +27 -0
  152. package/src/runtime/routes/call-routes.ts +41 -0
  153. package/src/runtime/routes/channel-readiness-routes.ts +20 -0
  154. package/src/runtime/routes/channel-routes.ts +70 -0
  155. package/src/runtime/routes/contact-routes.ts +96 -6
  156. package/src/runtime/routes/conversation-attention-routes.ts +15 -0
  157. package/src/runtime/routes/conversation-routes.ts +190 -193
  158. package/src/runtime/routes/debug-routes.ts +15 -0
  159. package/src/runtime/routes/events-routes.ts +16 -0
  160. package/src/runtime/routes/global-search-routes.ts +15 -0
  161. package/src/runtime/routes/guardian-action-routes.ts +22 -0
  162. package/src/runtime/routes/guardian-bootstrap-routes.ts +21 -6
  163. package/src/runtime/routes/guardian-refresh-routes.ts +20 -0
  164. package/src/runtime/routes/identity-routes.ts +20 -0
  165. package/src/runtime/routes/inbound-message-handler.ts +9 -3
  166. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +295 -10
  167. package/src/runtime/routes/inbound-stages/background-dispatch.ts +9 -42
  168. package/src/runtime/routes/inbound-stages/edit-intercept.ts +10 -0
  169. package/src/runtime/routes/integration-routes.ts +83 -0
  170. package/src/runtime/routes/invite-routes.ts +32 -0
  171. package/src/runtime/routes/migration-routes.ts +30 -0
  172. package/src/runtime/routes/pairing-routes.ts +18 -0
  173. package/src/runtime/routes/secret-routes.ts +20 -0
  174. package/src/runtime/routes/surface-action-routes.ts +26 -0
  175. package/src/runtime/routes/trust-rules-routes.ts +31 -0
  176. package/src/runtime/routes/twilio-routes.ts +79 -0
  177. package/src/schedule/recurrence-types.ts +1 -11
  178. package/src/tools/browser/browser-manager.ts +10 -1
  179. package/src/tools/browser/runtime-check.ts +3 -1
  180. package/src/tools/followups/followup_create.ts +9 -3
  181. package/src/tools/mcp/mcp-tool-factory.ts +0 -17
  182. package/src/tools/memory/definitions.ts +0 -6
  183. package/src/tools/network/script-proxy/session-manager.ts +38 -3
  184. package/src/tools/schedule/create.ts +1 -3
  185. package/src/tools/schedule/update.ts +9 -6
  186. package/src/tools/shared/shell-output.ts +7 -2
  187. package/src/twitter/session.ts +29 -77
  188. package/src/util/cookie-session.ts +114 -0
  189. package/src/util/platform.ts +0 -4
  190. package/src/workspace/git-service.ts +10 -4
  191. package/src/__tests__/conversation-routes.test.ts +0 -99
  192. package/src/__tests__/task-tools.test.ts +0 -685
  193. package/src/contacts/startup-migration.ts +0 -21
@@ -76,7 +76,7 @@ describe("schedule_create tool", () => {
76
76
  const result = await executeScheduleCreate(
77
77
  {
78
78
  name: "Daily standup",
79
- cron_expression: "0 9 * * 1-5",
79
+ expression: "0 9 * * 1-5",
80
80
  message: "Time for standup!",
81
81
  },
82
82
  ctx,
@@ -93,7 +93,7 @@ describe("schedule_create tool", () => {
93
93
  const result = await executeScheduleCreate(
94
94
  {
95
95
  name: "Paused job",
96
- cron_expression: "0 12 * * *",
96
+ expression: "0 12 * * *",
97
97
  message: "Noon check",
98
98
  enabled: false,
99
99
  },
@@ -108,7 +108,7 @@ describe("schedule_create tool", () => {
108
108
  const result = await executeScheduleCreate(
109
109
  {
110
110
  name: "LA morning",
111
- cron_expression: "0 8 * * *",
111
+ expression: "0 8 * * *",
112
112
  message: "Good morning LA",
113
113
  timezone: "America/Los_Angeles",
114
114
  },
@@ -122,7 +122,7 @@ describe("schedule_create tool", () => {
122
122
  test("rejects missing name", async () => {
123
123
  const result = await executeScheduleCreate(
124
124
  {
125
- cron_expression: "0 9 * * *",
125
+ expression: "0 9 * * *",
126
126
  message: "test",
127
127
  },
128
128
  ctx,
@@ -142,16 +142,14 @@ describe("schedule_create tool", () => {
142
142
  );
143
143
 
144
144
  expect(result.isError).toBe(true);
145
- expect(result.content).toContain(
146
- "expression (or cron_expression) is required",
147
- );
145
+ expect(result.content).toContain("expression is required");
148
146
  });
149
147
 
150
148
  test("rejects missing message", async () => {
151
149
  const result = await executeScheduleCreate(
152
150
  {
153
151
  name: "Test",
154
- cron_expression: "0 9 * * *",
152
+ expression: "0 9 * * *",
155
153
  },
156
154
  ctx,
157
155
  );
@@ -164,7 +162,8 @@ describe("schedule_create tool", () => {
164
162
  const result = await executeScheduleCreate(
165
163
  {
166
164
  name: "Bad cron",
167
- cron_expression: "not-a-cron",
165
+ syntax: "cron",
166
+ expression: "not-a-cron",
168
167
  message: "test",
169
168
  },
170
169
  ctx,
@@ -194,7 +193,7 @@ describe("schedule_list tool", () => {
194
193
  await executeScheduleCreate(
195
194
  {
196
195
  name: "Job Alpha",
197
- cron_expression: "0 9 * * *",
196
+ expression: "0 9 * * *",
198
197
  message: "Alpha",
199
198
  },
200
199
  ctx,
@@ -202,7 +201,7 @@ describe("schedule_list tool", () => {
202
201
  await executeScheduleCreate(
203
202
  {
204
203
  name: "Job Beta",
205
- cron_expression: "0 17 * * *",
204
+ expression: "0 17 * * *",
206
205
  message: "Beta",
207
206
  },
208
207
  ctx,
@@ -220,7 +219,7 @@ describe("schedule_list tool", () => {
220
219
  await executeScheduleCreate(
221
220
  {
222
221
  name: "Enabled Job",
223
- cron_expression: "0 9 * * *",
222
+ expression: "0 9 * * *",
224
223
  message: "enabled",
225
224
  },
226
225
  ctx,
@@ -228,7 +227,7 @@ describe("schedule_list tool", () => {
228
227
  await executeScheduleCreate(
229
228
  {
230
229
  name: "Disabled Job",
231
- cron_expression: "0 17 * * *",
230
+ expression: "0 17 * * *",
232
231
  message: "disabled",
233
232
  enabled: false,
234
233
  },
@@ -246,7 +245,7 @@ describe("schedule_list tool", () => {
246
245
  await executeScheduleCreate(
247
246
  {
248
247
  name: "Detail Job",
249
- cron_expression: "30 14 * * *",
248
+ expression: "30 14 * * *",
250
249
  message: "Afternoon check",
251
250
  },
252
251
  ctx,
@@ -286,7 +285,7 @@ describe("schedule_update tool", () => {
286
285
  await executeScheduleCreate(
287
286
  {
288
287
  name: "Old Name",
289
- cron_expression: "0 9 * * *",
288
+ expression: "0 9 * * *",
290
289
  message: "test",
291
290
  },
292
291
  ctx,
@@ -312,7 +311,7 @@ describe("schedule_update tool", () => {
312
311
  await executeScheduleCreate(
313
312
  {
314
313
  name: "Timing Test",
315
- cron_expression: "0 9 * * *",
314
+ expression: "0 9 * * *",
316
315
  message: "test",
317
316
  },
318
317
  ctx,
@@ -324,7 +323,7 @@ describe("schedule_update tool", () => {
324
323
  const result = await executeScheduleUpdate(
325
324
  {
326
325
  job_id: row.id,
327
- cron_expression: "0 17 * * *",
326
+ expression: "0 17 * * *",
328
327
  },
329
328
  ctx,
330
329
  );
@@ -337,7 +336,7 @@ describe("schedule_update tool", () => {
337
336
  await executeScheduleCreate(
338
337
  {
339
338
  name: "Disable Me",
340
- cron_expression: "0 9 * * *",
339
+ expression: "0 9 * * *",
341
340
  message: "test",
342
341
  },
343
342
  ctx,
@@ -370,7 +369,7 @@ describe("schedule_update tool", () => {
370
369
  await executeScheduleCreate(
371
370
  {
372
371
  name: "No Update",
373
- cron_expression: "0 9 * * *",
372
+ expression: "0 9 * * *",
374
373
  message: "test",
375
374
  },
376
375
  ctx,
@@ -402,7 +401,7 @@ describe("schedule_update tool", () => {
402
401
  await executeScheduleCreate(
403
402
  {
404
403
  name: "Bad Update",
405
- cron_expression: "0 9 * * *",
404
+ expression: "0 9 * * *",
406
405
  message: "test",
407
406
  },
408
407
  ctx,
@@ -414,7 +413,8 @@ describe("schedule_update tool", () => {
414
413
  const result = await executeScheduleUpdate(
415
414
  {
416
415
  job_id: row.id,
417
- cron_expression: "invalid",
416
+ syntax: "cron",
417
+ expression: "invalid",
418
418
  },
419
419
  ctx,
420
420
  );
@@ -432,22 +432,6 @@ describe("schedule_create with RRULE", () => {
432
432
  getRawDb().run("DELETE FROM cron_jobs");
433
433
  });
434
434
 
435
- test("creates a schedule with legacy cron_expression", async () => {
436
- const result = await executeScheduleCreate(
437
- {
438
- name: "Legacy cron",
439
- cron_expression: "0 9 * * 1-5",
440
- message: "Legacy test",
441
- },
442
- ctx,
443
- );
444
-
445
- expect(result.isError).toBe(false);
446
- expect(result.content).toContain("Schedule created successfully");
447
- expect(result.content).toContain("Syntax: cron");
448
- expect(result.content).toContain("Every weekday at 9:00 AM");
449
- });
450
-
451
435
  test("creates a schedule with RRULE syntax + expression", async () => {
452
436
  const result = await executeScheduleCreate(
453
437
  {
@@ -507,7 +491,7 @@ describe("schedule_update with RRULE", () => {
507
491
  await executeScheduleCreate(
508
492
  {
509
493
  name: "Cron to RRULE",
510
- cron_expression: "0 9 * * *",
494
+ expression: "0 9 * * *",
511
495
  message: "test",
512
496
  },
513
497
  ctx,
@@ -535,7 +519,7 @@ describe("schedule_update with RRULE", () => {
535
519
  await executeScheduleCreate(
536
520
  {
537
521
  name: "Auto-detect on update",
538
- cron_expression: "0 9 * * *",
522
+ expression: "0 9 * * *",
539
523
  message: "test",
540
524
  },
541
525
  ctx,
@@ -561,7 +545,7 @@ describe("schedule_update with RRULE", () => {
561
545
  await executeScheduleCreate(
562
546
  {
563
547
  name: "Cron auto-detect",
564
- cron_expression: "0 9 * * *",
548
+ expression: "0 9 * * *",
565
549
  message: "test",
566
550
  },
567
551
  ctx,
@@ -593,7 +577,7 @@ describe("schedule_list with RRULE", () => {
593
577
  await executeScheduleCreate(
594
578
  {
595
579
  name: "Cron Job",
596
- cron_expression: "0 9 * * 1-5",
580
+ expression: "0 9 * * 1-5",
597
581
  message: "Cron test",
598
582
  },
599
583
  ctx,
@@ -750,7 +734,7 @@ describe("schedule_update with RRULE set", () => {
750
734
  await executeScheduleCreate(
751
735
  {
752
736
  name: "Cron to set",
753
- cron_expression: "0 9 * * *",
737
+ expression: "0 9 * * *",
754
738
  message: "test",
755
739
  },
756
740
  ctx,
@@ -784,7 +768,7 @@ describe("schedule_update with RRULE set", () => {
784
768
  await executeScheduleCreate(
785
769
  {
786
770
  name: "Bad set update",
787
- cron_expression: "0 9 * * *",
771
+ expression: "0 9 * * *",
788
772
  message: "test",
789
773
  },
790
774
  ctx,
@@ -928,7 +912,7 @@ describe("schedule_delete tool", () => {
928
912
  await executeScheduleCreate(
929
913
  {
930
914
  name: "Delete Me",
931
- cron_expression: "0 9 * * *",
915
+ expression: "0 9 * * *",
932
916
  message: "test",
933
917
  },
934
918
  ctx,
@@ -107,13 +107,11 @@ mock.module("../util/platform.js", () => ({
107
107
  isWindows: () => process.platform === "win32",
108
108
  getPlatformName: () => process.platform,
109
109
  getClipboardCommand: () => null,
110
- getHttpTokenPath: () => join(testDir, "http-token"),
111
110
  getPlatformTokenPath: () => join(testDir, "platform-token"),
112
111
  getTCPHost: () => "127.0.0.1",
113
112
  getTCPPort: () => 8765,
114
113
  isIOSPairingEnabled: () => false,
115
114
  isTCPEnabled: () => false,
116
- readHttpToken: () => null,
117
115
  readLockfile: () => null,
118
116
  readPlatformToken: () => null,
119
117
  readSessionToken: () => null,
@@ -120,10 +120,10 @@ describe("isAssistantFeatureFlagEnabled", () => {
120
120
 
121
121
  test("declared keys with no persisted override use registry default", () => {
122
122
  const config = makeConfig();
123
- // browser is declared in the registry with defaultEnabled: false
123
+ // browser is declared in the registry with defaultEnabled: true
124
124
  expect(
125
125
  isAssistantFeatureFlagEnabled("feature_flags.browser.enabled", config),
126
- ).toBe(false);
126
+ ).toBe(true);
127
127
  });
128
128
  });
129
129
 
@@ -28,7 +28,6 @@ mock.module("../util/platform.js", () => ({
28
28
  getDbPath: () => join(testDir, "test.db"),
29
29
  getLogPath: () => join(testDir, "test.log"),
30
30
  ensureDataDir: () => {},
31
- readHttpToken: () => undefined,
32
31
  }));
33
32
 
34
33
  mock.module("../util/logger.js", () => ({
@@ -30,7 +30,6 @@ mock.module("../util/platform.js", () => ({
30
30
  getDbPath: () => join(testDir, "test.db"),
31
31
  getLogPath: () => join(testDir, "test.log"),
32
32
  ensureDataDir: () => {},
33
- readHttpToken: () => "test-bearer-token",
34
33
  }));
35
34
 
36
35
  mock.module("../util/logger.js", () => ({
@@ -33,10 +33,6 @@ mock.module("../security/secure-keys.js", () => ({
33
33
  getSecureKey: (key: string) => secureKeys[key],
34
34
  }));
35
35
 
36
- mock.module("../util/platform.js", () => ({
37
- readHttpToken: () => "runtime-token",
38
- }));
39
-
40
36
  mock.module("../runtime/auth/token-service.js", () => ({
41
37
  mintDaemonDeliveryToken: () => "runtime-token",
42
38
  }));
@@ -39,6 +39,8 @@ mock.module("../tools/registry.js", () => ({
39
39
  getAllTools: () => [],
40
40
  }));
41
41
 
42
+ import type { Database } from "bun:sqlite";
43
+
42
44
  import { getDb, initializeDb, resetDb } from "../memory/db.js";
43
45
  import { renderTemplate } from "../tasks/task-runner.js";
44
46
  import {
@@ -54,6 +56,7 @@ import {
54
56
  import { executeTaskDelete } from "../tools/tasks/task-delete.js";
55
57
  import { executeTaskList } from "../tools/tasks/task-list.js";
56
58
  import { executeTaskRun } from "../tools/tasks/task-run.js";
59
+ import { executeTaskSave } from "../tools/tasks/task-save.js";
57
60
  import { executeTaskListAdd } from "../tools/tasks/work-item-enqueue.js";
58
61
  import { executeTaskListShow } from "../tools/tasks/work-item-list.js";
59
62
  import { executeTaskListRemove } from "../tools/tasks/work-item-remove.js";
@@ -90,6 +93,10 @@ const ctx: ToolContext = {
90
93
  trustClass: "guardian",
91
94
  };
92
95
 
96
+ function getRawDb(): Database {
97
+ return (getDb() as unknown as { $client: Database }).$client;
98
+ }
99
+
93
100
  function clearTables() {
94
101
  const db = getDb();
95
102
  db.run("DELETE FROM work_items");
@@ -97,6 +104,41 @@ function clearTables() {
97
104
  db.run("DELETE FROM tasks");
98
105
  }
99
106
 
107
+ function clearTablesWithConversations() {
108
+ const raw = getRawDb();
109
+ raw.run("DELETE FROM work_items");
110
+ raw.run("DELETE FROM task_runs");
111
+ raw.run("DELETE FROM tasks");
112
+ raw.run("DELETE FROM messages");
113
+ raw.run("DELETE FROM conversations");
114
+ }
115
+
116
+ function createTestConversation(id: string): string {
117
+ const raw = getRawDb();
118
+ const now = Date.now();
119
+ raw
120
+ .query(
121
+ `INSERT INTO conversations (id, title, created_at, updated_at, thread_type, memory_scope_id) VALUES (?, 'Test', ?, ?, 'standard', 'default')`,
122
+ )
123
+ .run(id, now, now);
124
+ return id;
125
+ }
126
+
127
+ function addTestMessage(
128
+ conversationId: string,
129
+ role: string,
130
+ content: string,
131
+ ): void {
132
+ const raw = getRawDb();
133
+ const id = crypto.randomUUID();
134
+ const now = Date.now();
135
+ raw
136
+ .query(
137
+ `INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES (?, ?, ?, ?, ?)`,
138
+ )
139
+ .run(id, conversationId, role, content, now);
140
+ }
141
+
100
142
  // ═══════════════════════════════════════════════════════════════════
101
143
  // Task Store — CRUD
102
144
  // ═══════════════════════════════════════════════════════════════════
@@ -1043,3 +1085,72 @@ describe("executeTaskListRemove tool", () => {
1043
1085
  expect(result.content).toContain("Multiple items match");
1044
1086
  });
1045
1087
  });
1088
+
1089
+ // ═══════════════════════════════════════════════════════════════════
1090
+ // Tool: executeTaskSave
1091
+ // ═══════════════════════════════════════════════════════════════════
1092
+
1093
+ describe("executeTaskSave tool", () => {
1094
+ beforeEach(clearTablesWithConversations);
1095
+
1096
+ test("creates a task from a conversation", async () => {
1097
+ const convId = createTestConversation("conv-save-1");
1098
+ addTestMessage(convId, "user", "Please summarize the document");
1099
+ addTestMessage(
1100
+ convId,
1101
+ "assistant",
1102
+ JSON.stringify([
1103
+ {
1104
+ type: "tool_use",
1105
+ id: "tu1",
1106
+ name: "file_read",
1107
+ input: { path: "/tmp/doc.txt" },
1108
+ },
1109
+ ]),
1110
+ );
1111
+ addTestMessage(convId, "assistant", "Here is the summary...");
1112
+
1113
+ const result = await executeTaskSave({ conversation_id: convId }, ctx);
1114
+
1115
+ expect(result.isError).toBe(false);
1116
+ expect(result.content).toContain("Task saved successfully");
1117
+ expect(result.content).toContain("Please summarize the document");
1118
+ expect(result.content).toContain("file_read");
1119
+ });
1120
+
1121
+ test("uses title override when provided", async () => {
1122
+ const convId = createTestConversation("conv-save-2");
1123
+ addTestMessage(convId, "user", "Read and analyze the logs");
1124
+ addTestMessage(convId, "assistant", "Done!");
1125
+
1126
+ const result = await executeTaskSave(
1127
+ { conversation_id: convId, title: "My Custom Title" },
1128
+ ctx,
1129
+ );
1130
+
1131
+ expect(result.isError).toBe(false);
1132
+ expect(result.content).toContain("My Custom Title");
1133
+ });
1134
+
1135
+ test("uses context conversation_id when missing", async () => {
1136
+ const convId = createTestConversation(ctx.conversationId);
1137
+ addTestMessage(convId, "user", "Summarize the report");
1138
+ addTestMessage(convId, "assistant", "Done.");
1139
+
1140
+ const result = await executeTaskSave({}, ctx);
1141
+
1142
+ expect(result.isError).toBe(false);
1143
+ expect(result.content).toContain("Task saved successfully");
1144
+ expect(result.content).toContain("Summarize the report");
1145
+ });
1146
+
1147
+ test("returns error for nonexistent conversation", async () => {
1148
+ const result = await executeTaskSave(
1149
+ { conversation_id: "nonexistent" },
1150
+ ctx,
1151
+ );
1152
+
1153
+ expect(result.isError).toBe(true);
1154
+ expect(result.content).toContain("No messages found");
1155
+ });
1156
+ });
@@ -711,10 +711,13 @@ describe("formatShellOutput", () => {
711
711
  expect(result.isError).toBe(false);
712
712
  });
713
713
 
714
- test("failed command with no output shows exit code tag", () => {
714
+ test("failed command with no output shows exit code tag and descriptive message", () => {
715
715
  const result = formatShellOutput("", "", 1, false, 120);
716
- expect(result.content).toBe('<command_exit code="1" />');
716
+ expect(result.content).toContain('<command_exit code="1" />');
717
+ expect(result.content).toContain("Command failed with exit code 1");
718
+ expect(result.content).toContain("No stdout or stderr output was produced");
717
719
  expect(result.isError).toBe(true);
720
+ expect(result.status).toContain('<command_exit code="1" />');
718
721
  });
719
722
 
720
723
  test("failed command with output includes exit code in status", () => {