vellum 0.2.1 → 0.2.2
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 +15 -2
- package/bun.lock +5 -2
- package/package.json +4 -2
- package/scripts/capture-x-graphql.ts +562 -0
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -1
- package/scripts/test.sh +5 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +133 -34
- package/src/__tests__/account-registry.test.ts +2 -1
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- package/src/__tests__/asset-materialize-tool.test.ts +16 -15
- package/src/__tests__/asset-search-tool.test.ts +23 -22
- package/src/__tests__/attachments-store.test.ts +56 -127
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
- package/src/__tests__/browser-skill-endstate.test.ts +4 -3
- package/src/__tests__/call-bridge.test.ts +385 -0
- package/src/__tests__/call-constants.test.ts +40 -0
- package/src/__tests__/call-orchestrator.test.ts +130 -4
- package/src/__tests__/call-recovery.test.ts +518 -0
- package/src/__tests__/call-routes-http.test.ts +459 -0
- package/src/__tests__/call-state-machine.test.ts +143 -0
- package/src/__tests__/call-store.test.ts +216 -1
- package/src/__tests__/cli-discover.test.ts +1 -1
- package/src/__tests__/commit-message-enrichment-service.test.ts +148 -7
- package/src/__tests__/compaction.benchmark.test.ts +176 -0
- package/src/__tests__/computer-use-tools.test.ts +250 -0
- package/src/__tests__/config-schema.test.ts +299 -3
- package/src/__tests__/conflict-store.test.ts +2 -1
- package/src/__tests__/contacts-tools.test.ts +331 -0
- package/src/__tests__/conversation-store.test.ts +30 -32
- package/src/__tests__/credential-security-invariants.test.ts +4 -0
- package/src/__tests__/date-context.test.ts +373 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
- package/src/__tests__/followup-tools.test.ts +303 -0
- package/src/__tests__/handlers-twitter-config.test.ts +718 -0
- package/src/__tests__/intent-routing.test.ts +64 -57
- package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
- package/src/__tests__/ipc-snapshot.test.ts +62 -28
- package/src/__tests__/llm-usage-store.test.ts +3 -8
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
- package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
- package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
- package/src/__tests__/playbook-tools.test.ts +342 -0
- package/src/__tests__/profile-compiler.test.ts +2 -1
- package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
- package/src/__tests__/recurrence-engine.test.ts +69 -0
- package/src/__tests__/recurrence-types.test.ts +71 -0
- package/src/__tests__/registry.test.ts +5 -3
- package/src/__tests__/relay-server.test.ts +633 -0
- package/src/__tests__/reminder-store.test.ts +6 -3
- package/src/__tests__/reminder.test.ts +43 -77
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +8 -4
- package/src/__tests__/run-orchestrator.test.ts +4 -4
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -6
- package/src/__tests__/runtime-runs-http.test.ts +4 -4
- package/src/__tests__/runtime-runs.test.ts +4 -4
- package/src/__tests__/schedule-store.test.ts +482 -0
- package/src/__tests__/schedule-tools.test.ts +700 -0
- package/src/__tests__/scheduler-recurrence.test.ts +329 -0
- package/src/__tests__/server-history-render.test.ts +14 -13
- package/src/__tests__/session-error.test.ts +28 -0
- package/src/__tests__/session-init.benchmark.test.ts +462 -0
- package/src/__tests__/session-queue.test.ts +71 -48
- package/src/__tests__/session-runtime-assembly.test.ts +161 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
- package/src/__tests__/signup-e2e.test.ts +2 -1
- package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
- package/src/__tests__/skill-script-runner.test.ts +159 -0
- package/src/__tests__/speaker-identification.test.ts +52 -0
- package/src/__tests__/subagent-manager-notify.test.ts +42 -10
- package/src/__tests__/subagent-tools.test.ts +141 -41
- package/src/__tests__/task-compiler.test.ts +2 -1
- package/src/__tests__/task-runner.test.ts +2 -1
- package/src/__tests__/task-scheduler.test.ts +2 -1
- package/src/__tests__/task-tools.test.ts +49 -56
- package/src/__tests__/tool-audit-listener.test.ts +1 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
- package/src/__tests__/tool-executor.test.ts +13 -17
- package/src/__tests__/turn-commit.test.ts +218 -3
- package/src/__tests__/twilio-provider.test.ts +143 -0
- package/src/__tests__/twilio-routes.test.ts +789 -0
- package/src/__tests__/twitter-auth-handler.test.ts +581 -0
- package/src/__tests__/view-image-tool.test.ts +217 -0
- package/src/__tests__/workspace-git-service.test.ts +186 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +13 -3
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/bundler/app-bundler.ts +12 -8
- package/src/calls/call-bridge.ts +95 -0
- package/src/calls/call-constants.ts +43 -5
- package/src/calls/call-domain.ts +276 -0
- package/src/calls/call-orchestrator.ts +43 -17
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-store.ts +192 -5
- package/src/calls/relay-server.ts +41 -4
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-provider.ts +10 -6
- package/src/calls/twilio-routes.ts +90 -76
- package/src/calls/types.ts +1 -1
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/core-commands.ts +776 -0
- package/src/cli/doordash.ts +251 -1
- package/src/cli/ipc-client.ts +82 -0
- package/src/cli/map.ts +246 -0
- package/src/cli/twitter.ts +575 -0
- package/src/cli.ts +7 -5
- package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
- package/src/commands/cc-command-registry.ts +209 -0
- package/src/config/bundled-skills/contacts/SKILL.md +39 -0
- package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
- package/src/config/bundled-skills/document/SKILL.md +18 -0
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
- package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
- package/src/config/bundled-skills/doordash/SKILL.md +82 -23
- package/src/config/bundled-skills/followups/SKILL.md +32 -0
- package/src/config/bundled-skills/followups/TOOLS.json +100 -0
- package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +1 -23
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
- package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
- package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
- package/src/config/bundled-skills/reminder/SKILL.md +20 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
- package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
- package/src/config/bundled-skills/schedule/SKILL.md +74 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
- package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
- package/src/config/bundled-skills/subagent/SKILL.md +25 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
- package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
- package/src/config/bundled-skills/tasks/SKILL.md +28 -0
- package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
- package/src/config/bundled-skills/twitter/SKILL.md +134 -0
- package/src/config/bundled-skills/watcher/SKILL.md +27 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
- package/src/config/defaults.ts +33 -0
- package/src/config/loader.ts +4 -1
- package/src/config/schema.ts +161 -1
- package/src/config/system-prompt.ts +61 -16
- package/src/config/templates/IDENTITY.md +7 -0
- package/src/config/types.ts +4 -0
- package/src/contacts/contact-store.ts +4 -4
- package/src/daemon/assistant-attachments.ts +10 -0
- package/src/daemon/classifier.ts +3 -1
- package/src/daemon/computer-use-session.ts +3 -1
- package/src/daemon/date-context.ts +136 -0
- package/src/daemon/handlers/apps.ts +16 -1
- package/src/daemon/handlers/browser.ts +54 -0
- package/src/daemon/handlers/computer-use.ts +7 -1
- package/src/daemon/handlers/config.ts +163 -5
- package/src/daemon/handlers/diagnostics.ts +5 -1
- package/src/daemon/handlers/documents.ts +18 -29
- package/src/daemon/handlers/home-base.ts +5 -1
- package/src/daemon/handlers/index.ts +40 -277
- package/src/daemon/handlers/misc.ts +9 -1
- package/src/daemon/handlers/publish.ts +6 -1
- package/src/daemon/handlers/sessions.ts +65 -12
- package/src/daemon/handlers/shared.ts +36 -1
- package/src/daemon/handlers/signing.ts +37 -0
- package/src/daemon/handlers/skills.ts +20 -6
- package/src/daemon/handlers/subagents.ts +8 -3
- package/src/daemon/handlers/twitter-auth.ts +169 -0
- package/src/daemon/handlers/work-items.ts +384 -68
- package/src/daemon/ipc-contract-inventory.json +28 -4
- package/src/daemon/ipc-contract.ts +133 -37
- package/src/daemon/ipc-protocol.ts +7 -2
- package/src/daemon/lifecycle.ts +21 -0
- package/src/daemon/main.ts +10 -4
- package/src/daemon/ride-shotgun-handler.ts +74 -10
- package/src/daemon/server.ts +143 -26
- package/src/daemon/session-agent-loop.ts +887 -0
- package/src/daemon/session-attachments.ts +28 -5
- package/src/daemon/session-error.ts +24 -3
- package/src/daemon/session-lifecycle.ts +147 -0
- package/src/daemon/session-media-retry.ts +147 -0
- package/src/daemon/session-messaging.ts +145 -0
- package/src/daemon/session-notifiers.ts +164 -0
- package/src/daemon/session-process.ts +2 -2
- package/src/daemon/session-queue-manager.ts +1 -0
- package/src/daemon/session-runtime-assembly.ts +52 -0
- package/src/daemon/session-skill-tools.ts +124 -5
- package/src/daemon/session-slash.ts +3 -0
- package/src/daemon/session-surfaces.ts +77 -2
- package/src/daemon/session-tool-setup.ts +216 -2
- package/src/daemon/session-usage.ts +0 -2
- package/src/daemon/session.ts +114 -1404
- package/src/daemon/video-thumbnail.ts +60 -0
- package/src/doordash/client.ts +121 -27
- package/src/doordash/queries.ts +1 -2
- package/src/export/formatter.ts +3 -1
- package/src/followups/followup-store.ts +4 -2
- package/src/followups/types.ts +6 -0
- package/src/hooks/templates.ts +1 -1
- package/src/index.ts +32 -1153
- package/src/memory/attachments-store.ts +28 -83
- package/src/memory/channel-delivery-store.ts +7 -21
- package/src/memory/clarification-resolver.ts +6 -5
- package/src/memory/contradiction-checker.ts +3 -2
- package/src/memory/conversation-key-store.ts +10 -29
- package/src/memory/conversation-store.ts +2 -1
- package/src/memory/db.ts +96 -2
- package/src/memory/entity-extractor.ts +6 -3
- package/src/memory/items-extractor.ts +5 -4
- package/src/memory/jobs-store.ts +3 -2
- package/src/memory/llm-usage-store.ts +1 -2
- package/src/memory/runs-store.ts +1 -2
- package/src/memory/schema.ts +23 -2
- package/src/messaging/style-analyzer.ts +3 -2
- package/src/messaging/thread-summarizer.ts +8 -12
- package/src/messaging/triage-engine.ts +4 -2
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/registry.ts +8 -0
- package/src/runtime/http-server.ts +108 -20
- package/src/runtime/routes/attachment-routes.ts +2 -3
- package/src/runtime/routes/call-routes.ts +140 -0
- package/src/runtime/routes/channel-routes.ts +5 -10
- package/src/runtime/routes/conversation-routes.ts +5 -5
- package/src/runtime/routes/run-routes.ts +2 -2
- package/src/runtime/run-orchestrator.ts +9 -3
- package/src/schedule/recurrence-engine.ts +138 -0
- package/src/schedule/recurrence-types.ts +67 -0
- package/src/schedule/schedule-store.ts +102 -57
- package/src/schedule/scheduler.ts +9 -6
- package/src/security/oauth2.ts +29 -4
- package/src/security/secret-allowlist.ts +46 -0
- package/src/skills/clawhub.ts +1 -1
- package/src/subagent/manager.ts +40 -8
- package/src/swarm/backend-claude-code.ts +64 -9
- package/src/swarm/worker-prompts.ts +2 -1
- package/src/tasks/SPEC.md +34 -28
- package/src/tasks/ephemeral-permissions.ts +16 -7
- package/src/tasks/task-compiler.ts +5 -4
- package/src/tasks/task-runner.ts +10 -5
- package/src/tasks/task-scheduler.ts +1 -1
- package/src/tasks/tool-sanitizer.ts +36 -0
- package/src/tools/assets/search.ts +4 -4
- package/src/tools/browser/api-map.ts +220 -0
- package/src/tools/browser/auto-navigate.ts +270 -0
- package/src/tools/browser/browser-execution.ts +2 -1
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/browser/network-recorder.ts +5 -4
- package/src/tools/browser/x-auto-navigate.ts +207 -0
- package/src/tools/calls/call-end.ts +17 -67
- package/src/tools/calls/call-start.ts +24 -85
- package/src/tools/calls/call-status.ts +35 -51
- package/src/tools/claude-code/claude-code.ts +77 -11
- package/src/tools/contacts/contact-merge.ts +46 -78
- package/src/tools/contacts/contact-search.ts +35 -79
- package/src/tools/contacts/contact-upsert.ts +35 -108
- package/src/tools/credentials/vault.ts +20 -4
- package/src/tools/document/document-tool.ts +71 -144
- package/src/tools/executor.ts +129 -10
- package/src/tools/followups/followup_create.ts +46 -88
- package/src/tools/followups/followup_list.ts +34 -74
- package/src/tools/followups/followup_resolve.ts +31 -66
- package/src/tools/host-terminal/cli-discover.ts +2 -1
- package/src/tools/host-terminal/host-shell.ts +10 -0
- package/src/tools/memory/handlers.ts +5 -4
- package/src/tools/network/__tests__/web-search.test.ts +427 -0
- package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
- package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
- package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
- package/src/tools/network/web-fetch.ts +18 -6
- package/src/tools/playbooks/index.ts +4 -5
- package/src/tools/playbooks/playbook-create.ts +3 -47
- package/src/tools/playbooks/playbook-delete.ts +1 -25
- package/src/tools/playbooks/playbook-list.ts +1 -28
- package/src/tools/playbooks/playbook-update.ts +3 -51
- package/src/tools/reminder/reminder.ts +5 -78
- package/src/tools/schedule/create.ts +69 -74
- package/src/tools/schedule/delete.ts +21 -47
- package/src/tools/schedule/list.ts +55 -74
- package/src/tools/schedule/update.ts +77 -84
- package/src/tools/subagent/abort.ts +29 -58
- package/src/tools/subagent/message.ts +30 -63
- package/src/tools/subagent/read.ts +53 -84
- package/src/tools/subagent/spawn.ts +43 -82
- package/src/tools/subagent/status.ts +42 -71
- package/src/tools/swarm/delegate.ts +2 -1
- package/src/tools/tasks/index.ts +8 -8
- package/src/tools/tasks/task-delete.ts +60 -88
- package/src/tools/tasks/task-list.ts +31 -52
- package/src/tools/tasks/task-run.ts +72 -108
- package/src/tools/tasks/task-save.ts +33 -65
- package/src/tools/tasks/work-item-enqueue.ts +183 -215
- package/src/tools/tasks/work-item-list.ts +33 -63
- package/src/tools/tasks/work-item-remove.ts +45 -97
- package/src/tools/tasks/work-item-update.ts +91 -163
- package/src/tools/terminal/backends/native.ts +3 -1
- package/src/tools/tool-manifest.ts +0 -62
- package/src/tools/types.ts +6 -0
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/tools/watch/screen-watch.ts +3 -1
- package/src/tools/watcher/create.ts +52 -98
- package/src/tools/watcher/delete.ts +20 -46
- package/src/tools/watcher/digest.ts +36 -70
- package/src/tools/watcher/list.ts +49 -79
- package/src/tools/watcher/update.ts +45 -91
- package/src/twitter/client.ts +690 -0
- package/src/twitter/session.ts +91 -0
- package/src/usage/types.ts +0 -1
- package/src/util/truncate.ts +6 -0
- package/src/watcher/providers/slack.ts +2 -1
- package/src/watcher/watcher-store.ts +3 -2
- package/src/work-items/work-item-store.ts +27 -2
- package/src/workspace/commit-message-enrichment-service.ts +31 -7
- package/src/workspace/git-service.ts +87 -22
- package/src/workspace/provider-commit-message-generator.ts +242 -0
- package/src/workspace/turn-commit.ts +62 -3
- package/src/tools/contacts/index.ts +0 -4
- package/src/tools/document/index.ts +0 -5
- package/src/tools/followups/index.ts +0 -3
- package/src/tools/subagent/index.ts +0 -5
- /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
|
@@ -1,165 +1,92 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
|
|
3
|
-
import type { ToolDefinition } from '../../providers/types.js';
|
|
1
|
+
import type { ToolContext, ToolExecutionResult } from '../types.js';
|
|
4
2
|
import { randomUUID } from 'node:crypto';
|
|
5
3
|
|
|
6
|
-
// ──
|
|
7
|
-
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async execute(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
|
|
37
|
-
const title = (input.title as string | undefined) || 'Untitled Document';
|
|
38
|
-
const initialContent = (input.initial_content as string | undefined) || '';
|
|
39
|
-
const surfaceId = `doc-${randomUUID()}`;
|
|
40
|
-
|
|
41
|
-
// Send document_editor_show IPC message to open the built-in RTE
|
|
42
|
-
if (context.sendToClient) {
|
|
43
|
-
context.sendToClient({
|
|
44
|
-
type: 'document_editor_show',
|
|
45
|
-
sessionId: context.sessionId,
|
|
46
|
-
surfaceId,
|
|
47
|
-
title,
|
|
48
|
-
initialContent,
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
context.sendToClient({
|
|
52
|
-
type: 'ui_surface_show',
|
|
53
|
-
sessionId: context.sessionId,
|
|
54
|
-
surfaceId: `preview-${surfaceId}`,
|
|
55
|
-
surfaceType: 'document_preview',
|
|
56
|
-
display: 'inline',
|
|
4
|
+
// ── Exported execute functions ──────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export function executeDocumentCreate(input: Record<string, unknown>, context: ToolContext): ToolExecutionResult {
|
|
7
|
+
const title = (input.title as string | undefined) || 'Untitled Document';
|
|
8
|
+
const initialContent = (input.initial_content as string | undefined) || '';
|
|
9
|
+
const surfaceId = `doc-${randomUUID()}`;
|
|
10
|
+
|
|
11
|
+
// Send document_editor_show IPC message to open the built-in RTE
|
|
12
|
+
if (context.sendToClient) {
|
|
13
|
+
context.sendToClient({
|
|
14
|
+
type: 'document_editor_show',
|
|
15
|
+
sessionId: context.sessionId,
|
|
16
|
+
surfaceId,
|
|
17
|
+
title,
|
|
18
|
+
initialContent,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
context.sendToClient({
|
|
22
|
+
type: 'ui_surface_show',
|
|
23
|
+
sessionId: context.sessionId,
|
|
24
|
+
surfaceId: `preview-${surfaceId}`,
|
|
25
|
+
surfaceType: 'document_preview',
|
|
26
|
+
display: 'inline',
|
|
27
|
+
title,
|
|
28
|
+
data: {
|
|
57
29
|
title,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
content: JSON.stringify({
|
|
67
|
-
surface_id: surfaceId,
|
|
68
|
-
title,
|
|
69
|
-
opened: true,
|
|
70
|
-
message: 'Document editor opened in Directory panel',
|
|
71
|
-
}),
|
|
72
|
-
isError: false,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
30
|
+
surfaceId,
|
|
31
|
+
subtitle: 'Document',
|
|
32
|
+
},
|
|
33
|
+
});
|
|
75
34
|
|
|
76
|
-
// Fallback if no IPC client is connected
|
|
77
35
|
return {
|
|
78
36
|
content: JSON.stringify({
|
|
79
37
|
surface_id: surfaceId,
|
|
80
38
|
title,
|
|
81
|
-
opened:
|
|
82
|
-
|
|
39
|
+
opened: true,
|
|
40
|
+
message: 'Document editor opened in Directory panel',
|
|
83
41
|
}),
|
|
84
42
|
isError: false,
|
|
85
43
|
};
|
|
86
44
|
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// ── document_update ──────────────────────────────────────────────────
|
|
90
|
-
|
|
91
|
-
export class DocumentUpdateTool implements Tool {
|
|
92
|
-
name = 'document_update';
|
|
93
|
-
description =
|
|
94
|
-
'Update content in an open document editor. Use this to stream generated content or apply edits.';
|
|
95
|
-
category = 'document';
|
|
96
|
-
defaultRiskLevel = RiskLevel.Low;
|
|
97
|
-
executionMode = 'local' as const;
|
|
98
|
-
|
|
99
|
-
getDefinition(): ToolDefinition {
|
|
100
|
-
return {
|
|
101
|
-
name: this.name,
|
|
102
|
-
description: this.description,
|
|
103
|
-
input_schema: {
|
|
104
|
-
type: 'object',
|
|
105
|
-
properties: {
|
|
106
|
-
surface_id: {
|
|
107
|
-
type: 'string',
|
|
108
|
-
description: 'The ID of the document surface to update',
|
|
109
|
-
},
|
|
110
|
-
content: {
|
|
111
|
-
type: 'string',
|
|
112
|
-
description: 'Markdown content to set or append',
|
|
113
|
-
},
|
|
114
|
-
mode: {
|
|
115
|
-
type: 'string',
|
|
116
|
-
enum: ['replace', 'append'],
|
|
117
|
-
description: 'Whether to replace all content or append to the end. Defaults to append.',
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
required: ['surface_id', 'content'],
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
45
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
markdown: content,
|
|
137
|
-
mode,
|
|
138
|
-
});
|
|
46
|
+
// Fallback if no IPC client is connected
|
|
47
|
+
return {
|
|
48
|
+
content: JSON.stringify({
|
|
49
|
+
surface_id: surfaceId,
|
|
50
|
+
title,
|
|
51
|
+
opened: false,
|
|
52
|
+
error: 'No IPC client connected to open document editor',
|
|
53
|
+
}),
|
|
54
|
+
isError: false,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
139
57
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
58
|
+
export function executeDocumentUpdate(input: Record<string, unknown>, context: ToolContext): ToolExecutionResult {
|
|
59
|
+
const surfaceId = input.surface_id as string;
|
|
60
|
+
const content = input.content as string;
|
|
61
|
+
const mode = (input.mode as string | undefined) || 'append';
|
|
62
|
+
|
|
63
|
+
// Send document_editor_update IPC message to update the built-in RTE
|
|
64
|
+
if (context.sendToClient) {
|
|
65
|
+
context.sendToClient({
|
|
66
|
+
type: 'document_editor_update',
|
|
67
|
+
sessionId: context.sessionId,
|
|
68
|
+
surfaceId,
|
|
69
|
+
markdown: content,
|
|
70
|
+
mode,
|
|
71
|
+
});
|
|
150
72
|
|
|
151
|
-
// Fallback if no IPC client is connected
|
|
152
73
|
return {
|
|
153
74
|
content: JSON.stringify({
|
|
154
|
-
success:
|
|
155
|
-
|
|
75
|
+
success: true,
|
|
76
|
+
surface_id: surfaceId,
|
|
77
|
+
mode,
|
|
78
|
+
message: 'Document content updated',
|
|
156
79
|
}),
|
|
157
|
-
isError:
|
|
80
|
+
isError: false,
|
|
158
81
|
};
|
|
159
82
|
}
|
|
160
|
-
}
|
|
161
83
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
84
|
+
// Fallback if no IPC client is connected
|
|
85
|
+
return {
|
|
86
|
+
content: JSON.stringify({
|
|
87
|
+
success: false,
|
|
88
|
+
error: 'No IPC client connected to update document',
|
|
89
|
+
}),
|
|
90
|
+
isError: true,
|
|
91
|
+
};
|
|
92
|
+
}
|
package/src/tools/executor.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { getConfig } from '../config/loader.js';
|
|
|
17
17
|
import { scanText, redactSecrets } from '../security/secret-scanner.js';
|
|
18
18
|
import { redactSensitiveFields } from '../security/redaction.js';
|
|
19
19
|
import { getHookManager } from '../hooks/manager.js';
|
|
20
|
+
import { getTaskRunRules } from '../tasks/ephemeral-permissions.js';
|
|
20
21
|
|
|
21
22
|
const log = getLogger('tool-executor');
|
|
22
23
|
|
|
@@ -67,10 +68,39 @@ export class ToolExecutor {
|
|
|
67
68
|
durationMs,
|
|
68
69
|
errorMessage: msg,
|
|
69
70
|
isExpected: true,
|
|
71
|
+
errorCategory: 'tool_failure',
|
|
70
72
|
});
|
|
71
73
|
return { content: msg, isError: true };
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
// Belt-and-suspenders guard for task runs: only preflight-approved tools
|
|
77
|
+
// may execute. This catches cases where ephemeral rules might not cover
|
|
78
|
+
// a tool, ensuring unapproved calls fail deterministically instead of
|
|
79
|
+
// falling through to the interactive prompter.
|
|
80
|
+
if (context.taskRunId) {
|
|
81
|
+
const taskRules = getTaskRunRules(context.taskRunId);
|
|
82
|
+
const approvedToolNames = new Set(taskRules.map((r) => r.tool));
|
|
83
|
+
if (approvedToolNames.size > 0 && !approvedToolNames.has(name)) {
|
|
84
|
+
const msg = `Tool '${name}' was not approved in the task's preflight. Add it to required tools and re-approve.`;
|
|
85
|
+
const durationMs = Date.now() - startTime;
|
|
86
|
+
emitLifecycleEvent(context, {
|
|
87
|
+
type: 'permission_denied',
|
|
88
|
+
toolName: name,
|
|
89
|
+
executionTarget,
|
|
90
|
+
input,
|
|
91
|
+
workingDir: context.workingDir,
|
|
92
|
+
sessionId: context.sessionId,
|
|
93
|
+
conversationId: context.conversationId,
|
|
94
|
+
requestId: context.requestId,
|
|
95
|
+
riskLevel,
|
|
96
|
+
decision: 'deny',
|
|
97
|
+
reason: msg,
|
|
98
|
+
durationMs,
|
|
99
|
+
});
|
|
100
|
+
return { content: msg, isError: true };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
74
104
|
const tool = getTool(name);
|
|
75
105
|
if (!tool) {
|
|
76
106
|
const available = getAllTools().filter((t) => t.executionMode !== 'proxy' || context.proxyToolResolver).map((t) => t.name).sort().join(', ');
|
|
@@ -90,6 +120,7 @@ export class ToolExecutor {
|
|
|
90
120
|
durationMs,
|
|
91
121
|
errorMessage: msg,
|
|
92
122
|
isExpected: true,
|
|
123
|
+
errorCategory: 'tool_failure',
|
|
93
124
|
});
|
|
94
125
|
return { content: msg, isError: true };
|
|
95
126
|
}
|
|
@@ -100,8 +131,9 @@ export class ToolExecutor {
|
|
|
100
131
|
riskLevel = risk;
|
|
101
132
|
|
|
102
133
|
// Build principal context from tool metadata so policy rules can
|
|
103
|
-
// distinguish skill-provided tools from core built-ins.
|
|
104
|
-
|
|
134
|
+
// distinguish skill-provided tools from core built-ins. Also includes
|
|
135
|
+
// ephemeral rules when executing within a task run.
|
|
136
|
+
const policyContext = buildPolicyContext(tool, context);
|
|
105
137
|
const result = await check(name, input, context.workingDir, policyContext);
|
|
106
138
|
|
|
107
139
|
// Private threads force prompting for side-effect tools even when a
|
|
@@ -137,6 +169,32 @@ export class ToolExecutor {
|
|
|
137
169
|
}
|
|
138
170
|
|
|
139
171
|
if (result.decision === 'prompt') {
|
|
172
|
+
// Non-interactive sessions have no client to respond to prompts —
|
|
173
|
+
// deny immediately instead of blocking for the full permission timeout.
|
|
174
|
+
if (context.isInteractive === false) {
|
|
175
|
+
decision = 'denied';
|
|
176
|
+
const durationMs = Date.now() - startTime;
|
|
177
|
+
log.info({ toolName: name, riskLevel }, 'Auto-denying prompt for non-interactive session');
|
|
178
|
+
emitLifecycleEvent(context, {
|
|
179
|
+
type: 'permission_denied',
|
|
180
|
+
toolName: name,
|
|
181
|
+
executionTarget,
|
|
182
|
+
input,
|
|
183
|
+
workingDir: context.workingDir,
|
|
184
|
+
sessionId: context.sessionId,
|
|
185
|
+
conversationId: context.conversationId,
|
|
186
|
+
requestId: context.requestId,
|
|
187
|
+
riskLevel,
|
|
188
|
+
decision: 'deny',
|
|
189
|
+
reason: 'Non-interactive session: no client to approve prompt',
|
|
190
|
+
durationMs,
|
|
191
|
+
});
|
|
192
|
+
return {
|
|
193
|
+
content: `Permission denied: tool "${name}" requires user approval but no interactive client is connected. The tool was not executed. To allow this tool in non-interactive sessions, add a trust rule via permission settings.`,
|
|
194
|
+
isError: true,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
140
198
|
// Need user approval
|
|
141
199
|
const allowlistOptions = generateAllowlistOptions(name, input);
|
|
142
200
|
const scopeOptions = generateScopeOptions(context.workingDir, name);
|
|
@@ -325,6 +383,7 @@ export class ToolExecutor {
|
|
|
325
383
|
durationMs,
|
|
326
384
|
errorMessage: msg,
|
|
327
385
|
isExpected: true,
|
|
386
|
+
errorCategory: 'tool_failure',
|
|
328
387
|
});
|
|
329
388
|
return { content: msg, isError: true };
|
|
330
389
|
}
|
|
@@ -358,6 +417,7 @@ export class ToolExecutor {
|
|
|
358
417
|
durationMs,
|
|
359
418
|
errorMessage: msg,
|
|
360
419
|
isExpected: true,
|
|
420
|
+
errorCategory: 'tool_failure',
|
|
361
421
|
});
|
|
362
422
|
return { content: msg, isError: true };
|
|
363
423
|
}
|
|
@@ -464,6 +524,39 @@ export class ToolExecutor {
|
|
|
464
524
|
} else if (sdConfig.action === 'prompt') {
|
|
465
525
|
// Ask the user whether to allow tool output containing secrets
|
|
466
526
|
const types = [...new Set(allMatches.map((m) => m.type))].join(', ');
|
|
527
|
+
|
|
528
|
+
// Non-interactive sessions: auto-block secret output instead of waiting for prompt
|
|
529
|
+
if (context.isInteractive === false) {
|
|
530
|
+
const blockedContent = `Tool output blocked: detected ${allMatches.length} potential secret(s) (${types}). No interactive client available to approve.`;
|
|
531
|
+
const durationMs = Date.now() - startTime;
|
|
532
|
+
log.info({ toolName: name }, 'Auto-blocking secret output for non-interactive session');
|
|
533
|
+
emitLifecycleEvent(context, {
|
|
534
|
+
type: 'permission_denied',
|
|
535
|
+
toolName: name,
|
|
536
|
+
executionTarget,
|
|
537
|
+
input,
|
|
538
|
+
workingDir: context.workingDir,
|
|
539
|
+
sessionId: context.sessionId,
|
|
540
|
+
conversationId: context.conversationId,
|
|
541
|
+
requestId: context.requestId,
|
|
542
|
+
riskLevel: RiskLevel.High,
|
|
543
|
+
decision: 'deny',
|
|
544
|
+
reason: 'Non-interactive session: auto-blocked secret output',
|
|
545
|
+
durationMs,
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
void getHookManager().trigger('post-tool-execute', {
|
|
549
|
+
toolName: name,
|
|
550
|
+
input: sanitizeToolInput(name, input),
|
|
551
|
+
riskLevel,
|
|
552
|
+
isError: true,
|
|
553
|
+
durationMs,
|
|
554
|
+
sessionId: context.sessionId,
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
return { content: blockedContent, isError: true };
|
|
558
|
+
}
|
|
559
|
+
|
|
467
560
|
const promptInput = {
|
|
468
561
|
_secretDetection: true,
|
|
469
562
|
summary: `Tool output contains ${allMatches.length} potential secret(s): ${types}`,
|
|
@@ -565,6 +658,14 @@ export class ToolExecutor {
|
|
|
565
658
|
const msg = err instanceof Error ? err.message : String(err);
|
|
566
659
|
const isExpected = err instanceof PermissionDeniedError || err instanceof ToolError || err instanceof TokenExpiredError;
|
|
567
660
|
|
|
661
|
+
const errorCategory = err instanceof PermissionDeniedError
|
|
662
|
+
? 'permission_denied' as const
|
|
663
|
+
: err instanceof TokenExpiredError
|
|
664
|
+
? 'auth' as const
|
|
665
|
+
: err instanceof ToolError
|
|
666
|
+
? 'tool_failure' as const
|
|
667
|
+
: 'unexpected' as const;
|
|
668
|
+
|
|
568
669
|
emitLifecycleEvent(context, {
|
|
569
670
|
type: 'error',
|
|
570
671
|
toolName: name,
|
|
@@ -579,6 +680,7 @@ export class ToolExecutor {
|
|
|
579
680
|
durationMs,
|
|
580
681
|
errorMessage: msg,
|
|
581
682
|
isExpected,
|
|
683
|
+
errorCategory,
|
|
582
684
|
errorName: err instanceof Error ? err.name : undefined,
|
|
583
685
|
errorStack: err instanceof Error ? err.stack : undefined,
|
|
584
686
|
});
|
|
@@ -622,6 +724,8 @@ const SIDE_EFFECT_TOOLS: ReadonlySet<string> = new Set([
|
|
|
622
724
|
'browser_fill_credential',
|
|
623
725
|
'document_create',
|
|
624
726
|
'document_update',
|
|
727
|
+
'reminder_create',
|
|
728
|
+
'reminder_cancel',
|
|
625
729
|
'schedule_create',
|
|
626
730
|
'schedule_update',
|
|
627
731
|
'schedule_delete',
|
|
@@ -644,10 +748,6 @@ export function isSideEffectTool(toolName: string, input?: Record<string, unknow
|
|
|
644
748
|
const action = input?.action;
|
|
645
749
|
return action === 'create' || action === 'update';
|
|
646
750
|
}
|
|
647
|
-
if (toolName === 'reminder') {
|
|
648
|
-
const action = input?.action;
|
|
649
|
-
return action === 'create' || action === 'cancel';
|
|
650
|
-
}
|
|
651
751
|
if (toolName === 'credential_store') {
|
|
652
752
|
const action = input?.action;
|
|
653
753
|
return action === 'store' || action === 'delete' || action === 'prompt' || action === 'oauth2_connect';
|
|
@@ -705,11 +805,16 @@ async function executeWithTimeout(
|
|
|
705
805
|
}
|
|
706
806
|
|
|
707
807
|
/**
|
|
708
|
-
* Build a PolicyContext from tool metadata. Skill-origin
|
|
709
|
-
* principal identifying the owning skill
|
|
710
|
-
*
|
|
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.
|
|
711
812
|
*/
|
|
712
|
-
function buildPolicyContext(tool: Tool): PolicyContext | undefined {
|
|
813
|
+
function buildPolicyContext(tool: Tool, context?: ToolContext): PolicyContext | undefined {
|
|
814
|
+
const ephemeralRules = context?.taskRunId
|
|
815
|
+
? getTaskRunRules(context.taskRunId)
|
|
816
|
+
: undefined;
|
|
817
|
+
|
|
713
818
|
if (tool.origin === 'skill') {
|
|
714
819
|
return {
|
|
715
820
|
principal: {
|
|
@@ -718,8 +823,22 @@ function buildPolicyContext(tool: Tool): PolicyContext | undefined {
|
|
|
718
823
|
version: tool.ownerSkillVersionHash,
|
|
719
824
|
},
|
|
720
825
|
executionTarget: tool.executionTarget,
|
|
826
|
+
ephemeralRules: ephemeralRules?.length ? ephemeralRules : undefined,
|
|
721
827
|
};
|
|
722
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
|
+
|
|
723
842
|
return undefined;
|
|
724
843
|
}
|
|
725
844
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
|
|
3
|
-
import type { ToolDefinition } from '../../providers/types.js';
|
|
1
|
+
import type { ToolContext, ToolExecutionResult } from '../types.js';
|
|
4
2
|
import { createFollowUp } from '../../followups/followup-store.js';
|
|
5
3
|
import { getContact } from '../../contacts/contact-store.js';
|
|
6
4
|
import type { FollowUp } from '../../followups/types.js';
|
|
@@ -17,102 +15,62 @@ function formatFollowUp(f: FollowUp): string {
|
|
|
17
15
|
if (f.expectedResponseBy) {
|
|
18
16
|
lines.push(` Expected response by: ${new Date(f.expectedResponseBy).toISOString()}`);
|
|
19
17
|
}
|
|
20
|
-
if (f.
|
|
18
|
+
if (f.reminderScheduleId) lines.push(` Reminder schedule: ${f.reminderScheduleId}`);
|
|
21
19
|
return lines.join('\n');
|
|
22
20
|
}
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
type: 'string',
|
|
32
|
-
description: 'Communication channel (e.g. email, slack, whatsapp)',
|
|
33
|
-
},
|
|
34
|
-
thread_id: {
|
|
35
|
-
type: 'string',
|
|
36
|
-
description: 'External thread or conversation identifier on that channel',
|
|
37
|
-
},
|
|
38
|
-
contact_id: {
|
|
39
|
-
type: 'string',
|
|
40
|
-
description: 'Optional contact ID from the contact graph. Used for grace period context.',
|
|
41
|
-
},
|
|
42
|
-
expected_response_hours: {
|
|
43
|
-
type: 'number',
|
|
44
|
-
description: 'Hours to wait for a response before marking as overdue. If omitted, no deadline is set.',
|
|
45
|
-
},
|
|
46
|
-
reminder_cron_id: {
|
|
47
|
-
type: 'string',
|
|
48
|
-
description: 'Optional cron job ID to fire a reminder when overdue',
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
required: ['channel', 'thread_id'],
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
class FollowUpCreateTool implements Tool {
|
|
56
|
-
name = 'followup_create';
|
|
57
|
-
description = definition.description;
|
|
58
|
-
category = 'followups';
|
|
59
|
-
defaultRiskLevel = RiskLevel.Low;
|
|
60
|
-
|
|
61
|
-
getDefinition(): ToolDefinition {
|
|
62
|
-
return definition;
|
|
22
|
+
export async function executeFollowupCreate(
|
|
23
|
+
input: Record<string, unknown>,
|
|
24
|
+
_context: ToolContext,
|
|
25
|
+
): Promise<ToolExecutionResult> {
|
|
26
|
+
const channel = input.channel as string | undefined;
|
|
27
|
+
if (!channel || typeof channel !== 'string' || channel.trim().length === 0) {
|
|
28
|
+
return { content: 'Error: channel is required and must be a non-empty string', isError: true };
|
|
63
29
|
}
|
|
64
30
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const threadId = input.thread_id as string | undefined;
|
|
72
|
-
if (!threadId || typeof threadId !== 'string' || threadId.trim().length === 0) {
|
|
73
|
-
return { content: 'Error: thread_id is required and must be a non-empty string', isError: true };
|
|
74
|
-
}
|
|
31
|
+
const threadId = input.thread_id as string | undefined;
|
|
32
|
+
if (!threadId || typeof threadId !== 'string' || threadId.trim().length === 0) {
|
|
33
|
+
return { content: 'Error: thread_id is required and must be a non-empty string', isError: true };
|
|
34
|
+
}
|
|
75
35
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
36
|
+
const contactId = input.contact_id as string | undefined;
|
|
37
|
+
const expectedResponseHours = input.expected_response_hours as number | undefined;
|
|
38
|
+
// Canonical: reminder_schedule_id; deprecated alias: reminder_cron_id
|
|
39
|
+
const reminderScheduleId = (input.reminder_schedule_id ?? input.reminder_cron_id) as string | undefined;
|
|
79
40
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
41
|
+
// Validate contact exists if provided
|
|
42
|
+
if (contactId) {
|
|
43
|
+
const contact = getContact(contactId);
|
|
44
|
+
if (!contact) {
|
|
45
|
+
return { content: `Error: Contact "${contactId}" not found`, isError: true };
|
|
86
46
|
}
|
|
47
|
+
}
|
|
87
48
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
49
|
+
if (expectedResponseHours !== undefined && (typeof expectedResponseHours !== 'number' || expectedResponseHours <= 0)) {
|
|
50
|
+
return { content: 'Error: expected_response_hours must be a positive number', isError: true };
|
|
51
|
+
}
|
|
91
52
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
53
|
+
try {
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
const expectedResponseBy = expectedResponseHours
|
|
56
|
+
? now + expectedResponseHours * 60 * 60 * 1000
|
|
57
|
+
: null;
|
|
97
58
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
59
|
+
const followUp = createFollowUp({
|
|
60
|
+
channel: channel.trim(),
|
|
61
|
+
threadId: threadId.trim(),
|
|
62
|
+
contactId: contactId ?? null,
|
|
63
|
+
sentAt: now,
|
|
64
|
+
expectedResponseBy,
|
|
65
|
+
reminderScheduleId: reminderScheduleId ?? null,
|
|
66
|
+
});
|
|
106
67
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
68
|
+
return {
|
|
69
|
+
content: `Created follow-up:\n${formatFollowUp(followUp)}`,
|
|
70
|
+
isError: false,
|
|
71
|
+
};
|
|
72
|
+
} catch (err) {
|
|
73
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
74
|
+
return { content: `Error: ${msg}`, isError: true };
|
|
115
75
|
}
|
|
116
76
|
}
|
|
117
|
-
|
|
118
|
-
export const followupCreateTool = new FollowUpCreateTool();
|