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.
- package/README.md +32 -0
- package/bun.lock +2 -2
- package/docs/skills.md +4 -4
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +213 -3
- package/src/__tests__/app-git-history.test.ts +176 -0
- package/src/__tests__/app-git-service.test.ts +169 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +315 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +8 -8
- package/src/__tests__/browser-skill-endstate.test.ts +6 -6
- package/src/__tests__/call-bridge.test.ts +105 -13
- package/src/__tests__/call-domain.test.ts +163 -0
- package/src/__tests__/call-orchestrator.test.ts +113 -0
- package/src/__tests__/call-routes-http.test.ts +246 -6
- package/src/__tests__/channel-approval-routes.test.ts +438 -0
- package/src/__tests__/channel-approval.test.ts +266 -0
- package/src/__tests__/channel-approvals.test.ts +393 -0
- package/src/__tests__/channel-delivery-store.test.ts +447 -0
- package/src/__tests__/checker.test.ts +607 -1048
- package/src/__tests__/cli.test.ts +1 -56
- package/src/__tests__/config-schema.test.ts +137 -18
- package/src/__tests__/conflict-intent-tokenization.test.ts +141 -0
- package/src/__tests__/conflict-policy.test.ts +121 -0
- package/src/__tests__/conflict-store.test.ts +2 -0
- package/src/__tests__/contacts-tools.test.ts +3 -3
- package/src/__tests__/contradiction-checker.test.ts +99 -1
- package/src/__tests__/credential-security-invariants.test.ts +22 -6
- package/src/__tests__/credential-vault-unit.test.ts +780 -0
- package/src/__tests__/elevenlabs-client.test.ts +62 -0
- package/src/__tests__/ephemeral-permissions.test.ts +73 -23
- package/src/__tests__/filesystem-tools.test.ts +579 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +114 -4
- package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +202 -0
- package/src/__tests__/handlers-cu-observation-blob.test.ts +2 -1
- package/src/__tests__/handlers-ipc-blob-probe.test.ts +2 -1
- package/src/__tests__/handlers-slack-config.test.ts +2 -1
- package/src/__tests__/handlers-telegram-config.test.ts +855 -0
- package/src/__tests__/handlers-twitter-config.test.ts +141 -1
- package/src/__tests__/hooks-runner.test.ts +6 -2
- package/src/__tests__/host-file-edit-tool.test.ts +124 -0
- package/src/__tests__/host-file-read-tool.test.ts +62 -0
- package/src/__tests__/host-file-write-tool.test.ts +59 -0
- package/src/__tests__/host-shell-tool.test.ts +251 -0
- package/src/__tests__/ingress-reconcile.test.ts +581 -0
- package/src/__tests__/ipc-snapshot.test.ts +100 -41
- package/src/__tests__/ipc-validate.test.ts +50 -0
- package/src/__tests__/key-migration.test.ts +23 -0
- package/src/__tests__/memory-regressions.test.ts +99 -0
- package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
- package/src/__tests__/oauth-callback-registry.test.ts +11 -4
- package/src/__tests__/playbook-execution.test.ts +502 -0
- package/src/__tests__/playbook-tools.test.ts +4 -6
- package/src/__tests__/public-ingress-urls.test.ts +34 -0
- package/src/__tests__/qdrant-manager.test.ts +267 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +97 -0
- package/src/__tests__/recurrence-engine.test.ts +9 -0
- package/src/__tests__/recurrence-types.test.ts +8 -0
- package/src/__tests__/registry.test.ts +1 -1
- package/src/__tests__/runtime-runs.test.ts +1 -25
- package/src/__tests__/schedule-store.test.ts +16 -14
- package/src/__tests__/schedule-tools.test.ts +83 -0
- package/src/__tests__/scheduler-recurrence.test.ts +111 -10
- package/src/__tests__/secret-allowlist.test.ts +18 -17
- package/src/__tests__/secret-ingress-handler.test.ts +11 -0
- package/src/__tests__/secret-scanner.test.ts +43 -0
- package/src/__tests__/session-conflict-gate.test.ts +442 -6
- package/src/__tests__/session-init.benchmark.test.ts +3 -0
- package/src/__tests__/session-process-bridge.test.ts +242 -0
- package/src/__tests__/session-skill-tools.test.ts +1 -1
- package/src/__tests__/shell-identity.test.ts +256 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +11 -1
- package/src/__tests__/subagent-tools.test.ts +637 -54
- package/src/__tests__/task-management-tools.test.ts +936 -0
- package/src/__tests__/task-runner.test.ts +2 -2
- package/src/__tests__/terminal-tools.test.ts +840 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +301 -0
- package/src/__tests__/tool-executor.test.ts +85 -151
- package/src/__tests__/tool-permission-simulate-handler.test.ts +336 -0
- package/src/__tests__/trust-store.test.ts +27 -453
- package/src/__tests__/twilio-provider.test.ts +153 -3
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +375 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +4 -4
- package/src/__tests__/twilio-routes.test.ts +17 -262
- package/src/__tests__/twitter-auth-handler.test.ts +2 -1
- package/src/__tests__/twitter-cli-error-shaping.test.ts +208 -0
- package/src/__tests__/twitter-cli-routing.test.ts +252 -0
- package/src/__tests__/twitter-oauth-client.test.ts +209 -0
- package/src/__tests__/workspace-policy.test.ts +213 -0
- package/src/calls/call-bridge.ts +92 -19
- package/src/calls/call-domain.ts +157 -5
- package/src/calls/call-orchestrator.ts +93 -7
- package/src/calls/call-store.ts +6 -0
- package/src/calls/elevenlabs-client.ts +8 -0
- package/src/calls/elevenlabs-config.ts +7 -5
- package/src/calls/twilio-provider.ts +91 -0
- package/src/calls/twilio-routes.ts +32 -37
- package/src/calls/types.ts +3 -1
- package/src/calls/voice-quality.ts +29 -7
- package/src/cli/twitter.ts +200 -21
- package/src/cli.ts +1 -20
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +52 -4
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +55 -4
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +61 -4
- package/src/config/bundled-skills/messaging/SKILL.md +17 -2
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +4 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +142 -34
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +95 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +51 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +73 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +110 -6
- package/src/config/bundled-skills/public-ingress/SKILL.md +22 -5
- package/src/config/bundled-skills/twitter/SKILL.md +103 -17
- package/src/config/defaults.ts +10 -4
- package/src/config/schema.ts +80 -21
- package/src/config/types.ts +1 -0
- package/src/config/vellum-skills/telegram-setup/SKILL.md +56 -61
- package/src/daemon/assistant-attachments.ts +4 -2
- package/src/daemon/handlers/apps.ts +69 -0
- package/src/daemon/handlers/config.ts +543 -24
- package/src/daemon/handlers/index.ts +1 -0
- package/src/daemon/handlers/sessions.ts +22 -6
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +5 -20
- package/src/daemon/ipc-contract-inventory.json +28 -0
- package/src/daemon/ipc-contract.ts +168 -10
- package/src/daemon/ipc-validate.ts +17 -0
- package/src/daemon/lifecycle.ts +2 -0
- package/src/daemon/server.ts +78 -72
- package/src/daemon/session-attachments.ts +1 -1
- package/src/daemon/session-conflict-gate.ts +62 -6
- package/src/daemon/session-notifiers.ts +1 -1
- package/src/daemon/session-process.ts +62 -3
- package/src/daemon/session-tool-setup.ts +1 -2
- package/src/daemon/tls-certs.ts +189 -0
- package/src/daemon/video-thumbnail.ts +5 -3
- package/src/hooks/manager.ts +5 -9
- package/src/memory/app-git-service.ts +295 -0
- package/src/memory/app-store.ts +21 -0
- package/src/memory/conflict-intent.ts +47 -4
- package/src/memory/conflict-policy.ts +73 -0
- package/src/memory/conflict-store.ts +9 -1
- package/src/memory/contradiction-checker.ts +28 -0
- package/src/memory/conversation-key-store.ts +15 -0
- package/src/memory/db.ts +81 -0
- package/src/memory/embedding-local.ts +3 -13
- package/src/memory/external-conversation-store.ts +234 -0
- package/src/memory/job-handlers/conflict.ts +22 -2
- package/src/memory/jobs-worker.ts +67 -28
- package/src/memory/runs-store.ts +54 -7
- package/src/memory/schema.ts +20 -0
- package/src/messaging/provider.ts +9 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +162 -0
- package/src/messaging/providers/telegram-bot/client.ts +104 -0
- package/src/messaging/providers/telegram-bot/types.ts +15 -0
- package/src/messaging/registry.ts +1 -0
- package/src/permissions/checker.ts +48 -44
- package/src/permissions/prompter.ts +0 -4
- package/src/permissions/shell-identity.ts +227 -0
- package/src/permissions/trust-store.ts +76 -53
- package/src/permissions/types.ts +0 -19
- package/src/permissions/workspace-policy.ts +114 -0
- package/src/providers/retry.ts +12 -37
- package/src/runtime/assistant-event-hub.ts +41 -4
- package/src/runtime/channel-approval-parser.ts +60 -0
- package/src/runtime/channel-approval-types.ts +71 -0
- package/src/runtime/channel-approvals.ts +145 -0
- package/src/runtime/gateway-client.ts +16 -0
- package/src/runtime/http-server.ts +29 -9
- package/src/runtime/routes/call-routes.ts +52 -2
- package/src/runtime/routes/channel-routes.ts +296 -16
- package/src/runtime/routes/events-routes.ts +97 -28
- package/src/runtime/routes/run-routes.ts +2 -7
- package/src/runtime/run-orchestrator.ts +0 -3
- package/src/schedule/recurrence-engine.ts +26 -2
- package/src/schedule/recurrence-types.ts +1 -1
- package/src/schedule/schedule-store.ts +12 -3
- package/src/security/secret-scanner.ts +7 -0
- package/src/tasks/ephemeral-permissions.ts +0 -2
- package/src/tasks/task-scheduler.ts +2 -1
- package/src/tools/calls/call-start.ts +8 -0
- package/src/tools/execution-target.ts +21 -0
- package/src/tools/execution-timeout.ts +49 -0
- package/src/tools/executor.ts +6 -135
- package/src/tools/network/web-search.ts +9 -32
- package/src/tools/policy-context.ts +29 -0
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/terminal/parser.ts +16 -18
- package/src/tools/types.ts +4 -11
- package/src/twitter/oauth-client.ts +102 -0
- package/src/twitter/router.ts +101 -0
- package/src/util/debounce.ts +88 -0
- package/src/util/network-info.ts +47 -0
- package/src/util/platform.ts +29 -4
- package/src/util/promise-guard.ts +37 -0
- package/src/util/retry.ts +98 -0
- package/src/util/truncate.ts +1 -1
- package/src/workspace/git-service.ts +129 -112
- package/src/tools/contacts/contact-merge.ts +0 -55
- package/src/tools/contacts/contact-search.ts +0 -58
- package/src/tools/contacts/contact-upsert.ts +0 -64
- package/src/tools/playbooks/index.ts +0 -4
- package/src/tools/playbooks/playbook-create.ts +0 -96
- package/src/tools/playbooks/playbook-delete.ts +0 -52
- package/src/tools/playbooks/playbook-list.ts +0 -74
- 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
|
|
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
|
|
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
|
|
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
|
|
275
|
+
description: 'skill without target',
|
|
286
276
|
category: 'skill',
|
|
287
277
|
defaultRiskLevel: RiskLevel.Low,
|
|
288
278
|
origin: 'skill' as const,
|
|
289
|
-
ownerSkillId: 'no-
|
|
290
|
-
//
|
|
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('
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 +
|
|
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
|
-
//
|
|
696
|
-
expect(options.
|
|
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
|
|
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.
|
|
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
|
|
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,
|
|
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
|
+
});
|