@vellumai/assistant 0.3.14 → 0.3.16
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/ARCHITECTURE.md +142 -0
- package/Dockerfile +2 -2
- package/README.md +5 -5
- package/docs/architecture/http-token-refresh.md +252 -0
- package/docs/architecture/memory.md +5 -4
- package/docs/architecture/scheduling.md +4 -88
- package/docs/runbook-trusted-contacts.md +283 -0
- package/docs/trusted-contact-access.md +247 -0
- package/package.json +1 -1
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +2 -6
- package/src/__tests__/access-request-decision.test.ts +331 -0
- package/src/__tests__/asset-materialize-tool.test.ts +7 -7
- package/src/__tests__/asset-search-tool.test.ts +15 -15
- package/src/__tests__/attachments-store.test.ts +13 -13
- package/src/__tests__/call-controller.test.ts +150 -4
- package/src/__tests__/call-conversation-messages.test.ts +2 -2
- package/src/__tests__/call-pointer-messages.test.ts +28 -0
- package/src/__tests__/call-start-guardian-guard.test.ts +93 -0
- package/src/__tests__/channel-approval-routes.test.ts +108 -12
- package/src/__tests__/channel-guardian.test.ts +16 -14
- package/src/__tests__/checker.test.ts +24 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +2 -2
- package/src/__tests__/config-watcher.test.ts +358 -0
- package/src/__tests__/conversation-pairing.test.ts +24 -24
- package/src/__tests__/conversation-store.test.ts +36 -36
- package/src/__tests__/date-context.test.ts +179 -1
- package/src/__tests__/db-migration-rollback.test.ts +4 -7
- package/src/__tests__/deterministic-verification-control-plane.test.ts +5 -5
- package/src/__tests__/emit-signal-routing-intent.test.ts +179 -0
- package/src/__tests__/gateway-only-guard.test.ts +188 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +451 -0
- package/src/__tests__/guardian-action-copy-generator.test.ts +197 -0
- package/src/__tests__/guardian-action-followup-executor.test.ts +379 -0
- package/src/__tests__/guardian-action-followup-store.test.ts +376 -0
- package/src/__tests__/guardian-action-late-reply.test.ts +294 -0
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +71 -0
- package/src/__tests__/guardian-action-sweep.test.ts +9 -9
- package/src/__tests__/guardian-control-plane-policy.test.ts +1 -3
- package/src/__tests__/guardian-outbound-http.test.ts +202 -10
- package/src/__tests__/guardian-verification-intent-routing.test.ts +179 -0
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +141 -0
- package/src/__tests__/handlers-telegram-config.test.ts +6 -6
- package/src/__tests__/hooks-runner.test.ts +13 -4
- package/src/__tests__/ingress-routes-http.test.ts +443 -0
- package/src/__tests__/intent-routing.test.ts +14 -0
- package/src/__tests__/ipc-snapshot.test.ts +2 -5
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
- package/src/__tests__/memory-regressions.test.ts +16 -12
- package/src/__tests__/non-member-access-request.test.ts +282 -0
- package/src/__tests__/notification-decision-strategy.test.ts +136 -0
- package/src/__tests__/notification-routing-intent.test.ts +11 -2
- package/src/__tests__/notification-thread-candidates.test.ts +166 -0
- package/src/__tests__/recording-intent-fallback.test.ts +0 -1
- package/src/__tests__/recording-intent-handler.test.ts +6 -3
- package/src/__tests__/recording-intent.test.ts +3 -2
- package/src/__tests__/recording-state-machine.test.ts +337 -26
- package/src/__tests__/registry.test.ts +17 -8
- package/src/__tests__/relay-server.test.ts +105 -0
- package/src/__tests__/reminder.test.ts +13 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +4 -4
- package/src/__tests__/scheduler-recurrence.test.ts +50 -0
- package/src/__tests__/server-history-render.test.ts +8 -8
- package/src/__tests__/session-agent-loop.test.ts +1 -0
- package/src/__tests__/session-runtime-assembly.test.ts +49 -0
- package/src/__tests__/session-skill-tools.test.ts +1 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +11 -3
- package/src/__tests__/slack-channel-config.test.ts +230 -0
- package/src/__tests__/subagent-manager-notify.test.ts +4 -4
- package/src/__tests__/swarm-session-integration.test.ts +2 -2
- package/src/__tests__/system-prompt.test.ts +43 -0
- package/src/__tests__/task-management-tools.test.ts +3 -3
- package/src/__tests__/task-tools.test.ts +3 -3
- package/src/__tests__/trust-store.test.ts +17 -1
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +491 -0
- package/src/__tests__/trusted-contact-multichannel.test.ts +409 -0
- package/src/__tests__/trusted-contact-verification.test.ts +360 -0
- package/src/__tests__/update-bulletin-format.test.ts +119 -0
- package/src/__tests__/update-bulletin-state.test.ts +129 -0
- package/src/__tests__/update-bulletin.test.ts +260 -0
- package/src/__tests__/update-template-contract.test.ts +29 -0
- package/src/agent/loop.ts +2 -2
- package/src/amazon/client.ts +2 -3
- package/src/calls/call-controller.ts +115 -34
- package/src/calls/call-conversation-messages.ts +2 -2
- package/src/calls/call-domain.ts +10 -3
- package/src/calls/call-pointer-messages.ts +17 -5
- package/src/calls/guardian-action-sweep.ts +77 -36
- package/src/calls/relay-server.ts +51 -12
- package/src/calls/twilio-routes.ts +3 -1
- package/src/calls/types.ts +1 -1
- package/src/calls/voice-session-bridge.ts +4 -4
- package/src/cli/core-commands.ts +3 -3
- package/src/cli/map.ts +8 -5
- package/src/config/bundled-skills/phone-calls/SKILL.md +16 -1
- package/src/config/bundled-skills/tasks/SKILL.md +1 -1
- package/src/config/bundled-skills/tasks/TOOLS.json +4 -4
- package/src/config/bundled-skills/time-based-actions/SKILL.md +11 -1
- package/src/config/computer-use-prompt.ts +1 -0
- package/src/config/core-schema.ts +16 -0
- package/src/config/env-registry.ts +1 -0
- package/src/config/env.ts +16 -1
- package/src/config/memory-schema.ts +5 -0
- package/src/config/schema.ts +4 -0
- package/src/config/system-prompt.ts +69 -2
- package/src/config/templates/BOOTSTRAP.md +1 -1
- package/src/config/templates/IDENTITY.md +8 -4
- package/src/config/templates/SOUL.md +14 -0
- package/src/config/templates/UPDATES.md +16 -0
- package/src/config/templates/USER.md +5 -1
- package/src/config/types.ts +1 -0
- package/src/config/update-bulletin-format.ts +52 -0
- package/src/config/update-bulletin-state.ts +49 -0
- package/src/config/update-bulletin.ts +82 -0
- package/src/config/vellum-skills/catalog.json +6 -0
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
- package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +44 -10
- package/src/config/vellum-skills/telegram-setup/SKILL.md +4 -4
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +147 -0
- package/src/config/vellum-skills/twilio-setup/SKILL.md +2 -2
- package/src/context/window-manager.ts +43 -3
- package/src/daemon/config-watcher.ts +1 -0
- package/src/daemon/connection-policy.ts +21 -1
- package/src/daemon/daemon-control.ts +164 -7
- package/src/daemon/date-context.ts +174 -1
- package/src/daemon/guardian-action-generators.ts +175 -0
- package/src/daemon/guardian-verification-intent.ts +120 -0
- package/src/daemon/handlers/apps.ts +1 -3
- package/src/daemon/handlers/config-channels.ts +8 -8
- package/src/daemon/handlers/config-heartbeat.ts +1 -1
- package/src/daemon/handlers/config-inbox.ts +55 -159
- package/src/daemon/handlers/config-ingress.ts +1 -1
- package/src/daemon/handlers/config-integrations.ts +1 -1
- package/src/daemon/handlers/config-platform.ts +1 -1
- package/src/daemon/handlers/config-scheduling.ts +2 -2
- package/src/daemon/handlers/config-slack-channel.ts +190 -0
- package/src/daemon/handlers/config-telegram.ts +1 -1
- package/src/daemon/handlers/config-twilio.ts +1 -1
- package/src/daemon/handlers/config-voice.ts +100 -0
- package/src/daemon/handlers/config.ts +3 -0
- package/src/daemon/handlers/index.ts +1 -1
- package/src/daemon/handlers/misc.ts +84 -6
- package/src/daemon/handlers/navigate-settings.ts +27 -0
- package/src/daemon/handlers/recording.ts +270 -144
- package/src/daemon/handlers/sessions.ts +107 -24
- package/src/daemon/handlers/subagents.ts +3 -3
- package/src/daemon/handlers/work-items.ts +10 -7
- package/src/daemon/ipc-contract/integrations.ts +9 -1
- package/src/daemon/ipc-contract/messages.ts +4 -0
- package/src/daemon/ipc-contract/sessions.ts +1 -1
- package/src/daemon/ipc-contract/settings.ts +26 -0
- package/src/daemon/ipc-contract/shared.ts +2 -0
- package/src/daemon/ipc-contract/work-items.ts +1 -7
- package/src/daemon/ipc-contract-inventory.json +5 -1
- package/src/daemon/ipc-contract.ts +5 -1
- package/src/daemon/lifecycle.ts +306 -266
- package/src/daemon/recording-executor.ts +1 -1
- package/src/daemon/recording-intent.ts +0 -41
- package/src/daemon/response-tier.ts +2 -2
- package/src/daemon/server.ts +6 -6
- package/src/daemon/session-agent-loop-handlers.ts +34 -9
- package/src/daemon/session-agent-loop.ts +15 -8
- package/src/daemon/session-history.ts +3 -2
- package/src/daemon/session-media-retry.ts +3 -0
- package/src/daemon/session-messaging.ts +38 -4
- package/src/daemon/session-notifiers.ts +2 -2
- package/src/daemon/session-process.ts +256 -23
- package/src/daemon/session-queue-manager.ts +2 -0
- package/src/daemon/session-runtime-assembly.ts +39 -0
- package/src/daemon/session-skill-tools.ts +13 -4
- package/src/daemon/session-tool-setup.ts +6 -7
- package/src/daemon/session.ts +19 -8
- package/src/daemon/tls-certs.ts +55 -13
- package/src/daemon/tool-side-effects.ts +13 -5
- package/src/gallery/default-gallery.ts +32 -9
- package/src/influencer/client.ts +2 -1
- package/src/memory/channel-delivery-store.ts +37 -567
- package/src/memory/channel-guardian-store.ts +66 -1317
- package/src/memory/conflict-store.ts +4 -4
- package/src/memory/conversation-attention-store.ts +4 -7
- package/src/memory/conversation-crud.ts +668 -0
- package/src/memory/conversation-queries.ts +361 -0
- package/src/memory/conversation-store.ts +45 -983
- package/src/memory/db-connection.ts +3 -0
- package/src/memory/db-init.ts +25 -0
- package/src/memory/delivery-channels.ts +175 -0
- package/src/memory/delivery-crud.ts +211 -0
- package/src/memory/delivery-status.ts +199 -0
- package/src/memory/embedding-backend.ts +70 -4
- package/src/memory/embedding-local.ts +12 -2
- package/src/memory/entity-extractor.ts +3 -8
- package/src/memory/fts-reconciler.ts +121 -0
- package/src/memory/guardian-action-store.ts +366 -3
- package/src/memory/guardian-approvals.ts +569 -0
- package/src/memory/guardian-bindings.ts +130 -0
- package/src/memory/guardian-rate-limits.ts +196 -0
- package/src/memory/guardian-verification.ts +520 -0
- package/src/memory/job-handlers/index-maintenance.ts +2 -1
- package/src/memory/job-utils.ts +8 -5
- package/src/memory/jobs-store.ts +66 -6
- package/src/memory/jobs-worker.ts +23 -1
- package/src/memory/migrations/030-guardian-action-followup.ts +21 -0
- package/src/memory/migrations/030-guardian-verification-purpose.ts +17 -0
- package/src/memory/migrations/031-conversations-thread-type-index.ts +5 -0
- package/src/memory/migrations/100-core-tables.ts +1 -1
- package/src/memory/migrations/101-watchers-and-logs.ts +4 -0
- package/src/memory/migrations/108-tasks-and-work-items.ts +1 -1
- package/src/memory/migrations/112-assistant-inbox.ts +1 -1
- package/src/memory/migrations/113-late-migrations.ts +1 -1
- package/src/memory/migrations/116-messages-fts.ts +13 -0
- package/src/memory/migrations/119-schema-indexes-and-columns.ts +37 -0
- package/src/memory/migrations/120-fk-cascade-rebuilds.ts +161 -0
- package/src/memory/migrations/index.ts +8 -3
- package/src/memory/migrations/validate-migration-state.ts +114 -15
- package/src/memory/qdrant-circuit-breaker.ts +105 -0
- package/src/memory/retriever.ts +46 -13
- package/src/memory/schema-migration.ts +3 -0
- package/src/memory/schema.ts +25 -7
- package/src/memory/search/semantic.ts +8 -90
- package/src/notifications/README.md +1 -1
- package/src/notifications/broadcaster.ts +20 -2
- package/src/notifications/conversation-pairing.ts +3 -3
- package/src/notifications/decision-engine.ts +173 -8
- package/src/notifications/deliveries-store.ts +27 -8
- package/src/notifications/preferences-store.ts +7 -7
- package/src/notifications/thread-candidates.ts +234 -0
- package/src/notifications/types.ts +18 -0
- package/src/permissions/defaults.ts +11 -1
- package/src/permissions/prompter.ts +17 -0
- package/src/permissions/trust-store.ts +2 -0
- package/src/providers/failover.ts +19 -0
- package/src/providers/registry.ts +46 -1
- package/src/runtime/approval-message-composer.ts +1 -1
- package/src/runtime/channel-guardian-service.ts +15 -3
- package/src/runtime/channel-retry-sweep.ts +7 -2
- package/src/runtime/guardian-action-conversation-turn.ts +85 -0
- package/src/runtime/guardian-action-followup-executor.ts +301 -0
- package/src/runtime/guardian-action-message-composer.ts +245 -0
- package/src/runtime/guardian-outbound-actions.ts +35 -15
- package/src/runtime/guardian-verification-templates.ts +15 -9
- package/src/runtime/http-errors.ts +93 -0
- package/src/runtime/http-server.ts +140 -51
- package/src/runtime/http-types.ts +53 -0
- package/src/runtime/ingress-service.ts +237 -0
- package/src/runtime/middleware/error-handler.ts +4 -3
- package/src/runtime/middleware/rate-limiter.ts +160 -0
- package/src/runtime/middleware/request-logger.ts +71 -0
- package/src/runtime/middleware/twilio-validation.ts +7 -6
- package/src/runtime/pending-interactions.ts +12 -0
- package/src/runtime/routes/access-request-decision.ts +215 -0
- package/src/runtime/routes/app-routes.ts +25 -18
- package/src/runtime/routes/approval-routes.ts +18 -47
- package/src/runtime/routes/attachment-routes.ts +15 -41
- package/src/runtime/routes/call-routes.ts +20 -20
- package/src/runtime/routes/channel-delivery-routes.ts +6 -5
- package/src/runtime/routes/contact-routes.ts +4 -9
- package/src/runtime/routes/conversation-attention-routes.ts +5 -4
- package/src/runtime/routes/conversation-routes.ts +26 -57
- package/src/runtime/routes/debug-routes.ts +71 -0
- package/src/runtime/routes/events-routes.ts +3 -2
- package/src/runtime/routes/guardian-approval-interception.ts +221 -0
- package/src/runtime/routes/identity-routes.ts +14 -10
- package/src/runtime/routes/inbound-conversation.ts +3 -2
- package/src/runtime/routes/inbound-message-handler.ts +527 -62
- package/src/runtime/routes/ingress-routes.ts +174 -0
- package/src/runtime/routes/integration-routes.ts +82 -20
- package/src/runtime/routes/pairing-routes.ts +11 -10
- package/src/runtime/routes/secret-routes.ts +10 -18
- package/src/runtime/verification-rate-limiter.ts +83 -0
- package/src/schedule/schedule-store.ts +13 -1
- package/src/schedule/scheduler.ts +2 -2
- package/src/security/secret-ingress.ts +5 -2
- package/src/security/secret-scanner.ts +72 -6
- package/src/subagent/manager.ts +6 -4
- package/src/swarm/plan-validator.ts +4 -1
- package/src/tasks/task-runner.ts +3 -1
- package/src/tools/browser/api-map.ts +9 -6
- package/src/tools/calls/call-start.ts +20 -0
- package/src/tools/executor.ts +50 -568
- package/src/tools/permission-checker.ts +272 -0
- package/src/tools/registry.ts +14 -6
- package/src/tools/reminder/reminder-store.ts +7 -7
- package/src/tools/reminder/reminder.ts +6 -3
- package/src/tools/secret-detection-handler.ts +301 -0
- package/src/tools/subagent/message.ts +1 -1
- package/src/tools/system/voice-config.ts +62 -0
- package/src/tools/tasks/index.ts +3 -3
- package/src/tools/tasks/work-item-list.ts +3 -3
- package/src/tools/tasks/work-item-update.ts +4 -5
- package/src/tools/tool-approval-handler.ts +192 -0
- package/src/tools/tool-manifest.ts +2 -0
- package/src/watcher/watcher-store.ts +9 -9
- package/src/work-items/work-item-runner.ts +9 -6
- /package/src/memory/migrations/{026-embeddings-nullable-vector-json.ts → 026a-embeddings-nullable-vector-json.ts} +0 -0
- /package/src/memory/migrations/{027-guardian-bootstrap-token.ts → 027a-guardian-bootstrap-token.ts} +0 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { normalizeActivationKey } from '../../daemon/handlers/config-voice.js';
|
|
2
|
+
import { RiskLevel } from '../../permissions/types.js';
|
|
3
|
+
import type { ToolDefinition } from '../../providers/types.js';
|
|
4
|
+
import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
|
|
5
|
+
|
|
6
|
+
const TOOL_NAME = 'voice_config_update';
|
|
7
|
+
|
|
8
|
+
export const voiceConfigUpdateTool: Tool = {
|
|
9
|
+
name: TOOL_NAME,
|
|
10
|
+
description:
|
|
11
|
+
'Change the push-to-talk activation key. Valid keys: fn (Fn/Globe key), ctrl (Control key), fn_shift (Fn+Shift), none (disable PTT).',
|
|
12
|
+
category: 'system',
|
|
13
|
+
defaultRiskLevel: RiskLevel.Low,
|
|
14
|
+
|
|
15
|
+
getDefinition(): ToolDefinition {
|
|
16
|
+
return {
|
|
17
|
+
name: TOOL_NAME,
|
|
18
|
+
description: this.description,
|
|
19
|
+
input_schema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
activation_key: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description:
|
|
25
|
+
'The activation key to set. Accepts enum values (fn, ctrl, fn_shift, none) or natural language (e.g. "Control", "Fn+Shift", "Off").',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: ['activation_key'],
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
async execute(
|
|
34
|
+
input: Record<string, unknown>,
|
|
35
|
+
context: ToolContext,
|
|
36
|
+
): Promise<ToolExecutionResult> {
|
|
37
|
+
const rawKey = input.activation_key;
|
|
38
|
+
if (typeof rawKey !== 'string' || rawKey.trim() === '') {
|
|
39
|
+
return {
|
|
40
|
+
content: 'Error: activation_key is required and must be a non-empty string.',
|
|
41
|
+
isError: true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const result = normalizeActivationKey(rawKey);
|
|
46
|
+
if (!result.ok) {
|
|
47
|
+
return { content: result.reason, isError: true };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const labels: Record<string, string> = {
|
|
51
|
+
fn: 'Fn/Globe key',
|
|
52
|
+
ctrl: 'Control key',
|
|
53
|
+
fn_shift: 'Fn+Shift',
|
|
54
|
+
none: 'disabled',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
content: `Push-to-talk activation key updated to ${labels[result.value]} (${result.value}).`,
|
|
59
|
+
isError: false,
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
};
|
package/src/tools/tasks/index.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* UX Terminology Glossary
|
|
3
3
|
* ──────────────────────────────────────────────────────────────────────
|
|
4
4
|
*
|
|
5
|
-
* "Task" (user-facing) — A work item in the
|
|
6
|
-
* `work_items` table). This is what users
|
|
7
|
-
*
|
|
5
|
+
* "Task" (user-facing) — A work item in the task queue (backed by the
|
|
6
|
+
* `work_items` table). This is what users create, run, and track
|
|
7
|
+
* through conversation.
|
|
8
8
|
*
|
|
9
9
|
* "Task template" / "task definition" (internal) — A reusable template
|
|
10
10
|
* saved from a conversation (backed by the `tasks` table). Templates
|
|
@@ -37,8 +37,8 @@ export async function executeTaskListShow(
|
|
|
37
37
|
const filtered = statusFilter !== undefined;
|
|
38
38
|
|
|
39
39
|
if (count === 0) {
|
|
40
|
-
const suffix = filtered ? '
|
|
41
|
-
return { content:
|
|
40
|
+
const suffix = filtered ? 'No items matching that filter.' : 'No tasks queued.';
|
|
41
|
+
return { content: suffix, isError: false };
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const label = filtered
|
|
@@ -47,7 +47,7 @@ export async function executeTaskListShow(
|
|
|
47
47
|
|
|
48
48
|
const taskList = formatTaskList(items);
|
|
49
49
|
|
|
50
|
-
return { content: `
|
|
50
|
+
return { content: `Task queue (${label}):\n${taskList}`, isError: false };
|
|
51
51
|
} catch (err) {
|
|
52
52
|
const msg = err instanceof Error ? err.message : String(err);
|
|
53
53
|
return { content: `Error: ${msg}`, isError: true };
|
|
@@ -53,12 +53,11 @@ export async function executeTaskListUpdate(
|
|
|
53
53
|
|
|
54
54
|
const item = result.workItem;
|
|
55
55
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
log.warn({ selectorType, resolvedWorkItemId: item.id }, 'rejected attempt to set status to done directly');
|
|
56
|
+
// Allow direct transitions to 'done' only from 'awaiting_review'
|
|
57
|
+
if (input.status === 'done' && item.status !== 'awaiting_review') {
|
|
58
|
+
log.warn({ selectorType, resolvedWorkItemId: item.id, currentStatus: item.status }, 'rejected attempt to set status to done from non-review state');
|
|
60
59
|
return {
|
|
61
|
-
content:
|
|
60
|
+
content: `Error: Cannot mark as done from '${item.status}'. Tasks must reach 'awaiting_review' status first (typically after a task run completes). You can set the status to 'awaiting_review' if the task is ready to be completed.`,
|
|
62
61
|
isError: true,
|
|
63
62
|
};
|
|
64
63
|
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { isToolBlocked } from '../security/parental-control-store.js';
|
|
2
|
+
import { getTaskRunRules } from '../tasks/ephemeral-permissions.js';
|
|
3
|
+
import { getLogger } from '../util/logger.js';
|
|
4
|
+
import { enforceGuardianOnlyPolicy } from './guardian-control-plane-policy.js';
|
|
5
|
+
import { getAllTools, getTool } from './registry.js';
|
|
6
|
+
import type { ExecutionTarget, Tool, ToolContext, ToolExecutionResult, ToolLifecycleEvent } from './types.js';
|
|
7
|
+
|
|
8
|
+
const log = getLogger('tool-approval-handler');
|
|
9
|
+
|
|
10
|
+
export type PreExecutionGateResult =
|
|
11
|
+
| { allowed: true; tool: Tool }
|
|
12
|
+
| { allowed: false; result: ToolExecutionResult };
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Handles pre-execution approval gates: abort checks, parental controls,
|
|
16
|
+
* guardian policy, allowed-tool-set gating, and task-run preflight checks.
|
|
17
|
+
* These run before the interactive permission prompt flow.
|
|
18
|
+
*/
|
|
19
|
+
export class ToolApprovalHandler {
|
|
20
|
+
/**
|
|
21
|
+
* Evaluate all pre-execution approval gates for a tool invocation.
|
|
22
|
+
* Returns the resolved Tool if all gates pass, or an early-return
|
|
23
|
+
* ToolExecutionResult if any gate blocks execution.
|
|
24
|
+
*/
|
|
25
|
+
checkPreExecutionGates(
|
|
26
|
+
name: string,
|
|
27
|
+
input: Record<string, unknown>,
|
|
28
|
+
context: ToolContext,
|
|
29
|
+
executionTarget: ExecutionTarget,
|
|
30
|
+
riskLevel: string,
|
|
31
|
+
startTime: number,
|
|
32
|
+
emitLifecycleEvent: (event: ToolLifecycleEvent) => void,
|
|
33
|
+
): PreExecutionGateResult {
|
|
34
|
+
// Bail out immediately if the session was aborted before this tool started.
|
|
35
|
+
if (context.signal?.aborted) {
|
|
36
|
+
const durationMs = Date.now() - startTime;
|
|
37
|
+
emitLifecycleEvent({
|
|
38
|
+
type: 'error',
|
|
39
|
+
toolName: name,
|
|
40
|
+
executionTarget,
|
|
41
|
+
input,
|
|
42
|
+
workingDir: context.workingDir,
|
|
43
|
+
sessionId: context.sessionId,
|
|
44
|
+
conversationId: context.conversationId,
|
|
45
|
+
requestId: context.requestId,
|
|
46
|
+
riskLevel,
|
|
47
|
+
decision: 'error',
|
|
48
|
+
durationMs,
|
|
49
|
+
errorMessage: 'Cancelled',
|
|
50
|
+
isExpected: true,
|
|
51
|
+
errorCategory: 'tool_failure',
|
|
52
|
+
});
|
|
53
|
+
return { allowed: false, result: { content: 'Cancelled', isError: true } };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Reject tools blocked by parental control settings before any permission check.
|
|
57
|
+
if (isToolBlocked(name)) {
|
|
58
|
+
log.warn(
|
|
59
|
+
{
|
|
60
|
+
toolName: name,
|
|
61
|
+
sessionId: context.sessionId,
|
|
62
|
+
conversationId: context.conversationId,
|
|
63
|
+
principal: context.principal,
|
|
64
|
+
reason: 'blocked_by_parental_controls',
|
|
65
|
+
},
|
|
66
|
+
'Parental control blocked tool invocation',
|
|
67
|
+
);
|
|
68
|
+
const durationMs = Date.now() - startTime;
|
|
69
|
+
emitLifecycleEvent({
|
|
70
|
+
type: 'permission_denied',
|
|
71
|
+
toolName: name,
|
|
72
|
+
executionTarget,
|
|
73
|
+
input,
|
|
74
|
+
workingDir: context.workingDir,
|
|
75
|
+
sessionId: context.sessionId,
|
|
76
|
+
conversationId: context.conversationId,
|
|
77
|
+
requestId: context.requestId,
|
|
78
|
+
riskLevel,
|
|
79
|
+
decision: 'deny',
|
|
80
|
+
reason: 'Blocked by parental control settings',
|
|
81
|
+
durationMs,
|
|
82
|
+
});
|
|
83
|
+
return { allowed: false, result: { content: 'This tool is blocked by parental control settings.', isError: true } };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Reject tool invocations targeting guardian control-plane endpoints from non-guardian actors.
|
|
87
|
+
const guardianCheck = enforceGuardianOnlyPolicy(name, input, context.guardianActorRole);
|
|
88
|
+
if (guardianCheck.denied) {
|
|
89
|
+
log.warn({
|
|
90
|
+
toolName: name,
|
|
91
|
+
sessionId: context.sessionId,
|
|
92
|
+
conversationId: context.conversationId,
|
|
93
|
+
actorRole: context.guardianActorRole,
|
|
94
|
+
reason: 'guardian_only_policy',
|
|
95
|
+
}, 'Guardian-only policy blocked tool invocation');
|
|
96
|
+
const durationMs = Date.now() - startTime;
|
|
97
|
+
emitLifecycleEvent({
|
|
98
|
+
type: 'permission_denied',
|
|
99
|
+
toolName: name,
|
|
100
|
+
executionTarget,
|
|
101
|
+
input,
|
|
102
|
+
workingDir: context.workingDir,
|
|
103
|
+
sessionId: context.sessionId,
|
|
104
|
+
conversationId: context.conversationId,
|
|
105
|
+
requestId: context.requestId,
|
|
106
|
+
riskLevel,
|
|
107
|
+
decision: 'deny',
|
|
108
|
+
reason: guardianCheck.reason!,
|
|
109
|
+
durationMs,
|
|
110
|
+
});
|
|
111
|
+
return { allowed: false, result: { content: guardianCheck.reason!, isError: true } };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Gate tools not active for the current turn
|
|
115
|
+
if (context.allowedToolNames && !context.allowedToolNames.has(name)) {
|
|
116
|
+
const msg = `Tool "${name}" is not currently active. Load the skill that provides this tool first.`;
|
|
117
|
+
const durationMs = Date.now() - startTime;
|
|
118
|
+
emitLifecycleEvent({
|
|
119
|
+
type: 'error',
|
|
120
|
+
toolName: name,
|
|
121
|
+
executionTarget,
|
|
122
|
+
input,
|
|
123
|
+
workingDir: context.workingDir,
|
|
124
|
+
sessionId: context.sessionId,
|
|
125
|
+
conversationId: context.conversationId,
|
|
126
|
+
requestId: context.requestId,
|
|
127
|
+
riskLevel,
|
|
128
|
+
decision: 'error',
|
|
129
|
+
durationMs,
|
|
130
|
+
errorMessage: msg,
|
|
131
|
+
isExpected: true,
|
|
132
|
+
errorCategory: 'tool_failure',
|
|
133
|
+
});
|
|
134
|
+
return { allowed: false, result: { content: msg, isError: true } };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Belt-and-suspenders guard for task runs: only preflight-approved tools
|
|
138
|
+
// may execute. This catches cases where ephemeral rules might not cover
|
|
139
|
+
// a tool, ensuring unapproved calls fail deterministically instead of
|
|
140
|
+
// falling through to the interactive prompter.
|
|
141
|
+
if (context.taskRunId) {
|
|
142
|
+
const taskRules = getTaskRunRules(context.taskRunId);
|
|
143
|
+
const approvedToolNames = new Set(taskRules.map((r) => r.tool));
|
|
144
|
+
if (approvedToolNames.size > 0 && !approvedToolNames.has(name)) {
|
|
145
|
+
const msg = `Tool '${name}' was not approved in the task's preflight. Add it to required tools and re-approve.`;
|
|
146
|
+
const durationMs = Date.now() - startTime;
|
|
147
|
+
emitLifecycleEvent({
|
|
148
|
+
type: 'permission_denied',
|
|
149
|
+
toolName: name,
|
|
150
|
+
executionTarget,
|
|
151
|
+
input,
|
|
152
|
+
workingDir: context.workingDir,
|
|
153
|
+
sessionId: context.sessionId,
|
|
154
|
+
conversationId: context.conversationId,
|
|
155
|
+
requestId: context.requestId,
|
|
156
|
+
riskLevel,
|
|
157
|
+
decision: 'deny',
|
|
158
|
+
reason: msg,
|
|
159
|
+
durationMs,
|
|
160
|
+
});
|
|
161
|
+
return { allowed: false, result: { content: msg, isError: true } };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Resolve the tool from the registry
|
|
166
|
+
const tool = getTool(name);
|
|
167
|
+
if (!tool) {
|
|
168
|
+
const available = getAllTools().filter((t) => t.executionMode !== 'proxy' || context.proxyToolResolver).map((t) => t.name).sort().join(', ');
|
|
169
|
+
const msg = `Unknown tool: ${name}. Available tools: ${available}`;
|
|
170
|
+
const durationMs = Date.now() - startTime;
|
|
171
|
+
emitLifecycleEvent({
|
|
172
|
+
type: 'error',
|
|
173
|
+
toolName: name,
|
|
174
|
+
executionTarget,
|
|
175
|
+
input,
|
|
176
|
+
workingDir: context.workingDir,
|
|
177
|
+
sessionId: context.sessionId,
|
|
178
|
+
conversationId: context.conversationId,
|
|
179
|
+
requestId: context.requestId,
|
|
180
|
+
riskLevel,
|
|
181
|
+
decision: 'error',
|
|
182
|
+
durationMs,
|
|
183
|
+
errorMessage: msg,
|
|
184
|
+
isExpected: true,
|
|
185
|
+
errorCategory: 'tool_failure',
|
|
186
|
+
});
|
|
187
|
+
return { allowed: false, result: { content: msg, isError: true } };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return { allowed: true, tool };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -12,6 +12,7 @@ import { credentialStoreTool } from './credentials/vault.js';
|
|
|
12
12
|
import { memorySaveTool, memorySearchTool, memoryUpdateTool } from './memory/register.js';
|
|
13
13
|
import type { LazyToolDescriptor } from './registry.js';
|
|
14
14
|
import { vellumSkillsCatalogTool } from './skills/vellum-catalog.js';
|
|
15
|
+
import { voiceConfigUpdateTool } from './system/voice-config.js';
|
|
15
16
|
import type { Tool } from './types.js';
|
|
16
17
|
import { screenWatchTool } from './watch/screen-watch.js';
|
|
17
18
|
|
|
@@ -66,6 +67,7 @@ export const explicitTools: Tool[] = [
|
|
|
66
67
|
accountManageTool,
|
|
67
68
|
screenWatchTool,
|
|
68
69
|
vellumSkillsCatalogTool,
|
|
70
|
+
voiceConfigUpdateTool,
|
|
69
71
|
];
|
|
70
72
|
|
|
71
73
|
// ── Lazy tool descriptors ───────────────────────────────────────────
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { and, asc, desc, eq, gte, lte } from 'drizzle-orm';
|
|
2
2
|
import { v4 as uuid } from 'uuid';
|
|
3
3
|
|
|
4
|
-
import { getDb } from '../memory/db.js';
|
|
4
|
+
import { getDb, rawChanges } from '../memory/db.js';
|
|
5
5
|
import { watcherEvents,watchers } from '../memory/schema.js';
|
|
6
6
|
import { truncate } from '../util/truncate.js';
|
|
7
7
|
import { DEFAULT_POLL_INTERVAL_MS } from './constants.js';
|
|
@@ -142,8 +142,8 @@ export function updateWatcher(
|
|
|
142
142
|
|
|
143
143
|
export function deleteWatcher(id: string): boolean {
|
|
144
144
|
const db = getDb();
|
|
145
|
-
|
|
146
|
-
return (
|
|
145
|
+
db.delete(watchers).where(eq(watchers.id, id)).run();
|
|
146
|
+
return rawChanges() > 0;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
// ── Claim / Complete ────────────────────────────────────────────────
|
|
@@ -167,7 +167,7 @@ export function claimDueWatchers(now: number): Watcher[] {
|
|
|
167
167
|
|
|
168
168
|
const claimed: Watcher[] = [];
|
|
169
169
|
for (const row of candidates) {
|
|
170
|
-
|
|
170
|
+
db
|
|
171
171
|
.update(watchers)
|
|
172
172
|
.set({ status: 'polling', updatedAt: now })
|
|
173
173
|
.where(and(
|
|
@@ -175,9 +175,9 @@ export function claimDueWatchers(now: number): Watcher[] {
|
|
|
175
175
|
eq(watchers.nextPollAt, row.nextPollAt),
|
|
176
176
|
eq(watchers.status, 'idle'),
|
|
177
177
|
))
|
|
178
|
-
.run()
|
|
178
|
+
.run();
|
|
179
179
|
|
|
180
|
-
if ((
|
|
180
|
+
if (rawChanges() === 0) continue;
|
|
181
181
|
claimed.push(parseWatcherRow({ ...row, status: 'polling', updatedAt: now }));
|
|
182
182
|
}
|
|
183
183
|
return claimed;
|
|
@@ -271,12 +271,12 @@ export function setWatcherConversationId(id: string, conversationId: string): vo
|
|
|
271
271
|
*/
|
|
272
272
|
export function resetStuckWatchers(): number {
|
|
273
273
|
const db = getDb();
|
|
274
|
-
|
|
274
|
+
db
|
|
275
275
|
.update(watchers)
|
|
276
276
|
.set({ status: 'idle', updatedAt: Date.now() })
|
|
277
277
|
.where(eq(watchers.status, 'polling'))
|
|
278
|
-
.run()
|
|
279
|
-
return
|
|
278
|
+
.run();
|
|
279
|
+
return rawChanges();
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
// ── Watcher Events ──────────────────────────────────────────────────
|
|
@@ -127,8 +127,8 @@ export function runWorkItemInBackground(workItemId: string): RunWorkItemResult {
|
|
|
127
127
|
workItemId,
|
|
128
128
|
title: workItem.title,
|
|
129
129
|
} as ServerMessage);
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
session.taskRunId = taskRunId;
|
|
131
|
+
session.headlessLock = true;
|
|
132
132
|
}
|
|
133
133
|
await session.processMessage(message, [], (event) => {
|
|
134
134
|
broadcast(event);
|
|
@@ -136,8 +136,10 @@ export function runWorkItemInBackground(workItemId: string): RunWorkItemResult {
|
|
|
136
136
|
},
|
|
137
137
|
);
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
// TS can't track that session is mutated inside the closure above
|
|
140
|
+
const doneSession = session as { headlessLock: boolean } | null;
|
|
141
|
+
if (doneSession) {
|
|
142
|
+
doneSession.headlessLock = false;
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
const current = getWorkItem(workItemId);
|
|
@@ -154,8 +156,9 @@ export function runWorkItemInBackground(workItemId: string): RunWorkItemResult {
|
|
|
154
156
|
broadcastWorkItemStatus(broadcast, workItemId);
|
|
155
157
|
broadcast({ type: 'tasks_changed' } as ServerMessage);
|
|
156
158
|
} catch (err) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
const errSession = session as { headlessLock: boolean } | null;
|
|
160
|
+
if (errSession) {
|
|
161
|
+
errSession.headlessLock = false;
|
|
159
162
|
}
|
|
160
163
|
log.error({ err, workItemId }, 'work item background run failed');
|
|
161
164
|
updateWorkItem(workItemId, {
|
|
File without changes
|
/package/src/memory/migrations/{027-guardian-bootstrap-token.ts → 027a-guardian-bootstrap-token.ts}
RENAMED
|
File without changes
|