@vellumai/assistant 0.3.26 → 0.3.28

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 (82) hide show
  1. package/ARCHITECTURE.md +48 -1
  2. package/Dockerfile +2 -2
  3. package/package.json +1 -1
  4. package/scripts/ipc/generate-swift.ts +6 -2
  5. package/src/__tests__/agent-loop.test.ts +119 -0
  6. package/src/__tests__/bundled-asset.test.ts +107 -0
  7. package/src/__tests__/canonical-guardian-store.test.ts +636 -0
  8. package/src/__tests__/channel-approval-routes.test.ts +174 -1
  9. package/src/__tests__/emit-signal-routing-intent.test.ts +43 -1
  10. package/src/__tests__/guardian-actions-endpoint.test.ts +205 -345
  11. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +599 -0
  12. package/src/__tests__/guardian-dispatch.test.ts +19 -19
  13. package/src/__tests__/guardian-routing-invariants.test.ts +954 -0
  14. package/src/__tests__/mcp-cli.test.ts +77 -0
  15. package/src/__tests__/non-member-access-request.test.ts +31 -29
  16. package/src/__tests__/notification-decision-fallback.test.ts +61 -3
  17. package/src/__tests__/notification-decision-strategy.test.ts +17 -0
  18. package/src/__tests__/notification-guardian-path.test.ts +13 -15
  19. package/src/__tests__/onboarding-template-contract.test.ts +116 -21
  20. package/src/__tests__/secret-scanner-executor.test.ts +59 -0
  21. package/src/__tests__/secret-scanner.test.ts +8 -0
  22. package/src/__tests__/sensitive-output-placeholders.test.ts +208 -0
  23. package/src/__tests__/session-runtime-assembly.test.ts +76 -47
  24. package/src/__tests__/tool-grant-request-escalation.test.ts +497 -0
  25. package/src/agent/loop.ts +46 -3
  26. package/src/approvals/guardian-decision-primitive.ts +285 -0
  27. package/src/approvals/guardian-request-resolvers.ts +539 -0
  28. package/src/calls/guardian-dispatch.ts +46 -40
  29. package/src/calls/relay-server.ts +147 -2
  30. package/src/calls/types.ts +1 -1
  31. package/src/config/system-prompt.ts +2 -1
  32. package/src/config/templates/BOOTSTRAP.md +47 -31
  33. package/src/config/templates/USER.md +5 -0
  34. package/src/config/update-bulletin-template-path.ts +4 -1
  35. package/src/config/vellum-skills/trusted-contacts/SKILL.md +22 -17
  36. package/src/daemon/handlers/guardian-actions.ts +45 -66
  37. package/src/daemon/ipc-contract/guardian-actions.ts +7 -0
  38. package/src/daemon/lifecycle.ts +3 -16
  39. package/src/daemon/server.ts +18 -0
  40. package/src/daemon/session-agent-loop-handlers.ts +5 -4
  41. package/src/daemon/session-agent-loop.ts +32 -5
  42. package/src/daemon/session-process.ts +68 -307
  43. package/src/daemon/session-runtime-assembly.ts +112 -24
  44. package/src/daemon/session-tool-setup.ts +1 -0
  45. package/src/daemon/session.ts +1 -0
  46. package/src/home-base/prebuilt/seed.ts +2 -1
  47. package/src/hooks/templates.ts +2 -1
  48. package/src/memory/canonical-guardian-store.ts +524 -0
  49. package/src/memory/channel-guardian-store.ts +1 -0
  50. package/src/memory/db-init.ts +16 -0
  51. package/src/memory/guardian-action-store.ts +7 -60
  52. package/src/memory/guardian-approvals.ts +9 -4
  53. package/src/memory/migrations/036-normalize-phone-identities.ts +289 -0
  54. package/src/memory/migrations/118-reminder-routing-intent.ts +3 -3
  55. package/src/memory/migrations/121-canonical-guardian-requests.ts +59 -0
  56. package/src/memory/migrations/122-canonical-guardian-requester-chat-id.ts +15 -0
  57. package/src/memory/migrations/123-canonical-guardian-deliveries-destination-index.ts +15 -0
  58. package/src/memory/migrations/index.ts +4 -0
  59. package/src/memory/migrations/registry.ts +5 -0
  60. package/src/memory/schema-migration.ts +1 -0
  61. package/src/memory/schema.ts +52 -0
  62. package/src/notifications/copy-composer.ts +16 -4
  63. package/src/notifications/decision-engine.ts +57 -0
  64. package/src/permissions/defaults.ts +2 -0
  65. package/src/runtime/access-request-helper.ts +137 -0
  66. package/src/runtime/actor-trust-resolver.ts +225 -0
  67. package/src/runtime/channel-guardian-service.ts +12 -4
  68. package/src/runtime/guardian-context-resolver.ts +32 -7
  69. package/src/runtime/guardian-decision-types.ts +6 -0
  70. package/src/runtime/guardian-reply-router.ts +687 -0
  71. package/src/runtime/http-server.ts +8 -0
  72. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +116 -0
  73. package/src/runtime/routes/conversation-routes.ts +18 -0
  74. package/src/runtime/routes/guardian-action-routes.ts +100 -109
  75. package/src/runtime/routes/inbound-message-handler.ts +170 -525
  76. package/src/runtime/tool-grant-request-helper.ts +195 -0
  77. package/src/tools/executor.ts +13 -1
  78. package/src/tools/sensitive-output-placeholders.ts +203 -0
  79. package/src/tools/tool-approval-handler.ts +44 -1
  80. package/src/tools/types.ts +11 -0
  81. package/src/util/bundled-asset.ts +31 -0
  82. package/src/util/canonicalize-identity.ts +52 -0
@@ -103,6 +103,8 @@ function ensureConversation(id: string): void {
103
103
 
104
104
  function resetTables(): void {
105
105
  const db = getDb();
106
+ db.run('DELETE FROM canonical_guardian_deliveries');
107
+ db.run('DELETE FROM canonical_guardian_requests');
106
108
  db.run('DELETE FROM guardian_action_deliveries');
107
109
  db.run('DELETE FROM guardian_action_requests');
108
110
  db.run('DELETE FROM call_pending_questions');
@@ -160,7 +162,7 @@ describe('guardian-dispatch', () => {
160
162
 
161
163
  const db = getDb();
162
164
  const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
163
- const request = raw.query('SELECT * FROM guardian_action_requests WHERE call_session_id = ?').get(session.id) as
165
+ const request = raw.query('SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?').get(session.id) as
164
166
  | { id: string; status: string; question_text: string }
165
167
  | undefined;
166
168
  expect(request).toBeDefined();
@@ -168,7 +170,7 @@ describe('guardian-dispatch', () => {
168
170
  expect(request!.question_text).toBe('What is the gate code?');
169
171
 
170
172
  const vellumDelivery = raw.query(
171
- 'SELECT * FROM guardian_action_deliveries WHERE request_id = ? AND destination_channel = ?',
173
+ 'SELECT * FROM canonical_guardian_deliveries WHERE request_id = ? AND destination_channel = ?',
172
174
  ).get(request!.id, 'vellum') as { status: string; destination_conversation_id: string | null } | undefined;
173
175
  expect(vellumDelivery).toBeDefined();
174
176
  expect(vellumDelivery!.status).toBe('sent');
@@ -224,18 +226,17 @@ describe('guardian-dispatch', () => {
224
226
 
225
227
  const db = getDb();
226
228
  const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
227
- const request = raw.query('SELECT * FROM guardian_action_requests WHERE call_session_id = ?').get(session.id) as
229
+ const request = raw.query('SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?').get(session.id) as
228
230
  | { id: string }
229
231
  | undefined;
230
232
  const telegramDelivery = raw.query(
231
- 'SELECT * FROM guardian_action_deliveries WHERE request_id = ? AND destination_channel = ?',
233
+ 'SELECT * FROM canonical_guardian_deliveries WHERE request_id = ? AND destination_channel = ?',
232
234
  ).get(request!.id, 'telegram') as
233
- | { status: string; destination_chat_id: string | null; destination_external_user_id: string | null }
235
+ | { status: string; destination_chat_id: string | null }
234
236
  | undefined;
235
237
  expect(telegramDelivery).toBeDefined();
236
238
  expect(telegramDelivery!.status).toBe('sent');
237
239
  expect(telegramDelivery!.destination_chat_id).toBe('tg-chat-999');
238
- expect(telegramDelivery!.destination_external_user_id).toBe('tg-user-888');
239
240
  });
240
241
 
241
242
  test('marks non-sent pipeline delivery results as failed', async () => {
@@ -275,15 +276,14 @@ describe('guardian-dispatch', () => {
275
276
 
276
277
  const db = getDb();
277
278
  const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
278
- const request = raw.query('SELECT * FROM guardian_action_requests WHERE call_session_id = ?').get(session.id) as
279
+ const request = raw.query('SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?').get(session.id) as
279
280
  | { id: string }
280
281
  | undefined;
281
282
  const vellumDelivery = raw.query(
282
- 'SELECT * FROM guardian_action_deliveries WHERE request_id = ? AND destination_channel = ?',
283
- ).get(request!.id, 'vellum') as { status: string; last_error: string | null } | undefined;
283
+ 'SELECT * FROM canonical_guardian_deliveries WHERE request_id = ? AND destination_channel = ?',
284
+ ).get(request!.id, 'vellum') as { status: string } | undefined;
284
285
  expect(vellumDelivery).toBeDefined();
285
286
  expect(vellumDelivery!.status).toBe('failed');
286
- expect(vellumDelivery!.last_error).toContain('IPC unavailable');
287
287
  });
288
288
 
289
289
  test('uses onThreadCreated callback conversation when delivery result omits conversationId', async () => {
@@ -326,17 +326,17 @@ describe('guardian-dispatch', () => {
326
326
 
327
327
  const db = getDb();
328
328
  const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
329
- const request = raw.query('SELECT * FROM guardian_action_requests WHERE call_session_id = ?').get(session.id) as
329
+ const request = raw.query('SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?').get(session.id) as
330
330
  | { id: string }
331
331
  | undefined;
332
332
  const vellumDelivery = raw.query(
333
- 'SELECT * FROM guardian_action_deliveries WHERE request_id = ? AND destination_channel = ?',
333
+ 'SELECT * FROM canonical_guardian_deliveries WHERE request_id = ? AND destination_channel = ?',
334
334
  ).get(request!.id, 'vellum') as { destination_conversation_id: string | null } | undefined;
335
335
  expect(vellumDelivery).toBeDefined();
336
336
  expect(vellumDelivery!.destination_conversation_id).toBe('conv-from-thread-created');
337
337
  });
338
338
 
339
- test('persists toolName and inputDigest on guardian action request for tool-approval dispatches', async () => {
339
+ test('persists toolName and inputDigest on canonical guardian request for tool-approval dispatches', async () => {
340
340
  const convId = 'conv-dispatch-5';
341
341
  ensureConversation(convId);
342
342
 
@@ -359,7 +359,7 @@ describe('guardian-dispatch', () => {
359
359
 
360
360
  const db = getDb();
361
361
  const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
362
- const request = raw.query('SELECT * FROM guardian_action_requests WHERE call_session_id = ?').get(session.id) as
362
+ const request = raw.query('SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?').get(session.id) as
363
363
  | { id: string; tool_name: string | null; input_digest: string | null }
364
364
  | undefined;
365
365
  expect(request).toBeDefined();
@@ -388,7 +388,7 @@ describe('guardian-dispatch', () => {
388
388
 
389
389
  const db = getDb();
390
390
  const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
391
- const request = raw.query('SELECT * FROM guardian_action_requests WHERE call_session_id = ?').get(session.id) as
391
+ const request = raw.query('SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?').get(session.id) as
392
392
  | { id: string; tool_name: string | null; input_digest: string | null }
393
393
  | undefined;
394
394
  expect(request).toBeDefined();
@@ -485,11 +485,11 @@ describe('guardian-dispatch', () => {
485
485
  pendingQuestion: pq2,
486
486
  });
487
487
 
488
- // Both dispatches should have created separate action requests
488
+ // Both dispatches should have created separate canonical requests
489
489
  const db = getDb();
490
490
  const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
491
491
  const requests = raw.query(
492
- 'SELECT * FROM guardian_action_requests WHERE call_session_id = ? ORDER BY created_at ASC',
492
+ 'SELECT * FROM canonical_guardian_requests WHERE call_session_id = ? ORDER BY created_at ASC',
493
493
  ).all(session.id) as Array<{ id: string; question_text: string }>;
494
494
  expect(requests).toHaveLength(2);
495
495
  expect(requests[0].question_text).toBe('What is the gate code?');
@@ -498,7 +498,7 @@ describe('guardian-dispatch', () => {
498
498
  // Each request should have its own delivery row, both pointing to the shared conversation
499
499
  for (const req of requests) {
500
500
  const delivery = raw.query(
501
- 'SELECT * FROM guardian_action_deliveries WHERE request_id = ? AND destination_channel = ?',
501
+ 'SELECT * FROM canonical_guardian_deliveries WHERE request_id = ? AND destination_channel = ?',
502
502
  ).get(req.id, 'vellum') as { status: string; destination_conversation_id: string | null } | undefined;
503
503
  expect(delivery).toBeDefined();
504
504
  expect(delivery!.status).toBe('sent');
@@ -507,7 +507,7 @@ describe('guardian-dispatch', () => {
507
507
 
508
508
  // Total delivery rows should be 2 (one per request), not 1
509
509
  const allDeliveries = raw.query(
510
- 'SELECT * FROM guardian_action_deliveries WHERE destination_conversation_id = ?',
510
+ 'SELECT * FROM canonical_guardian_deliveries WHERE destination_conversation_id = ?',
511
511
  ).all(sharedConversationId) as Array<{ request_id: string }>;
512
512
  expect(allDeliveries).toHaveLength(2);
513
513