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
|
@@ -11,8 +11,24 @@ export interface ScheduleSpec {
|
|
|
11
11
|
const SUPPORTED_RRULE_PREFIXES = ['DTSTART', 'RRULE:', 'RDATE', 'EXDATE', 'EXRULE'];
|
|
12
12
|
|
|
13
13
|
function normalizeRruleExpression(expression: string): string {
|
|
14
|
-
// Handle escaped newlines from JSON transport
|
|
15
|
-
|
|
14
|
+
// Handle escaped newlines from JSON transport, then uppercase property name
|
|
15
|
+
// prefixes (before the first ';' or ':') on each line so rrulestr() receives
|
|
16
|
+
// the canonical uppercase form regardless of what the caller provided. We
|
|
17
|
+
// stop at the earliest delimiter to preserve case-sensitive parameter values
|
|
18
|
+
// such as timezone names in DTSTART;TZID=America/New_York:...
|
|
19
|
+
return expression
|
|
20
|
+
.replace(/\\n/g, '\n')
|
|
21
|
+
.trim()
|
|
22
|
+
.split(/\r?\n/)
|
|
23
|
+
.map(line => {
|
|
24
|
+
const colonIdx = line.indexOf(':');
|
|
25
|
+
const semiIdx = line.indexOf(';');
|
|
26
|
+
if (colonIdx === -1 && semiIdx === -1) return line;
|
|
27
|
+
// Uppercase only the property name (before the first ';' or ':')
|
|
28
|
+
const nameEnd = semiIdx !== -1 && (colonIdx === -1 || semiIdx < colonIdx) ? semiIdx : colonIdx;
|
|
29
|
+
return line.slice(0, nameEnd).toUpperCase() + line.slice(nameEnd);
|
|
30
|
+
})
|
|
31
|
+
.join('\n');
|
|
16
32
|
}
|
|
17
33
|
|
|
18
34
|
function parseRruleLines(expression: string): string[] {
|
|
@@ -129,6 +145,14 @@ export function computeNextRunAt(spec: ScheduleSpec, nowMs?: number): number {
|
|
|
129
145
|
: rrulestr(normalized, { tzid });
|
|
130
146
|
const next = parsed.after(new Date(now));
|
|
131
147
|
if (!next) {
|
|
148
|
+
// When after() (exclusive) returns null the rule may still have a
|
|
149
|
+
// terminal occurrence that lands exactly on `now` — e.g. COUNT=1 or the
|
|
150
|
+
// final UNTIL instance. Treat that as "due right now" so claimDueSchedules
|
|
151
|
+
// doesn't silently skip the last run.
|
|
152
|
+
const exactMatch = parsed.before(new Date(now), true);
|
|
153
|
+
if (exactMatch && exactMatch.getTime() === now) {
|
|
154
|
+
return now;
|
|
155
|
+
}
|
|
132
156
|
throw new Error(`RRULE expression has no upcoming runs after ${new Date(now).toISOString()}`);
|
|
133
157
|
}
|
|
134
158
|
return next.getTime();
|
|
@@ -60,7 +60,7 @@ export function normalizeScheduleSyntax(input: {
|
|
|
60
60
|
|
|
61
61
|
// Legacy cron_expression fallback
|
|
62
62
|
if (input.legacyCronExpression) {
|
|
63
|
-
return { syntax: 'cron', expression: input.legacyCronExpression };
|
|
63
|
+
return { syntax: input.syntax ?? 'cron', expression: input.legacyCronExpression };
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
return null;
|
|
@@ -4,8 +4,11 @@ import { Cron } from 'croner';
|
|
|
4
4
|
import { getDb } from '../memory/db.js';
|
|
5
5
|
import { scheduleJobs, scheduleRuns } from '../memory/schema.js';
|
|
6
6
|
import { computeNextRunAt as computeNextRunAtEngine, isValidScheduleExpression } from './recurrence-engine.js';
|
|
7
|
+
import { getLogger } from '../util/logger.js';
|
|
7
8
|
import type { ScheduleSyntax } from './recurrence-types.js';
|
|
8
9
|
|
|
10
|
+
const logger = getLogger('schedule-store');
|
|
11
|
+
|
|
9
12
|
export interface ScheduleJob {
|
|
10
13
|
id: string;
|
|
11
14
|
name: string;
|
|
@@ -216,9 +219,15 @@ export function claimDueSchedules(now: number): ScheduleJob[] {
|
|
|
216
219
|
expression: row.cronExpression,
|
|
217
220
|
timezone: row.timezone,
|
|
218
221
|
});
|
|
219
|
-
} catch {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
+
} catch (err) {
|
|
223
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
224
|
+
if (!msg.includes('no upcoming runs')) {
|
|
225
|
+
// Log but don't abort — one bad schedule shouldn't block everything
|
|
226
|
+
logger.warn({ err, scheduleId: row.id }, 'Failed to compute next run for schedule');
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
// Expired schedules fire their final pending due run then auto-disable,
|
|
230
|
+
// ensuring no due run is silently dropped.
|
|
222
231
|
newNextRunAt = null;
|
|
223
232
|
exhausted = true;
|
|
224
233
|
}
|
|
@@ -81,6 +81,13 @@ const PATTERNS: SecretPattern[] = [
|
|
|
81
81
|
regex: /(https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+)/g,
|
|
82
82
|
},
|
|
83
83
|
|
|
84
|
+
// -- Telegram --
|
|
85
|
+
{
|
|
86
|
+
type: 'Telegram Bot Token',
|
|
87
|
+
// Format: <bot_id>:<secret> where bot_id is 8-10 digits and secret is 35 alphanumeric/dash/underscore chars
|
|
88
|
+
regex: /\b([0-9]{8,10}:[A-Za-z0-9_-]{35})(?=[^A-Za-z0-9_-]|$)/g,
|
|
89
|
+
},
|
|
90
|
+
|
|
84
91
|
// -- Anthropic --
|
|
85
92
|
{
|
|
86
93
|
type: 'Anthropic API Key',
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createSchedule } from '../schedule/schedule-store.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Create a
|
|
4
|
+
* Create a cron schedule that runs a task on a recurring cron expression.
|
|
5
|
+
* RRULE syntax is supported at the store layer but this helper currently defaults to cron.
|
|
5
6
|
* The scheduler detects the `run_task:<taskId>` message format
|
|
6
7
|
* and delegates to runTask() instead of processMessage().
|
|
7
8
|
*/
|
|
@@ -24,6 +24,11 @@ const definition: ToolDefinition = {
|
|
|
24
24
|
type: 'string',
|
|
25
25
|
description: 'Additional context for the conversation',
|
|
26
26
|
},
|
|
27
|
+
caller_identity_mode: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
enum: ['assistant_number', 'user_number'],
|
|
30
|
+
description: 'Which phone number to use as the caller ID. assistant_number uses the AI assistant\'s Twilio number; user_number uses the user\'s verified personal number.',
|
|
31
|
+
},
|
|
27
32
|
},
|
|
28
33
|
required: ['phone_number', 'task'],
|
|
29
34
|
},
|
|
@@ -49,6 +54,7 @@ class CallStartTool implements Tool {
|
|
|
49
54
|
task: input.task as string,
|
|
50
55
|
context: input.context as string | undefined,
|
|
51
56
|
conversationId: context.conversationId,
|
|
57
|
+
callerIdentityMode: input.caller_identity_mode as 'assistant_number' | 'user_number' | undefined,
|
|
52
58
|
});
|
|
53
59
|
|
|
54
60
|
if (!result.ok) {
|
|
@@ -61,6 +67,8 @@ class CallStartTool implements Tool {
|
|
|
61
67
|
` Call Session ID: ${result.session.id}`,
|
|
62
68
|
` Call SID: ${result.callSid}`,
|
|
63
69
|
` To: ${result.session.toNumber}`,
|
|
70
|
+
` From: ${result.session.fromNumber}`,
|
|
71
|
+
` Caller Identity Mode: ${result.callerIdentityMode}`,
|
|
64
72
|
` Status: initiated`,
|
|
65
73
|
'',
|
|
66
74
|
'The AI voice assistant is now placing the call. Use call_status to check progress.',
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ExecutionTarget } from './types.js';
|
|
2
|
+
import { getTool } from './registry.js';
|
|
3
|
+
|
|
4
|
+
export function resolveExecutionTarget(toolName: string): ExecutionTarget {
|
|
5
|
+
const tool = getTool(toolName);
|
|
6
|
+
// Manifest-declared execution target is authoritative — check it first so
|
|
7
|
+
// skill tools with host_/computer_use_ prefixes aren't mis-classified.
|
|
8
|
+
if (tool?.executionTarget) {
|
|
9
|
+
return tool.executionTarget;
|
|
10
|
+
}
|
|
11
|
+
// Check the tool's executionMode metadata — proxy tools run on the connected
|
|
12
|
+
// client (host), not inside the sandbox.
|
|
13
|
+
if (tool?.executionMode === 'proxy') {
|
|
14
|
+
return 'host';
|
|
15
|
+
}
|
|
16
|
+
// Prefix heuristics for core tools that don't declare an explicit target.
|
|
17
|
+
if (toolName.startsWith('host_') || toolName.startsWith('computer_use_')) {
|
|
18
|
+
return 'host';
|
|
19
|
+
}
|
|
20
|
+
return 'sandbox';
|
|
21
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ToolExecutionResult } from './types.js';
|
|
2
|
+
|
|
3
|
+
const TIMEOUT_SENTINEL = Symbol('tool-timeout');
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_TOOL_TIMEOUT_SEC = 120;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Convert a config-provided seconds value to a safe milliseconds value,
|
|
9
|
+
* falling back to the default if the input is NaN, non-finite, zero, or negative.
|
|
10
|
+
*/
|
|
11
|
+
export function safeTimeoutMs(sec: unknown): number {
|
|
12
|
+
const n = Number(sec);
|
|
13
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
14
|
+
return DEFAULT_TOOL_TIMEOUT_SEC * 1000;
|
|
15
|
+
}
|
|
16
|
+
return n * 1000;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Race a tool execution promise against a timeout. Returns a timeout error
|
|
21
|
+
* result instead of throwing so the agent loop can continue gracefully.
|
|
22
|
+
*/
|
|
23
|
+
export async function executeWithTimeout(
|
|
24
|
+
promise: Promise<ToolExecutionResult>,
|
|
25
|
+
timeoutMs: number,
|
|
26
|
+
toolName: string,
|
|
27
|
+
): Promise<ToolExecutionResult> {
|
|
28
|
+
// Guard against NaN/invalid values that would cause setTimeout to fire immediately
|
|
29
|
+
const safeMs = Number.isFinite(timeoutMs) && timeoutMs > 0
|
|
30
|
+
? timeoutMs
|
|
31
|
+
: DEFAULT_TOOL_TIMEOUT_SEC * 1000;
|
|
32
|
+
let timeoutHandle: ReturnType<typeof setTimeout>;
|
|
33
|
+
const timeoutPromise = new Promise<typeof TIMEOUT_SENTINEL>((resolve) => {
|
|
34
|
+
timeoutHandle = setTimeout(() => resolve(TIMEOUT_SENTINEL), safeMs);
|
|
35
|
+
});
|
|
36
|
+
try {
|
|
37
|
+
const result = await Promise.race([promise, timeoutPromise]);
|
|
38
|
+
if (result === TIMEOUT_SENTINEL) {
|
|
39
|
+
const sec = Math.round(safeMs / 1000);
|
|
40
|
+
return {
|
|
41
|
+
content: `Tool "${toolName}" timed out after ${sec}s. The operation may still be running in the background. Consider increasing timeouts.toolExecutionTimeoutSec in the config.`,
|
|
42
|
+
isError: true,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
} finally {
|
|
47
|
+
clearTimeout(timeoutHandle!);
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/tools/executor.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
2
2
|
import { getTool, getAllTools } from './registry.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { ToolContext, ToolExecutionResult, ToolLifecycleEvent } from './types.js';
|
|
4
4
|
import { RiskLevel } from '../permissions/types.js';
|
|
5
|
-
import type { PolicyContext } from '../permissions/types.js';
|
|
6
5
|
import { check, classifyRisk, generateAllowlistOptions, generateScopeOptions } from '../permissions/checker.js';
|
|
7
6
|
import { addRule } from '../permissions/trust-store.js';
|
|
8
7
|
import { PermissionPrompter } from '../permissions/prompter.js';
|
|
@@ -18,6 +17,9 @@ import { scanText, redactSecrets } from '../security/secret-scanner.js';
|
|
|
18
17
|
import { redactSensitiveFields } from '../security/redaction.js';
|
|
19
18
|
import { getHookManager } from '../hooks/manager.js';
|
|
20
19
|
import { getTaskRunRules } from '../tasks/ephemeral-permissions.js';
|
|
20
|
+
import { safeTimeoutMs, executeWithTimeout } from './execution-timeout.js';
|
|
21
|
+
import { buildPolicyContext } from './policy-context.js';
|
|
22
|
+
import { resolveExecutionTarget } from './execution-target.js';
|
|
21
23
|
|
|
22
24
|
const log = getLogger('tool-executor');
|
|
23
25
|
|
|
@@ -196,7 +198,7 @@ export class ToolExecutor {
|
|
|
196
198
|
}
|
|
197
199
|
|
|
198
200
|
// Need user approval
|
|
199
|
-
const allowlistOptions = generateAllowlistOptions(name, input);
|
|
201
|
+
const allowlistOptions = await generateAllowlistOptions(name, input);
|
|
200
202
|
const scopeOptions = generateScopeOptions(context.workingDir, name);
|
|
201
203
|
|
|
202
204
|
// Compute preview diff for file tools so the user sees what will change
|
|
@@ -253,11 +255,6 @@ export class ToolExecutor {
|
|
|
253
255
|
sandboxed,
|
|
254
256
|
context.conversationId,
|
|
255
257
|
executionTarget,
|
|
256
|
-
policyContext?.principal ? {
|
|
257
|
-
kind: policyContext.principal.kind,
|
|
258
|
-
id: policyContext.principal.id,
|
|
259
|
-
version: policyContext.principal.version,
|
|
260
|
-
} : undefined,
|
|
261
258
|
persistentDecisionsAllowed,
|
|
262
259
|
);
|
|
263
260
|
|
|
@@ -325,9 +322,6 @@ export class ToolExecutor {
|
|
|
325
322
|
) {
|
|
326
323
|
const ruleOptions: {
|
|
327
324
|
allowHighRisk?: boolean;
|
|
328
|
-
principalKind?: string;
|
|
329
|
-
principalId?: string;
|
|
330
|
-
principalVersion?: string;
|
|
331
325
|
executionTarget?: string;
|
|
332
326
|
} = {};
|
|
333
327
|
|
|
@@ -335,19 +329,6 @@ export class ToolExecutor {
|
|
|
335
329
|
ruleOptions.allowHighRisk = true;
|
|
336
330
|
}
|
|
337
331
|
|
|
338
|
-
// Capture the principal context from the tool so the saved rule
|
|
339
|
-
// is scoped to the specific skill/version that was approved.
|
|
340
|
-
if (policyContext?.principal) {
|
|
341
|
-
if (policyContext.principal.kind != null) {
|
|
342
|
-
ruleOptions.principalKind = policyContext.principal.kind;
|
|
343
|
-
}
|
|
344
|
-
if (policyContext.principal.id != null) {
|
|
345
|
-
ruleOptions.principalId = policyContext.principal.id;
|
|
346
|
-
}
|
|
347
|
-
if (policyContext.principal.version != null) {
|
|
348
|
-
ruleOptions.principalVersion = policyContext.principal.version;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
332
|
if (policyContext?.executionTarget != null) {
|
|
352
333
|
ruleOptions.executionTarget = policyContext.executionTarget;
|
|
353
334
|
}
|
|
@@ -393,11 +374,7 @@ export class ToolExecutor {
|
|
|
393
374
|
const rawTimeoutSec = getConfig().timeouts.toolExecutionTimeoutSec;
|
|
394
375
|
const toolTimeoutMs = safeTimeoutMs(rawTimeoutSec);
|
|
395
376
|
|
|
396
|
-
|
|
397
|
-
// forward it through sub-tool confirmation requests.
|
|
398
|
-
const execContext = policyContext?.principal
|
|
399
|
-
? { ...context, principal: policyContext.principal }
|
|
400
|
-
: context;
|
|
377
|
+
const execContext = context;
|
|
401
378
|
|
|
402
379
|
if (tool.executionMode === 'proxy') {
|
|
403
380
|
if (!context.proxyToolResolver) {
|
|
@@ -589,7 +566,6 @@ export class ToolExecutor {
|
|
|
589
566
|
undefined, // not sandboxed
|
|
590
567
|
context.conversationId,
|
|
591
568
|
executionTarget,
|
|
592
|
-
undefined, // no principal
|
|
593
569
|
false, // no persistent decisions
|
|
594
570
|
);
|
|
595
571
|
|
|
@@ -756,111 +732,6 @@ export function isSideEffectTool(toolName: string, input?: Record<string, unknow
|
|
|
756
732
|
return false;
|
|
757
733
|
}
|
|
758
734
|
|
|
759
|
-
const TIMEOUT_SENTINEL = Symbol('tool-timeout');
|
|
760
|
-
|
|
761
|
-
const DEFAULT_TOOL_TIMEOUT_SEC = 120;
|
|
762
|
-
|
|
763
|
-
/**
|
|
764
|
-
* Convert a config-provided seconds value to a safe milliseconds value,
|
|
765
|
-
* falling back to the default if the input is NaN, non-finite, zero, or negative.
|
|
766
|
-
*/
|
|
767
|
-
function safeTimeoutMs(sec: unknown): number {
|
|
768
|
-
const n = Number(sec);
|
|
769
|
-
if (!Number.isFinite(n) || n <= 0) {
|
|
770
|
-
return DEFAULT_TOOL_TIMEOUT_SEC * 1000;
|
|
771
|
-
}
|
|
772
|
-
return n * 1000;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
/**
|
|
776
|
-
* Race a tool execution promise against a timeout. Returns a timeout error
|
|
777
|
-
* result instead of throwing so the agent loop can continue gracefully.
|
|
778
|
-
*/
|
|
779
|
-
async function executeWithTimeout(
|
|
780
|
-
promise: Promise<ToolExecutionResult>,
|
|
781
|
-
timeoutMs: number,
|
|
782
|
-
toolName: string,
|
|
783
|
-
): Promise<ToolExecutionResult> {
|
|
784
|
-
// Guard against NaN/invalid values that would cause setTimeout to fire immediately
|
|
785
|
-
const safeMs = Number.isFinite(timeoutMs) && timeoutMs > 0
|
|
786
|
-
? timeoutMs
|
|
787
|
-
: DEFAULT_TOOL_TIMEOUT_SEC * 1000;
|
|
788
|
-
let timeoutHandle: ReturnType<typeof setTimeout>;
|
|
789
|
-
const timeoutPromise = new Promise<typeof TIMEOUT_SENTINEL>((resolve) => {
|
|
790
|
-
timeoutHandle = setTimeout(() => resolve(TIMEOUT_SENTINEL), safeMs);
|
|
791
|
-
});
|
|
792
|
-
try {
|
|
793
|
-
const result = await Promise.race([promise, timeoutPromise]);
|
|
794
|
-
if (result === TIMEOUT_SENTINEL) {
|
|
795
|
-
const sec = Math.round(safeMs / 1000);
|
|
796
|
-
return {
|
|
797
|
-
content: `Tool "${toolName}" timed out after ${sec}s. The operation may still be running in the background. Consider increasing timeouts.toolExecutionTimeoutSec in the config.`,
|
|
798
|
-
isError: true,
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
return result;
|
|
802
|
-
} finally {
|
|
803
|
-
clearTimeout(timeoutHandle!);
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
/**
|
|
808
|
-
* Build a PolicyContext from tool metadata and execution context. Skill-origin
|
|
809
|
-
* tools carry a principal identifying the owning skill. When executing within
|
|
810
|
-
* a task run, ephemeral permission rules are included so pre-approved tools
|
|
811
|
-
* are auto-allowed without prompting.
|
|
812
|
-
*/
|
|
813
|
-
function buildPolicyContext(tool: Tool, context?: ToolContext): PolicyContext | undefined {
|
|
814
|
-
const ephemeralRules = context?.taskRunId
|
|
815
|
-
? getTaskRunRules(context.taskRunId)
|
|
816
|
-
: undefined;
|
|
817
|
-
|
|
818
|
-
if (tool.origin === 'skill') {
|
|
819
|
-
return {
|
|
820
|
-
principal: {
|
|
821
|
-
kind: 'skill',
|
|
822
|
-
id: tool.ownerSkillId,
|
|
823
|
-
version: tool.ownerSkillVersionHash,
|
|
824
|
-
},
|
|
825
|
-
executionTarget: tool.executionTarget,
|
|
826
|
-
ephemeralRules: ephemeralRules?.length ? ephemeralRules : undefined,
|
|
827
|
-
};
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
// For non-skill tools in a task run, create a context with task principal
|
|
831
|
-
// and ephemeral rules so pre-approved tools are honored.
|
|
832
|
-
if (context?.taskRunId && ephemeralRules?.length) {
|
|
833
|
-
return {
|
|
834
|
-
principal: {
|
|
835
|
-
kind: 'task',
|
|
836
|
-
id: context.taskRunId,
|
|
837
|
-
},
|
|
838
|
-
ephemeralRules,
|
|
839
|
-
};
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
return undefined;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
function resolveExecutionTarget(toolName: string): ExecutionTarget {
|
|
846
|
-
const tool = getTool(toolName);
|
|
847
|
-
// Manifest-declared execution target is authoritative — check it first so
|
|
848
|
-
// skill tools with host_/computer_use_ prefixes aren't mis-classified.
|
|
849
|
-
if (tool?.executionTarget) {
|
|
850
|
-
return tool.executionTarget;
|
|
851
|
-
}
|
|
852
|
-
// Check the tool's executionMode metadata — proxy tools run on the connected
|
|
853
|
-
// client (host), not inside the sandbox.
|
|
854
|
-
if (tool?.executionMode === 'proxy') {
|
|
855
|
-
return 'host';
|
|
856
|
-
}
|
|
857
|
-
// Prefix heuristics for core tools that don't declare an explicit target.
|
|
858
|
-
if (toolName.startsWith('host_') || toolName.startsWith('computer_use_')) {
|
|
859
|
-
return 'host';
|
|
860
|
-
}
|
|
861
|
-
return 'sandbox';
|
|
862
|
-
}
|
|
863
|
-
|
|
864
735
|
/**
|
|
865
736
|
* Sanitize tool inputs before they are emitted in lifecycle events and hooks.
|
|
866
737
|
* Applies recursive field-level redaction for known-sensitive keys.
|
|
@@ -5,31 +5,12 @@ import { registerTool } from '../registry.js';
|
|
|
5
5
|
import { getConfig } from '../../config/loader.js';
|
|
6
6
|
import { getSecureKey } from '../../security/secure-keys.js';
|
|
7
7
|
import { getLogger } from '../../util/logger.js';
|
|
8
|
+
import { getHttpRetryDelay, sleep, DEFAULT_MAX_RETRIES, DEFAULT_BASE_DELAY_MS } from '../../util/retry.js';
|
|
8
9
|
|
|
9
10
|
const log = getLogger('web-search');
|
|
10
11
|
|
|
11
12
|
const BRAVE_API_URL = 'https://api.search.brave.com/res/v1/web/search';
|
|
12
13
|
const PERPLEXITY_API_URL = 'https://api.perplexity.ai/chat/completions';
|
|
13
|
-
const RATE_LIMIT_MAX_RETRIES = 3;
|
|
14
|
-
const RATE_LIMIT_BASE_DELAY_MS = 1000;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Parse a Retry-After header value into milliseconds.
|
|
18
|
-
* RFC 7231 allows either delta-seconds (e.g. "120") or an HTTP-date
|
|
19
|
-
* (e.g. "Tue, 17 Feb 2026 12:00:00 GMT"). Returns undefined if unparseable.
|
|
20
|
-
*/
|
|
21
|
-
function parseRetryAfterMs(value: string): number | undefined {
|
|
22
|
-
const seconds = Number(value);
|
|
23
|
-
if (!isNaN(seconds)) {
|
|
24
|
-
return seconds * 1000;
|
|
25
|
-
}
|
|
26
|
-
// Try HTTP-date format — Date.parse handles RFC 2822 / IMF-fixdate
|
|
27
|
-
const dateMs = Date.parse(value);
|
|
28
|
-
if (!isNaN(dateMs)) {
|
|
29
|
-
return Math.max(0, dateMs - Date.now());
|
|
30
|
-
}
|
|
31
|
-
return undefined;
|
|
32
|
-
}
|
|
33
14
|
|
|
34
15
|
type WebSearchProvider = 'perplexity' | 'brave';
|
|
35
16
|
|
|
@@ -148,7 +129,7 @@ async function executeBraveSearch(
|
|
|
148
129
|
|
|
149
130
|
const url = `${BRAVE_API_URL}?${params.toString()}`;
|
|
150
131
|
|
|
151
|
-
for (let attempt = 0; attempt <=
|
|
132
|
+
for (let attempt = 0; attempt <= DEFAULT_MAX_RETRIES; attempt++) {
|
|
152
133
|
const response = await fetch(url, {
|
|
153
134
|
headers: {
|
|
154
135
|
'Accept': 'application/json',
|
|
@@ -169,12 +150,10 @@ async function executeBraveSearch(
|
|
|
169
150
|
return { content: 'Error: Invalid or expired Brave Search API key', isError: true };
|
|
170
151
|
}
|
|
171
152
|
|
|
172
|
-
if (response.status === 429 && attempt <
|
|
173
|
-
const
|
|
174
|
-
const parsed = retryAfter ? parseRetryAfterMs(retryAfter) : undefined;
|
|
175
|
-
const delayMs = parsed ?? RATE_LIMIT_BASE_DELAY_MS * Math.pow(2, attempt);
|
|
153
|
+
if (response.status === 429 && attempt < DEFAULT_MAX_RETRIES) {
|
|
154
|
+
const delayMs = getHttpRetryDelay(response, attempt, DEFAULT_BASE_DELAY_MS);
|
|
176
155
|
log.warn({ attempt: attempt + 1, delayMs }, 'Brave Search rate limited, retrying');
|
|
177
|
-
await
|
|
156
|
+
await sleep(delayMs);
|
|
178
157
|
continue;
|
|
179
158
|
}
|
|
180
159
|
|
|
@@ -192,7 +171,7 @@ async function executePerplexitySearch(
|
|
|
192
171
|
query: string,
|
|
193
172
|
apiKey: string,
|
|
194
173
|
): Promise<ToolExecutionResult> {
|
|
195
|
-
for (let attempt = 0; attempt <=
|
|
174
|
+
for (let attempt = 0; attempt <= DEFAULT_MAX_RETRIES; attempt++) {
|
|
196
175
|
const response = await fetch(PERPLEXITY_API_URL, {
|
|
197
176
|
method: 'POST',
|
|
198
177
|
headers: {
|
|
@@ -218,12 +197,10 @@ async function executePerplexitySearch(
|
|
|
218
197
|
return { content: 'Error: Invalid or expired Perplexity API key', isError: true };
|
|
219
198
|
}
|
|
220
199
|
|
|
221
|
-
if (response.status === 429 && attempt <
|
|
222
|
-
const
|
|
223
|
-
const parsed = retryAfter ? parseRetryAfterMs(retryAfter) : undefined;
|
|
224
|
-
const delayMs = parsed ?? RATE_LIMIT_BASE_DELAY_MS * Math.pow(2, attempt);
|
|
200
|
+
if (response.status === 429 && attempt < DEFAULT_MAX_RETRIES) {
|
|
201
|
+
const delayMs = getHttpRetryDelay(response, attempt, DEFAULT_BASE_DELAY_MS);
|
|
225
202
|
log.warn({ attempt: attempt + 1, delayMs }, 'Perplexity rate limited, retrying');
|
|
226
|
-
await
|
|
203
|
+
await sleep(delayMs);
|
|
227
204
|
continue;
|
|
228
205
|
}
|
|
229
206
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { PolicyContext } from '../permissions/types.js';
|
|
2
|
+
import type { Tool, ToolContext } from './types.js';
|
|
3
|
+
import { getTaskRunRules } from '../tasks/ephemeral-permissions.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Build a PolicyContext from tool metadata and execution context.
|
|
7
|
+
* When executing within a task run, ephemeral permission rules are
|
|
8
|
+
* included so pre-approved tools are auto-allowed without prompting.
|
|
9
|
+
*/
|
|
10
|
+
export function buildPolicyContext(tool: Tool, context?: ToolContext): PolicyContext | undefined {
|
|
11
|
+
const ephemeralRules = context?.taskRunId
|
|
12
|
+
? getTaskRunRules(context.taskRunId)
|
|
13
|
+
: undefined;
|
|
14
|
+
|
|
15
|
+
if (tool.origin === 'skill') {
|
|
16
|
+
return {
|
|
17
|
+
executionTarget: tool.executionTarget,
|
|
18
|
+
ephemeralRules: ephemeralRules?.length ? ephemeralRules : undefined,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (context?.taskRunId && ephemeralRules?.length) {
|
|
23
|
+
return {
|
|
24
|
+
ephemeralRules,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ToolContext, ToolExecutionResult } from '../types.js';
|
|
2
2
|
import { updateSchedule, formatLocalDate, describeCronExpression } from '../../schedule/schedule-store.js';
|
|
3
|
-
import { normalizeScheduleSyntax, type ScheduleSyntax } from '../../schedule/recurrence-types.js';
|
|
3
|
+
import { normalizeScheduleSyntax, detectScheduleSyntax, type ScheduleSyntax } from '../../schedule/recurrence-types.js';
|
|
4
4
|
import { validateRruleSetLines } from '../../schedule/recurrence-engine.js';
|
|
5
5
|
|
|
6
6
|
export async function executeScheduleUpdate(
|
|
@@ -31,9 +31,16 @@ export async function executeScheduleUpdate(
|
|
|
31
31
|
updates.expression = resolved.expression;
|
|
32
32
|
} else if (input.expression !== undefined) {
|
|
33
33
|
updates.expression = input.expression;
|
|
34
|
+
const detected = detectScheduleSyntax(input.expression as string);
|
|
35
|
+
if (detected) updates.syntax = detected;
|
|
34
36
|
} else if (input.cron_expression !== undefined) {
|
|
35
37
|
updates.cronExpression = input.cron_expression;
|
|
36
38
|
}
|
|
39
|
+
// When only syntax is provided (no expression), normalizeScheduleSyntax returns null
|
|
40
|
+
// but we still need to persist the explicit syntax value.
|
|
41
|
+
if (input.syntax !== undefined && updates.syntax === undefined) {
|
|
42
|
+
updates.syntax = input.syntax;
|
|
43
|
+
}
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
if (Object.keys(updates).length === 0) {
|
|
@@ -3,6 +3,7 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { getLogger } from '../../util/logger.js';
|
|
5
5
|
import { IntegrityError } from '../../util/errors.js';
|
|
6
|
+
import { PromiseGuard } from '../../util/promise-guard.js';
|
|
6
7
|
import { Parser, Language, type Node as TSNode } from 'web-tree-sitter';
|
|
7
8
|
|
|
8
9
|
const log = getLogger('shell-parser');
|
|
@@ -73,7 +74,7 @@ function verifyWasmChecksum(filePath: string, label: string): void {
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
let parserInstance: Parser | null = null;
|
|
76
|
-
|
|
77
|
+
const initGuard = new PromiseGuard<void>();
|
|
77
78
|
|
|
78
79
|
/**
|
|
79
80
|
* Locate a WASM file from a dependency package.
|
|
@@ -105,27 +106,24 @@ function findWasmPath(pkg: string, file: string): string {
|
|
|
105
106
|
async function ensureParser(): Promise<Parser> {
|
|
106
107
|
if (parserInstance) return parserInstance;
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const bashWasmPath = findWasmPath('tree-sitter-bash', 'tree-sitter-bash.wasm');
|
|
109
|
+
await initGuard.run(async () => {
|
|
110
|
+
const treeSitterWasm = findWasmPath('web-tree-sitter', 'web-tree-sitter.wasm');
|
|
111
|
+
const bashWasmPath = findWasmPath('tree-sitter-bash', 'tree-sitter-bash.wasm');
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
verifyWasmChecksum(treeSitterWasm, 'web-tree-sitter.wasm');
|
|
114
|
+
verifyWasmChecksum(bashWasmPath, 'tree-sitter-bash.wasm');
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
await Parser.init({
|
|
117
|
+
locateFile: () => treeSitterWasm,
|
|
118
|
+
});
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
120
|
+
const Bash = await Language.load(bashWasmPath);
|
|
121
|
+
const parser = new Parser();
|
|
122
|
+
parser.setLanguage(Bash);
|
|
123
|
+
parserInstance = parser;
|
|
124
|
+
log.info('Shell parser initialized (web-tree-sitter + bash, checksums verified)');
|
|
125
|
+
});
|
|
127
126
|
|
|
128
|
-
await initPromise;
|
|
129
127
|
return parserInstance!;
|
|
130
128
|
}
|
|
131
129
|
|
package/src/tools/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RiskLevel, AllowlistOption, ScopeOption
|
|
1
|
+
import type { RiskLevel, AllowlistOption, ScopeOption } from '../permissions/types.js';
|
|
2
2
|
import type { ToolDefinition, ContentBlock } from '../providers/types.js';
|
|
3
3
|
import type { SecretPromptResult } from '../permissions/secret-prompter.js';
|
|
4
4
|
|
|
@@ -12,12 +12,6 @@ interface ToolLifecycleEventBase {
|
|
|
12
12
|
conversationId: string;
|
|
13
13
|
requestId?: string;
|
|
14
14
|
executionTarget?: ExecutionTarget;
|
|
15
|
-
/** Security principal kind (e.g. 'core', 'skill', or 'task'). */
|
|
16
|
-
principalKind?: ToolPrincipalKind;
|
|
17
|
-
/** Security principal ID (skill ID when principalKind is 'skill'; task ID when 'task'). */
|
|
18
|
-
principalId?: string;
|
|
19
|
-
/** Content-hash of the principal's source at invocation time. */
|
|
20
|
-
principalVersion?: string;
|
|
21
15
|
}
|
|
22
16
|
|
|
23
17
|
export interface ToolExecutionStartEvent extends ToolLifecycleEventBase {
|
|
@@ -109,16 +103,13 @@ export interface ToolContext {
|
|
|
109
103
|
proxyToolResolver?: ProxyToolResolver;
|
|
110
104
|
/** When set, only tools in this set may execute. Tools outside the set are blocked with an error. */
|
|
111
105
|
allowedToolNames?: Set<string>;
|
|
112
|
-
/** Principal that owns this tool invocation (set by executor from tool metadata). */
|
|
113
|
-
principal?: { kind?: string; id?: string; version?: string };
|
|
114
106
|
/** Request user confirmation for a sub-tool operation (used by claude_code tool). */
|
|
115
107
|
requestConfirmation?: (req: {
|
|
116
108
|
toolName: string;
|
|
117
109
|
input: Record<string, unknown>;
|
|
118
110
|
riskLevel: string;
|
|
119
111
|
executionTarget?: ExecutionTarget;
|
|
120
|
-
|
|
121
|
-
principal?: { kind?: string; id?: string; version?: string };
|
|
112
|
+
principal?: string;
|
|
122
113
|
}) => Promise<{ decision: 'allow' | 'deny' }>;
|
|
123
114
|
/** Prompt the user for a secret value via native SecureField UI. */
|
|
124
115
|
requestSecret?: (params: {
|
|
@@ -141,6 +132,8 @@ export interface ToolContext {
|
|
|
141
132
|
forcePromptSideEffects?: boolean;
|
|
142
133
|
/** Approval callback for proxy policy decisions that require user confirmation. */
|
|
143
134
|
proxyApprovalCallback?: import('./network/script-proxy/types.js').ProxyApprovalCallback;
|
|
135
|
+
/** Optional principal identifier propagated to sub-tool confirmation flows. */
|
|
136
|
+
principal?: string;
|
|
144
137
|
}
|
|
145
138
|
|
|
146
139
|
export interface DiffInfo {
|