vellum 0.2.0 → 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 +161 -34
- package/src/__tests__/account-registry.test.ts +2 -1
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- package/src/__tests__/app-bundler.test.ts +12 -33
- 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 +5 -8
- 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 +454 -0
- 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-state.test.ts +133 -0
- package/src/__tests__/call-store.test.ts +691 -0
- package/src/__tests__/cli-discover.test.ts +1 -1
- package/src/__tests__/commit-message-enrichment-service.test.ts +550 -0
- 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 +348 -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__/doordash-session.test.ts +9 -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 +96 -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 +17 -10
- 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 +222 -0
- package/src/__tests__/run-orchestrator.test.ts +7 -7
- package/src/__tests__/runtime-attachment-metadata.test.ts +19 -20
- package/src/__tests__/runtime-runs-http.test.ts +5 -23
- package/src/__tests__/runtime-runs.test.ts +11 -11
- 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 +89 -16
- 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 +273 -2
- 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 +403 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +141 -2
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/bundler/app-bundler.ts +35 -14
- package/src/calls/call-bridge.ts +95 -0
- package/src/calls/call-constants.ts +48 -0
- package/src/calls/call-domain.ts +276 -0
- package/src/calls/call-orchestrator.ts +390 -0
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-state.ts +64 -0
- package/src/calls/call-store.ts +416 -0
- package/src/calls/relay-server.ts +335 -0
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-config.ts +34 -0
- package/src/calls/twilio-provider.ts +173 -0
- package/src/calls/twilio-routes.ts +250 -0
- package/src/calls/types.ts +37 -0
- package/src/calls/voice-provider.ts +14 -0
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/core-commands.ts +776 -0
- package/src/cli/doordash.ts +256 -25
- 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 +163 -0
- 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.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -24
- 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 +44 -0
- package/src/config/loader.ts +4 -1
- package/src/config/schema.ts +218 -1
- package/src/config/system-prompt.ts +100 -6
- package/src/config/templates/IDENTITY.md +7 -0
- package/src/config/types.ts +5 -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 +192 -4
- 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 -271
- 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 +495 -39
- package/src/daemon/ipc-contract-inventory.json +40 -4
- package/src/daemon/ipc-contract.ts +185 -37
- package/src/daemon/ipc-protocol.ts +7 -2
- package/src/daemon/lifecycle.ts +48 -5
- package/src/daemon/main.ts +10 -4
- package/src/daemon/ride-shotgun-handler.ts +74 -10
- package/src/daemon/server.ts +144 -29
- 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 +222 -2
- package/src/daemon/session-usage.ts +0 -2
- package/src/daemon/session.ts +114 -1365
- 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 -1151
- package/src/media/gemini-image-service.ts +1 -1
- 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 +362 -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 +65 -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 +277 -25
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/routes/attachment-routes.ts +5 -6
- package/src/runtime/routes/call-routes.ts +140 -0
- package/src/runtime/routes/channel-routes.ts +12 -19
- package/src/runtime/routes/conversation-routes.ts +5 -9
- package/src/runtime/routes/run-routes.ts +4 -8
- package/src/runtime/run-orchestrator.ts +39 -6
- 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 +67 -0
- package/src/tools/calls/call-start.ts +73 -0
- package/src/tools/calls/call-status.ts +81 -0
- 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 +21 -5
- 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/registry.ts +2 -4
- 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 -6
- package/src/tools/tasks/task-delete.ts +69 -56
- package/src/tools/tasks/task-list.ts +31 -52
- package/src/tools/tasks/task-run.ts +74 -102
- package/src/tools/tasks/task-save.ts +33 -65
- package/src/tools/tasks/work-item-enqueue.ts +192 -134
- package/src/tools/tasks/work-item-list.ts +33 -78
- package/src/tools/tasks/work-item-remove.ts +60 -0
- package/src/tools/tasks/work-item-update.ts +114 -0
- package/src/tools/terminal/backends/native.ts +3 -1
- package/src/tools/tool-manifest.ts +20 -74
- package/src/tools/types.ts +6 -0
- package/src/tools/ui-surface/definitions.ts +6 -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 +236 -2
- package/src/workspace/commit-message-enrichment-service.ts +284 -0
- package/src/workspace/commit-message-provider.ts +95 -0
- package/src/workspace/git-service.ts +272 -52
- package/src/workspace/heartbeat-service.ts +70 -13
- package/src/workspace/provider-commit-message-generator.ts +242 -0
- package/src/workspace/turn-commit.ts +100 -51
- 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
|
@@ -7,7 +7,7 @@ import { validateAttachmentUpload, AttachmentUploadError } from '../../memory/at
|
|
|
7
7
|
/** 30 MB — base64-encoded 20 MB attachment ≈ 27 MB plus JSON wrapper overhead. */
|
|
8
8
|
const MAX_UPLOAD_BODY_BYTES = 30 * 1024 * 1024;
|
|
9
9
|
|
|
10
|
-
export async function handleUploadAttachment(
|
|
10
|
+
export async function handleUploadAttachment(req: Request): Promise<Response> {
|
|
11
11
|
const rawBody = await req.arrayBuffer();
|
|
12
12
|
if (rawBody.byteLength > MAX_UPLOAD_BODY_BYTES) {
|
|
13
13
|
return Response.json(
|
|
@@ -56,7 +56,6 @@ export async function handleUploadAttachment(assistantId: string, req: Request):
|
|
|
56
56
|
let attachment: attachmentsStore.StoredAttachment;
|
|
57
57
|
try {
|
|
58
58
|
attachment = attachmentsStore.uploadAttachment(
|
|
59
|
-
assistantId,
|
|
60
59
|
filename,
|
|
61
60
|
mimeType,
|
|
62
61
|
data,
|
|
@@ -78,7 +77,7 @@ export async function handleUploadAttachment(assistantId: string, req: Request):
|
|
|
78
77
|
});
|
|
79
78
|
}
|
|
80
79
|
|
|
81
|
-
export async function handleDeleteAttachment(
|
|
80
|
+
export async function handleDeleteAttachment(req: Request): Promise<Response> {
|
|
82
81
|
let body: { attachmentId?: string };
|
|
83
82
|
try {
|
|
84
83
|
body = await req.json() as { attachmentId?: string };
|
|
@@ -98,7 +97,7 @@ export async function handleDeleteAttachment(assistantId: string, req: Request):
|
|
|
98
97
|
);
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
const result = attachmentsStore.deleteAttachment(
|
|
100
|
+
const result = attachmentsStore.deleteAttachment(attachmentId);
|
|
102
101
|
|
|
103
102
|
if (result === 'not_found') {
|
|
104
103
|
return Response.json(
|
|
@@ -117,8 +116,8 @@ export async function handleDeleteAttachment(assistantId: string, req: Request):
|
|
|
117
116
|
return new Response(null, { status: 204 });
|
|
118
117
|
}
|
|
119
118
|
|
|
120
|
-
export function handleGetAttachment(
|
|
121
|
-
const attachment = attachmentsStore.getAttachmentById(
|
|
119
|
+
export function handleGetAttachment(attachmentId: string): Response {
|
|
120
|
+
const attachment = attachmentsStore.getAttachmentById(attachmentId);
|
|
122
121
|
if (!attachment) {
|
|
123
122
|
return Response.json({ error: 'Attachment not found' }, { status: 404 });
|
|
124
123
|
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime HTTP route handlers for the call API.
|
|
3
|
+
*
|
|
4
|
+
* POST /v1/calls/start — initiate a new call
|
|
5
|
+
* GET /v1/calls/:callSessionId — get call status
|
|
6
|
+
* POST /v1/calls/:callSessionId/cancel — cancel a call
|
|
7
|
+
* POST /v1/calls/:callSessionId/answer — answer a pending question
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { startCall, getCallStatus, cancelCall, answerCall } from '../../calls/call-domain.js';
|
|
11
|
+
import { getConfig } from '../../config/loader.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* POST /v1/calls/start
|
|
15
|
+
*
|
|
16
|
+
* Body: { phoneNumber: string; task: string; context?: string; conversationId: string }
|
|
17
|
+
*/
|
|
18
|
+
export async function handleStartCall(req: Request): Promise<Response> {
|
|
19
|
+
if (!getConfig().calls.enabled) {
|
|
20
|
+
return Response.json(
|
|
21
|
+
{ error: 'Calls feature is disabled via configuration. Set calls.enabled to true to use this feature.' },
|
|
22
|
+
{ status: 403 },
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let body: {
|
|
27
|
+
phoneNumber?: string;
|
|
28
|
+
task?: string;
|
|
29
|
+
context?: string;
|
|
30
|
+
conversationId?: string;
|
|
31
|
+
};
|
|
32
|
+
try {
|
|
33
|
+
body = await req.json() as typeof body;
|
|
34
|
+
} catch {
|
|
35
|
+
return Response.json({ error: 'Invalid JSON in request body' }, { status: 400 });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!body.conversationId) {
|
|
39
|
+
return Response.json({ error: 'conversationId is required' }, { status: 400 });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const result = await startCall({
|
|
43
|
+
phoneNumber: body.phoneNumber ?? '',
|
|
44
|
+
task: body.task ?? '',
|
|
45
|
+
context: body.context,
|
|
46
|
+
conversationId: body.conversationId,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!result.ok) {
|
|
50
|
+
return Response.json({ error: result.error }, { status: result.status ?? 500 });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return Response.json({
|
|
54
|
+
callSessionId: result.session.id,
|
|
55
|
+
callSid: result.callSid,
|
|
56
|
+
status: result.session.status,
|
|
57
|
+
toNumber: result.session.toNumber,
|
|
58
|
+
fromNumber: result.session.fromNumber,
|
|
59
|
+
}, { status: 201 });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* GET /v1/calls/:callSessionId
|
|
64
|
+
*/
|
|
65
|
+
export function handleGetCallStatus(callSessionId: string): Response {
|
|
66
|
+
const result = getCallStatus(callSessionId);
|
|
67
|
+
|
|
68
|
+
if (!result.ok) {
|
|
69
|
+
return Response.json({ error: result.error }, { status: result.status ?? 500 });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const { session } = result;
|
|
73
|
+
return Response.json({
|
|
74
|
+
callSessionId: session.id,
|
|
75
|
+
conversationId: session.conversationId,
|
|
76
|
+
status: session.status,
|
|
77
|
+
toNumber: session.toNumber,
|
|
78
|
+
fromNumber: session.fromNumber,
|
|
79
|
+
provider: session.provider,
|
|
80
|
+
providerCallSid: session.providerCallSid,
|
|
81
|
+
task: session.task,
|
|
82
|
+
startedAt: session.startedAt ? new Date(session.startedAt).toISOString() : null,
|
|
83
|
+
endedAt: session.endedAt ? new Date(session.endedAt).toISOString() : null,
|
|
84
|
+
lastError: session.lastError,
|
|
85
|
+
pendingQuestion: result.pendingQuestion ?? null,
|
|
86
|
+
createdAt: new Date(session.createdAt).toISOString(),
|
|
87
|
+
updatedAt: new Date(session.updatedAt).toISOString(),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* POST /v1/calls/:callSessionId/cancel
|
|
93
|
+
*
|
|
94
|
+
* Body: { reason?: string }
|
|
95
|
+
*/
|
|
96
|
+
export async function handleCancelCall(req: Request, callSessionId: string): Promise<Response> {
|
|
97
|
+
let reason: string | undefined;
|
|
98
|
+
try {
|
|
99
|
+
const body = await req.json() as { reason?: string };
|
|
100
|
+
reason = body.reason;
|
|
101
|
+
} catch {
|
|
102
|
+
// Empty body is fine
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const result = await cancelCall({ callSessionId, reason });
|
|
106
|
+
|
|
107
|
+
if (!result.ok) {
|
|
108
|
+
return Response.json({ error: result.error }, { status: result.status ?? 500 });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return Response.json({
|
|
112
|
+
callSessionId: result.session.id,
|
|
113
|
+
status: result.session.status,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* POST /v1/calls/:callSessionId/answer
|
|
119
|
+
*
|
|
120
|
+
* Body: { answer: string }
|
|
121
|
+
*/
|
|
122
|
+
export async function handleAnswerCall(req: Request, callSessionId: string): Promise<Response> {
|
|
123
|
+
let body: { answer?: string };
|
|
124
|
+
try {
|
|
125
|
+
body = await req.json() as typeof body;
|
|
126
|
+
} catch {
|
|
127
|
+
return Response.json({ error: 'Invalid JSON in request body' }, { status: 400 });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const result = await answerCall({
|
|
131
|
+
callSessionId,
|
|
132
|
+
answer: body.answer ?? '',
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (!result.ok) {
|
|
136
|
+
return Response.json({ error: result.error }, { status: result.status ?? 500 });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return Response.json({ ok: true, questionId: result.questionId });
|
|
140
|
+
}
|
|
@@ -18,7 +18,7 @@ import type {
|
|
|
18
18
|
|
|
19
19
|
const log = getLogger('runtime-http');
|
|
20
20
|
|
|
21
|
-
export async function handleDeleteConversation(
|
|
21
|
+
export async function handleDeleteConversation(req: Request): Promise<Response> {
|
|
22
22
|
const body = await req.json() as {
|
|
23
23
|
sourceChannel?: string;
|
|
24
24
|
externalChatId?: string;
|
|
@@ -34,13 +34,12 @@ export async function handleDeleteConversation(assistantId: string, req: Request
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const conversationKey = `${sourceChannel}:${externalChatId}`;
|
|
37
|
-
deleteConversationKey(
|
|
37
|
+
deleteConversationKey(conversationKey);
|
|
38
38
|
|
|
39
39
|
return Response.json({ ok: true });
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export async function handleChannelInbound(
|
|
43
|
-
assistantId: string,
|
|
44
43
|
req: Request,
|
|
45
44
|
processMessage?: MessageProcessor,
|
|
46
45
|
): Promise<Response> {
|
|
@@ -90,7 +89,7 @@ export async function handleChannelInbound(
|
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
if (hasAttachments) {
|
|
93
|
-
const resolved = attachmentsStore.getAttachmentsByIds(
|
|
92
|
+
const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds);
|
|
94
93
|
if (resolved.length !== attachmentIds.length) {
|
|
95
94
|
const resolvedIds = new Set(resolved.map((a) => a.id));
|
|
96
95
|
const missing = attachmentIds.filter((id) => !resolvedIds.has(id));
|
|
@@ -113,7 +112,6 @@ export async function handleChannelInbound(
|
|
|
113
112
|
if (isEdit && sourceMessageId) {
|
|
114
113
|
// Dedup the edit event itself (retried edited_message webhooks)
|
|
115
114
|
const editResult = channelDeliveryStore.recordInbound(
|
|
116
|
-
assistantId,
|
|
117
115
|
sourceChannel,
|
|
118
116
|
externalChatId,
|
|
119
117
|
externalMessageId,
|
|
@@ -137,7 +135,6 @@ export async function handleChannelInbound(
|
|
|
137
135
|
let original: { messageId: string; conversationId: string } | null = null;
|
|
138
136
|
for (let attempt = 0; attempt <= EDIT_LOOKUP_RETRIES; attempt++) {
|
|
139
137
|
original = channelDeliveryStore.findMessageBySourceId(
|
|
140
|
-
assistantId,
|
|
141
138
|
sourceChannel,
|
|
142
139
|
externalChatId,
|
|
143
140
|
sourceMessageId,
|
|
@@ -145,7 +142,7 @@ export async function handleChannelInbound(
|
|
|
145
142
|
if (original) break;
|
|
146
143
|
if (attempt < EDIT_LOOKUP_RETRIES) {
|
|
147
144
|
log.info(
|
|
148
|
-
{ assistantId, sourceMessageId, attempt: attempt + 1, maxAttempts: EDIT_LOOKUP_RETRIES },
|
|
145
|
+
{ assistantId: "self", sourceMessageId, attempt: attempt + 1, maxAttempts: EDIT_LOOKUP_RETRIES },
|
|
149
146
|
'Original message not linked yet, retrying edit lookup',
|
|
150
147
|
);
|
|
151
148
|
await new Promise((resolve) => setTimeout(resolve, EDIT_LOOKUP_DELAY_MS));
|
|
@@ -155,12 +152,12 @@ export async function handleChannelInbound(
|
|
|
155
152
|
if (original) {
|
|
156
153
|
conversationStore.updateMessageContent(original.messageId, content ?? '');
|
|
157
154
|
log.info(
|
|
158
|
-
{ assistantId, sourceMessageId, messageId: original.messageId },
|
|
155
|
+
{ assistantId: "self", sourceMessageId, messageId: original.messageId },
|
|
159
156
|
'Updated message content from edited_message',
|
|
160
157
|
);
|
|
161
158
|
} else {
|
|
162
159
|
log.warn(
|
|
163
|
-
{ assistantId, sourceChannel, externalChatId, sourceMessageId },
|
|
160
|
+
{ assistantId: "self", sourceChannel, externalChatId, sourceMessageId },
|
|
164
161
|
'Could not find original message for edit after retries, ignoring',
|
|
165
162
|
);
|
|
166
163
|
}
|
|
@@ -174,7 +171,6 @@ export async function handleChannelInbound(
|
|
|
174
171
|
|
|
175
172
|
// ── New message path ──
|
|
176
173
|
const result = channelDeliveryStore.recordInbound(
|
|
177
|
-
assistantId,
|
|
178
174
|
sourceChannel,
|
|
179
175
|
externalChatId,
|
|
180
176
|
externalMessageId,
|
|
@@ -221,7 +217,6 @@ export async function handleChannelInbound(
|
|
|
221
217
|
}
|
|
222
218
|
|
|
223
219
|
const { messageId: userMessageId } = await processMessage(
|
|
224
|
-
assistantId,
|
|
225
220
|
result.conversationId,
|
|
226
221
|
content ?? '',
|
|
227
222
|
hasAttachments ? attachmentIds : undefined,
|
|
@@ -241,7 +236,6 @@ export async function handleChannelInbound(
|
|
|
241
236
|
} catch (err) {
|
|
242
237
|
// Secret ingress blocks are not retryable — let the top-level handler return 422
|
|
243
238
|
if (err instanceof IngressBlockedError) throw err;
|
|
244
|
-
console.error(`[runtime-http] Processing failed`, err);
|
|
245
239
|
log.error({ err, conversationId: result.conversationId }, 'Failed to process channel inbound message');
|
|
246
240
|
channelDeliveryStore.recordProcessingFailure(result.eventId, err);
|
|
247
241
|
}
|
|
@@ -259,7 +253,7 @@ export async function handleChannelInbound(
|
|
|
259
253
|
try { parsed = JSON.parse(msgs[i].content); } catch { parsed = msgs[i].content; }
|
|
260
254
|
const rendered = renderHistoryContent(parsed);
|
|
261
255
|
|
|
262
|
-
const linked = attachmentsStore.getAttachmentMetadataForMessage(msgs[i].id
|
|
256
|
+
const linked = attachmentsStore.getAttachmentMetadataForMessage(msgs[i].id);
|
|
263
257
|
const replyAttachments: RuntimeAttachmentMetadata[] = linked.map((a) => ({
|
|
264
258
|
id: a.id,
|
|
265
259
|
filename: a.originalFilename,
|
|
@@ -291,12 +285,12 @@ export async function handleChannelInbound(
|
|
|
291
285
|
});
|
|
292
286
|
}
|
|
293
287
|
|
|
294
|
-
export function handleListDeadLetters(
|
|
295
|
-
const events = channelDeliveryStore.getDeadLetterEvents(
|
|
288
|
+
export function handleListDeadLetters(): Response {
|
|
289
|
+
const events = channelDeliveryStore.getDeadLetterEvents();
|
|
296
290
|
return Response.json({ events });
|
|
297
291
|
}
|
|
298
292
|
|
|
299
|
-
export async function handleReplayDeadLetters(
|
|
293
|
+
export async function handleReplayDeadLetters(req: Request): Promise<Response> {
|
|
300
294
|
const body = await req.json() as { eventIds?: string[] };
|
|
301
295
|
const eventIds = body.eventIds;
|
|
302
296
|
|
|
@@ -304,11 +298,11 @@ export async function handleReplayDeadLetters(assistantId: string, req: Request)
|
|
|
304
298
|
return Response.json({ error: 'eventIds array is required' }, { status: 400 });
|
|
305
299
|
}
|
|
306
300
|
|
|
307
|
-
const replayed = channelDeliveryStore.replayDeadLetters(
|
|
301
|
+
const replayed = channelDeliveryStore.replayDeadLetters(eventIds);
|
|
308
302
|
return Response.json({ replayed });
|
|
309
303
|
}
|
|
310
304
|
|
|
311
|
-
export async function handleChannelDeliveryAck(
|
|
305
|
+
export async function handleChannelDeliveryAck(req: Request): Promise<Response> {
|
|
312
306
|
const body = await req.json() as {
|
|
313
307
|
sourceChannel?: string;
|
|
314
308
|
externalChatId?: string;
|
|
@@ -328,7 +322,6 @@ export async function handleChannelDeliveryAck(assistantId: string, req: Request
|
|
|
328
322
|
}
|
|
329
323
|
|
|
330
324
|
const acked = channelDeliveryStore.acknowledgeDelivery(
|
|
331
|
-
assistantId,
|
|
332
325
|
sourceChannel,
|
|
333
326
|
externalChatId,
|
|
334
327
|
externalMessageId,
|
|
@@ -43,7 +43,6 @@ function getInterfaceFilesWithMtimes(interfacesDir: string | null): Array<{ path
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export function handleListMessages(
|
|
46
|
-
assistantId: string,
|
|
47
46
|
url: URL,
|
|
48
47
|
interfacesDir: string | null,
|
|
49
48
|
): Response {
|
|
@@ -55,7 +54,7 @@ export function handleListMessages(
|
|
|
55
54
|
);
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
const mapping = getConversationByKey(
|
|
57
|
+
const mapping = getConversationByKey(conversationKey);
|
|
59
58
|
if (!mapping) {
|
|
60
59
|
return Response.json({ messages: [] });
|
|
61
60
|
}
|
|
@@ -90,7 +89,7 @@ export function handleListMessages(
|
|
|
90
89
|
const messages: RuntimeMessagePayload[] = merged.map((m) => {
|
|
91
90
|
let msgAttachments: RuntimeAttachmentMetadata[] = [];
|
|
92
91
|
if (m.role === 'assistant' && m.id) {
|
|
93
|
-
const linked = attachmentsStore.getAttachmentMetadataForMessage(m.id
|
|
92
|
+
const linked = attachmentsStore.getAttachmentMetadataForMessage(m.id);
|
|
94
93
|
if (linked.length > 0) {
|
|
95
94
|
msgAttachments = linked.map((a) => ({
|
|
96
95
|
id: a.id,
|
|
@@ -129,7 +128,6 @@ export function handleListMessages(
|
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
export async function handleSendMessage(
|
|
132
|
-
assistantId: string,
|
|
133
131
|
req: Request,
|
|
134
132
|
deps: {
|
|
135
133
|
processMessage?: MessageProcessor;
|
|
@@ -172,7 +170,7 @@ export async function handleSendMessage(
|
|
|
172
170
|
|
|
173
171
|
// Validate that all attachment IDs resolve
|
|
174
172
|
if (hasAttachments) {
|
|
175
|
-
const resolved = attachmentsStore.getAttachmentsByIds(
|
|
173
|
+
const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds);
|
|
176
174
|
if (resolved.length !== attachmentIds.length) {
|
|
177
175
|
const resolvedIds = new Set(resolved.map((a) => a.id));
|
|
178
176
|
const missing = attachmentIds.filter((id) => !resolvedIds.has(id));
|
|
@@ -183,7 +181,7 @@ export async function handleSendMessage(
|
|
|
183
181
|
}
|
|
184
182
|
}
|
|
185
183
|
|
|
186
|
-
const mapping = getOrCreateConversation(
|
|
184
|
+
const mapping = getOrCreateConversation(conversationKey);
|
|
187
185
|
|
|
188
186
|
const processor = deps.persistAndProcessMessage ?? deps.processMessage;
|
|
189
187
|
if (!processor) {
|
|
@@ -192,7 +190,6 @@ export async function handleSendMessage(
|
|
|
192
190
|
|
|
193
191
|
try {
|
|
194
192
|
const result = await processor(
|
|
195
|
-
assistantId,
|
|
196
193
|
mapping.conversationId,
|
|
197
194
|
content ?? '',
|
|
198
195
|
hasAttachments ? attachmentIds : undefined,
|
|
@@ -236,7 +233,6 @@ async function generateLlmSuggestion(provider: Provider, assistantText: string):
|
|
|
236
233
|
}
|
|
237
234
|
|
|
238
235
|
export async function handleGetSuggestion(
|
|
239
|
-
assistantId: string,
|
|
240
236
|
url: URL,
|
|
241
237
|
deps: {
|
|
242
238
|
suggestionCache: Map<string, string>;
|
|
@@ -251,7 +247,7 @@ export async function handleGetSuggestion(
|
|
|
251
247
|
);
|
|
252
248
|
}
|
|
253
249
|
|
|
254
|
-
const mapping = getConversationByKey(
|
|
250
|
+
const mapping = getConversationByKey(conversationKey);
|
|
255
251
|
if (!mapping) {
|
|
256
252
|
return Response.json({ suggestion: null, messageId: null, source: 'none' as const });
|
|
257
253
|
}
|
|
@@ -11,7 +11,6 @@ import type { RunOrchestrator } from '../run-orchestrator.js';
|
|
|
11
11
|
const log = getLogger('runtime-http');
|
|
12
12
|
|
|
13
13
|
export async function handleCreateRun(
|
|
14
|
-
assistantId: string,
|
|
15
14
|
req: Request,
|
|
16
15
|
runOrchestrator: RunOrchestrator,
|
|
17
16
|
): Promise<Response> {
|
|
@@ -39,7 +38,7 @@ export async function handleCreateRun(
|
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
if (hasAttachments) {
|
|
42
|
-
const resolved = attachmentsStore.getAttachmentsByIds(
|
|
41
|
+
const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds);
|
|
43
42
|
if (resolved.length !== attachmentIds.length) {
|
|
44
43
|
const resolvedIds = new Set(resolved.map((a) => a.id));
|
|
45
44
|
const missing = attachmentIds.filter((id) => !resolvedIds.has(id));
|
|
@@ -50,11 +49,10 @@ export async function handleCreateRun(
|
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
const mapping = getOrCreateConversation(
|
|
52
|
+
const mapping = getOrCreateConversation(conversationKey);
|
|
54
53
|
|
|
55
54
|
try {
|
|
56
55
|
const run = await runOrchestrator.startRun(
|
|
57
|
-
assistantId,
|
|
58
56
|
mapping.conversationId,
|
|
59
57
|
content ?? '',
|
|
60
58
|
hasAttachments ? attachmentIds : undefined,
|
|
@@ -77,12 +75,11 @@ export async function handleCreateRun(
|
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
export function handleGetRun(
|
|
80
|
-
assistantId: string,
|
|
81
78
|
runId: string,
|
|
82
79
|
runOrchestrator: RunOrchestrator,
|
|
83
80
|
): Response {
|
|
84
81
|
const run = runOrchestrator.getRun(runId);
|
|
85
|
-
if (!run
|
|
82
|
+
if (!run) {
|
|
86
83
|
return Response.json({ error: 'Run not found' }, { status: 404 });
|
|
87
84
|
}
|
|
88
85
|
|
|
@@ -98,13 +95,12 @@ export function handleGetRun(
|
|
|
98
95
|
}
|
|
99
96
|
|
|
100
97
|
export async function handleRunDecision(
|
|
101
|
-
assistantId: string,
|
|
102
98
|
runId: string,
|
|
103
99
|
req: Request,
|
|
104
100
|
runOrchestrator: RunOrchestrator,
|
|
105
101
|
): Promise<Response> {
|
|
106
102
|
const run = runOrchestrator.getRun(runId);
|
|
107
|
-
if (!run
|
|
103
|
+
if (!run) {
|
|
108
104
|
return Response.json({ error: 'Run not found' }, { status: 404 });
|
|
109
105
|
}
|
|
110
106
|
|
|
@@ -14,10 +14,13 @@ import * as runsStore from '../memory/runs-store.js';
|
|
|
14
14
|
import type { Run } from '../memory/runs-store.js';
|
|
15
15
|
import type { Session } from '../daemon/session.js';
|
|
16
16
|
import type { ServerMessage } from '../daemon/ipc-protocol.js';
|
|
17
|
+
import { resolveChannelCapabilities } from '../daemon/session-runtime-assembly.js';
|
|
17
18
|
import type { UserDecision } from '../permissions/types.js';
|
|
18
19
|
import { checkIngressForSecrets } from '../security/secret-ingress.js';
|
|
19
20
|
import { IngressBlockedError } from '../util/errors.js';
|
|
20
21
|
import { getLogger } from '../util/logger.js';
|
|
22
|
+
import { assistantEventHub } from './assistant-event-hub.js';
|
|
23
|
+
import { buildAssistantEvent } from './assistant-event.js';
|
|
21
24
|
|
|
22
25
|
const log = getLogger('run-orchestrator');
|
|
23
26
|
|
|
@@ -32,7 +35,7 @@ interface PendingRunState {
|
|
|
32
35
|
|
|
33
36
|
export interface RunOrchestratorDeps {
|
|
34
37
|
getOrCreateSession: (conversationId: string) => Promise<Session>;
|
|
35
|
-
resolveAttachments: (
|
|
38
|
+
resolveAttachments: (attachmentIds: string[]) => Array<{
|
|
36
39
|
id: string;
|
|
37
40
|
filename: string;
|
|
38
41
|
mimeType: string;
|
|
@@ -64,7 +67,6 @@ export class RunOrchestrator {
|
|
|
64
67
|
* and fire the agent loop in the background.
|
|
65
68
|
*/
|
|
66
69
|
async startRun(
|
|
67
|
-
assistantId: string,
|
|
68
70
|
conversationId: string,
|
|
69
71
|
content: string,
|
|
70
72
|
attachmentIds?: string[],
|
|
@@ -82,15 +84,34 @@ export class RunOrchestrator {
|
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
const attachments = attachmentIds
|
|
85
|
-
? this.deps.resolveAttachments(
|
|
87
|
+
? this.deps.resolveAttachments(attachmentIds)
|
|
86
88
|
: [];
|
|
87
89
|
|
|
88
90
|
const requestId = crypto.randomUUID();
|
|
89
91
|
const messageId = session.persistUserMessage(content, attachments, requestId);
|
|
90
|
-
const run = runsStore.createRun(
|
|
92
|
+
const run = runsStore.createRun(conversationId, messageId);
|
|
93
|
+
|
|
94
|
+
// Runs are always HTTP-originated; set channel capabilities so the attachment
|
|
95
|
+
// scope heuristic resolves to 'self' rather than 'local-assistant'.
|
|
96
|
+
session.setChannelCapabilities(resolveChannelCapabilities('http-api'));
|
|
97
|
+
|
|
98
|
+
// Serialized publish chain so hub subscribers observe events in order.
|
|
99
|
+
let hubChain: Promise<void> = Promise.resolve();
|
|
100
|
+
const publishToHub = (msg: ServerMessage): void => {
|
|
101
|
+
const msgRecord = msg as unknown as Record<string, unknown>;
|
|
102
|
+
const msgSessionId =
|
|
103
|
+
'sessionId' in msg && typeof msgRecord.sessionId === 'string'
|
|
104
|
+
? (msgRecord.sessionId as string)
|
|
105
|
+
: undefined;
|
|
106
|
+
const resolvedSessionId = msgSessionId ?? conversationId;
|
|
107
|
+
const event = buildAssistantEvent('self', msg, resolvedSessionId);
|
|
108
|
+
hubChain = hubChain
|
|
109
|
+
.then(() => assistantEventHub.publish(event))
|
|
110
|
+
.catch((err: unknown) => {
|
|
111
|
+
log.warn({ err }, 'assistant-events hub subscriber threw during HTTP run');
|
|
112
|
+
});
|
|
113
|
+
};
|
|
91
114
|
|
|
92
|
-
// Set the assistant ID so attachments are scoped correctly.
|
|
93
|
-
session.setAssistantId(assistantId);
|
|
94
115
|
|
|
95
116
|
// Hook into session to intercept confirmation_request events.
|
|
96
117
|
// When the prompter sends a confirmation_request, we record it in the
|
|
@@ -118,11 +139,17 @@ export class RunOrchestrator {
|
|
|
118
139
|
session,
|
|
119
140
|
});
|
|
120
141
|
}
|
|
142
|
+
// Mirror every outbound message to the assistant-events hub so SSE
|
|
143
|
+
// subscribers receive the same payload parity as IPC clients.
|
|
144
|
+
publishToHub(msg);
|
|
121
145
|
});
|
|
122
146
|
|
|
123
147
|
// Fire-and-forget the agent loop
|
|
124
148
|
const cleanup = () => {
|
|
125
149
|
this.pending.delete(run.id);
|
|
150
|
+
// Reset channel capabilities so a subsequent IPC/desktop session on the
|
|
151
|
+
// same conversation is not incorrectly treated as an HTTP-API client.
|
|
152
|
+
session.setChannelCapabilities(null);
|
|
126
153
|
// Reset the session's client callback to a no-op so the stale
|
|
127
154
|
// closure doesn't intercept events from future runs on the same session.
|
|
128
155
|
// Set hasNoClient=true here since the run is done and no HTTP caller
|
|
@@ -136,6 +163,12 @@ export class RunOrchestrator {
|
|
|
136
163
|
} else if (msg.type === 'session_error') {
|
|
137
164
|
lastError = msg.userMessage;
|
|
138
165
|
}
|
|
166
|
+
// Mirror agent-loop events (assistant_text_delta, message_complete,
|
|
167
|
+
// tool_use_start, tool_result, etc.) to the hub. These travel through
|
|
168
|
+
// the onEvent path, distinct from the updateClient path used by the
|
|
169
|
+
// prompter (confirmation_request). Both paths must publish so SSE
|
|
170
|
+
// consumers receive the full response stream.
|
|
171
|
+
publishToHub(msg);
|
|
139
172
|
}).then(() => {
|
|
140
173
|
if (lastError) {
|
|
141
174
|
log.error({ runId: run.id, error: lastError }, 'Run failed (error event from agent loop)');
|