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
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/**
|
|
3
|
+
* Integration tests: ToolExecutor → real checker.js → real shell-identity → real tree-sitter parser.
|
|
4
|
+
*
|
|
5
|
+
* Unlike tool-executor.test.ts, this file does NOT mock ../permissions/checker.js,
|
|
6
|
+
* so generateAllowlistOptions and generateScopeOptions run through the actual
|
|
7
|
+
* implementation (buildShellAllowlistOptions → analyzeShellCommand → tree-sitter
|
|
8
|
+
* WASM parser). This validates the full e2e chain from executor to parser-derived
|
|
9
|
+
* allowlist options.
|
|
10
|
+
*/
|
|
11
|
+
import { describe, test, expect, beforeAll, mock } from 'bun:test';
|
|
12
|
+
import type { ToolContext } from '../tools/types.js';
|
|
13
|
+
import { PermissionPrompter } from '../permissions/prompter.js';
|
|
14
|
+
|
|
15
|
+
// ── Config mock ──────────────────────────────────────────────────────
|
|
16
|
+
const mockConfig = {
|
|
17
|
+
provider: 'anthropic',
|
|
18
|
+
model: 'test',
|
|
19
|
+
apiKeys: {},
|
|
20
|
+
maxTokens: 4096,
|
|
21
|
+
dataDir: '/tmp',
|
|
22
|
+
timeouts: { shellDefaultTimeoutSec: 120, shellMaxTimeoutSec: 600, permissionTimeoutSec: 300 },
|
|
23
|
+
sandbox: { enabled: false, backend: 'native' as const, docker: { image: 'vellum-sandbox:latest', cpus: 1, memoryMb: 512, pidsLimit: 256, network: 'none' as const } },
|
|
24
|
+
rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
|
|
25
|
+
secretDetection: { enabled: false, action: 'warn' as const, entropyThreshold: 4.0 },
|
|
26
|
+
permissions: { mode: 'strict' as const },
|
|
27
|
+
skills: { entries: {}, load: { extraDirs: [], watch: false, watchDebounceMs: 250 }, install: { nodeManager: 'npm' }, allowBundled: null },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
mock.module('../config/loader.js', () => ({
|
|
31
|
+
getConfig: () => mockConfig,
|
|
32
|
+
loadConfig: () => mockConfig,
|
|
33
|
+
invalidateConfigCache: () => {},
|
|
34
|
+
saveConfig: () => {},
|
|
35
|
+
loadRawConfig: () => ({}),
|
|
36
|
+
saveRawConfig: () => {},
|
|
37
|
+
getNestedValue: () => undefined,
|
|
38
|
+
setNestedValue: () => {},
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
// ── Logger mock (must accept name argument — checker.ts calls getLogger('checker')) ──
|
|
42
|
+
mock.module('../util/logger.js', () => ({
|
|
43
|
+
getLogger: (_name?: string) => new Proxy({} as Record<string, unknown>, {
|
|
44
|
+
get: () => () => {},
|
|
45
|
+
}),
|
|
46
|
+
isDebug: () => false,
|
|
47
|
+
truncateForLog: (value: string) => value,
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
// ── Tool registry mock — returns medium-risk tools so check() returns prompt ──
|
|
51
|
+
mock.module('../tools/registry.js', () => ({
|
|
52
|
+
getTool: (name: string) => ({
|
|
53
|
+
name,
|
|
54
|
+
description: 'test tool',
|
|
55
|
+
category: 'test',
|
|
56
|
+
defaultRiskLevel: 'medium',
|
|
57
|
+
getDefinition: () => ({}),
|
|
58
|
+
execute: async () => ({ content: 'ok', isError: false }),
|
|
59
|
+
}),
|
|
60
|
+
getAllTools: () => [],
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
// ── Trust store — no matching rules so check() returns prompt for medium-risk ──
|
|
64
|
+
mock.module('../permissions/trust-store.js', () => ({
|
|
65
|
+
findHighestPriorityRule: () => null,
|
|
66
|
+
addRule: () => ({ id: 'test-rule' }),
|
|
67
|
+
getRules: () => [],
|
|
68
|
+
removeRule: () => {},
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
// ── Tool usage store ──
|
|
72
|
+
mock.module('../memory/tool-usage-store.js', () => ({
|
|
73
|
+
recordToolInvocation: () => {},
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
// ── Path policy ──
|
|
77
|
+
mock.module('../tools/shared/filesystem/path-policy.js', () => ({
|
|
78
|
+
sandboxPolicy: () => ({ ok: false }),
|
|
79
|
+
hostPolicy: () => ({ ok: false }),
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
// ── Sandbox ──
|
|
83
|
+
mock.module('../tools/terminal/sandbox.js', () => ({
|
|
84
|
+
wrapCommand: () => ({ command: '', sandboxed: false }),
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
// ── Hooks manager ──
|
|
88
|
+
mock.module('../hooks/manager.js', () => ({
|
|
89
|
+
getHookManager: () => ({
|
|
90
|
+
trigger: async () => ({ blocked: false }),
|
|
91
|
+
}),
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
// ── Ephemeral permissions ──
|
|
95
|
+
mock.module('../tasks/ephemeral-permissions.js', () => ({
|
|
96
|
+
getTaskRunRules: () => [],
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
// ── Secret scanner ──
|
|
100
|
+
mock.module('../security/secret-scanner.js', () => ({
|
|
101
|
+
scanText: () => [],
|
|
102
|
+
redactSecrets: (text: string) => text,
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
// ── Redaction ──
|
|
106
|
+
mock.module('../security/redaction.js', () => ({
|
|
107
|
+
redactSensitiveFields: (input: Record<string, unknown>) => input,
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
// ── Token manager ──
|
|
111
|
+
mock.module('../security/token-manager.js', () => ({
|
|
112
|
+
TokenExpiredError: class TokenExpiredError extends Error {},
|
|
113
|
+
}));
|
|
114
|
+
|
|
115
|
+
// IMPORTANT: Do NOT mock ../permissions/checker.js — that's the whole point.
|
|
116
|
+
// Also do NOT mock ../permissions/shell-identity.js or ../tools/terminal/parser.js.
|
|
117
|
+
|
|
118
|
+
// ── Import executor AFTER mocks are set up ──
|
|
119
|
+
import { ToolExecutor } from '../tools/executor.js';
|
|
120
|
+
import { parse } from '../tools/terminal/parser.js';
|
|
121
|
+
|
|
122
|
+
function makeContext(overrides?: Partial<ToolContext>): ToolContext {
|
|
123
|
+
return {
|
|
124
|
+
workingDir: '/tmp/project',
|
|
125
|
+
sessionId: 'session-integration',
|
|
126
|
+
conversationId: 'conversation-integration',
|
|
127
|
+
...overrides,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Capturing prompter that intercepts the allowlist and scope options
|
|
133
|
+
* passed to the prompter by the executor, then allows the tool.
|
|
134
|
+
*/
|
|
135
|
+
function makeCapturingPrompter() {
|
|
136
|
+
let capturedAllowlist: any[] | undefined;
|
|
137
|
+
let capturedScopes: any[] | undefined;
|
|
138
|
+
|
|
139
|
+
const prompter = {
|
|
140
|
+
prompt: async (
|
|
141
|
+
_toolName: string,
|
|
142
|
+
_input: Record<string, unknown>,
|
|
143
|
+
_riskLevel: string,
|
|
144
|
+
allowlistOptions: any[],
|
|
145
|
+
scopeOptions: any[],
|
|
146
|
+
) => {
|
|
147
|
+
capturedAllowlist = allowlistOptions;
|
|
148
|
+
capturedScopes = scopeOptions;
|
|
149
|
+
return { decision: 'allow' as const };
|
|
150
|
+
},
|
|
151
|
+
resolveConfirmation: () => {},
|
|
152
|
+
updateSender: () => {},
|
|
153
|
+
dispose: () => {},
|
|
154
|
+
} as unknown as PermissionPrompter;
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
prompter,
|
|
158
|
+
getAllowlist: () => capturedAllowlist,
|
|
159
|
+
getScopes: () => capturedScopes,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── Warm up WASM parser before tests ─────────────────────────────────
|
|
164
|
+
beforeAll(async () => {
|
|
165
|
+
await parse('echo warmup');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('ToolExecutor → real shell allowlist integration', () => {
|
|
169
|
+
|
|
170
|
+
test('simple command produces parser-derived action keys', async () => {
|
|
171
|
+
const { prompter, getAllowlist, getScopes } = makeCapturingPrompter();
|
|
172
|
+
const executor = new ToolExecutor(prompter);
|
|
173
|
+
|
|
174
|
+
await executor.execute('bash', { command: 'npm install express' }, makeContext());
|
|
175
|
+
|
|
176
|
+
const allowlist = getAllowlist();
|
|
177
|
+
expect(allowlist).toBeDefined();
|
|
178
|
+
expect(allowlist!.length).toBeGreaterThan(1);
|
|
179
|
+
|
|
180
|
+
const patterns = allowlist!.map((o: any) => o.pattern);
|
|
181
|
+
|
|
182
|
+
// Should contain the exact command
|
|
183
|
+
expect(patterns).toContain('npm install express');
|
|
184
|
+
|
|
185
|
+
// Should contain action keys derived by the parser
|
|
186
|
+
expect(patterns).toContain('action:npm install');
|
|
187
|
+
expect(patterns).toContain('action:npm');
|
|
188
|
+
|
|
189
|
+
// Every option should have label, description, and pattern
|
|
190
|
+
for (const opt of allowlist!) {
|
|
191
|
+
expect(opt).toHaveProperty('label');
|
|
192
|
+
expect(opt).toHaveProperty('description');
|
|
193
|
+
expect(opt).toHaveProperty('pattern');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Verify scope options are also real (not the canned mock)
|
|
197
|
+
const scopes = getScopes();
|
|
198
|
+
expect(scopes).toBeDefined();
|
|
199
|
+
expect(scopes!.length).toBeGreaterThanOrEqual(2);
|
|
200
|
+
expect(scopes!.some((s: any) => s.scope === '/tmp/project')).toBe(true);
|
|
201
|
+
expect(scopes!.some((s: any) => s.scope === 'everywhere')).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('compound command produces only exact compound option (no action keys)', async () => {
|
|
205
|
+
const { prompter, getAllowlist } = makeCapturingPrompter();
|
|
206
|
+
const executor = new ToolExecutor(prompter);
|
|
207
|
+
|
|
208
|
+
await executor.execute('bash', { command: 'git add . && git commit -m "fix"' }, makeContext());
|
|
209
|
+
|
|
210
|
+
const allowlist = getAllowlist();
|
|
211
|
+
expect(allowlist).toBeDefined();
|
|
212
|
+
|
|
213
|
+
// Compound commands with two non-setup actions get only the exact compound option
|
|
214
|
+
expect(allowlist!.length).toBe(1);
|
|
215
|
+
expect(allowlist![0].pattern).toBe('git add . && git commit -m "fix"');
|
|
216
|
+
expect(allowlist![0].description).toContain('compound');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('setup prefix + action produces canonical primary command and action keys', async () => {
|
|
220
|
+
const { prompter, getAllowlist } = makeCapturingPrompter();
|
|
221
|
+
const executor = new ToolExecutor(prompter);
|
|
222
|
+
|
|
223
|
+
await executor.execute('bash', { command: 'cd /repo && gh pr view 123' }, makeContext());
|
|
224
|
+
|
|
225
|
+
const allowlist = getAllowlist();
|
|
226
|
+
expect(allowlist).toBeDefined();
|
|
227
|
+
expect(allowlist!.length).toBeGreaterThan(1);
|
|
228
|
+
|
|
229
|
+
const patterns = allowlist!.map((o: any) => o.pattern);
|
|
230
|
+
|
|
231
|
+
// Should contain the full original command as the exact option
|
|
232
|
+
expect(patterns).toContain('cd /repo && gh pr view 123');
|
|
233
|
+
|
|
234
|
+
// Should contain action keys: cd is a setup prefix, so gh is the primary action
|
|
235
|
+
expect(patterns).toContain('action:gh pr view');
|
|
236
|
+
expect(patterns).toContain('action:gh pr');
|
|
237
|
+
expect(patterns).toContain('action:gh');
|
|
238
|
+
|
|
239
|
+
// Should NOT contain action keys for the setup prefix (cd)
|
|
240
|
+
expect(patterns).not.toContain('action:cd');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('scope options include project directory and everywhere', async () => {
|
|
244
|
+
const { prompter, getScopes } = makeCapturingPrompter();
|
|
245
|
+
const executor = new ToolExecutor(prompter);
|
|
246
|
+
|
|
247
|
+
await executor.execute('bash', { command: 'echo hello' }, makeContext({ workingDir: '/Users/test/my-project' }));
|
|
248
|
+
|
|
249
|
+
const scopes = getScopes();
|
|
250
|
+
expect(scopes).toBeDefined();
|
|
251
|
+
expect(scopes!.length).toBeGreaterThanOrEqual(2);
|
|
252
|
+
|
|
253
|
+
const scopeValues = scopes!.map((s: any) => s.scope);
|
|
254
|
+
|
|
255
|
+
// Project-scoped option
|
|
256
|
+
expect(scopeValues).toContain('/Users/test/my-project');
|
|
257
|
+
// Parent directory option
|
|
258
|
+
expect(scopeValues).toContain('/Users/test');
|
|
259
|
+
// Global everywhere option
|
|
260
|
+
expect(scopeValues).toContain('everywhere');
|
|
261
|
+
|
|
262
|
+
// Every option has a label and scope
|
|
263
|
+
for (const opt of scopes!) {
|
|
264
|
+
expect(opt).toHaveProperty('label');
|
|
265
|
+
expect(opt).toHaveProperty('scope');
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('host_bash command also produces real parser-derived options', async () => {
|
|
270
|
+
const { prompter, getAllowlist } = makeCapturingPrompter();
|
|
271
|
+
const executor = new ToolExecutor(prompter);
|
|
272
|
+
|
|
273
|
+
await executor.execute('host_bash', { command: 'git status' }, makeContext());
|
|
274
|
+
|
|
275
|
+
const allowlist = getAllowlist();
|
|
276
|
+
expect(allowlist).toBeDefined();
|
|
277
|
+
expect(allowlist!.length).toBeGreaterThan(1);
|
|
278
|
+
|
|
279
|
+
const patterns = allowlist!.map((o: any) => o.pattern);
|
|
280
|
+
|
|
281
|
+
// Should contain exact command and action keys
|
|
282
|
+
expect(patterns).toContain('git status');
|
|
283
|
+
expect(patterns).toContain('action:git status');
|
|
284
|
+
expect(patterns).toContain('action:git');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test('pipeline command produces only exact option', async () => {
|
|
288
|
+
const { prompter, getAllowlist } = makeCapturingPrompter();
|
|
289
|
+
const executor = new ToolExecutor(prompter);
|
|
290
|
+
|
|
291
|
+
await executor.execute('bash', { command: 'cat file.txt | grep error' }, makeContext());
|
|
292
|
+
|
|
293
|
+
const allowlist = getAllowlist();
|
|
294
|
+
expect(allowlist).toBeDefined();
|
|
295
|
+
|
|
296
|
+
// Pipelines are complex commands — only exact option, no action keys
|
|
297
|
+
expect(allowlist!.length).toBe(1);
|
|
298
|
+
expect(allowlist![0].pattern).toBe('cat file.txt | grep error');
|
|
299
|
+
expect(allowlist![0].description).toContain('compound');
|
|
300
|
+
});
|
|
301
|
+
});
|