vellum 0.2.13 → 0.2.14

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 (207) hide show
  1. package/README.md +32 -0
  2. package/bun.lock +2 -2
  3. package/docs/skills.md +4 -4
  4. package/package.json +2 -2
  5. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +213 -3
  6. package/src/__tests__/app-git-history.test.ts +176 -0
  7. package/src/__tests__/app-git-service.test.ts +169 -0
  8. package/src/__tests__/assistant-events-sse-hardening.test.ts +315 -0
  9. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +8 -8
  10. package/src/__tests__/browser-skill-endstate.test.ts +6 -6
  11. package/src/__tests__/call-bridge.test.ts +105 -13
  12. package/src/__tests__/call-domain.test.ts +163 -0
  13. package/src/__tests__/call-orchestrator.test.ts +113 -0
  14. package/src/__tests__/call-routes-http.test.ts +246 -6
  15. package/src/__tests__/channel-approval-routes.test.ts +438 -0
  16. package/src/__tests__/channel-approval.test.ts +266 -0
  17. package/src/__tests__/channel-approvals.test.ts +393 -0
  18. package/src/__tests__/channel-delivery-store.test.ts +447 -0
  19. package/src/__tests__/checker.test.ts +607 -1048
  20. package/src/__tests__/cli.test.ts +1 -56
  21. package/src/__tests__/config-schema.test.ts +137 -18
  22. package/src/__tests__/conflict-intent-tokenization.test.ts +141 -0
  23. package/src/__tests__/conflict-policy.test.ts +121 -0
  24. package/src/__tests__/conflict-store.test.ts +2 -0
  25. package/src/__tests__/contacts-tools.test.ts +3 -3
  26. package/src/__tests__/contradiction-checker.test.ts +99 -1
  27. package/src/__tests__/credential-security-invariants.test.ts +22 -6
  28. package/src/__tests__/credential-vault-unit.test.ts +780 -0
  29. package/src/__tests__/elevenlabs-client.test.ts +62 -0
  30. package/src/__tests__/ephemeral-permissions.test.ts +73 -23
  31. package/src/__tests__/filesystem-tools.test.ts +579 -0
  32. package/src/__tests__/gateway-only-enforcement.test.ts +114 -4
  33. package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +202 -0
  34. package/src/__tests__/handlers-cu-observation-blob.test.ts +2 -1
  35. package/src/__tests__/handlers-ipc-blob-probe.test.ts +2 -1
  36. package/src/__tests__/handlers-slack-config.test.ts +2 -1
  37. package/src/__tests__/handlers-telegram-config.test.ts +855 -0
  38. package/src/__tests__/handlers-twitter-config.test.ts +141 -1
  39. package/src/__tests__/hooks-runner.test.ts +6 -2
  40. package/src/__tests__/host-file-edit-tool.test.ts +124 -0
  41. package/src/__tests__/host-file-read-tool.test.ts +62 -0
  42. package/src/__tests__/host-file-write-tool.test.ts +59 -0
  43. package/src/__tests__/host-shell-tool.test.ts +251 -0
  44. package/src/__tests__/ingress-reconcile.test.ts +581 -0
  45. package/src/__tests__/ipc-snapshot.test.ts +100 -41
  46. package/src/__tests__/ipc-validate.test.ts +50 -0
  47. package/src/__tests__/key-migration.test.ts +23 -0
  48. package/src/__tests__/memory-regressions.test.ts +99 -0
  49. package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
  50. package/src/__tests__/oauth-callback-registry.test.ts +11 -4
  51. package/src/__tests__/playbook-execution.test.ts +502 -0
  52. package/src/__tests__/playbook-tools.test.ts +4 -6
  53. package/src/__tests__/public-ingress-urls.test.ts +34 -0
  54. package/src/__tests__/qdrant-manager.test.ts +267 -0
  55. package/src/__tests__/recurrence-engine-rruleset.test.ts +97 -0
  56. package/src/__tests__/recurrence-engine.test.ts +9 -0
  57. package/src/__tests__/recurrence-types.test.ts +8 -0
  58. package/src/__tests__/registry.test.ts +1 -1
  59. package/src/__tests__/runtime-runs.test.ts +1 -25
  60. package/src/__tests__/schedule-store.test.ts +16 -14
  61. package/src/__tests__/schedule-tools.test.ts +83 -0
  62. package/src/__tests__/scheduler-recurrence.test.ts +111 -10
  63. package/src/__tests__/secret-allowlist.test.ts +18 -17
  64. package/src/__tests__/secret-ingress-handler.test.ts +11 -0
  65. package/src/__tests__/secret-scanner.test.ts +43 -0
  66. package/src/__tests__/session-conflict-gate.test.ts +442 -6
  67. package/src/__tests__/session-init.benchmark.test.ts +3 -0
  68. package/src/__tests__/session-process-bridge.test.ts +242 -0
  69. package/src/__tests__/session-skill-tools.test.ts +1 -1
  70. package/src/__tests__/shell-identity.test.ts +256 -0
  71. package/src/__tests__/skill-projection.benchmark.test.ts +11 -1
  72. package/src/__tests__/subagent-tools.test.ts +637 -54
  73. package/src/__tests__/task-management-tools.test.ts +936 -0
  74. package/src/__tests__/task-runner.test.ts +2 -2
  75. package/src/__tests__/terminal-tools.test.ts +840 -0
  76. package/src/__tests__/tool-executor-shell-integration.test.ts +301 -0
  77. package/src/__tests__/tool-executor.test.ts +85 -151
  78. package/src/__tests__/tool-permission-simulate-handler.test.ts +336 -0
  79. package/src/__tests__/trust-store.test.ts +27 -453
  80. package/src/__tests__/twilio-provider.test.ts +153 -3
  81. package/src/__tests__/twilio-routes-elevenlabs.test.ts +375 -0
  82. package/src/__tests__/twilio-routes-twiml.test.ts +4 -4
  83. package/src/__tests__/twilio-routes.test.ts +17 -262
  84. package/src/__tests__/twitter-auth-handler.test.ts +2 -1
  85. package/src/__tests__/twitter-cli-error-shaping.test.ts +208 -0
  86. package/src/__tests__/twitter-cli-routing.test.ts +252 -0
  87. package/src/__tests__/twitter-oauth-client.test.ts +209 -0
  88. package/src/__tests__/workspace-policy.test.ts +213 -0
  89. package/src/calls/call-bridge.ts +92 -19
  90. package/src/calls/call-domain.ts +157 -5
  91. package/src/calls/call-orchestrator.ts +93 -7
  92. package/src/calls/call-store.ts +6 -0
  93. package/src/calls/elevenlabs-client.ts +8 -0
  94. package/src/calls/elevenlabs-config.ts +7 -5
  95. package/src/calls/twilio-provider.ts +91 -0
  96. package/src/calls/twilio-routes.ts +32 -37
  97. package/src/calls/types.ts +3 -1
  98. package/src/calls/voice-quality.ts +29 -7
  99. package/src/cli/twitter.ts +200 -21
  100. package/src/cli.ts +1 -20
  101. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +52 -4
  102. package/src/config/bundled-skills/contacts/tools/contact-search.ts +55 -4
  103. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +61 -4
  104. package/src/config/bundled-skills/messaging/SKILL.md +17 -2
  105. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +4 -1
  106. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
  107. package/src/config/bundled-skills/messaging/tools/shared.ts +5 -0
  108. package/src/config/bundled-skills/phone-calls/SKILL.md +142 -34
  109. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +95 -6
  110. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +51 -6
  111. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +73 -6
  112. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +110 -6
  113. package/src/config/bundled-skills/public-ingress/SKILL.md +22 -5
  114. package/src/config/bundled-skills/twitter/SKILL.md +103 -17
  115. package/src/config/defaults.ts +10 -4
  116. package/src/config/schema.ts +80 -21
  117. package/src/config/types.ts +1 -0
  118. package/src/config/vellum-skills/telegram-setup/SKILL.md +56 -61
  119. package/src/daemon/assistant-attachments.ts +4 -2
  120. package/src/daemon/handlers/apps.ts +69 -0
  121. package/src/daemon/handlers/config.ts +543 -24
  122. package/src/daemon/handlers/index.ts +1 -0
  123. package/src/daemon/handlers/sessions.ts +22 -6
  124. package/src/daemon/handlers/shared.ts +2 -1
  125. package/src/daemon/handlers/skills.ts +5 -20
  126. package/src/daemon/ipc-contract-inventory.json +28 -0
  127. package/src/daemon/ipc-contract.ts +168 -10
  128. package/src/daemon/ipc-validate.ts +17 -0
  129. package/src/daemon/lifecycle.ts +2 -0
  130. package/src/daemon/server.ts +78 -72
  131. package/src/daemon/session-attachments.ts +1 -1
  132. package/src/daemon/session-conflict-gate.ts +62 -6
  133. package/src/daemon/session-notifiers.ts +1 -1
  134. package/src/daemon/session-process.ts +62 -3
  135. package/src/daemon/session-tool-setup.ts +1 -2
  136. package/src/daemon/tls-certs.ts +189 -0
  137. package/src/daemon/video-thumbnail.ts +5 -3
  138. package/src/hooks/manager.ts +5 -9
  139. package/src/memory/app-git-service.ts +295 -0
  140. package/src/memory/app-store.ts +21 -0
  141. package/src/memory/conflict-intent.ts +47 -4
  142. package/src/memory/conflict-policy.ts +73 -0
  143. package/src/memory/conflict-store.ts +9 -1
  144. package/src/memory/contradiction-checker.ts +28 -0
  145. package/src/memory/conversation-key-store.ts +15 -0
  146. package/src/memory/db.ts +81 -0
  147. package/src/memory/embedding-local.ts +3 -13
  148. package/src/memory/external-conversation-store.ts +234 -0
  149. package/src/memory/job-handlers/conflict.ts +22 -2
  150. package/src/memory/jobs-worker.ts +67 -28
  151. package/src/memory/runs-store.ts +54 -7
  152. package/src/memory/schema.ts +20 -0
  153. package/src/messaging/provider.ts +9 -0
  154. package/src/messaging/providers/telegram-bot/adapter.ts +162 -0
  155. package/src/messaging/providers/telegram-bot/client.ts +104 -0
  156. package/src/messaging/providers/telegram-bot/types.ts +15 -0
  157. package/src/messaging/registry.ts +1 -0
  158. package/src/permissions/checker.ts +48 -44
  159. package/src/permissions/prompter.ts +0 -4
  160. package/src/permissions/shell-identity.ts +227 -0
  161. package/src/permissions/trust-store.ts +76 -53
  162. package/src/permissions/types.ts +0 -19
  163. package/src/permissions/workspace-policy.ts +114 -0
  164. package/src/providers/retry.ts +12 -37
  165. package/src/runtime/assistant-event-hub.ts +41 -4
  166. package/src/runtime/channel-approval-parser.ts +60 -0
  167. package/src/runtime/channel-approval-types.ts +71 -0
  168. package/src/runtime/channel-approvals.ts +145 -0
  169. package/src/runtime/gateway-client.ts +16 -0
  170. package/src/runtime/http-server.ts +29 -9
  171. package/src/runtime/routes/call-routes.ts +52 -2
  172. package/src/runtime/routes/channel-routes.ts +296 -16
  173. package/src/runtime/routes/events-routes.ts +97 -28
  174. package/src/runtime/routes/run-routes.ts +2 -7
  175. package/src/runtime/run-orchestrator.ts +0 -3
  176. package/src/schedule/recurrence-engine.ts +26 -2
  177. package/src/schedule/recurrence-types.ts +1 -1
  178. package/src/schedule/schedule-store.ts +12 -3
  179. package/src/security/secret-scanner.ts +7 -0
  180. package/src/tasks/ephemeral-permissions.ts +0 -2
  181. package/src/tasks/task-scheduler.ts +2 -1
  182. package/src/tools/calls/call-start.ts +8 -0
  183. package/src/tools/execution-target.ts +21 -0
  184. package/src/tools/execution-timeout.ts +49 -0
  185. package/src/tools/executor.ts +6 -135
  186. package/src/tools/network/web-search.ts +9 -32
  187. package/src/tools/policy-context.ts +29 -0
  188. package/src/tools/schedule/update.ts +8 -1
  189. package/src/tools/terminal/parser.ts +16 -18
  190. package/src/tools/types.ts +4 -11
  191. package/src/twitter/oauth-client.ts +102 -0
  192. package/src/twitter/router.ts +101 -0
  193. package/src/util/debounce.ts +88 -0
  194. package/src/util/network-info.ts +47 -0
  195. package/src/util/platform.ts +29 -4
  196. package/src/util/promise-guard.ts +37 -0
  197. package/src/util/retry.ts +98 -0
  198. package/src/util/truncate.ts +1 -1
  199. package/src/workspace/git-service.ts +129 -112
  200. package/src/tools/contacts/contact-merge.ts +0 -55
  201. package/src/tools/contacts/contact-search.ts +0 -58
  202. package/src/tools/contacts/contact-upsert.ts +0 -64
  203. package/src/tools/playbooks/index.ts +0 -4
  204. package/src/tools/playbooks/playbook-create.ts +0 -96
  205. package/src/tools/playbooks/playbook-delete.ts +0 -52
  206. package/src/tools/playbooks/playbook-list.ts +0 -74
  207. package/src/tools/playbooks/playbook-update.ts +0 -111
@@ -169,7 +169,7 @@ describe('ToolExecutor allowedToolNames gating', () => {
169
169
  });
170
170
  });
171
171
 
172
- describe('ToolExecutor principal context plumbing', () => {
172
+ describe('ToolExecutor policy context plumbing', () => {
173
173
  beforeEach(() => {
174
174
  fakeToolResult = { content: 'ok', isError: false };
175
175
  lastCheckArgs = undefined;
@@ -179,7 +179,7 @@ describe('ToolExecutor principal context plumbing', () => {
179
179
  if (addRuleSpy) { addRuleSpy.mockRestore(); addRuleSpy = undefined; }
180
180
  });
181
181
 
182
- test('passes PolicyContext with skill principal for skill-origin tools', async () => {
182
+ test('passes PolicyContext with executionTarget for skill-origin tools', async () => {
183
183
  getToolOverride = (name: string) => {
184
184
  if (name === 'unknown_tool') return undefined;
185
185
  return {
@@ -202,11 +202,6 @@ describe('ToolExecutor principal context plumbing', () => {
202
202
  expect(result.isError).toBe(false);
203
203
  expect(lastCheckArgs).toBeDefined();
204
204
  expect(lastCheckArgs!.policyContext).toEqual({
205
- principal: {
206
- kind: 'skill',
207
- id: 'my-skill-123',
208
- version: 'abc123hash',
209
- },
210
205
  executionTarget: 'sandbox',
211
206
  });
212
207
  });
@@ -268,42 +263,32 @@ describe('ToolExecutor principal context plumbing', () => {
268
263
  expect(result.isError).toBe(false);
269
264
  expect(lastCheckArgs).toBeDefined();
270
265
  expect(lastCheckArgs!.policyContext).toEqual({
271
- principal: {
272
- kind: 'skill',
273
- id: 'host-skill',
274
- version: 'host-hash',
275
- },
276
266
  executionTarget: 'host',
277
267
  });
278
268
  });
279
269
 
280
- test('skill tool without version hash passes undefined version in principal', async () => {
270
+ test('skill tool without executionTarget passes undefined executionTarget', async () => {
281
271
  getToolOverride = (name: string) => {
282
272
  if (name === 'unknown_tool') return undefined;
283
273
  return {
284
274
  name,
285
- description: 'skill without hash',
275
+ description: 'skill without target',
286
276
  category: 'skill',
287
277
  defaultRiskLevel: RiskLevel.Low,
288
278
  origin: 'skill' as const,
289
- ownerSkillId: 'no-hash-skill',
290
- // ownerSkillVersionHash intentionally omitted
279
+ ownerSkillId: 'no-target-skill',
280
+ // executionTarget intentionally omitted
291
281
  getDefinition: () => ({ name, description: 'skill tool', input_schema: { type: 'object' as const, properties: {} } }),
292
282
  execute: async () => fakeToolResult,
293
283
  };
294
284
  };
295
285
 
296
286
  const executor = new ToolExecutor(makePrompter());
297
- const result = await executor.execute('no_hash_tool', {}, makeContext());
287
+ const result = await executor.execute('no_target_tool', {}, makeContext());
298
288
 
299
289
  expect(result.isError).toBe(false);
300
290
  expect(lastCheckArgs).toBeDefined();
301
291
  expect(lastCheckArgs!.policyContext).toEqual({
302
- principal: {
303
- kind: 'skill',
304
- id: 'no-hash-skill',
305
- version: undefined,
306
- },
307
292
  executionTarget: undefined,
308
293
  });
309
294
  });
@@ -344,7 +329,7 @@ describe('ToolExecutor contextual rule creation', () => {
344
329
  return addRuleSpy;
345
330
  }
346
331
 
347
- test('always_allow for a skill tool captures principal context in the rule', async () => {
332
+ test('always_allow for a skill tool captures execution target in the rule', async () => {
348
333
  checkResultOverride = { decision: 'prompt', reason: 'test prompt' };
349
334
  const spy = setupAddRuleSpy();
350
335
 
@@ -376,13 +361,10 @@ describe('ToolExecutor contextual rule creation', () => {
376
361
  expect(scope).toBe('/tmp/project');
377
362
  expect(decision).toBe('allow');
378
363
  expect(options).toBeDefined();
379
- expect(options.principalKind).toBe('skill');
380
- expect(options.principalId).toBe('my-skill-42');
381
- expect(options.principalVersion).toBe('sha256-deadbeef');
382
364
  expect(options.executionTarget).toBe('sandbox');
383
365
  });
384
366
 
385
- test('always_allow_high_risk sets allowHighRisk and captures principal context', async () => {
367
+ test('always_allow_high_risk sets allowHighRisk and captures execution target', async () => {
386
368
  checkResultOverride = { decision: 'prompt', reason: 'test prompt' };
387
369
  const spy = setupAddRuleSpy();
388
370
 
@@ -415,13 +397,10 @@ describe('ToolExecutor contextual rule creation', () => {
415
397
  expect(decision).toBe('allow');
416
398
  expect(options).toBeDefined();
417
399
  expect(options.allowHighRisk).toBe(true);
418
- expect(options.principalKind).toBe('skill');
419
- expect(options.principalId).toBe('dangerous-skill');
420
- expect(options.principalVersion).toBe('sha256-abc');
421
400
  expect(options.executionTarget).toBe('host');
422
401
  });
423
402
 
424
- test('always_allow for a core tool creates rule without principal context', async () => {
403
+ test('always_allow for a core tool creates rule without options', async () => {
425
404
  checkResultOverride = { decision: 'prompt', reason: 'test prompt' };
426
405
  const spy = setupAddRuleSpy();
427
406
 
@@ -439,7 +418,7 @@ describe('ToolExecutor contextual rule creation', () => {
439
418
  expect(pattern).toBe('git *');
440
419
  expect(scope).toBe('/tmp/project');
441
420
  expect(decision).toBe('allow');
442
- // No options since there's no principal context for core tools
421
+ // No options since there's no execution target for core tools
443
422
  expect(options).toBeUndefined();
444
423
  });
445
424
 
@@ -467,7 +446,7 @@ describe('ToolExecutor contextual rule creation', () => {
467
446
  expect(spy).not.toHaveBeenCalled();
468
447
  });
469
448
 
470
- test('always_allow_high_risk for core tool sets allowHighRisk without principal fields', async () => {
449
+ test('always_allow_high_risk for core tool sets allowHighRisk without execution target', async () => {
471
450
  checkResultOverride = { decision: 'prompt', reason: 'test prompt' };
472
451
  const spy = setupAddRuleSpy();
473
452
  getToolOverride = undefined;
@@ -481,10 +460,7 @@ describe('ToolExecutor contextual rule creation', () => {
481
460
  const [,,,, , options] = spy.mock.calls[0];
482
461
  expect(options).toBeDefined();
483
462
  expect(options.allowHighRisk).toBe(true);
484
- // No principal fields for core tools
485
- expect(options.principalKind).toBeUndefined();
486
- expect(options.principalId).toBeUndefined();
487
- expect(options.principalVersion).toBeUndefined();
463
+ // No execution target for core tools
488
464
  expect(options.executionTarget).toBeUndefined();
489
465
  });
490
466
 
@@ -517,89 +493,6 @@ describe('ToolExecutor contextual rule creation', () => {
517
493
  const [,,,, , options] = spy.mock.calls[0];
518
494
  expect(options).toBeDefined();
519
495
  expect(options.executionTarget).toBe('host');
520
- expect(options.principalKind).toBe('skill');
521
- expect(options.principalId).toBe('host-skill');
522
- expect(options.principalVersion).toBe('host-hash-v1');
523
- });
524
- });
525
-
526
- describe('ToolExecutor prompter principal arg (PR fix3)', () => {
527
- beforeEach(() => {
528
- fakeToolResult = { content: 'ok', isError: false };
529
- lastCheckArgs = undefined;
530
- getToolOverride = undefined;
531
- checkResultOverride = { decision: 'prompt', reason: 'test prompt' };
532
- checkFnOverride = undefined;
533
- if (addRuleSpy) { addRuleSpy.mockRestore(); addRuleSpy = undefined; }
534
- });
535
-
536
- test('passes principal context (kind, id, version) to prompter for skill-origin tools', async () => {
537
- getToolOverride = (name: string) => {
538
- if (name === 'unknown_tool') return undefined;
539
- return {
540
- name,
541
- description: 'skill tool',
542
- category: 'skill',
543
- defaultRiskLevel: RiskLevel.Low,
544
- origin: 'skill' as const,
545
- ownerSkillId: 'prompt-skill-42',
546
- ownerSkillVersionHash: 'sha256-prompt-v1',
547
- executionTarget: 'sandbox' as const,
548
- getDefinition: () => ({ name, description: 'skill tool', input_schema: { type: 'object' as const, properties: {} } }),
549
- execute: async () => fakeToolResult,
550
- };
551
- };
552
-
553
- let capturedPrincipal: unknown;
554
- const prompter = {
555
- prompt: async (
556
- _toolName: string, _input: Record<string, unknown>, _riskLevel: string,
557
- _allowlistOptions: any[], _scopeOptions: any[], _diff: any, _sandboxed: any,
558
- _sessionId: any, _executionTarget: any, principal: any,
559
- ) => {
560
- capturedPrincipal = principal;
561
- return { decision: 'allow' as const };
562
- },
563
- resolveConfirmation: () => {},
564
- updateSender: () => {},
565
- dispose: () => {},
566
- } as unknown as PermissionPrompter;
567
-
568
- const executor = new ToolExecutor(prompter);
569
- const result = await executor.execute('skill_tool', { action: 'run' }, makeContext());
570
-
571
- expect(result.isError).toBe(false);
572
- expect(capturedPrincipal).toEqual({
573
- kind: 'skill',
574
- id: 'prompt-skill-42',
575
- version: 'sha256-prompt-v1',
576
- });
577
- });
578
-
579
- test('passes undefined principal to prompter for core tools', async () => {
580
- // Default getTool returns core tools with no origin field
581
- getToolOverride = undefined;
582
-
583
- let capturedPrincipal: unknown = 'NOT_CALLED';
584
- const prompter = {
585
- prompt: async (
586
- _toolName: string, _input: Record<string, unknown>, _riskLevel: string,
587
- _allowlistOptions: any[], _scopeOptions: any[], _diff: any, _sandboxed: any,
588
- _sessionId: any, _executionTarget: any, principal: any,
589
- ) => {
590
- capturedPrincipal = principal;
591
- return { decision: 'allow' as const };
592
- },
593
- resolveConfirmation: () => {},
594
- updateSender: () => {},
595
- dispose: () => {},
596
- } as unknown as PermissionPrompter;
597
-
598
- const executor = new ToolExecutor(prompter);
599
- const result = await executor.execute('file_read', { path: 'test.txt' }, makeContext());
600
-
601
- expect(result.isError).toBe(false);
602
- expect(capturedPrincipal).toBeUndefined();
603
496
  });
604
497
  });
605
498
 
@@ -653,11 +546,8 @@ describe('ToolExecutor strict mode + high-risk integration (PR 25)', () => {
653
546
  expect(pattern).toBe('deploy_tool:*');
654
547
  expect(scope).toBe('everywhere');
655
548
  expect(decision).toBe('allow');
656
- // The key integration assertion: allowHighRisk + principal context together
549
+ // The key integration assertion: allowHighRisk + execution target together
657
550
  expect(options.allowHighRisk).toBe(true);
658
- expect(options.principalKind).toBe('skill');
659
- expect(options.principalId).toBe('deploy-skill');
660
- expect(options.principalVersion).toBe('sha256-deploy-v1');
661
551
  expect(options.executionTarget).toBe('host');
662
552
  });
663
553
 
@@ -692,9 +582,8 @@ describe('ToolExecutor strict mode + high-risk integration (PR 25)', () => {
692
582
  expect(spy).toHaveBeenCalledTimes(1);
693
583
  const [,,,, , options] = spy.mock.calls[0];
694
584
  expect(options).toBeDefined();
695
- // Principal context should be present
696
- expect(options.principalKind).toBe('skill');
697
- expect(options.principalId).toBe('risky-skill');
585
+ // executionTarget should be present
586
+ expect(options.executionTarget).toBe('sandbox');
698
587
  // But allowHighRisk should NOT be set
699
588
  expect(options.allowHighRisk).toBeUndefined();
700
589
  });
@@ -721,18 +610,13 @@ describe('ToolExecutor strict mode + high-risk integration (PR 25)', () => {
721
610
 
722
611
  expect(lastCheckArgs).toBeDefined();
723
612
  expect(lastCheckArgs!.policyContext).toEqual({
724
- principal: {
725
- kind: 'skill',
726
- id: 'versioned-skill',
727
- version: 'v3:content-hash-xyz',
728
- },
729
613
  executionTarget: 'sandbox',
730
614
  });
731
615
  });
732
616
 
733
617
  // ── Skill mutation approval regression tests (PR 30) ──────────
734
618
 
735
- test('always_allow_high_risk for skill source write creates rule with allowHighRisk and principal context', async () => {
619
+ test('always_allow_high_risk for skill source write creates rule with allowHighRisk and execution target', async () => {
736
620
  checkResultOverride = { decision: 'prompt', reason: 'High risk: always requires approval' };
737
621
  const spy = setupAddRuleSpy();
738
622
 
@@ -764,9 +648,6 @@ describe('ToolExecutor strict mode + high-risk integration (PR 25)', () => {
764
648
  expect(scope).toBe('everywhere');
765
649
  expect(decision).toBe('allow');
766
650
  expect(options.allowHighRisk).toBe(true);
767
- expect(options.principalKind).toBe('skill');
768
- expect(options.principalId).toBe('code-editor-skill');
769
- expect(options.principalVersion).toBe('sha256-v1-original');
770
651
  expect(options.executionTarget).toBe('sandbox');
771
652
  });
772
653
 
@@ -799,8 +680,7 @@ describe('ToolExecutor strict mode + high-risk integration (PR 25)', () => {
799
680
  expect(spy).toHaveBeenCalledTimes(1);
800
681
  const [,,,, , options] = spy.mock.calls[0];
801
682
  expect(options).toBeDefined();
802
- expect(options.principalKind).toBe('skill');
803
- expect(options.principalId).toBe('editor-skill');
683
+ expect(options.executionTarget).toBe('sandbox');
804
684
  // Without always_allow_high_risk, the allowHighRisk flag should NOT be set
805
685
  expect(options.allowHighRisk).toBeUndefined();
806
686
  });
@@ -833,10 +713,8 @@ describe('ToolExecutor strict mode + high-risk integration (PR 25)', () => {
833
713
  expect(spy).toHaveBeenCalledTimes(1);
834
714
  const [tool, , , , , options] = spy.mock.calls[0];
835
715
  expect(tool).toBe('file_edit');
836
- // Verify the version hash is persisted — a changed skill will have a
837
- // different hash, so the rule won't match (version mismatch rejection).
838
- expect(options.principalVersion).toBe('v3:content-hash-xyz789');
839
716
  expect(options.allowHighRisk).toBe(true);
717
+ expect(options.executionTarget).toBe('sandbox');
840
718
  });
841
719
 
842
720
  test('executor forwards policyContext with version for skill source mutation', async () => {
@@ -861,16 +739,11 @@ describe('ToolExecutor strict mode + high-risk integration (PR 25)', () => {
861
739
 
862
740
  expect(lastCheckArgs).toBeDefined();
863
741
  expect(lastCheckArgs!.policyContext).toEqual({
864
- principal: {
865
- kind: 'skill',
866
- id: 'editor-skill',
867
- version: 'sha256-v2-updated',
868
- },
869
742
  executionTarget: 'sandbox',
870
743
  });
871
744
  });
872
745
 
873
- test('executor creates principal-scoped rule on always_allow_high_risk with full context', async () => {
746
+ test('executor creates rule on always_allow_high_risk with full context', async () => {
874
747
  checkResultOverride = { decision: 'prompt', reason: 'High risk: always requires approval' };
875
748
  const spy = setupAddRuleSpy();
876
749
 
@@ -904,9 +777,6 @@ describe('ToolExecutor strict mode + high-risk integration (PR 25)', () => {
904
777
  expect(scope).toBe('everywhere');
905
778
  expect(decision).toBe('allow');
906
779
  expect(options.allowHighRisk).toBe(true);
907
- expect(options.principalKind).toBe('skill');
908
- expect(options.principalId).toBe('admin-skill');
909
- expect(options.principalVersion).toBe('sha256-admin-v2');
910
780
  expect(options.executionTarget).toBe('host');
911
781
  });
912
782
  });
@@ -1515,6 +1385,25 @@ describe('ToolExecutor forcePromptSideEffects enforcement', () => {
1515
1385
  expect(promptCalled).toBe(false);
1516
1386
  });
1517
1387
 
1388
+ // ── Workspace mode + forcePromptSideEffects interaction ──────────
1389
+
1390
+ test('workspace mode allow → prompt promotion still works for side-effect tools in private threads', async () => {
1391
+ // Simulate workspace mode returning 'allow' for a workspace-scoped file_write
1392
+ checkResultOverride = { decision: 'allow', reason: 'Workspace mode: workspace-scoped operation auto-allowed' };
1393
+
1394
+ const executor = new ToolExecutor(makeTrackingPrompter());
1395
+ const result = await executor.execute(
1396
+ 'file_write',
1397
+ { path: '/tmp/project/test.txt', content: 'data' },
1398
+ makeContext({ forcePromptSideEffects: true }),
1399
+ );
1400
+
1401
+ expect(result.isError).toBe(false);
1402
+ // file_write is a side-effect tool, so forcePromptSideEffects must promote
1403
+ // the workspace mode allow → prompt, requiring explicit user approval
1404
+ expect(promptCalled).toBe(true);
1405
+ });
1406
+
1518
1407
  // ── Action-aware mixed-action tools (PR fix5) ──────────
1519
1408
 
1520
1409
  test('account_manage create forces prompt in private thread', async () => {
@@ -1692,7 +1581,7 @@ describe('ToolExecutor persistentDecisionsAllowed contract', () => {
1692
1581
  prompt: async (
1693
1582
  _toolName: string, _input: Record<string, unknown>, _riskLevel: string,
1694
1583
  _allowlistOptions: any[], _scopeOptions: any[], _diff: any, _sandboxed: any,
1695
- _sessionId: any, _executionTarget: any, _principal: any, persistentDecisionsAllowed: any,
1584
+ _sessionId: any, _executionTarget: any, persistentDecisionsAllowed: any,
1696
1585
  ) => {
1697
1586
  capturedPersistent = persistentDecisionsAllowed;
1698
1587
  return { decision: 'allow' as const };
@@ -2053,3 +1942,48 @@ describe('ToolExecutor persistent-allow lifecycle', () => {
2053
1942
  expect(decision).toBe('allow');
2054
1943
  });
2055
1944
  });
1945
+
1946
+ describe('integration regressions — prompt payload (PR 11)', () => {
1947
+ beforeEach(() => {
1948
+ fakeToolResult = { content: 'ok', isError: false };
1949
+ checkResultOverride = undefined;
1950
+ checkFnOverride = undefined;
1951
+ getToolOverride = undefined;
1952
+ });
1953
+
1954
+ test('shell command prompt payload includes allowlist and scope options', async () => {
1955
+ checkResultOverride = { decision: 'prompt', reason: 'Medium risk: requires approval' };
1956
+
1957
+ let capturedAllowlist: any[] | undefined;
1958
+ let capturedScopes: any[] | undefined;
1959
+ const prompter = {
1960
+ prompt: async (
1961
+ _toolName: string, _input: Record<string, unknown>, _riskLevel: string,
1962
+ allowlistOptions: any[], scopeOptions: any[],
1963
+ ) => {
1964
+ capturedAllowlist = allowlistOptions;
1965
+ capturedScopes = scopeOptions;
1966
+ return { decision: 'allow' as const };
1967
+ },
1968
+ resolveConfirmation: () => {},
1969
+ updateSender: () => {},
1970
+ dispose: () => {},
1971
+ } as unknown as PermissionPrompter;
1972
+
1973
+ const executor = new ToolExecutor(prompter);
1974
+ await executor.execute('bash', { command: 'npm install' }, makeContext());
1975
+
1976
+ // Verify that the prompter received allowlist options
1977
+ expect(capturedAllowlist).toBeDefined();
1978
+ expect(capturedAllowlist!.length).toBeGreaterThan(0);
1979
+ // The mock returns [{label: 'exact', description: 'exact', pattern: 'exact'}]
1980
+ expect(capturedAllowlist![0]).toHaveProperty('pattern');
1981
+ expect(capturedAllowlist![0]).toHaveProperty('label');
1982
+ expect(capturedAllowlist![0]).toHaveProperty('description');
1983
+
1984
+ // Verify scope options are also passed
1985
+ expect(capturedScopes).toBeDefined();
1986
+ expect(capturedScopes!.length).toBeGreaterThan(0);
1987
+ expect(capturedScopes![0]).toHaveProperty('scope');
1988
+ });
1989
+ });