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,7 +3,8 @@ import type { UserMessageAttachment } from './ipc-protocol.js';
|
|
|
3
3
|
import { check, classifyRisk, generateAllowlistOptions, generateScopeOptions } from '../permissions/checker.js';
|
|
4
4
|
import { addRule } from '../permissions/trust-store.js';
|
|
5
5
|
import type { PermissionPrompter } from '../permissions/prompter.js';
|
|
6
|
-
import { uploadAttachment, linkAttachmentToMessage, AttachmentUploadError } from '../memory/attachments-store.js';
|
|
6
|
+
import { uploadAttachment, linkAttachmentToMessage, setAttachmentThumbnail, AttachmentUploadError } from '../memory/attachments-store.js';
|
|
7
|
+
import { generateVideoThumbnail } from './video-thumbnail.js';
|
|
7
8
|
import {
|
|
8
9
|
resolveDirectives,
|
|
9
10
|
contentBlocksToDrafts,
|
|
@@ -92,7 +93,6 @@ export async function resolveAssistantAttachments(
|
|
|
92
93
|
workingDir: string,
|
|
93
94
|
approveHostRead: ApproveHostRead,
|
|
94
95
|
lastAssistantMessageId: string | undefined,
|
|
95
|
-
assistantScope: string,
|
|
96
96
|
): Promise<AttachmentResolutionResult> {
|
|
97
97
|
let assistantAttachments: AssistantAttachmentDraft[] = [];
|
|
98
98
|
const emittedAttachments: UserMessageAttachment[] = [];
|
|
@@ -131,14 +131,17 @@ export async function resolveAssistantAttachments(
|
|
|
131
131
|
log.info('No directives or tool content blocks to resolve');
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
// Persist resolved attachments and link to the last assistant message
|
|
134
|
+
// Persist resolved attachments and link to the last assistant message.
|
|
135
|
+
// Large video attachments are omitted from the IPC payload and lazy-loaded
|
|
136
|
+
// by the client via the HTTP endpoint (same pattern as history_response).
|
|
137
|
+
const MAX_INLINE_B64_SIZE = 512 * 1024;
|
|
138
|
+
|
|
135
139
|
if (assistantAttachments.length > 0 && lastAssistantMessageId) {
|
|
136
140
|
for (let i = 0; i < assistantAttachments.length; i++) {
|
|
137
141
|
const draft = assistantAttachments[i];
|
|
138
142
|
let stored;
|
|
139
143
|
try {
|
|
140
144
|
stored = uploadAttachment(
|
|
141
|
-
assistantScope,
|
|
142
145
|
draft.filename,
|
|
143
146
|
draft.mimeType,
|
|
144
147
|
draft.dataBase64,
|
|
@@ -152,11 +155,31 @@ export async function resolveAssistantAttachments(
|
|
|
152
155
|
throw err;
|
|
153
156
|
}
|
|
154
157
|
linkAttachmentToMessage(lastAssistantMessageId, stored.id, i);
|
|
158
|
+
const isVideo = draft.mimeType.startsWith('video/');
|
|
159
|
+
const omitData = isVideo && draft.dataBase64.length > MAX_INLINE_B64_SIZE;
|
|
160
|
+
|
|
161
|
+
// Generate and persist a thumbnail for video attachments.
|
|
162
|
+
let thumbnailData: string | undefined;
|
|
163
|
+
if (isVideo) {
|
|
164
|
+
const existing = stored.thumbnailBase64;
|
|
165
|
+
if (existing) {
|
|
166
|
+
thumbnailData = existing;
|
|
167
|
+
} else {
|
|
168
|
+
const generated = await generateVideoThumbnail(draft.dataBase64);
|
|
169
|
+
if (generated) {
|
|
170
|
+
setAttachmentThumbnail(stored.id, generated);
|
|
171
|
+
thumbnailData = generated;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
155
176
|
emittedAttachments.push({
|
|
156
177
|
id: stored.id,
|
|
157
178
|
filename: draft.filename,
|
|
158
179
|
mimeType: draft.mimeType,
|
|
159
|
-
data: draft.dataBase64,
|
|
180
|
+
data: omitData ? '' : draft.dataBase64,
|
|
181
|
+
...(omitData ? { sizeBytes: draft.sizeBytes } : {}),
|
|
182
|
+
...(thumbnailData ? { thumbnailData } : {}),
|
|
160
183
|
});
|
|
161
184
|
}
|
|
162
185
|
} else if (assistantAttachments.length > 0) {
|
|
@@ -41,7 +41,16 @@ const CONTEXT_TOO_LARGE_PATTERNS = [
|
|
|
41
41
|
/prompt.?is.?too.?long/i,
|
|
42
42
|
/request too large/i,
|
|
43
43
|
/too many.*input.*tokens/i,
|
|
44
|
-
/max_tokens/i,
|
|
44
|
+
/max_tokens.*exceeded/i,
|
|
45
|
+
/exceeded.*max_tokens/i,
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// Generic timeout patterns — checked after NETWORK_PATTERNS and PROVIDER_API_PATTERNS
|
|
49
|
+
// so that "connection timeout" → PROVIDER_NETWORK and "gateway timeout" → PROVIDER_API
|
|
50
|
+
const TIMEOUT_PATTERNS = [
|
|
51
|
+
/\btimeout\b/i,
|
|
52
|
+
/deadline.?exceeded/i,
|
|
53
|
+
/request.?timed?.?out/i,
|
|
45
54
|
];
|
|
46
55
|
|
|
47
56
|
// Provider API error patterns (5xx, server error, etc.)
|
|
@@ -210,7 +219,7 @@ function classifyByMessage(message: string): Omit<ClassifiedSessionError, 'debug
|
|
|
210
219
|
}
|
|
211
220
|
}
|
|
212
221
|
|
|
213
|
-
// Network errors
|
|
222
|
+
// Network errors (before timeout so "connection timeout" is classified as network)
|
|
214
223
|
for (const pattern of NETWORK_PATTERNS) {
|
|
215
224
|
if (pattern.test(message)) {
|
|
216
225
|
return {
|
|
@@ -221,7 +230,7 @@ function classifyByMessage(message: string): Omit<ClassifiedSessionError, 'debug
|
|
|
221
230
|
}
|
|
222
231
|
}
|
|
223
232
|
|
|
224
|
-
// Provider API errors (
|
|
233
|
+
// Provider API errors (before timeout so "gateway timeout" keeps its specific message)
|
|
225
234
|
for (const pattern of PROVIDER_API_PATTERNS) {
|
|
226
235
|
if (pattern.test(message)) {
|
|
227
236
|
return {
|
|
@@ -232,6 +241,18 @@ function classifyByMessage(message: string): Omit<ClassifiedSessionError, 'debug
|
|
|
232
241
|
}
|
|
233
242
|
}
|
|
234
243
|
|
|
244
|
+
// Generic timeout errors (checked after network and provider API patterns so
|
|
245
|
+
// specific timeouts like "connection timeout" and "gateway timeout" aren't misclassified)
|
|
246
|
+
for (const pattern of TIMEOUT_PATTERNS) {
|
|
247
|
+
if (pattern.test(message)) {
|
|
248
|
+
return {
|
|
249
|
+
code: 'PROVIDER_API',
|
|
250
|
+
userMessage: 'The request took too long. This is usually temporary — try again shortly.',
|
|
251
|
+
retryable: true,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
235
256
|
// Non-user abort/failure (e.g. AbortError from internal logic, not user cancel)
|
|
236
257
|
for (const pattern of CANCEL_PATTERNS) {
|
|
237
258
|
if (pattern.test(message)) {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session lifecycle methods extracted from Session: loadFromDb, abort,
|
|
3
|
+
* and dispose. Each operates on a context interface so the Session class
|
|
4
|
+
* can delegate without exposing its full surface.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Message, ContentBlock } from '../providers/types.js';
|
|
8
|
+
import type { UsageStats, SurfaceType, SurfaceData } from './ipc-protocol.js';
|
|
9
|
+
import { repairHistory } from './history-repair.js';
|
|
10
|
+
import { createContextSummaryMessage } from '../context/window-manager.js';
|
|
11
|
+
import * as conversationStore from '../memory/conversation-store.js';
|
|
12
|
+
import type { PermissionPrompter } from '../permissions/prompter.js';
|
|
13
|
+
import type { SecretPrompter } from '../permissions/secret-prompter.js';
|
|
14
|
+
import type { ToolProfiler } from '../events/tool-profiling-listener.js';
|
|
15
|
+
import type { EventBus } from '../events/bus.js';
|
|
16
|
+
import type { AssistantDomainEvents } from '../events/domain-events.js';
|
|
17
|
+
import type { MessageQueue } from './session-queue-manager.js';
|
|
18
|
+
import { getHookManager } from '../hooks/manager.js';
|
|
19
|
+
import { getLogger } from '../util/logger.js';
|
|
20
|
+
import { unregisterWatchNotifiers, unregisterCallNotifiers } from './session-notifiers.js';
|
|
21
|
+
import { unregisterSessionSender } from '../tools/browser/browser-screencast.js';
|
|
22
|
+
import { resetSkillToolProjection } from './session-skill-tools.js';
|
|
23
|
+
|
|
24
|
+
const log = getLogger('session-lifecycle');
|
|
25
|
+
|
|
26
|
+
// ── Context Interfaces ───────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
export interface LoadFromDbContext {
|
|
29
|
+
readonly conversationId: string;
|
|
30
|
+
messages: Message[];
|
|
31
|
+
usageStats: UsageStats;
|
|
32
|
+
contextCompactedMessageCount: number;
|
|
33
|
+
contextCompactedAt: number | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AbortContext {
|
|
37
|
+
readonly conversationId: string;
|
|
38
|
+
processing: boolean;
|
|
39
|
+
abortController: AbortController | null;
|
|
40
|
+
prompter: PermissionPrompter;
|
|
41
|
+
secretPrompter: SecretPrompter;
|
|
42
|
+
pendingSurfaceActions: Map<string, { surfaceType: SurfaceType }>;
|
|
43
|
+
surfaceState: Map<string, { surfaceType: SurfaceType; data: SurfaceData }>;
|
|
44
|
+
readonly queue: MessageQueue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface DisposeContext extends AbortContext {
|
|
48
|
+
eventBus: EventBus<AssistantDomainEvents>;
|
|
49
|
+
readonly skillProjectionState: Map<string, string>;
|
|
50
|
+
profiler: ToolProfiler;
|
|
51
|
+
messages: Message[];
|
|
52
|
+
surfaceUndoStacks: Map<string, string[]>;
|
|
53
|
+
currentTurnSurfaces: Array<unknown>;
|
|
54
|
+
lastSurfaceAction: Map<string, unknown>;
|
|
55
|
+
workspaceTopLevelContext: string | null;
|
|
56
|
+
abort(): void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── loadFromDb ───────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
export async function loadFromDb(ctx: LoadFromDbContext): Promise<void> {
|
|
62
|
+
const dbMessages = conversationStore.getMessages(ctx.conversationId);
|
|
63
|
+
|
|
64
|
+
const conv = conversationStore.getConversation(ctx.conversationId);
|
|
65
|
+
const contextSummary = conv?.contextSummary?.trim() || null;
|
|
66
|
+
ctx.contextCompactedMessageCount = Math.max(
|
|
67
|
+
0,
|
|
68
|
+
Math.min(conv?.contextCompactedMessageCount ?? 0, dbMessages.length),
|
|
69
|
+
);
|
|
70
|
+
ctx.contextCompactedAt = conv?.contextCompactedAt ?? null;
|
|
71
|
+
|
|
72
|
+
const parsedMessages: Message[] = dbMessages
|
|
73
|
+
.slice(ctx.contextCompactedMessageCount)
|
|
74
|
+
.map((m) => {
|
|
75
|
+
const role = m.role as 'user' | 'assistant';
|
|
76
|
+
let content: ContentBlock[];
|
|
77
|
+
try {
|
|
78
|
+
const parsed = JSON.parse(m.content);
|
|
79
|
+
content = Array.isArray(parsed) ? parsed : [{ type: 'text', text: m.content }];
|
|
80
|
+
} catch {
|
|
81
|
+
log.warn({ conversationId: ctx.conversationId, messageId: m.id }, 'Invalid JSON in persisted message content, replacing with safe text block');
|
|
82
|
+
content = [{ type: 'text', text: m.content }];
|
|
83
|
+
}
|
|
84
|
+
return { role, content };
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const { messages: repairedMessages, stats } = repairHistory(parsedMessages);
|
|
88
|
+
if (stats.assistantToolResultsMigrated > 0 || stats.missingToolResultsInserted > 0 || stats.orphanToolResultsDowngraded > 0 || stats.consecutiveSameRoleMerged > 0) {
|
|
89
|
+
log.warn({ conversationId: ctx.conversationId, phase: 'load', ...stats }, 'Repaired persisted history');
|
|
90
|
+
}
|
|
91
|
+
ctx.messages = repairedMessages;
|
|
92
|
+
|
|
93
|
+
if (contextSummary) {
|
|
94
|
+
ctx.messages.unshift(createContextSummaryMessage(contextSummary));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (conv) {
|
|
98
|
+
ctx.usageStats = {
|
|
99
|
+
inputTokens: conv.totalInputTokens,
|
|
100
|
+
outputTokens: conv.totalOutputTokens,
|
|
101
|
+
estimatedCost: conv.totalEstimatedCost,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
log.info({ conversationId: ctx.conversationId, count: ctx.messages.length }, 'Loaded messages from DB');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── abort ─────────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
export function abortSession(ctx: AbortContext): void {
|
|
111
|
+
if (ctx.processing) {
|
|
112
|
+
log.info({ conversationId: ctx.conversationId }, 'Aborting in-flight processing');
|
|
113
|
+
ctx.abortController?.abort();
|
|
114
|
+
ctx.prompter.dispose();
|
|
115
|
+
ctx.secretPrompter.dispose();
|
|
116
|
+
ctx.pendingSurfaceActions.clear();
|
|
117
|
+
ctx.surfaceState.clear();
|
|
118
|
+
unregisterWatchNotifiers(ctx.conversationId);
|
|
119
|
+
for (const queued of ctx.queue) {
|
|
120
|
+
queued.onEvent({ type: 'generation_cancelled', sessionId: ctx.conversationId });
|
|
121
|
+
}
|
|
122
|
+
ctx.queue.clear();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── dispose ──────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
export function disposeSession(ctx: DisposeContext): void {
|
|
129
|
+
void getHookManager().trigger('session-end', {
|
|
130
|
+
sessionId: ctx.conversationId,
|
|
131
|
+
});
|
|
132
|
+
ctx.abort();
|
|
133
|
+
unregisterCallNotifiers(ctx.conversationId);
|
|
134
|
+
unregisterSessionSender(ctx.conversationId);
|
|
135
|
+
resetSkillToolProjection(ctx.skillProjectionState);
|
|
136
|
+
ctx.eventBus.dispose();
|
|
137
|
+
|
|
138
|
+
// Release heavy in-memory data so GC can reclaim it
|
|
139
|
+
ctx.messages = [];
|
|
140
|
+
ctx.profiler.clear();
|
|
141
|
+
ctx.surfaceUndoStacks.clear();
|
|
142
|
+
ctx.currentTurnSurfaces = [];
|
|
143
|
+
ctx.pendingSurfaceActions.clear();
|
|
144
|
+
ctx.surfaceState.clear();
|
|
145
|
+
ctx.lastSurfaceAction.clear();
|
|
146
|
+
ctx.workspaceTopLevelContext = null;
|
|
147
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media payload trimming for context-too-large retry scenarios.
|
|
3
|
+
*
|
|
4
|
+
* When the provider rejects a request because the context is too large,
|
|
5
|
+
* this module replaces older image and file content blocks with lightweight
|
|
6
|
+
* text stubs to shrink the payload before retrying.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Message, ContentBlock } from '../providers/types.js';
|
|
10
|
+
import { getSummaryFromContextMessage } from '../context/window-manager.js';
|
|
11
|
+
|
|
12
|
+
const RETRY_KEEP_LATEST_MEDIA_BLOCKS = 3;
|
|
13
|
+
const MAX_MEDIA_STUB_TEXT = 2_000;
|
|
14
|
+
|
|
15
|
+
export function stripMediaPayloadsForRetry(messages: Message[]): { messages: Message[]; modified: boolean; replacedBlocks: number; latestUserIndex: number | null } {
|
|
16
|
+
let latestUserIndex: number | null = null;
|
|
17
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
18
|
+
const msg = messages[i];
|
|
19
|
+
if (msg.role !== 'user') continue;
|
|
20
|
+
if (getSummaryFromContextMessage(msg) !== null) continue;
|
|
21
|
+
if (isToolResultOnlyMessage(msg)) continue;
|
|
22
|
+
latestUserIndex = i;
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let modified = false;
|
|
27
|
+
let replacedBlocks = 0;
|
|
28
|
+
let keptLatestMediaBlocks = 0;
|
|
29
|
+
|
|
30
|
+
const nextMessages = messages.map((msg, msgIndex) => {
|
|
31
|
+
const nextContent: ContentBlock[] = [];
|
|
32
|
+
for (const block of msg.content) {
|
|
33
|
+
if (block.type === 'image') {
|
|
34
|
+
const keep = latestUserIndex === msgIndex && keptLatestMediaBlocks < RETRY_KEEP_LATEST_MEDIA_BLOCKS;
|
|
35
|
+
if (keep) {
|
|
36
|
+
keptLatestMediaBlocks += 1;
|
|
37
|
+
nextContent.push(block);
|
|
38
|
+
} else {
|
|
39
|
+
replacedBlocks += 1;
|
|
40
|
+
modified = true;
|
|
41
|
+
nextContent.push(imageBlockToStub(block));
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (block.type === 'file') {
|
|
47
|
+
const keep = latestUserIndex === msgIndex && keptLatestMediaBlocks < RETRY_KEEP_LATEST_MEDIA_BLOCKS;
|
|
48
|
+
if (keep) {
|
|
49
|
+
keptLatestMediaBlocks += 1;
|
|
50
|
+
nextContent.push(block);
|
|
51
|
+
} else {
|
|
52
|
+
replacedBlocks += 1;
|
|
53
|
+
modified = true;
|
|
54
|
+
nextContent.push(fileBlockToStub(block));
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (block.type === 'tool_result' && block.contentBlocks && block.contentBlocks.length > 0) {
|
|
60
|
+
let toolResultChanged = false;
|
|
61
|
+
const nextToolContentBlocks: ContentBlock[] = block.contentBlocks.map((cb) => {
|
|
62
|
+
if (cb.type === 'image') {
|
|
63
|
+
replacedBlocks += 1;
|
|
64
|
+
modified = true;
|
|
65
|
+
toolResultChanged = true;
|
|
66
|
+
return imageBlockToStub(cb);
|
|
67
|
+
}
|
|
68
|
+
if (cb.type === 'file') {
|
|
69
|
+
replacedBlocks += 1;
|
|
70
|
+
modified = true;
|
|
71
|
+
toolResultChanged = true;
|
|
72
|
+
return fileBlockToStub(cb);
|
|
73
|
+
}
|
|
74
|
+
return cb;
|
|
75
|
+
});
|
|
76
|
+
if (toolResultChanged) {
|
|
77
|
+
nextContent.push({ ...block, contentBlocks: nextToolContentBlocks });
|
|
78
|
+
} else {
|
|
79
|
+
nextContent.push(block);
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
nextContent.push(block);
|
|
85
|
+
}
|
|
86
|
+
return { ...msg, content: nextContent };
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
messages: modified ? nextMessages : messages,
|
|
91
|
+
modified,
|
|
92
|
+
replacedBlocks,
|
|
93
|
+
latestUserIndex,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function imageBlockToStub(block: Extract<ContentBlock, { type: 'image' }>): Extract<ContentBlock, { type: 'text' }> {
|
|
98
|
+
const sizeBytes = Math.ceil(block.source.data.length / 4) * 3;
|
|
99
|
+
return {
|
|
100
|
+
type: 'text',
|
|
101
|
+
text: `[Image omitted from retry context: ${block.source.media_type}, ${sizeBytes} bytes]`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function fileBlockToStub(block: Extract<ContentBlock, { type: 'file' }>): Extract<ContentBlock, { type: 'text' }> {
|
|
106
|
+
const sizeBytes = Math.ceil(block.source.data.length / 4) * 3;
|
|
107
|
+
const extracted = (block.extracted_text ?? '').trim();
|
|
108
|
+
const preview = extracted.length > MAX_MEDIA_STUB_TEXT
|
|
109
|
+
? `${extracted.slice(0, MAX_MEDIA_STUB_TEXT)}...`
|
|
110
|
+
: extracted;
|
|
111
|
+
return {
|
|
112
|
+
type: 'text',
|
|
113
|
+
text: preview.length > 0
|
|
114
|
+
? `[File omitted from retry context: ${block.source.filename} (${block.source.media_type}, ${sizeBytes} bytes)]\n${preview}`
|
|
115
|
+
: `[File omitted from retry context: ${block.source.filename} (${block.source.media_type}, ${sizeBytes} bytes)]`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isToolResultOnlyMessage(message: Message): boolean {
|
|
120
|
+
return message.content.length > 0
|
|
121
|
+
&& message.content.every((block) => block.type === 'tool_result');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Race a promise against a timeout. Returns 'completed' if the promise
|
|
126
|
+
* resolves/rejects within the budget, or 'timed_out' if the timeout fires
|
|
127
|
+
* first. The timer is always cleared in `finally` to prevent handle leaks.
|
|
128
|
+
*/
|
|
129
|
+
export async function raceWithTimeout<T>(
|
|
130
|
+
promise: Promise<T>,
|
|
131
|
+
timeoutMs: number,
|
|
132
|
+
): Promise<'completed' | 'timed_out'> {
|
|
133
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
134
|
+
try {
|
|
135
|
+
const result = await Promise.race([
|
|
136
|
+
promise.then(() => 'completed' as const, () => 'completed' as const),
|
|
137
|
+
new Promise<'timed_out'>((resolve) => {
|
|
138
|
+
timer = setTimeout(() => resolve('timed_out'), timeoutMs);
|
|
139
|
+
}),
|
|
140
|
+
]);
|
|
141
|
+
return result;
|
|
142
|
+
} finally {
|
|
143
|
+
if (timer !== undefined) {
|
|
144
|
+
clearTimeout(timer);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session messaging methods: enqueue, persistUserMessage,
|
|
3
|
+
* redirectToSecurePrompt, and queue/confirmation helpers.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from Session to keep the class focused on coordination.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { v4 as uuid } from 'uuid';
|
|
9
|
+
import type { Message } from '../providers/types.js';
|
|
10
|
+
import type { ServerMessage, UserMessageAttachment } from './ipc-protocol.js';
|
|
11
|
+
import { createUserMessage } from '../agent/message-types.js';
|
|
12
|
+
import * as conversationStore from '../memory/conversation-store.js';
|
|
13
|
+
import type { SecretPrompter } from '../permissions/secret-prompter.js';
|
|
14
|
+
import type { MessageQueue } from './session-queue-manager.js';
|
|
15
|
+
import { getLogger } from '../util/logger.js';
|
|
16
|
+
|
|
17
|
+
const log = getLogger('session-messaging');
|
|
18
|
+
|
|
19
|
+
// ── Context Interface ────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
export interface MessagingSessionContext {
|
|
22
|
+
readonly conversationId: string;
|
|
23
|
+
messages: Message[];
|
|
24
|
+
processing: boolean;
|
|
25
|
+
abortController: AbortController | null;
|
|
26
|
+
currentRequestId?: string;
|
|
27
|
+
readonly queue: MessageQueue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ── enqueueMessage ───────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
export function enqueueMessage(
|
|
33
|
+
ctx: MessagingSessionContext,
|
|
34
|
+
content: string,
|
|
35
|
+
attachments: UserMessageAttachment[],
|
|
36
|
+
onEvent: (msg: ServerMessage) => void,
|
|
37
|
+
requestId: string,
|
|
38
|
+
activeSurfaceId?: string,
|
|
39
|
+
currentPage?: string,
|
|
40
|
+
metadata?: Record<string, unknown>,
|
|
41
|
+
): { queued: boolean; rejected?: boolean; requestId: string } {
|
|
42
|
+
if (!ctx.processing) {
|
|
43
|
+
return { queued: false, requestId };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const pushed = ctx.queue.push({ content, attachments, requestId, onEvent, activeSurfaceId, currentPage, metadata });
|
|
47
|
+
if (!pushed) {
|
|
48
|
+
return { queued: false, rejected: true, requestId };
|
|
49
|
+
}
|
|
50
|
+
return { queued: true, requestId };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── persistUserMessage ───────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
export function persistUserMessage(
|
|
56
|
+
ctx: MessagingSessionContext,
|
|
57
|
+
content: string,
|
|
58
|
+
attachments: UserMessageAttachment[],
|
|
59
|
+
requestId?: string,
|
|
60
|
+
metadata?: Record<string, unknown>,
|
|
61
|
+
): string {
|
|
62
|
+
if (ctx.processing) {
|
|
63
|
+
throw new Error('Session is already processing a message');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!content.trim() && attachments.length === 0) {
|
|
67
|
+
throw new Error('Message content or attachments are required');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const reqId = requestId ?? uuid();
|
|
71
|
+
ctx.currentRequestId = reqId;
|
|
72
|
+
ctx.processing = true;
|
|
73
|
+
ctx.abortController = new AbortController();
|
|
74
|
+
|
|
75
|
+
const userMessage = createUserMessage(content, attachments.map((attachment) => ({
|
|
76
|
+
id: attachment.id,
|
|
77
|
+
filename: attachment.filename,
|
|
78
|
+
mimeType: attachment.mimeType,
|
|
79
|
+
data: attachment.data,
|
|
80
|
+
extractedText: attachment.extractedText,
|
|
81
|
+
})));
|
|
82
|
+
ctx.messages.push(userMessage);
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const persistedUserMessage = conversationStore.addMessage(
|
|
86
|
+
ctx.conversationId,
|
|
87
|
+
'user',
|
|
88
|
+
JSON.stringify(userMessage.content),
|
|
89
|
+
metadata,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (!persistedUserMessage.id) {
|
|
93
|
+
throw new Error('Failed to persist user message');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return persistedUserMessage.id;
|
|
97
|
+
} catch (err) {
|
|
98
|
+
ctx.messages.pop();
|
|
99
|
+
ctx.processing = false;
|
|
100
|
+
ctx.abortController = null;
|
|
101
|
+
ctx.currentRequestId = undefined;
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── redirectToSecurePrompt ───────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
export function redirectToSecurePrompt(
|
|
109
|
+
conversationId: string,
|
|
110
|
+
secretPrompter: SecretPrompter,
|
|
111
|
+
detectedTypes: string[],
|
|
112
|
+
onComplete?: () => void,
|
|
113
|
+
): void {
|
|
114
|
+
const service = 'detected';
|
|
115
|
+
const field = detectedTypes.join(',');
|
|
116
|
+
secretPrompter.prompt(
|
|
117
|
+
service, field,
|
|
118
|
+
'Secure Credential Entry',
|
|
119
|
+
'Your message contained a secret. Please enter it here instead — it will be stored securely and never sent to the AI.',
|
|
120
|
+
undefined, conversationId,
|
|
121
|
+
).then(async (result) => {
|
|
122
|
+
if (!result.value) return;
|
|
123
|
+
|
|
124
|
+
const { setSecureKey } = await import('../security/secure-keys.js');
|
|
125
|
+
const { upsertCredentialMetadata } = await import('../tools/credentials/metadata-store.js');
|
|
126
|
+
|
|
127
|
+
if (result.delivery === 'transient_send') {
|
|
128
|
+
const { credentialBroker } = await import('../tools/credentials/broker.js');
|
|
129
|
+
credentialBroker.injectTransient(service, field, result.value);
|
|
130
|
+
try { upsertCredentialMetadata(service, field, {}); } catch (e) { log.debug({ err: e, service, field }, 'Non-critical credential metadata upsert failed'); }
|
|
131
|
+
log.info({ service, field, delivery: 'transient_send' }, 'Ingress redirect: transient credential injected');
|
|
132
|
+
} else {
|
|
133
|
+
const key = `credential:${service}:${field}`;
|
|
134
|
+
const stored = setSecureKey(key, result.value);
|
|
135
|
+
if (stored) {
|
|
136
|
+
try { upsertCredentialMetadata(service, field, {}); } catch (e) { log.debug({ err: e, service, field }, 'Non-critical credential metadata upsert failed'); }
|
|
137
|
+
log.info({ service, field }, 'Ingress redirect: credential stored');
|
|
138
|
+
} else {
|
|
139
|
+
log.warn({ service, field }, 'Ingress redirect: secure storage write failed');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}).catch(() => { /* prompt timeout or cancel is fine */ }).finally(() => {
|
|
143
|
+
onComplete?.();
|
|
144
|
+
});
|
|
145
|
+
}
|