@vellumai/assistant 0.3.15 → 0.3.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +142 -0
- package/Dockerfile +1 -1
- package/README.md +5 -5
- package/docs/architecture/http-token-refresh.md +252 -0
- package/docs/architecture/memory.md +5 -4
- package/docs/architecture/scheduling.md +4 -88
- package/docs/runbook-trusted-contacts.md +283 -0
- package/docs/trusted-contact-access.md +247 -0
- package/package.json +1 -1
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +2 -6
- package/src/__tests__/access-request-decision.test.ts +331 -0
- package/src/__tests__/asset-materialize-tool.test.ts +7 -7
- package/src/__tests__/asset-search-tool.test.ts +15 -15
- package/src/__tests__/attachments-store.test.ts +13 -13
- package/src/__tests__/call-controller.test.ts +150 -4
- package/src/__tests__/call-conversation-messages.test.ts +2 -2
- package/src/__tests__/call-pointer-messages.test.ts +28 -0
- package/src/__tests__/call-start-guardian-guard.test.ts +93 -0
- package/src/__tests__/channel-approval-routes.test.ts +108 -12
- package/src/__tests__/channel-guardian.test.ts +16 -14
- package/src/__tests__/checker.test.ts +24 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +2 -2
- package/src/__tests__/config-watcher.test.ts +358 -0
- package/src/__tests__/conversation-pairing.test.ts +24 -24
- package/src/__tests__/conversation-store.test.ts +36 -36
- package/src/__tests__/date-context.test.ts +179 -1
- package/src/__tests__/db-migration-rollback.test.ts +4 -7
- package/src/__tests__/deterministic-verification-control-plane.test.ts +5 -5
- package/src/__tests__/emit-signal-routing-intent.test.ts +179 -0
- package/src/__tests__/gateway-only-guard.test.ts +188 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +451 -0
- package/src/__tests__/guardian-action-copy-generator.test.ts +197 -0
- package/src/__tests__/guardian-action-followup-executor.test.ts +379 -0
- package/src/__tests__/guardian-action-followup-store.test.ts +376 -0
- package/src/__tests__/guardian-action-late-reply.test.ts +294 -0
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +71 -0
- package/src/__tests__/guardian-action-sweep.test.ts +9 -9
- package/src/__tests__/guardian-outbound-http.test.ts +194 -2
- package/src/__tests__/guardian-verification-intent-routing.test.ts +179 -0
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +141 -0
- package/src/__tests__/handlers-telegram-config.test.ts +6 -6
- package/src/__tests__/hooks-runner.test.ts +13 -4
- package/src/__tests__/ingress-routes-http.test.ts +443 -0
- package/src/__tests__/intent-routing.test.ts +14 -0
- package/src/__tests__/ipc-snapshot.test.ts +2 -5
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
- package/src/__tests__/memory-regressions.test.ts +16 -12
- package/src/__tests__/non-member-access-request.test.ts +282 -0
- package/src/__tests__/notification-decision-strategy.test.ts +136 -0
- package/src/__tests__/notification-routing-intent.test.ts +11 -1
- package/src/__tests__/notification-thread-candidates.test.ts +166 -0
- package/src/__tests__/recording-intent.test.ts +1 -0
- package/src/__tests__/recording-state-machine.test.ts +328 -17
- package/src/__tests__/registry.test.ts +17 -8
- package/src/__tests__/relay-server.test.ts +105 -0
- package/src/__tests__/reminder.test.ts +13 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +4 -4
- package/src/__tests__/scheduler-recurrence.test.ts +50 -0
- package/src/__tests__/server-history-render.test.ts +8 -8
- package/src/__tests__/session-agent-loop.test.ts +1 -0
- package/src/__tests__/session-runtime-assembly.test.ts +49 -0
- package/src/__tests__/session-skill-tools.test.ts +1 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +11 -3
- package/src/__tests__/slack-channel-config.test.ts +230 -0
- package/src/__tests__/subagent-manager-notify.test.ts +4 -4
- package/src/__tests__/swarm-session-integration.test.ts +2 -2
- package/src/__tests__/system-prompt.test.ts +43 -0
- package/src/__tests__/task-management-tools.test.ts +3 -3
- package/src/__tests__/task-tools.test.ts +3 -3
- package/src/__tests__/trust-store.test.ts +17 -1
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +491 -0
- package/src/__tests__/trusted-contact-multichannel.test.ts +409 -0
- package/src/__tests__/trusted-contact-verification.test.ts +360 -0
- package/src/__tests__/update-bulletin-format.test.ts +119 -0
- package/src/__tests__/update-bulletin-state.test.ts +129 -0
- package/src/__tests__/update-bulletin.test.ts +260 -0
- package/src/__tests__/update-template-contract.test.ts +29 -0
- package/src/agent/loop.ts +2 -2
- package/src/amazon/client.ts +2 -3
- package/src/calls/call-controller.ts +115 -34
- package/src/calls/call-conversation-messages.ts +2 -2
- package/src/calls/call-domain.ts +10 -3
- package/src/calls/call-pointer-messages.ts +17 -5
- package/src/calls/guardian-action-sweep.ts +77 -36
- package/src/calls/relay-server.ts +51 -12
- package/src/calls/twilio-routes.ts +3 -1
- package/src/calls/types.ts +1 -1
- package/src/calls/voice-session-bridge.ts +4 -4
- package/src/cli/core-commands.ts +3 -3
- package/src/cli/map.ts +8 -5
- package/src/config/bundled-skills/phone-calls/SKILL.md +16 -1
- package/src/config/bundled-skills/tasks/SKILL.md +1 -1
- package/src/config/bundled-skills/tasks/TOOLS.json +4 -4
- package/src/config/bundled-skills/time-based-actions/SKILL.md +11 -1
- package/src/config/computer-use-prompt.ts +1 -0
- package/src/config/core-schema.ts +16 -0
- package/src/config/env-registry.ts +1 -0
- package/src/config/env.ts +16 -1
- package/src/config/memory-schema.ts +5 -0
- package/src/config/schema.ts +4 -0
- package/src/config/system-prompt.ts +69 -2
- package/src/config/templates/BOOTSTRAP.md +1 -1
- package/src/config/templates/IDENTITY.md +8 -4
- package/src/config/templates/SOUL.md +14 -0
- package/src/config/templates/UPDATES.md +16 -0
- package/src/config/templates/USER.md +5 -1
- package/src/config/types.ts +1 -0
- package/src/config/update-bulletin-format.ts +52 -0
- package/src/config/update-bulletin-state.ts +49 -0
- package/src/config/update-bulletin.ts +82 -0
- package/src/config/vellum-skills/catalog.json +6 -0
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
- package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +44 -10
- package/src/config/vellum-skills/telegram-setup/SKILL.md +4 -4
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +147 -0
- package/src/config/vellum-skills/twilio-setup/SKILL.md +2 -2
- package/src/context/window-manager.ts +43 -3
- package/src/daemon/config-watcher.ts +1 -0
- package/src/daemon/connection-policy.ts +21 -1
- package/src/daemon/daemon-control.ts +164 -7
- package/src/daemon/date-context.ts +174 -1
- package/src/daemon/guardian-action-generators.ts +175 -0
- package/src/daemon/guardian-verification-intent.ts +120 -0
- package/src/daemon/handlers/apps.ts +1 -3
- package/src/daemon/handlers/config-channels.ts +2 -2
- package/src/daemon/handlers/config-heartbeat.ts +1 -1
- package/src/daemon/handlers/config-inbox.ts +55 -159
- package/src/daemon/handlers/config-ingress.ts +1 -1
- package/src/daemon/handlers/config-integrations.ts +1 -1
- package/src/daemon/handlers/config-platform.ts +1 -1
- package/src/daemon/handlers/config-scheduling.ts +2 -2
- package/src/daemon/handlers/config-slack-channel.ts +190 -0
- package/src/daemon/handlers/config-telegram.ts +1 -1
- package/src/daemon/handlers/config-twilio.ts +1 -1
- package/src/daemon/handlers/config-voice.ts +100 -0
- package/src/daemon/handlers/config.ts +3 -0
- package/src/daemon/handlers/misc.ts +83 -5
- package/src/daemon/handlers/navigate-settings.ts +27 -0
- package/src/daemon/handlers/recording.ts +270 -144
- package/src/daemon/handlers/sessions.ts +100 -17
- package/src/daemon/handlers/subagents.ts +3 -3
- package/src/daemon/handlers/work-items.ts +10 -7
- package/src/daemon/ipc-contract/integrations.ts +9 -1
- package/src/daemon/ipc-contract/messages.ts +4 -0
- package/src/daemon/ipc-contract/sessions.ts +1 -1
- package/src/daemon/ipc-contract/settings.ts +26 -0
- package/src/daemon/ipc-contract/shared.ts +2 -0
- package/src/daemon/ipc-contract/work-items.ts +1 -7
- package/src/daemon/ipc-contract-inventory.json +5 -1
- package/src/daemon/ipc-contract.ts +5 -1
- package/src/daemon/lifecycle.ts +306 -266
- package/src/daemon/recording-intent.ts +0 -41
- package/src/daemon/response-tier.ts +2 -2
- package/src/daemon/server.ts +6 -6
- package/src/daemon/session-agent-loop-handlers.ts +34 -9
- package/src/daemon/session-agent-loop.ts +15 -8
- package/src/daemon/session-history.ts +3 -2
- package/src/daemon/session-media-retry.ts +3 -0
- package/src/daemon/session-messaging.ts +38 -4
- package/src/daemon/session-notifiers.ts +2 -2
- package/src/daemon/session-process.ts +256 -23
- package/src/daemon/session-queue-manager.ts +2 -0
- package/src/daemon/session-runtime-assembly.ts +39 -0
- package/src/daemon/session-skill-tools.ts +13 -4
- package/src/daemon/session-tool-setup.ts +5 -6
- package/src/daemon/session.ts +19 -8
- package/src/daemon/tls-certs.ts +55 -13
- package/src/daemon/tool-side-effects.ts +13 -5
- package/src/gallery/default-gallery.ts +32 -9
- package/src/influencer/client.ts +2 -1
- package/src/memory/channel-delivery-store.ts +37 -567
- package/src/memory/channel-guardian-store.ts +66 -1317
- package/src/memory/conflict-store.ts +4 -4
- package/src/memory/conversation-attention-store.ts +0 -3
- package/src/memory/conversation-crud.ts +668 -0
- package/src/memory/conversation-queries.ts +361 -0
- package/src/memory/conversation-store.ts +45 -983
- package/src/memory/db-connection.ts +3 -0
- package/src/memory/db-init.ts +25 -0
- package/src/memory/delivery-channels.ts +175 -0
- package/src/memory/delivery-crud.ts +211 -0
- package/src/memory/delivery-status.ts +199 -0
- package/src/memory/embedding-backend.ts +70 -4
- package/src/memory/embedding-local.ts +12 -2
- package/src/memory/entity-extractor.ts +3 -8
- package/src/memory/fts-reconciler.ts +121 -0
- package/src/memory/guardian-action-store.ts +366 -3
- package/src/memory/guardian-approvals.ts +569 -0
- package/src/memory/guardian-bindings.ts +130 -0
- package/src/memory/guardian-rate-limits.ts +196 -0
- package/src/memory/guardian-verification.ts +520 -0
- package/src/memory/job-handlers/index-maintenance.ts +2 -1
- package/src/memory/job-utils.ts +8 -5
- package/src/memory/jobs-store.ts +66 -6
- package/src/memory/jobs-worker.ts +23 -1
- package/src/memory/migrations/030-guardian-action-followup.ts +21 -0
- package/src/memory/migrations/030-guardian-verification-purpose.ts +17 -0
- package/src/memory/migrations/031-conversations-thread-type-index.ts +5 -0
- package/src/memory/migrations/100-core-tables.ts +1 -1
- package/src/memory/migrations/101-watchers-and-logs.ts +4 -0
- package/src/memory/migrations/108-tasks-and-work-items.ts +1 -1
- package/src/memory/migrations/112-assistant-inbox.ts +1 -1
- package/src/memory/migrations/113-late-migrations.ts +1 -1
- package/src/memory/migrations/116-messages-fts.ts +13 -0
- package/src/memory/migrations/119-schema-indexes-and-columns.ts +37 -0
- package/src/memory/migrations/120-fk-cascade-rebuilds.ts +161 -0
- package/src/memory/migrations/index.ts +8 -3
- package/src/memory/migrations/validate-migration-state.ts +114 -15
- package/src/memory/qdrant-circuit-breaker.ts +105 -0
- package/src/memory/retriever.ts +46 -13
- package/src/memory/schema-migration.ts +3 -0
- package/src/memory/schema.ts +25 -7
- package/src/memory/search/semantic.ts +8 -90
- package/src/notifications/README.md +1 -1
- package/src/notifications/broadcaster.ts +20 -2
- package/src/notifications/conversation-pairing.ts +3 -3
- package/src/notifications/decision-engine.ts +173 -8
- package/src/notifications/deliveries-store.ts +27 -8
- package/src/notifications/preferences-store.ts +7 -7
- package/src/notifications/thread-candidates.ts +234 -0
- package/src/notifications/types.ts +18 -0
- package/src/permissions/defaults.ts +11 -1
- package/src/permissions/prompter.ts +17 -0
- package/src/permissions/trust-store.ts +2 -0
- package/src/providers/failover.ts +19 -0
- package/src/providers/registry.ts +46 -1
- package/src/runtime/approval-message-composer.ts +1 -1
- package/src/runtime/channel-guardian-service.ts +15 -3
- package/src/runtime/channel-retry-sweep.ts +7 -2
- package/src/runtime/guardian-action-conversation-turn.ts +85 -0
- package/src/runtime/guardian-action-followup-executor.ts +301 -0
- package/src/runtime/guardian-action-message-composer.ts +245 -0
- package/src/runtime/guardian-outbound-actions.ts +26 -6
- package/src/runtime/guardian-verification-templates.ts +15 -9
- package/src/runtime/http-errors.ts +93 -0
- package/src/runtime/http-server.ts +133 -44
- package/src/runtime/http-types.ts +53 -0
- package/src/runtime/ingress-service.ts +237 -0
- package/src/runtime/middleware/error-handler.ts +4 -3
- package/src/runtime/middleware/rate-limiter.ts +160 -0
- package/src/runtime/middleware/request-logger.ts +71 -0
- package/src/runtime/middleware/twilio-validation.ts +7 -6
- package/src/runtime/pending-interactions.ts +12 -0
- package/src/runtime/routes/access-request-decision.ts +215 -0
- package/src/runtime/routes/app-routes.ts +25 -18
- package/src/runtime/routes/approval-routes.ts +18 -47
- package/src/runtime/routes/attachment-routes.ts +15 -41
- package/src/runtime/routes/call-routes.ts +20 -20
- package/src/runtime/routes/channel-delivery-routes.ts +6 -5
- package/src/runtime/routes/contact-routes.ts +4 -9
- package/src/runtime/routes/conversation-attention-routes.ts +2 -1
- package/src/runtime/routes/conversation-routes.ts +26 -57
- package/src/runtime/routes/debug-routes.ts +71 -0
- package/src/runtime/routes/events-routes.ts +3 -2
- package/src/runtime/routes/guardian-approval-interception.ts +221 -0
- package/src/runtime/routes/identity-routes.ts +14 -10
- package/src/runtime/routes/inbound-conversation.ts +3 -2
- package/src/runtime/routes/inbound-message-handler.ts +527 -62
- package/src/runtime/routes/ingress-routes.ts +174 -0
- package/src/runtime/routes/integration-routes.ts +78 -16
- package/src/runtime/routes/pairing-routes.ts +11 -10
- package/src/runtime/routes/secret-routes.ts +10 -18
- package/src/runtime/verification-rate-limiter.ts +83 -0
- package/src/schedule/schedule-store.ts +13 -1
- package/src/schedule/scheduler.ts +1 -1
- package/src/security/secret-ingress.ts +5 -2
- package/src/security/secret-scanner.ts +72 -6
- package/src/subagent/manager.ts +6 -4
- package/src/swarm/plan-validator.ts +4 -1
- package/src/tasks/task-runner.ts +3 -1
- package/src/tools/browser/api-map.ts +9 -6
- package/src/tools/calls/call-start.ts +20 -0
- package/src/tools/executor.ts +50 -568
- package/src/tools/permission-checker.ts +272 -0
- package/src/tools/registry.ts +14 -6
- package/src/tools/reminder/reminder-store.ts +7 -7
- package/src/tools/reminder/reminder.ts +6 -3
- package/src/tools/secret-detection-handler.ts +301 -0
- package/src/tools/subagent/message.ts +1 -1
- package/src/tools/system/voice-config.ts +62 -0
- package/src/tools/tasks/index.ts +3 -3
- package/src/tools/tasks/work-item-list.ts +3 -3
- package/src/tools/tasks/work-item-update.ts +4 -5
- package/src/tools/tool-approval-handler.ts +192 -0
- package/src/tools/tool-manifest.ts +2 -0
- package/src/watcher/watcher-store.ts +9 -9
- package/src/work-items/work-item-runner.ts +9 -6
- /package/src/memory/migrations/{026-embeddings-nullable-vector-json.ts → 026a-embeddings-nullable-vector-json.ts} +0 -0
- /package/src/memory/migrations/{027-guardian-bootstrap-token.ts → 027a-guardian-bootstrap-token.ts} +0 -0
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
// Internal helpers (detect/strip/classify) are kept as private utilities
|
|
8
8
|
// consumed only by `resolveRecordingIntent`.
|
|
9
9
|
|
|
10
|
-
type RecordingIntentClass = 'start_only' | 'stop_only' | 'mixed' | 'none';
|
|
11
|
-
|
|
12
10
|
export type RecordingIntentResult =
|
|
13
11
|
| { kind: 'none' }
|
|
14
12
|
| { kind: 'start_only' }
|
|
@@ -370,45 +368,6 @@ function isInterrogative(text: string, dynamicNames?: string[]): boolean {
|
|
|
370
368
|
return false;
|
|
371
369
|
}
|
|
372
370
|
|
|
373
|
-
// ─── Unified classification ─────────────────────────────────────────────────
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Classifies the recording intent of a user message into one of four categories:
|
|
377
|
-
* - 'start_only': the prompt is purely about starting a recording
|
|
378
|
-
* - 'stop_only': the prompt is purely about stopping a recording
|
|
379
|
-
* - 'mixed': the prompt contains recording intent mixed with other tasks,
|
|
380
|
-
* or contains both start and stop recording patterns
|
|
381
|
-
* - 'none': no recording intent detected
|
|
382
|
-
*
|
|
383
|
-
* If `dynamicNames` are provided, they are stripped from the beginning of the
|
|
384
|
-
* text before classification (e.g., "Nova, record my screen" -> "record my screen").
|
|
385
|
-
*/
|
|
386
|
-
function _classifyRecordingIntent(
|
|
387
|
-
taskText: string,
|
|
388
|
-
dynamicNames?: string[],
|
|
389
|
-
): RecordingIntentClass {
|
|
390
|
-
const normalized =
|
|
391
|
-
dynamicNames && dynamicNames.length > 0
|
|
392
|
-
? stripDynamicNames(taskText, dynamicNames)
|
|
393
|
-
: taskText;
|
|
394
|
-
|
|
395
|
-
const hasStart = detectRecordingIntent(normalized);
|
|
396
|
-
const hasStop = detectStopRecordingIntent(normalized);
|
|
397
|
-
|
|
398
|
-
// Both start and stop patterns present -> mixed
|
|
399
|
-
if (hasStart && hasStop) return 'mixed';
|
|
400
|
-
|
|
401
|
-
if (hasStop) {
|
|
402
|
-
return isStopRecordingOnly(normalized) ? 'stop_only' : 'mixed';
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
if (hasStart) {
|
|
406
|
-
return isRecordingOnly(normalized) ? 'start_only' : 'mixed';
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
return 'none';
|
|
410
|
-
}
|
|
411
|
-
|
|
412
371
|
// ─── Structured intent resolver ─────────────────────────────────────────────
|
|
413
372
|
|
|
414
373
|
/**
|
|
@@ -226,12 +226,12 @@ export function tierMaxTokens(tier: ResponseTier, configuredMax: number): number
|
|
|
226
226
|
|
|
227
227
|
/**
|
|
228
228
|
* Map for Anthropic provider: tier → model.
|
|
229
|
-
* low →
|
|
229
|
+
* low → sonnet (balanced)
|
|
230
230
|
* medium → sonnet (balanced)
|
|
231
231
|
* high → undefined (use configured default, typically opus)
|
|
232
232
|
*/
|
|
233
233
|
const ANTHROPIC_TIER_MODELS: Record<ResponseTier, string | undefined> = {
|
|
234
|
-
low: 'claude-
|
|
234
|
+
low: 'claude-sonnet-4-6',
|
|
235
235
|
medium: 'claude-sonnet-4-6',
|
|
236
236
|
high: undefined, // use configured default
|
|
237
237
|
};
|
package/src/daemon/server.ts
CHANGED
|
@@ -188,7 +188,7 @@ export class DaemonServer {
|
|
|
188
188
|
const children = getSubagentManager().getChildrenOf(sessionId);
|
|
189
189
|
return children.some((c) => c.status === 'running' || c.status === 'pending');
|
|
190
190
|
};
|
|
191
|
-
getSubagentManager().onSubagentFinished = (parentSessionId, message, sendToClient, notification) => {
|
|
191
|
+
getSubagentManager().onSubagentFinished = async (parentSessionId, message, sendToClient, notification) => {
|
|
192
192
|
const parentSession = this.sessions.get(parentSessionId);
|
|
193
193
|
if (!parentSession) {
|
|
194
194
|
log.warn({ parentSessionId }, 'Subagent finished but parent session not found');
|
|
@@ -202,7 +202,7 @@ export class DaemonServer {
|
|
|
202
202
|
return;
|
|
203
203
|
}
|
|
204
204
|
if (!enqueueResult.queued) {
|
|
205
|
-
const messageId = parentSession.persistUserMessage(message, [], undefined, metadata);
|
|
205
|
+
const messageId = await parentSession.persistUserMessage(message, [], undefined, metadata);
|
|
206
206
|
parentSession.runAgentLoop(message, messageId, sendToClient).catch((err) => {
|
|
207
207
|
log.error({ parentSessionId, err }, 'Failed to process subagent notification in parent');
|
|
208
208
|
});
|
|
@@ -809,7 +809,7 @@ export class DaemonServer {
|
|
|
809
809
|
);
|
|
810
810
|
|
|
811
811
|
const requestId = crypto.randomUUID();
|
|
812
|
-
const messageId = session.persistUserMessage(content, attachments, requestId);
|
|
812
|
+
const messageId = await session.persistUserMessage(content, attachments, requestId);
|
|
813
813
|
|
|
814
814
|
// Register pending interactions so channel approval interception can
|
|
815
815
|
// find the session by requestId when confirmation/secret events fire.
|
|
@@ -865,7 +865,7 @@ export class DaemonServer {
|
|
|
865
865
|
: {}),
|
|
866
866
|
};
|
|
867
867
|
const userMsg = createUserMessage(content, attachments);
|
|
868
|
-
const persisted = conversationStore.addMessage(
|
|
868
|
+
const persisted = await conversationStore.addMessage(
|
|
869
869
|
conversationId,
|
|
870
870
|
'user',
|
|
871
871
|
JSON.stringify(userMsg.content),
|
|
@@ -889,7 +889,7 @@ export class DaemonServer {
|
|
|
889
889
|
}
|
|
890
890
|
|
|
891
891
|
const assistantMsg = createAssistantMessage(slashResult.message);
|
|
892
|
-
conversationStore.addMessage(
|
|
892
|
+
await conversationStore.addMessage(
|
|
893
893
|
conversationId,
|
|
894
894
|
'assistant',
|
|
895
895
|
JSON.stringify(assistantMsg.content),
|
|
@@ -908,7 +908,7 @@ export class DaemonServer {
|
|
|
908
908
|
const requestId = crypto.randomUUID();
|
|
909
909
|
let messageId: string;
|
|
910
910
|
try {
|
|
911
|
-
messageId = session.persistUserMessage(resolvedContent, attachments, requestId);
|
|
911
|
+
messageId = await session.persistUserMessage(resolvedContent, attachments, requestId);
|
|
912
912
|
} catch (err) {
|
|
913
913
|
session.setPreactivatedSkillIds(undefined);
|
|
914
914
|
throw err;
|
|
@@ -104,6 +104,26 @@ export function emitLlmCallStartedIfNeeded(
|
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// ── IPC Size Caps ────────────────────────────────────────────────────
|
|
108
|
+
// The client truncates tool results anyway (2 000 chars in ChatViewModel),
|
|
109
|
+
// but the full string can be megabytes (file_read, bash output). Capping
|
|
110
|
+
// here avoids sending oversized payloads over IPC which get decoded on
|
|
111
|
+
// the client's main thread.
|
|
112
|
+
|
|
113
|
+
const TOOL_RESULT_MAX_CHARS = 2_000;
|
|
114
|
+
const TOOL_RESULT_TRUNCATION_SUFFIX = '...[truncated]';
|
|
115
|
+
|
|
116
|
+
// tool_input_delta streams accumulated JSON as tools run. For non-app
|
|
117
|
+
// tools the client discards it (extractCodePreview only handles app tools),
|
|
118
|
+
// so we cap it aggressively to avoid excessive IPC traffic.
|
|
119
|
+
const TOOL_INPUT_DELTA_MAX_CHARS = 50_000;
|
|
120
|
+
const APP_TOOL_NAMES = new Set(['app_create', 'app_update']);
|
|
121
|
+
|
|
122
|
+
function truncateForIpc(value: string, maxChars: number, suffix: string): string {
|
|
123
|
+
if (value.length <= maxChars) return value;
|
|
124
|
+
return value.slice(0, maxChars - suffix.length) + suffix;
|
|
125
|
+
}
|
|
126
|
+
|
|
107
127
|
// ── Individual Handlers ──────────────────────────────────────────────
|
|
108
128
|
|
|
109
129
|
export function handleTextDelta(
|
|
@@ -194,7 +214,12 @@ export function handleInputJsonDelta(
|
|
|
194
214
|
deps: EventHandlerDeps,
|
|
195
215
|
event: Extract<AgentEvent, { type: 'input_json_delta' }>,
|
|
196
216
|
): void {
|
|
197
|
-
|
|
217
|
+
// Cap non-app tool input deltas — the client only uses this data for
|
|
218
|
+
// app_create/app_update code previews; all other tools discard it.
|
|
219
|
+
const content = APP_TOOL_NAMES.has(event.toolName)
|
|
220
|
+
? event.accumulatedJson
|
|
221
|
+
: truncateForIpc(event.accumulatedJson, TOOL_INPUT_DELTA_MAX_CHARS, TOOL_RESULT_TRUNCATION_SUFFIX);
|
|
222
|
+
deps.onEvent({ type: 'tool_input_delta', toolName: event.toolName, content, sessionId: deps.ctx.conversationId });
|
|
198
223
|
}
|
|
199
224
|
|
|
200
225
|
export function handleToolResult(
|
|
@@ -206,7 +231,7 @@ export function handleToolResult(
|
|
|
206
231
|
deps.onEvent({
|
|
207
232
|
type: 'tool_result',
|
|
208
233
|
toolName: '',
|
|
209
|
-
result: event.content,
|
|
234
|
+
result: truncateForIpc(event.content, TOOL_RESULT_MAX_CHARS, TOOL_RESULT_TRUNCATION_SUFFIX),
|
|
210
235
|
isError: event.isError,
|
|
211
236
|
diff: event.diff,
|
|
212
237
|
status: event.status,
|
|
@@ -256,11 +281,11 @@ export function handleError(
|
|
|
256
281
|
}
|
|
257
282
|
}
|
|
258
283
|
|
|
259
|
-
export function handleMessageComplete(
|
|
284
|
+
export async function handleMessageComplete(
|
|
260
285
|
state: EventHandlerState,
|
|
261
286
|
deps: EventHandlerDeps,
|
|
262
287
|
event: Extract<AgentEvent, { type: 'message_complete' }>,
|
|
263
|
-
): void {
|
|
288
|
+
): Promise<void> {
|
|
264
289
|
// Flush any remaining directive display buffer
|
|
265
290
|
if (state.pendingDirectiveDisplayBuffer.length > 0) {
|
|
266
291
|
deps.onEvent({
|
|
@@ -290,7 +315,7 @@ export function handleMessageComplete(
|
|
|
290
315
|
userMessageInterface: deps.turnInterfaceContext.userMessageInterface,
|
|
291
316
|
assistantMessageInterface: deps.turnInterfaceContext.assistantMessageInterface,
|
|
292
317
|
};
|
|
293
|
-
conversationStore.addMessage(
|
|
318
|
+
await conversationStore.addMessage(
|
|
294
319
|
deps.ctx.conversationId,
|
|
295
320
|
'user',
|
|
296
321
|
JSON.stringify(toolResultBlocks),
|
|
@@ -335,7 +360,7 @@ export function handleMessageComplete(
|
|
|
335
360
|
userMessageInterface: deps.turnInterfaceContext.userMessageInterface,
|
|
336
361
|
assistantMessageInterface: deps.turnInterfaceContext.assistantMessageInterface,
|
|
337
362
|
};
|
|
338
|
-
const assistantMsg = conversationStore.addMessage(
|
|
363
|
+
const assistantMsg = await conversationStore.addMessage(
|
|
339
364
|
deps.ctx.conversationId,
|
|
340
365
|
'assistant',
|
|
341
366
|
JSON.stringify(contentWithSurfaces),
|
|
@@ -399,11 +424,11 @@ export function handleUsage(
|
|
|
399
424
|
// ── Dispatcher ───────────────────────────────────────────────────────
|
|
400
425
|
|
|
401
426
|
/** Routes an AgentEvent to the appropriate handler. */
|
|
402
|
-
export function dispatchAgentEvent(
|
|
427
|
+
export async function dispatchAgentEvent(
|
|
403
428
|
state: EventHandlerState,
|
|
404
429
|
deps: EventHandlerDeps,
|
|
405
430
|
event: AgentEvent,
|
|
406
|
-
): void {
|
|
431
|
+
): Promise<void> {
|
|
407
432
|
switch (event.type) {
|
|
408
433
|
case 'text_delta':
|
|
409
434
|
handleTextDelta(state, deps, event);
|
|
@@ -427,7 +452,7 @@ export function dispatchAgentEvent(
|
|
|
427
452
|
handleError(state, deps, event);
|
|
428
453
|
break;
|
|
429
454
|
case 'message_complete':
|
|
430
|
-
handleMessageComplete(state, deps, event);
|
|
455
|
+
await handleMessageComplete(state, deps, event);
|
|
431
456
|
break;
|
|
432
457
|
case 'usage':
|
|
433
458
|
handleUsage(state, deps, event);
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
type AssistantAttachmentDraft,
|
|
35
35
|
cleanAssistantContent,
|
|
36
36
|
} from './assistant-attachments.js';
|
|
37
|
-
import { buildTemporalContext } from './date-context.js';
|
|
37
|
+
import { buildTemporalContext, extractUserTimeZoneFromDynamicProfile } from './date-context.js';
|
|
38
38
|
import { deepRepairHistory,repairHistory } from './history-repair.js';
|
|
39
39
|
import type { DynamicPageSurfaceData,ServerMessage, SurfaceData, SurfaceType, UsageStats } from './ipc-protocol.js';
|
|
40
40
|
import {
|
|
@@ -132,7 +132,7 @@ export interface AgentLoopSessionContext {
|
|
|
132
132
|
getQueueDepth(): number;
|
|
133
133
|
hasQueuedMessages(): boolean;
|
|
134
134
|
canHandoffAtCheckpoint(): boolean;
|
|
135
|
-
drainQueue(reason: QueueDrainReason): void
|
|
135
|
+
drainQueue(reason: QueueDrainReason): Promise<void>;
|
|
136
136
|
getTurnChannelContext(): TurnChannelContext | null;
|
|
137
137
|
getTurnInterfaceContext(): TurnInterfaceContext | null;
|
|
138
138
|
}
|
|
@@ -144,7 +144,7 @@ export async function runAgentLoopImpl(
|
|
|
144
144
|
content: string,
|
|
145
145
|
userMessageId: string,
|
|
146
146
|
onEvent: (msg: ServerMessage) => void,
|
|
147
|
-
options?: { skipPreMessageRollback?: boolean; isInteractive?: boolean },
|
|
147
|
+
options?: { skipPreMessageRollback?: boolean; isInteractive?: boolean; titleText?: string },
|
|
148
148
|
): Promise<void> {
|
|
149
149
|
if (!ctx.abortController) {
|
|
150
150
|
throw new Error('runAgentLoop called without prior persistUserMessage');
|
|
@@ -272,7 +272,7 @@ export async function runAgentLoopImpl(
|
|
|
272
272
|
assistantMessageInterface: capturedTurnInterfaceContext.assistantMessageInterface,
|
|
273
273
|
};
|
|
274
274
|
const assistantMessage = createAssistantMessage(memoryResult.conflictClarification);
|
|
275
|
-
conversationStore.addMessage(
|
|
275
|
+
await conversationStore.addMessage(
|
|
276
276
|
ctx.conversationId,
|
|
277
277
|
'assistant',
|
|
278
278
|
JSON.stringify(assistantMessage.content),
|
|
@@ -325,8 +325,15 @@ export async function runAgentLoopImpl(
|
|
|
325
325
|
ctx.refreshWorkspaceTopLevelContextIfNeeded();
|
|
326
326
|
|
|
327
327
|
// Compute fresh temporal context each turn for date grounding.
|
|
328
|
+
// Absolute "now" is always anchored to assistant host clock, while local
|
|
329
|
+
// date semantics prefer configured user timezone, then profile memory.
|
|
330
|
+
const hostTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
331
|
+
const userTimeZone = extractUserTimeZoneFromDynamicProfile(dynamicProfile.text);
|
|
332
|
+
const configuredUserTimeZone = getConfig().ui.userTimezone ?? null;
|
|
328
333
|
const temporalContext = buildTemporalContext({
|
|
329
|
-
|
|
334
|
+
hostTimeZone,
|
|
335
|
+
configuredUserTimeZone,
|
|
336
|
+
userTimeZone,
|
|
330
337
|
});
|
|
331
338
|
|
|
332
339
|
// Use the channel/interface context captured at the top of this function
|
|
@@ -584,7 +591,7 @@ export async function runAgentLoopImpl(
|
|
|
584
591
|
userMessageInterface: capturedTurnInterfaceContext.userMessageInterface,
|
|
585
592
|
assistantMessageInterface: capturedTurnInterfaceContext.assistantMessageInterface,
|
|
586
593
|
};
|
|
587
|
-
conversationStore.addMessage(
|
|
594
|
+
await conversationStore.addMessage(
|
|
588
595
|
ctx.conversationId,
|
|
589
596
|
'user',
|
|
590
597
|
JSON.stringify(toolResultBlocks),
|
|
@@ -610,7 +617,7 @@ export async function runAgentLoopImpl(
|
|
|
610
617
|
assistantMessageInterface: capturedTurnInterfaceContext.assistantMessageInterface,
|
|
611
618
|
};
|
|
612
619
|
const errorAssistantMessage = createAssistantMessage(state.providerErrorUserMessage);
|
|
613
|
-
conversationStore.addMessage(
|
|
620
|
+
await conversationStore.addMessage(
|
|
614
621
|
ctx.conversationId,
|
|
615
622
|
'assistant',
|
|
616
623
|
JSON.stringify(errorAssistantMessage.content),
|
|
@@ -695,7 +702,7 @@ export async function runAgentLoopImpl(
|
|
|
695
702
|
queueGenerateConversationTitle({
|
|
696
703
|
conversationId: ctx.conversationId,
|
|
697
704
|
provider: ctx.provider,
|
|
698
|
-
userMessage: content,
|
|
705
|
+
userMessage: options?.titleText ?? content,
|
|
699
706
|
assistantResponse: state.firstAssistantText || undefined,
|
|
700
707
|
onTitleUpdated: (title) => {
|
|
701
708
|
onEvent({
|
|
@@ -3,6 +3,7 @@ import { v4 as uuid } from 'uuid';
|
|
|
3
3
|
import { getSummaryFromContextMessage } from '../context/window-manager.js';
|
|
4
4
|
import * as conversationStore from '../memory/conversation-store.js';
|
|
5
5
|
import { enqueueMemoryJob } from '../memory/jobs-store.js';
|
|
6
|
+
import { withQdrantBreaker } from '../memory/qdrant-circuit-breaker.js';
|
|
6
7
|
import { getQdrantClient } from '../memory/qdrant-client.js';
|
|
7
8
|
import type { ContentBlock,Message } from '../providers/types.js';
|
|
8
9
|
import { getLogger } from '../util/logger.js';
|
|
@@ -67,7 +68,7 @@ export async function cleanupQdrantVectors(
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
const results = await Promise.allSettled(
|
|
70
|
-
targets.map((t) => qdrant.deleteByTarget(t.targetType, t.targetId)),
|
|
71
|
+
targets.map((t) => withQdrantBreaker(() => qdrant.deleteByTarget(t.targetType, t.targetId))),
|
|
71
72
|
);
|
|
72
73
|
|
|
73
74
|
let succeeded = 0;
|
|
@@ -263,7 +264,7 @@ export interface HistorySessionContext {
|
|
|
263
264
|
content: string,
|
|
264
265
|
userMessageId: string,
|
|
265
266
|
onEvent: (msg: ServerMessage) => void,
|
|
266
|
-
options?: { skipPreMessageRollback?: boolean },
|
|
267
|
+
options?: { skipPreMessageRollback?: boolean; titleText?: string },
|
|
267
268
|
): Promise<void>;
|
|
268
269
|
}
|
|
269
270
|
|
|
@@ -30,6 +30,9 @@ export function stripMediaPayloadsForRetry(messages: Message[]): { messages: Mes
|
|
|
30
30
|
const nextMessages = messages.map((msg, msgIndex) => {
|
|
31
31
|
const nextContent: ContentBlock[] = [];
|
|
32
32
|
for (const block of msg.content) {
|
|
33
|
+
// Top-level image blocks are user-uploaded attachments. Keep the latest
|
|
34
|
+
// few (in the most recent user message) and strip older ones so the
|
|
35
|
+
// retry can actually reduce context size when images are the cause.
|
|
33
36
|
if (block.type === 'image') {
|
|
34
37
|
const keep = latestUserIndex === msgIndex && keptLatestMediaBlocks < RETRY_KEEP_LATEST_MEDIA_BLOCKS;
|
|
35
38
|
if (keep) {
|
|
@@ -10,6 +10,7 @@ import { v4 as uuid } from 'uuid';
|
|
|
10
10
|
import { createUserMessage } from '../agent/message-types.js';
|
|
11
11
|
import type { TurnChannelContext, TurnInterfaceContext } from '../channels/types.js';
|
|
12
12
|
import { parseChannelId, parseInterfaceId } from '../channels/types.js';
|
|
13
|
+
import { AttachmentUploadError, linkAttachmentToMessage, uploadAttachment, validateAttachmentUpload } from '../memory/attachments-store.js';
|
|
13
14
|
import * as conversationStore from '../memory/conversation-store.js';
|
|
14
15
|
import { provenanceFromGuardianContext } from '../memory/conversation-store.js';
|
|
15
16
|
import type { SecretPrompter } from '../permissions/secret-prompter.js';
|
|
@@ -124,6 +125,7 @@ export function enqueueMessage(
|
|
|
124
125
|
currentPage?: string,
|
|
125
126
|
metadata?: Record<string, unknown>,
|
|
126
127
|
options?: { isInteractive?: boolean },
|
|
128
|
+
displayContent?: string,
|
|
127
129
|
): { queued: boolean; rejected?: boolean; requestId: string } {
|
|
128
130
|
if (!ctx.processing) {
|
|
129
131
|
return { queued: false, requestId };
|
|
@@ -143,6 +145,7 @@ export function enqueueMessage(
|
|
|
143
145
|
turnInterfaceContext,
|
|
144
146
|
isInteractive: options?.isInteractive,
|
|
145
147
|
queuedAt: Date.now(),
|
|
148
|
+
displayContent,
|
|
146
149
|
});
|
|
147
150
|
if (!pushed) {
|
|
148
151
|
return { queued: false, rejected: true, requestId };
|
|
@@ -152,13 +155,14 @@ export function enqueueMessage(
|
|
|
152
155
|
|
|
153
156
|
// ── persistUserMessage ───────────────────────────────────────────────
|
|
154
157
|
|
|
155
|
-
export function persistUserMessage(
|
|
158
|
+
export async function persistUserMessage(
|
|
156
159
|
ctx: MessagingSessionContext,
|
|
157
160
|
content: string,
|
|
158
161
|
attachments: UserMessageAttachment[],
|
|
159
162
|
requestId?: string,
|
|
160
163
|
metadata?: Record<string, unknown>,
|
|
161
|
-
|
|
164
|
+
displayContent?: string,
|
|
165
|
+
): Promise<string> {
|
|
162
166
|
if (ctx.processing) {
|
|
163
167
|
throw new Error('Session is already processing a message');
|
|
164
168
|
}
|
|
@@ -202,10 +206,19 @@ export function persistUserMessage(
|
|
|
202
206
|
: {}),
|
|
203
207
|
};
|
|
204
208
|
|
|
205
|
-
|
|
209
|
+
// When displayContent is provided (e.g. original text before recording
|
|
210
|
+
// intent stripping), persist that to DB so users see the full message
|
|
211
|
+
// after restart. The in-memory userMessage (sent to the LLM) still uses
|
|
212
|
+
// the stripped content.
|
|
213
|
+
const contentToPersist = displayContent
|
|
214
|
+
? JSON.stringify(createUserMessage(displayContent, attachments.map((a) => ({
|
|
215
|
+
id: a.id, filename: a.filename, mimeType: a.mimeType, data: a.data, extractedText: a.extractedText,
|
|
216
|
+
}))).content)
|
|
217
|
+
: JSON.stringify(userMessage.content);
|
|
218
|
+
const persistedUserMessage = await conversationStore.addMessage(
|
|
206
219
|
ctx.conversationId,
|
|
207
220
|
'user',
|
|
208
|
-
|
|
221
|
+
contentToPersist,
|
|
209
222
|
mergedMetadata,
|
|
210
223
|
);
|
|
211
224
|
|
|
@@ -220,6 +233,27 @@ export function persistUserMessage(
|
|
|
220
233
|
throw new Error('Failed to persist user message');
|
|
221
234
|
}
|
|
222
235
|
|
|
236
|
+
// Index user attachments in the attachments table so asset_search can find them.
|
|
237
|
+
for (let i = 0; i < attachments.length; i++) {
|
|
238
|
+
const a = attachments[i];
|
|
239
|
+
if (!a.data) continue;
|
|
240
|
+
try {
|
|
241
|
+
const validation = validateAttachmentUpload(a.filename, a.mimeType);
|
|
242
|
+
if (!validation.ok) {
|
|
243
|
+
log.warn({ filename: a.filename, error: validation.error }, 'Skipping user attachment indexing: validation failed');
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const stored = uploadAttachment(a.filename, a.mimeType, a.data);
|
|
247
|
+
linkAttachmentToMessage(persistedUserMessage.id, stored.id, i);
|
|
248
|
+
} catch (err) {
|
|
249
|
+
if (err instanceof AttachmentUploadError) {
|
|
250
|
+
log.warn({ filename: a.filename, error: err.message }, 'Skipping user attachment indexing');
|
|
251
|
+
} else {
|
|
252
|
+
log.error({ filename: a.filename, err }, 'Failed to index user attachment');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
223
257
|
return persistedUserMessage.id;
|
|
224
258
|
} catch (err) {
|
|
225
259
|
ctx.messages.pop();
|
|
@@ -96,12 +96,12 @@ export function registerSessionNotifiers(
|
|
|
96
96
|
}
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
-
registerCallQuestionNotifier(conversationId, (callSessionId: string, question: string) => {
|
|
99
|
+
registerCallQuestionNotifier(conversationId, async (callSessionId: string, question: string) => {
|
|
100
100
|
const callSession = getCallSession(callSessionId);
|
|
101
101
|
const callee = callSession?.toNumber ?? 'the caller';
|
|
102
102
|
const questionText = `**Live call question** (to ${callee}):\n\n${question}\n\n_Use the call answer API to respond._`;
|
|
103
103
|
|
|
104
|
-
conversationStore.addMessage(
|
|
104
|
+
await conversationStore.addMessage(
|
|
105
105
|
conversationId,
|
|
106
106
|
'assistant',
|
|
107
107
|
JSON.stringify([{ type: 'text', text: questionText }]),
|