@vellumai/assistant 0.3.16 → 0.3.19

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 (114) hide show
  1. package/ARCHITECTURE.md +74 -13
  2. package/README.md +6 -0
  3. package/docs/architecture/http-token-refresh.md +23 -1
  4. package/docs/architecture/security.md +80 -0
  5. package/package.json +1 -1
  6. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +4 -0
  7. package/src/__tests__/access-request-decision.test.ts +4 -7
  8. package/src/__tests__/call-controller.test.ts +170 -0
  9. package/src/__tests__/channel-guardian.test.ts +3 -1
  10. package/src/__tests__/checker.test.ts +139 -48
  11. package/src/__tests__/config-watcher.test.ts +11 -13
  12. package/src/__tests__/conversation-pairing.test.ts +103 -3
  13. package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -1
  14. package/src/__tests__/guardian-action-followup-executor.test.ts +1 -1
  15. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +511 -0
  16. package/src/__tests__/guardian-action-late-reply.test.ts +131 -0
  17. package/src/__tests__/guardian-action-store.test.ts +182 -0
  18. package/src/__tests__/guardian-dispatch.test.ts +180 -0
  19. package/src/__tests__/guardian-grant-minting.test.ts +543 -0
  20. package/src/__tests__/ipc-snapshot.test.ts +22 -0
  21. package/src/__tests__/non-member-access-request.test.ts +1 -2
  22. package/src/__tests__/notification-broadcaster.test.ts +115 -4
  23. package/src/__tests__/notification-decision-strategy.test.ts +2 -1
  24. package/src/__tests__/notification-deep-link.test.ts +44 -1
  25. package/src/__tests__/notification-guardian-path.test.ts +157 -0
  26. package/src/__tests__/notification-thread-candidate-validation.test.ts +215 -0
  27. package/src/__tests__/remote-skill-policy.test.ts +215 -0
  28. package/src/__tests__/scoped-approval-grants.test.ts +521 -0
  29. package/src/__tests__/scoped-grant-security-matrix.test.ts +443 -0
  30. package/src/__tests__/slack-channel-config.test.ts +3 -3
  31. package/src/__tests__/trust-store.test.ts +23 -21
  32. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +5 -7
  33. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
  34. package/src/__tests__/trusted-contact-verification.test.ts +9 -9
  35. package/src/__tests__/update-bulletin-state.test.ts +1 -1
  36. package/src/__tests__/update-bulletin.test.ts +66 -3
  37. package/src/__tests__/update-template-contract.test.ts +6 -11
  38. package/src/__tests__/voice-scoped-grant-consumer.test.ts +571 -0
  39. package/src/__tests__/voice-session-bridge.test.ts +109 -9
  40. package/src/calls/call-controller.ts +150 -8
  41. package/src/calls/call-domain.ts +12 -0
  42. package/src/calls/guardian-action-sweep.ts +1 -1
  43. package/src/calls/guardian-dispatch.ts +16 -0
  44. package/src/calls/relay-server.ts +13 -0
  45. package/src/calls/voice-session-bridge.ts +46 -5
  46. package/src/cli/core-commands.ts +41 -1
  47. package/src/config/bundled-skills/notifications/SKILL.md +18 -0
  48. package/src/config/schema.ts +6 -0
  49. package/src/config/skills-schema.ts +27 -0
  50. package/src/config/templates/UPDATES.md +5 -6
  51. package/src/config/update-bulletin-format.ts +2 -0
  52. package/src/config/update-bulletin-state.ts +1 -1
  53. package/src/config/update-bulletin-template-path.ts +6 -0
  54. package/src/config/update-bulletin.ts +21 -6
  55. package/src/daemon/config-watcher.ts +3 -2
  56. package/src/daemon/daemon-control.ts +64 -10
  57. package/src/daemon/handlers/config-channels.ts +18 -0
  58. package/src/daemon/handlers/config-slack-channel.ts +1 -1
  59. package/src/daemon/handlers/identity.ts +45 -25
  60. package/src/daemon/handlers/sessions.ts +1 -1
  61. package/src/daemon/handlers/skills.ts +45 -2
  62. package/src/daemon/ipc-contract/sessions.ts +1 -1
  63. package/src/daemon/ipc-contract/skills.ts +1 -0
  64. package/src/daemon/ipc-contract/workspace.ts +12 -1
  65. package/src/daemon/ipc-contract-inventory.json +1 -0
  66. package/src/daemon/lifecycle.ts +8 -0
  67. package/src/daemon/server.ts +25 -3
  68. package/src/daemon/session-process.ts +450 -184
  69. package/src/daemon/tls-certs.ts +17 -12
  70. package/src/daemon/tool-side-effects.ts +1 -1
  71. package/src/memory/channel-delivery-store.ts +18 -20
  72. package/src/memory/channel-guardian-store.ts +39 -42
  73. package/src/memory/conversation-crud.ts +2 -2
  74. package/src/memory/conversation-queries.ts +2 -2
  75. package/src/memory/conversation-store.ts +24 -25
  76. package/src/memory/db-init.ts +17 -1
  77. package/src/memory/embedding-local.ts +16 -7
  78. package/src/memory/fts-reconciler.ts +41 -26
  79. package/src/memory/guardian-action-store.ts +65 -7
  80. package/src/memory/guardian-verification.ts +1 -0
  81. package/src/memory/jobs-worker.ts +2 -2
  82. package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +15 -0
  83. package/src/memory/migrations/032-notification-delivery-thread-decision.ts +20 -0
  84. package/src/memory/migrations/033-scoped-approval-grants.ts +51 -0
  85. package/src/memory/migrations/034-guardian-action-tool-metadata.ts +12 -0
  86. package/src/memory/migrations/index.ts +6 -2
  87. package/src/memory/schema-migration.ts +1 -0
  88. package/src/memory/schema.ts +36 -1
  89. package/src/memory/scoped-approval-grants.ts +509 -0
  90. package/src/memory/search/semantic.ts +3 -3
  91. package/src/notifications/README.md +158 -17
  92. package/src/notifications/broadcaster.ts +68 -50
  93. package/src/notifications/conversation-pairing.ts +96 -18
  94. package/src/notifications/decision-engine.ts +6 -3
  95. package/src/notifications/deliveries-store.ts +12 -0
  96. package/src/notifications/emit-signal.ts +1 -0
  97. package/src/notifications/thread-candidates.ts +60 -25
  98. package/src/notifications/types.ts +2 -1
  99. package/src/permissions/checker.ts +28 -16
  100. package/src/permissions/defaults.ts +14 -4
  101. package/src/runtime/guardian-action-followup-executor.ts +1 -1
  102. package/src/runtime/guardian-action-grant-minter.ts +97 -0
  103. package/src/runtime/http-server.ts +11 -11
  104. package/src/runtime/routes/access-request-decision.ts +1 -1
  105. package/src/runtime/routes/debug-routes.ts +4 -4
  106. package/src/runtime/routes/guardian-approval-interception.ts +120 -4
  107. package/src/runtime/routes/inbound-message-handler.ts +100 -33
  108. package/src/runtime/routes/integration-routes.ts +2 -2
  109. package/src/security/tool-approval-digest.ts +67 -0
  110. package/src/skills/remote-skill-policy.ts +131 -0
  111. package/src/tools/permission-checker.ts +1 -2
  112. package/src/tools/secret-detection-handler.ts +1 -1
  113. package/src/tools/system/voice-config.ts +1 -1
  114. package/src/version.ts +29 -2
@@ -42,20 +42,20 @@ mock.module('../util/logger.js', () => ({
42
42
  }),
43
43
  }));
44
44
 
45
- import { initializeDb, resetDb } from '../memory/db.js';
46
45
  import {
47
- createOutboundSession,
48
- validateAndConsumeChallenge,
49
- } from '../runtime/channel-guardian-service.js';
46
+ createBinding,
47
+ getActiveBinding,
48
+ } from '../memory/channel-guardian-store.js';
49
+ import { getDb, initializeDb, resetDb } from '../memory/db.js';
50
50
  import {
51
51
  findMember,
52
- upsertMember,
53
52
  revokeMember,
53
+ upsertMember,
54
54
  } from '../memory/ingress-member-store.js';
55
55
  import {
56
- getActiveBinding,
57
- createBinding,
58
- } from '../memory/channel-guardian-store.js';
56
+ createOutboundSession,
57
+ validateAndConsumeChallenge,
58
+ } from '../runtime/channel-guardian-service.js';
59
59
 
60
60
  initializeDb();
61
61
 
@@ -69,7 +69,6 @@ afterAll(() => {
69
69
  // ---------------------------------------------------------------------------
70
70
 
71
71
  function resetTables(): void {
72
- const { getDb } = require('../memory/db.js');
73
72
  const db = getDb();
74
73
  db.run('DELETE FROM channel_guardian_verification_challenges');
75
74
  db.run('DELETE FROM channel_guardian_bindings');
@@ -339,6 +338,7 @@ describe('trusted contact verification → member activation', () => {
339
338
 
340
339
  test('guardian inbound verification still creates binding (backward compat)', () => {
341
340
  // Create an inbound challenge (no expected identity — guardian flow)
341
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
342
342
  const { createVerificationChallenge } = require('../runtime/channel-guardian-service.js');
343
343
  const { secret } = createVerificationChallenge('self', 'telegram');
344
344
 
@@ -1,4 +1,4 @@
1
- import { describe, it, expect, beforeEach, mock } from 'bun:test';
1
+ import { beforeEach, describe, expect, it, mock } from 'bun:test';
2
2
 
3
3
  const store = new Map<string, string>();
4
4
 
@@ -1,7 +1,9 @@
1
- import { describe, it, expect, beforeEach, afterEach, mock } from 'bun:test';
2
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
3
- import { join } from 'node:path';
1
+ import * as fs from 'node:fs';
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
3
  import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+
6
+ import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from 'bun:test';
5
7
 
6
8
  // --- In-memory checkpoint store ---
7
9
  const store = new Map<string, string>();
@@ -14,6 +16,12 @@ mock.module('../memory/checkpoints.js', () => ({
14
16
  // --- Temp directory for workspace paths ---
15
17
  let tempDir: string;
16
18
 
19
+ // --- Temp directory for template files ---
20
+ // Avoids mutating the real source-controlled UPDATES.md template, preventing
21
+ // race conditions with parallel test execution and working tree corruption
22
+ // if the test process crashes.
23
+ let tempTemplateDir: string;
24
+
17
25
  // Mock platform to avoid env-registry transitive imports.
18
26
  // All needed exports are stubbed; getWorkspacePromptPath is the only one
19
27
  // exercised by update-bulletin.ts.
@@ -92,17 +100,31 @@ mock.module('../version.js', () => ({
92
100
  APP_VERSION: '1.0.0',
93
101
  }));
94
102
 
103
+ // Mock the template path module so tests read from a temp directory instead
104
+ // of the real source-controlled template file.
105
+ mock.module('../config/update-bulletin-template-path.js', () => ({
106
+ getTemplatePath: () => join(tempTemplateDir, 'UPDATES.md'),
107
+ }));
108
+
95
109
  const { syncUpdateBulletinOnStartup } = await import('../config/update-bulletin.js');
96
110
 
111
+ const TEST_TEMPLATE = '## What\'s New\n\nTest release notes.\n';
112
+ const COMMENT_ONLY_TEMPLATE = '_ This is a comment-only template.\n_ No real content here.\n';
113
+
97
114
  describe('syncUpdateBulletinOnStartup', () => {
98
115
  beforeEach(() => {
99
116
  store.clear();
100
117
  tempDir = join(tmpdir(), `update-bulletin-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
101
118
  mkdirSync(tempDir, { recursive: true });
119
+ tempTemplateDir = join(tmpdir(), `update-bulletin-tpl-${Date.now()}-${Math.random().toString(36).slice(2)}`);
120
+ mkdirSync(tempTemplateDir, { recursive: true });
121
+ // Write a test template with real content so materialization proceeds
122
+ writeFileSync(join(tempTemplateDir, 'UPDATES.md'), TEST_TEMPLATE, 'utf-8');
102
123
  });
103
124
 
104
125
  afterEach(() => {
105
126
  rmSync(tempDir, { recursive: true, force: true });
127
+ rmSync(tempTemplateDir, { recursive: true, force: true });
106
128
  });
107
129
 
108
130
  it('creates workspace file on first eligible run', () => {
@@ -257,4 +279,45 @@ describe('syncUpdateBulletinOnStartup', () => {
257
279
  const tmpFiles = entries.filter((e) => e.includes('.tmp.'));
258
280
  expect(tmpFiles).toHaveLength(0);
259
281
  });
282
+
283
+ it('skips materialization when template is comment-only', () => {
284
+ // Write a comment-only template fixture (no real content after stripping)
285
+ writeFileSync(join(tempTemplateDir, 'UPDATES.md'), COMMENT_ONLY_TEMPLATE, 'utf-8');
286
+
287
+ const workspacePath = join(tempDir, 'UPDATES.md');
288
+ syncUpdateBulletinOnStartup();
289
+
290
+ expect(existsSync(workspacePath)).toBe(false);
291
+ });
292
+
293
+ it('preserves existing file when atomic write fails', () => {
294
+ const workspacePath = join(tempDir, 'UPDATES.md');
295
+ const originalContent = '<!-- vellum-update-release:0.9.0 -->\nOriginal content.\n';
296
+ writeFileSync(workspacePath, originalContent, 'utf-8');
297
+
298
+ // Mock writeFileSync to throw when writing the temp file, simulating a
299
+ // disk-full or permission error deterministically (chmod-based approaches
300
+ // are unreliable when running as root or with CAP_DAC_OVERRIDE).
301
+ const originalWriteFileSync = fs.writeFileSync;
302
+ const spy = spyOn(fs, 'writeFileSync').mockImplementation((...args: Parameters<typeof fs.writeFileSync>) => {
303
+ if (typeof args[0] === 'string' && args[0].includes('.tmp.')) {
304
+ throw new Error('Simulated write failure');
305
+ }
306
+ return originalWriteFileSync(...args);
307
+ });
308
+ try {
309
+ expect(() => syncUpdateBulletinOnStartup()).toThrow('Simulated write failure');
310
+ } finally {
311
+ spy.mockRestore();
312
+ }
313
+
314
+ // Original content should be preserved (atomic write never renamed over it)
315
+ const content = readFileSync(workspacePath, 'utf-8');
316
+ expect(content).toBe(originalContent);
317
+
318
+ // No temp file leftovers
319
+ const entries = readdirSync(tempDir);
320
+ const tmpFiles = entries.filter((e: string) => e.includes('.tmp.'));
321
+ expect(tmpFiles).toHaveLength(0);
322
+ });
260
323
  });
@@ -1,13 +1,13 @@
1
1
  /**
2
- * Contract test: ensures the bundled UPDATES.md template exists and meets
3
- * the format expectations that the bulletin system depends on at runtime.
2
+ * Contract test: ensures the bundled UPDATES.md template exists and is readable.
4
3
  *
5
- * The "## What's New" heading is a structural contract bulletin rendering
6
- * logic expects this section to be present in the template.
4
+ * The template may be comment-only (no real content) for no-op releases
5
+ * the bulletin system treats an empty-after-stripping template as a skip signal.
7
6
  */
8
7
 
9
8
  import { existsSync, readFileSync } from 'node:fs';
10
9
  import { join } from 'node:path';
10
+
11
11
  import { describe, expect, test } from 'bun:test';
12
12
 
13
13
  const TEMPLATE_PATH = join(import.meta.dirname, '..', 'config', 'templates', 'UPDATES.md');
@@ -17,13 +17,8 @@ describe('UPDATES.md template contract', () => {
17
17
  expect(existsSync(TEMPLATE_PATH)).toBe(true);
18
18
  });
19
19
 
20
- test('template contains non-whitespace content', () => {
21
- const content = readFileSync(TEMPLATE_PATH, 'utf-8');
22
- expect(content.trim().length).toBeGreaterThan(0);
23
- });
24
-
25
- test('template contains the "## What\'s New" heading', () => {
20
+ test('template is a readable UTF-8 file', () => {
26
21
  const content = readFileSync(TEMPLATE_PATH, 'utf-8');
27
- expect(content).toContain("## What's New");
22
+ expect(typeof content).toBe('string');
28
23
  });
29
24
  });