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
|
@@ -3,6 +3,7 @@ import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
|
|
|
3
3
|
import type { ToolDefinition } from '../../providers/types.js';
|
|
4
4
|
import { getConfig } from '../../config/loader.js';
|
|
5
5
|
import { getLogger } from '../../util/logger.js';
|
|
6
|
+
import { truncate } from '../../util/truncate.js';
|
|
6
7
|
import { getProfilePolicy } from '../../swarm/worker-backend.js';
|
|
7
8
|
import type { WorkerProfile } from '../../swarm/worker-backend.js';
|
|
8
9
|
|
|
@@ -22,6 +23,11 @@ const APPROVAL_REQUIRED_TOOLS = new Set([
|
|
|
22
23
|
|
|
23
24
|
const VALID_PROFILES: readonly WorkerProfile[] = ['general', 'researcher', 'coder', 'reviewer'];
|
|
24
25
|
|
|
26
|
+
// Maximum nesting depth for Claude Code subprocesses.
|
|
27
|
+
// Depth 0 = top-level assistant, depth 1 = first subprocess, etc.
|
|
28
|
+
const MAX_CLAUDE_CODE_DEPTH = 1;
|
|
29
|
+
const DEPTH_ENV_VAR = 'VELLUM_CLAUDE_CODE_DEPTH';
|
|
30
|
+
|
|
25
31
|
export const claudeCodeTool: Tool = {
|
|
26
32
|
name: 'claude_code',
|
|
27
33
|
description: 'Delegate a coding task to Claude Code, an AI-powered coding agent that can read, write, and edit files, run shell commands, and perform complex multi-step software engineering tasks autonomously.',
|
|
@@ -108,7 +114,10 @@ export const claudeCodeTool: Tool = {
|
|
|
108
114
|
|
|
109
115
|
const { query } = sdkModule;
|
|
110
116
|
|
|
111
|
-
|
|
117
|
+
// Collect stderr output from the Claude Code subprocess for debugging
|
|
118
|
+
const stderrLines: string[] = [];
|
|
119
|
+
|
|
120
|
+
log.info({ prompt: truncate(prompt, 100, ''), workingDir, model, resume: !!resumeSessionId }, 'Starting Claude Code session');
|
|
112
121
|
|
|
113
122
|
// Build the canUseTool callback, enforcing profile-based restrictions
|
|
114
123
|
const canUseTool: import('@anthropic-ai/claude-agent-sdk').CanUseTool = async (toolName, toolInput, _options) => {
|
|
@@ -151,6 +160,26 @@ export const claudeCodeTool: Tool = {
|
|
|
151
160
|
}
|
|
152
161
|
};
|
|
153
162
|
|
|
163
|
+
// Enforce nesting depth limit to prevent infinite recursion.
|
|
164
|
+
const currentDepth = parseInt(process.env[DEPTH_ENV_VAR] ?? '0', 10);
|
|
165
|
+
if (currentDepth >= MAX_CLAUDE_CODE_DEPTH) {
|
|
166
|
+
log.warn({ currentDepth, max: MAX_CLAUDE_CODE_DEPTH }, 'Claude Code nesting depth exceeded');
|
|
167
|
+
return {
|
|
168
|
+
content: `Error: Claude Code nesting depth exceeded (depth ${currentDepth}, max ${MAX_CLAUDE_CODE_DEPTH}). Cannot spawn another Claude Code subprocess.`,
|
|
169
|
+
isError: true,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Build a clean env for the subprocess. Strip the SDK's own nesting guard
|
|
174
|
+
// (CLAUDECODE) so it can launch, but set our depth counter to enforce our limit.
|
|
175
|
+
const subprocessEnv: Record<string, string | undefined> = {
|
|
176
|
+
...process.env,
|
|
177
|
+
ANTHROPIC_API_KEY: apiKey,
|
|
178
|
+
[DEPTH_ENV_VAR]: String(currentDepth + 1),
|
|
179
|
+
};
|
|
180
|
+
delete subprocessEnv.CLAUDECODE;
|
|
181
|
+
delete subprocessEnv.CLAUDE_CODE_ENTRYPOINT;
|
|
182
|
+
|
|
154
183
|
// Build query options
|
|
155
184
|
const queryOptions: import('@anthropic-ai/claude-agent-sdk').Options = {
|
|
156
185
|
cwd: workingDir,
|
|
@@ -158,12 +187,16 @@ export const claudeCodeTool: Tool = {
|
|
|
158
187
|
canUseTool,
|
|
159
188
|
permissionMode: 'default',
|
|
160
189
|
allowedTools: [...AUTO_APPROVE_TOOLS],
|
|
161
|
-
env:
|
|
162
|
-
...process.env,
|
|
163
|
-
ANTHROPIC_API_KEY: apiKey,
|
|
164
|
-
},
|
|
190
|
+
env: subprocessEnv,
|
|
165
191
|
maxTurns: 50,
|
|
166
192
|
persistSession: true,
|
|
193
|
+
stderr: (data: string) => {
|
|
194
|
+
const trimmed = data.trimEnd();
|
|
195
|
+
if (trimmed) {
|
|
196
|
+
stderrLines.push(trimmed);
|
|
197
|
+
log.debug({ stderr: trimmed }, 'Claude Code subprocess stderr');
|
|
198
|
+
}
|
|
199
|
+
},
|
|
167
200
|
};
|
|
168
201
|
|
|
169
202
|
if (resumeSessionId) {
|
|
@@ -179,6 +212,12 @@ export const claudeCodeTool: Tool = {
|
|
|
179
212
|
for await (const message of conversation) {
|
|
180
213
|
switch (message.type) {
|
|
181
214
|
case 'assistant': {
|
|
215
|
+
// Check for SDK-level errors on the assistant message
|
|
216
|
+
if (message.error) {
|
|
217
|
+
log.error({ error: message.error, sessionId: message.session_id }, 'Claude Code assistant message error');
|
|
218
|
+
hasError = true;
|
|
219
|
+
resultText += `\n\n[Claude Code error: ${message.error}]`;
|
|
220
|
+
}
|
|
182
221
|
// Extract text from assistant messages
|
|
183
222
|
if (message.message?.content) {
|
|
184
223
|
for (const block of message.message.content) {
|
|
@@ -193,22 +232,43 @@ export const claudeCodeTool: Tool = {
|
|
|
193
232
|
}
|
|
194
233
|
case 'result': {
|
|
195
234
|
sessionId = message.session_id;
|
|
235
|
+
const resultMeta = {
|
|
236
|
+
subtype: message.subtype,
|
|
237
|
+
numTurns: message.num_turns,
|
|
238
|
+
durationMs: message.duration_ms,
|
|
239
|
+
costUsd: message.total_cost_usd,
|
|
240
|
+
stopReason: message.stop_reason,
|
|
241
|
+
};
|
|
242
|
+
|
|
196
243
|
if (message.subtype === 'success') {
|
|
244
|
+
log.info(resultMeta, 'Claude Code session completed successfully');
|
|
197
245
|
if (message.result && !resultText) {
|
|
198
246
|
resultText = message.result;
|
|
199
247
|
}
|
|
200
248
|
} else {
|
|
201
|
-
// Error result
|
|
249
|
+
// Error result — surface the subtype and details
|
|
202
250
|
hasError = true;
|
|
203
251
|
const errors = message.errors ?? [];
|
|
252
|
+
const denials = message.permission_denials ?? [];
|
|
253
|
+
|
|
254
|
+
log.error({ ...resultMeta, errors, permissionDenials: denials.length }, 'Claude Code session failed');
|
|
255
|
+
|
|
256
|
+
const parts: string[] = [];
|
|
257
|
+
parts.push(`[${message.subtype}] (${message.num_turns} turns, ${(message.duration_ms / 1000).toFixed(1)}s)`);
|
|
204
258
|
if (errors.length > 0) {
|
|
205
|
-
|
|
259
|
+
parts.push(`Errors: ${errors.join('; ')}`);
|
|
260
|
+
}
|
|
261
|
+
if (denials.length > 0) {
|
|
262
|
+
const denialSummary = denials.map(d => `${d.tool_name}`).join(', ');
|
|
263
|
+
parts.push(`Permission denied: ${denialSummary}`);
|
|
206
264
|
}
|
|
265
|
+
resultText += `\n\n${parts.join('\n')}`;
|
|
207
266
|
}
|
|
208
267
|
break;
|
|
209
268
|
}
|
|
210
269
|
default:
|
|
211
|
-
//
|
|
270
|
+
// Log unhandled message types at debug level for diagnostics
|
|
271
|
+
log.debug({ messageType: message.type }, 'Claude Code unhandled message type');
|
|
212
272
|
break;
|
|
213
273
|
}
|
|
214
274
|
}
|
|
@@ -221,10 +281,16 @@ export const claudeCodeTool: Tool = {
|
|
|
221
281
|
isError: hasError,
|
|
222
282
|
};
|
|
223
283
|
} catch (err) {
|
|
224
|
-
const
|
|
225
|
-
|
|
284
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
285
|
+
const recentStderr = stderrLines.slice(-20);
|
|
286
|
+
log.error({ err, stderrTail: recentStderr }, 'Claude Code execution failed');
|
|
287
|
+
|
|
288
|
+
const parts = [`Claude Code error: ${errMessage}`];
|
|
289
|
+
if (recentStderr.length > 0) {
|
|
290
|
+
parts.push(`\nSubprocess stderr (last ${recentStderr.length} lines):\n${recentStderr.join('\n')}`);
|
|
291
|
+
}
|
|
226
292
|
return {
|
|
227
|
-
content:
|
|
293
|
+
content: parts.join(''),
|
|
228
294
|
isError: true,
|
|
229
295
|
};
|
|
230
296
|
}
|
|
@@ -1,87 +1,55 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
|
|
3
|
-
import type { ToolDefinition } from '../../providers/types.js';
|
|
4
|
-
import { registerTool } from '../registry.js';
|
|
1
|
+
import type { ToolContext, ToolExecutionResult } from '../types.js';
|
|
5
2
|
import { mergeContacts, getContact } from '../../contacts/contact-store.js';
|
|
6
3
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
keep_id: {
|
|
14
|
-
type: 'string',
|
|
15
|
-
description: 'ID of the contact to keep (the surviving contact)',
|
|
16
|
-
},
|
|
17
|
-
merge_id: {
|
|
18
|
-
type: 'string',
|
|
19
|
-
description: 'ID of the contact to merge into the kept contact (will be deleted)',
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
required: ['keep_id', 'merge_id'],
|
|
23
|
-
},
|
|
24
|
-
};
|
|
4
|
+
export async function executeContactMerge(
|
|
5
|
+
input: Record<string, unknown>,
|
|
6
|
+
_context: ToolContext,
|
|
7
|
+
): Promise<ToolExecutionResult> {
|
|
8
|
+
const keepId = input.keep_id as string | undefined;
|
|
9
|
+
const mergeId = input.merge_id as string | undefined;
|
|
25
10
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
getDefinition(): ToolDefinition {
|
|
33
|
-
return definition;
|
|
11
|
+
if (!keepId || typeof keepId !== 'string') {
|
|
12
|
+
return { content: 'Error: keep_id is required', isError: true };
|
|
13
|
+
}
|
|
14
|
+
if (!mergeId || typeof mergeId !== 'string') {
|
|
15
|
+
return { content: 'Error: merge_id is required', isError: true };
|
|
34
16
|
}
|
|
35
17
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (!keepId || typeof keepId !== 'string') {
|
|
41
|
-
return { content: 'Error: keep_id is required', isError: true };
|
|
42
|
-
}
|
|
43
|
-
if (!mergeId || typeof mergeId !== 'string') {
|
|
44
|
-
return { content: 'Error: merge_id is required', isError: true };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Show what will be merged for clarity
|
|
48
|
-
const keepContact = getContact(keepId);
|
|
49
|
-
const mergeContact = getContact(mergeId);
|
|
50
|
-
|
|
51
|
-
if (!keepContact) {
|
|
52
|
-
return { content: `Error: Contact "${keepId}" not found`, isError: true };
|
|
53
|
-
}
|
|
54
|
-
if (!mergeContact) {
|
|
55
|
-
return { content: `Error: Contact "${mergeId}" not found`, isError: true };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
const merged = mergeContacts(keepId, mergeId);
|
|
18
|
+
// Show what will be merged for clarity
|
|
19
|
+
const keepContact = getContact(keepId);
|
|
20
|
+
const mergeContact = getContact(mergeId);
|
|
60
21
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
22
|
+
if (!keepContact) {
|
|
23
|
+
return { content: `Error: Contact "${keepId}" not found`, isError: true };
|
|
24
|
+
}
|
|
25
|
+
if (!mergeContact) {
|
|
26
|
+
return { content: `Error: Contact "${mergeId}" not found`, isError: true };
|
|
27
|
+
}
|
|
64
28
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
29
|
+
try {
|
|
30
|
+
const merged = mergeContacts(keepId, mergeId);
|
|
31
|
+
|
|
32
|
+
const channelList = merged.channels
|
|
33
|
+
.map((ch) => ` - ${ch.type}: ${ch.address}${ch.isPrimary ? ' (primary)' : ''}`)
|
|
34
|
+
.join('\n');
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
`Merged "${mergeContact.displayName}" into "${keepContact.displayName}".`,
|
|
39
|
+
``,
|
|
40
|
+
`Surviving contact (${merged.id}):`,
|
|
41
|
+
` Name: ${merged.displayName}`,
|
|
42
|
+
` Importance: ${merged.importance.toFixed(2)}`,
|
|
43
|
+
` Interactions: ${merged.interactionCount}`,
|
|
44
|
+
merged.relationship ? ` Relationship: ${merged.relationship}` : null,
|
|
45
|
+
merged.channels.length > 0 ? ` Channels:\n${channelList}` : null,
|
|
46
|
+
``,
|
|
47
|
+
`Deleted contact: ${mergeContact.displayName} (${mergeId})`,
|
|
48
|
+
].filter(Boolean).join('\n'),
|
|
49
|
+
isError: false,
|
|
50
|
+
};
|
|
51
|
+
} catch (err) {
|
|
52
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
53
|
+
return { content: `Error: ${msg}`, isError: true };
|
|
84
54
|
}
|
|
85
55
|
}
|
|
86
|
-
|
|
87
|
-
registerTool(new ContactMergeTool());
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
|
|
3
|
-
import type { ToolDefinition } from '../../providers/types.js';
|
|
4
|
-
import { registerTool } from '../registry.js';
|
|
1
|
+
import type { ToolContext, ToolExecutionResult } from '../types.js';
|
|
5
2
|
import { searchContacts } from '../../contacts/contact-store.js';
|
|
6
3
|
import type { ContactWithChannels } from '../../contacts/types.js';
|
|
7
4
|
|
|
@@ -18,85 +15,44 @@ function formatContactSummary(c: ContactWithChannels): string {
|
|
|
18
15
|
return parts.join('\n');
|
|
19
16
|
}
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
type: 'string',
|
|
37
|
-
description: 'Filter by channel type when searching by address (email, slack, whatsapp, phone, etc.)',
|
|
38
|
-
},
|
|
39
|
-
relationship: {
|
|
40
|
-
type: 'string',
|
|
41
|
-
description: 'Filter by relationship type (exact match)',
|
|
42
|
-
},
|
|
43
|
-
limit: {
|
|
44
|
-
type: 'number',
|
|
45
|
-
description: 'Maximum results to return (default 20, max 100)',
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
required: [],
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
class ContactSearchTool implements Tool {
|
|
53
|
-
name = 'contact_search';
|
|
54
|
-
description = definition.description;
|
|
55
|
-
category = 'contacts';
|
|
56
|
-
defaultRiskLevel = RiskLevel.Low;
|
|
57
|
-
|
|
58
|
-
getDefinition(): ToolDefinition {
|
|
59
|
-
return definition;
|
|
18
|
+
export async function executeContactSearch(
|
|
19
|
+
input: Record<string, unknown>,
|
|
20
|
+
_context: ToolContext,
|
|
21
|
+
): Promise<ToolExecutionResult> {
|
|
22
|
+
const query = input.query as string | undefined;
|
|
23
|
+
const channelAddress = input.channel_address as string | undefined;
|
|
24
|
+
const channelType = input.channel_type as string | undefined;
|
|
25
|
+
const relationship = input.relationship as string | undefined;
|
|
26
|
+
const limit = input.limit as number | undefined;
|
|
27
|
+
|
|
28
|
+
if (!query && !channelAddress && !relationship) {
|
|
29
|
+
return {
|
|
30
|
+
content: 'Error: At least one search criterion is required (query, channel_address, or relationship)',
|
|
31
|
+
isError: true,
|
|
32
|
+
};
|
|
60
33
|
}
|
|
61
34
|
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
};
|
|
35
|
+
try {
|
|
36
|
+
const results = searchContacts({
|
|
37
|
+
query,
|
|
38
|
+
channelAddress,
|
|
39
|
+
channelType,
|
|
40
|
+
relationship,
|
|
41
|
+
limit,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (results.length === 0) {
|
|
45
|
+
return { content: 'No contacts found matching the search criteria.', isError: false };
|
|
74
46
|
}
|
|
75
47
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
channelAddress,
|
|
80
|
-
channelType,
|
|
81
|
-
relationship,
|
|
82
|
-
limit,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
if (results.length === 0) {
|
|
86
|
-
return { content: 'No contacts found matching the search criteria.', isError: false };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const lines = [`Found ${results.length} contact(s):\n`];
|
|
90
|
-
for (const contact of results) {
|
|
91
|
-
lines.push(formatContactSummary(contact));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return { content: lines.join('\n'), isError: false };
|
|
95
|
-
} catch (err) {
|
|
96
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
97
|
-
return { content: `Error: ${msg}`, isError: true };
|
|
48
|
+
const lines = [`Found ${results.length} contact(s):\n`];
|
|
49
|
+
for (const contact of results) {
|
|
50
|
+
lines.push(formatContactSummary(contact));
|
|
98
51
|
}
|
|
52
|
+
|
|
53
|
+
return { content: lines.join('\n'), isError: false };
|
|
54
|
+
} catch (err) {
|
|
55
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
56
|
+
return { content: `Error: ${msg}`, isError: true };
|
|
99
57
|
}
|
|
100
58
|
}
|
|
101
|
-
|
|
102
|
-
registerTool(new ContactSearchTool());
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
|
|
3
|
-
import type { ToolDefinition } from '../../providers/types.js';
|
|
4
|
-
import { registerTool } from '../registry.js';
|
|
1
|
+
import type { ToolContext, ToolExecutionResult } from '../types.js';
|
|
5
2
|
import { upsertContact } from '../../contacts/contact-store.js';
|
|
6
|
-
import { CHANNEL_TYPES } from '../../contacts/types.js';
|
|
7
3
|
|
|
8
4
|
function formatContact(c: ReturnType<typeof upsertContact>): string {
|
|
9
5
|
const lines = [
|
|
@@ -25,113 +21,44 @@ function formatContact(c: ReturnType<typeof upsertContact>): string {
|
|
|
25
21
|
return lines.join('\n');
|
|
26
22
|
}
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
type: 'string',
|
|
36
|
-
description: 'Contact ID to update. Omit to create a new contact (or auto-match by channel address).',
|
|
37
|
-
},
|
|
38
|
-
display_name: {
|
|
39
|
-
type: 'string',
|
|
40
|
-
description: 'Display name for the contact',
|
|
41
|
-
},
|
|
42
|
-
relationship: {
|
|
43
|
-
type: 'string',
|
|
44
|
-
description: 'Relationship type (e.g. colleague, friend, manager, client, family)',
|
|
45
|
-
},
|
|
46
|
-
importance: {
|
|
47
|
-
type: 'number',
|
|
48
|
-
description: 'Importance score 0-1 (default 0.5). Higher = more important.',
|
|
49
|
-
},
|
|
50
|
-
response_expectation: {
|
|
51
|
-
type: 'string',
|
|
52
|
-
description: 'Expected response speed (e.g. immediate, within_hours, within_day, casual)',
|
|
53
|
-
},
|
|
54
|
-
preferred_tone: {
|
|
55
|
-
type: 'string',
|
|
56
|
-
description: 'Preferred communication tone (e.g. formal, casual, friendly, professional)',
|
|
57
|
-
},
|
|
58
|
-
channels: {
|
|
59
|
-
type: 'array',
|
|
60
|
-
description: 'Communication channels for this contact',
|
|
61
|
-
items: {
|
|
62
|
-
type: 'object',
|
|
63
|
-
properties: {
|
|
64
|
-
type: {
|
|
65
|
-
type: 'string',
|
|
66
|
-
enum: [...CHANNEL_TYPES],
|
|
67
|
-
description: 'Channel type',
|
|
68
|
-
},
|
|
69
|
-
address: {
|
|
70
|
-
type: 'string',
|
|
71
|
-
description: 'Channel address (email address, Slack handle, phone number, etc.)',
|
|
72
|
-
},
|
|
73
|
-
is_primary: {
|
|
74
|
-
type: 'boolean',
|
|
75
|
-
description: 'Whether this is the primary channel for this type',
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
required: ['type', 'address'],
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
required: ['display_name'],
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
class ContactUpsertTool implements Tool {
|
|
87
|
-
name = 'contact_upsert';
|
|
88
|
-
description = definition.description;
|
|
89
|
-
category = 'contacts';
|
|
90
|
-
defaultRiskLevel = RiskLevel.Low;
|
|
91
|
-
|
|
92
|
-
getDefinition(): ToolDefinition {
|
|
93
|
-
return definition;
|
|
24
|
+
export async function executeContactUpsert(
|
|
25
|
+
input: Record<string, unknown>,
|
|
26
|
+
_context: ToolContext,
|
|
27
|
+
): Promise<ToolExecutionResult> {
|
|
28
|
+
const displayName = input.display_name as string | undefined;
|
|
29
|
+
if (!displayName || typeof displayName !== 'string' || displayName.trim().length === 0) {
|
|
30
|
+
return { content: 'Error: display_name is required and must be a non-empty string', isError: true };
|
|
94
31
|
}
|
|
95
32
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const importance = input.importance as number | undefined;
|
|
103
|
-
if (importance !== undefined && (typeof importance !== 'number' || importance < 0 || importance > 1)) {
|
|
104
|
-
return { content: 'Error: importance must be a number between 0 and 1', isError: true };
|
|
105
|
-
}
|
|
33
|
+
const importance = input.importance as number | undefined;
|
|
34
|
+
if (importance !== undefined && (typeof importance !== 'number' || importance < 0 || importance > 1)) {
|
|
35
|
+
return { content: 'Error: importance must be a number between 0 and 1', isError: true };
|
|
36
|
+
}
|
|
106
37
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
38
|
+
const rawChannels = input.channels as Array<{ type: string; address: string; is_primary?: boolean }> | undefined;
|
|
39
|
+
const channels = rawChannels?.map((ch) => ({
|
|
40
|
+
type: ch.type,
|
|
41
|
+
address: ch.address,
|
|
42
|
+
isPrimary: ch.is_primary,
|
|
43
|
+
}));
|
|
113
44
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
45
|
+
try {
|
|
46
|
+
const contact = upsertContact({
|
|
47
|
+
id: input.id as string | undefined,
|
|
48
|
+
displayName: displayName.trim(),
|
|
49
|
+
relationship: input.relationship as string | undefined,
|
|
50
|
+
importance,
|
|
51
|
+
responseExpectation: input.response_expectation as string | undefined,
|
|
52
|
+
preferredTone: input.preferred_tone as string | undefined,
|
|
53
|
+
channels,
|
|
54
|
+
});
|
|
124
55
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return { content: `Error: ${msg}`, isError: true };
|
|
133
|
-
}
|
|
56
|
+
return {
|
|
57
|
+
content: `${contact.created ? 'Created' : 'Updated'} contact:\n${formatContact(contact)}`,
|
|
58
|
+
isError: false,
|
|
59
|
+
};
|
|
60
|
+
} catch (err) {
|
|
61
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
62
|
+
return { content: `Error: ${msg}`, isError: true };
|
|
134
63
|
}
|
|
135
64
|
}
|
|
136
|
-
|
|
137
|
-
registerTool(new ContactUpsertTool());
|
|
@@ -95,15 +95,19 @@ function findStoredOAuthField(service: string, fieldNames: string[]): string | u
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
//
|
|
99
|
-
//
|
|
98
|
+
// Legacy fallback: check credential metadata on the access_token record.
|
|
99
|
+
// Older OAuth2 flows stored client_id/client_secret only in metadata JSON.
|
|
100
|
+
// New flows persist them in the keychain (checked above) for defense in depth.
|
|
100
101
|
const metadataKey = fieldNames.some((f) => f.includes('client_id'))
|
|
101
102
|
? 'oauth2ClientId' as const
|
|
102
103
|
: 'oauth2ClientSecret' as const;
|
|
103
104
|
for (const svc of servicesToCheck) {
|
|
104
105
|
const meta = getCredentialMetadata(svc, 'access_token');
|
|
105
106
|
const value = meta?.[metadataKey];
|
|
106
|
-
if (value)
|
|
107
|
+
if (value) {
|
|
108
|
+
log.debug({ service: svc, field: metadataKey }, 'OAuth client credential resolved from metadata (legacy fallback)');
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
return undefined;
|
|
@@ -569,7 +573,7 @@ class CredentialStoreTool implements Tool {
|
|
|
569
573
|
return { content: 'Error: failed to store access token in secure storage', isError: true };
|
|
570
574
|
}
|
|
571
575
|
|
|
572
|
-
const expiresAt = tokens.expiresIn ? Date.now() + tokens.expiresIn * 1000 :
|
|
576
|
+
const expiresAt = tokens.expiresIn ? Date.now() + tokens.expiresIn * 1000 : null;
|
|
573
577
|
|
|
574
578
|
let accountInfo: string | undefined;
|
|
575
579
|
if (userinfoUrl && grantedScopes.some((s) => s.includes('userinfo'))) {
|
|
@@ -586,6 +590,18 @@ class CredentialStoreTool implements Tool {
|
|
|
586
590
|
}
|
|
587
591
|
}
|
|
588
592
|
|
|
593
|
+
// Persist client credentials in keychain for defense in depth
|
|
594
|
+
const clientIdStored = setSecureKey(`credential:${service}:client_id`, clientId);
|
|
595
|
+
if (!clientIdStored) {
|
|
596
|
+
return { content: 'Error: failed to store client_id in secure storage', isError: true };
|
|
597
|
+
}
|
|
598
|
+
if (clientSecret) {
|
|
599
|
+
const clientSecretStored = setSecureKey(`credential:${service}:client_secret`, clientSecret);
|
|
600
|
+
if (!clientSecretStored) {
|
|
601
|
+
return { content: 'Error: failed to store client_secret in secure storage', isError: true };
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
589
605
|
upsertCredentialMetadata(service, 'access_token', {
|
|
590
606
|
allowedTools: allowedTools ?? [],
|
|
591
607
|
expiresAt,
|