@vellumai/assistant 0.5.1 → 0.5.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/ARCHITECTURE.md +54 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +111 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
- package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
- package/src/__tests__/app-dir-path-guard.test.ts +78 -0
- package/src/__tests__/app-executors.test.ts +1 -291
- package/src/__tests__/app-git-history.test.ts +4 -4
- package/src/__tests__/app-routes-csp.test.ts +1 -0
- package/src/__tests__/app-store-dir-names.test.ts +426 -0
- package/src/__tests__/attachments-store.test.ts +169 -21
- package/src/__tests__/attachments.test.ts +115 -1
- package/src/__tests__/btw-routes.test.ts +1 -0
- package/src/__tests__/canonical-guardian-store.test.ts +38 -0
- package/src/__tests__/channel-reply-delivery.test.ts +55 -0
- package/src/__tests__/checker.test.ts +54 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/config-schema-cmd.test.ts +68 -21
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +149 -5
- package/src/__tests__/conversation-agent-loop.test.ts +290 -2
- package/src/__tests__/conversation-attachments.test.ts +17 -19
- package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
- package/src/__tests__/conversation-disk-view.test.ts +810 -0
- package/src/__tests__/conversation-error.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +551 -0
- package/src/__tests__/conversation-fork-route.test.ts +386 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
- package/src/__tests__/conversation-media-retry.test.ts +8 -2
- package/src/__tests__/conversation-queue.test.ts +36 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
- package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
- package/src/__tests__/conversation-skill-tools.test.ts +4 -9
- package/src/__tests__/conversation-slash-commands.test.ts +149 -0
- package/src/__tests__/conversation-store.test.ts +24 -21
- package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/conversation-title-service.test.ts +137 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-vault-unit.test.ts +5 -10
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
- package/src/__tests__/diagnostics-export.test.ts +70 -1
- package/src/__tests__/first-greeting.test.ts +80 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
- package/src/__tests__/history-repair.test.ts +32 -10
- package/src/__tests__/http-conversation-lineage.test.ts +251 -0
- package/src/__tests__/image-source-path-reinject.test.ts +136 -0
- package/src/__tests__/llm-context-normalization.test.ts +1116 -0
- package/src/__tests__/llm-context-route-provider.test.ts +217 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
- package/src/__tests__/media-generate-image.test.ts +47 -94
- package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-recall-quality.test.ts +5 -5
- package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
- package/src/__tests__/migration-export-http.test.ts +3 -1
- package/src/__tests__/migration-import-commit-http.test.ts +18 -4
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
- package/src/__tests__/mime-builder.test.ts +3 -2
- package/src/__tests__/non-member-access-request.test.ts +12 -1
- package/src/__tests__/notification-decision-identity.test.ts +52 -0
- package/src/__tests__/oauth-apps-routes.test.ts +103 -0
- package/src/__tests__/oauth-store.test.ts +115 -0
- package/src/__tests__/provider-error-scenarios.test.ts +1 -3
- package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
- package/src/__tests__/recording-handler.test.ts +17 -0
- package/src/__tests__/registry.test.ts +3 -8
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
- package/src/__tests__/schema-transforms.test.ts +165 -5
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -2
- package/src/__tests__/starter-task-flow.test.ts +1 -0
- package/src/__tests__/suggestion-routes.test.ts +443 -0
- package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
- package/src/__tests__/swarm-recursion.test.ts +1 -0
- package/src/__tests__/swarm-tool.test.ts +1 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
- package/src/__tests__/top-level-renderer.test.ts +22 -0
- package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
- package/src/__tests__/web-fetch.test.ts +6 -2
- package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
- package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
- package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
- package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
- package/src/agent/attachments.ts +27 -1
- package/src/agent/loop.ts +29 -1
- package/src/avatar/traits-png-sync.ts +80 -25
- package/src/bundler/app-bundler.ts +4 -4
- package/src/calls/call-domain.ts +1 -0
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/auth.ts +92 -0
- package/src/cli/commands/avatar.ts +7 -6
- package/src/cli/commands/config.ts +2 -0
- package/src/cli/commands/oauth/providers.ts +29 -0
- package/src/cli/program.ts +12 -0
- package/src/cli.ts +15 -48
- package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
- package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
- package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
- package/src/config/bundled-tool-registry.ts +2 -14
- package/src/config/feature-flag-registry.json +8 -0
- package/src/config/loader.ts +64 -0
- package/src/config/raw-config-utils.ts +30 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/services.ts +8 -6
- package/src/contacts/contact-store.ts +13 -6
- package/src/contacts/contacts-write.ts +0 -1
- package/src/context/window-manager.ts +13 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +48 -7
- package/src/daemon/conversation-agent-loop.ts +56 -19
- package/src/daemon/conversation-attachments.ts +18 -36
- package/src/daemon/conversation-error.ts +2 -1
- package/src/daemon/conversation-history.ts +18 -4
- package/src/daemon/conversation-lifecycle.ts +39 -15
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +21 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +143 -20
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +51 -29
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +148 -0
- package/src/daemon/handlers/config-model.ts +71 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +106 -64
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +19 -0
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/shared.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/message-types/upgrades.ts +23 -0
- package/src/daemon/server.ts +83 -12
- package/src/daemon/shutdown-handlers.ts +8 -5
- package/src/daemon/startup-error.ts +9 -0
- package/src/daemon/tool-side-effects.ts +11 -28
- package/src/events/tool-permission-telemetry-listener.ts +1 -3
- package/src/instrument.ts +0 -4
- package/src/media/app-icon-generator.ts +2 -2
- package/src/memory/app-git-service.ts +28 -16
- package/src/memory/app-store.ts +230 -41
- package/src/memory/attachments-store.ts +558 -130
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +442 -3
- package/src/memory/conversation-directories.ts +125 -0
- package/src/memory/conversation-disk-view.ts +390 -0
- package/src/memory/conversation-key-store.ts +17 -5
- package/src/memory/conversation-queries.ts +5 -1
- package/src/memory/conversation-title-service.ts +21 -49
- package/src/memory/db-init.ts +28 -0
- package/src/memory/embedding-backend.ts +42 -53
- package/src/memory/embedding-gemini.test.ts +4 -4
- package/src/memory/embedding-local.ts +1 -3
- package/src/memory/embedding-ollama.ts +1 -3
- package/src/memory/embedding-openai.ts +1 -3
- package/src/memory/indexer.ts +9 -7
- package/src/memory/items-extractor.ts +42 -13
- package/src/memory/job-handlers/conversation-starters.ts +6 -1
- package/src/memory/job-handlers/embedding.test.ts +1 -4
- package/src/memory/llm-request-log-store.ts +100 -1
- package/src/memory/migrations/102-alter-table-columns.ts +5 -0
- package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
- package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
- package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
- package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
- package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
- package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
- package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
- package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
- package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +6 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/oauth.ts +6 -0
- package/src/messaging/providers/gmail/mime-builder.ts +3 -1
- package/src/notifications/copy-composer.ts +26 -0
- package/src/notifications/decision-engine.ts +14 -1
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +36 -0
- package/src/oauth/byo-connection.test.ts +1 -45
- package/src/oauth/byo-connection.ts +2 -8
- package/src/oauth/connect-orchestrator.ts +15 -11
- package/src/oauth/connection-resolver.test.ts +191 -0
- package/src/oauth/connection-resolver.ts +66 -38
- package/src/oauth/connection.ts +0 -1
- package/src/oauth/oauth-store.ts +97 -47
- package/src/oauth/platform-connection.test.ts +0 -1
- package/src/oauth/platform-connection.ts +11 -3
- package/src/oauth/seed-providers.ts +78 -3
- package/src/oauth/token-persistence.ts +16 -10
- package/src/permissions/checker.ts +71 -8
- package/src/prompts/templates/BOOTSTRAP.md +2 -0
- package/src/providers/anthropic/client.ts +8 -1
- package/src/providers/failover.ts +4 -1
- package/src/providers/gemini/client.ts +50 -0
- package/src/providers/model-catalog.ts +92 -0
- package/src/providers/model-intents.ts +29 -20
- package/src/providers/openai/client.ts +49 -0
- package/src/providers/types.ts +2 -0
- package/src/runtime/access-request-helper.ts +16 -7
- package/src/runtime/auth/credential-service.ts +3 -1
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/btw-sidechain.ts +101 -0
- package/src/runtime/channel-reply-delivery.ts +17 -1
- package/src/runtime/http-router.ts +3 -1
- package/src/runtime/http-server.ts +196 -141
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/migrations/vbundle-builder.ts +5 -1
- package/src/runtime/routes/access-request-decision.ts +41 -0
- package/src/runtime/routes/app-management-routes.ts +6 -3
- package/src/runtime/routes/app-routes.ts +7 -3
- package/src/runtime/routes/approval-routes.ts +1 -0
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
- package/src/runtime/routes/attachment-routes.ts +45 -15
- package/src/runtime/routes/btw-routes.ts +21 -61
- package/src/runtime/routes/conversation-management-routes.ts +68 -0
- package/src/runtime/routes/conversation-query-routes.ts +180 -10
- package/src/runtime/routes/conversation-routes.ts +222 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1199 -0
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/memory-item-routes.test.ts +34 -0
- package/src/runtime/routes/memory-item-routes.ts +4 -0
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/oauth-apps.ts +291 -0
- package/src/runtime/routes/secret-routes.ts +28 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +9 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/swarm/backend-claude-code.ts +3 -6
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +3 -1
- package/src/tools/AGENTS.md +6 -10
- package/src/tools/apps/executors.ts +17 -232
- package/src/tools/claude-code/claude-code.ts +2 -3
- package/src/tools/credentials/vault.ts +7 -12
- package/src/tools/host-filesystem/read.ts +13 -10
- package/src/tools/network/__tests__/web-search.test.ts +4 -2
- package/src/tools/schedule/list.ts +2 -7
- package/src/tools/schema-transforms.ts +5 -0
- package/src/tools/shared/filesystem/format-diff.ts +2 -7
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/tool-manifest.ts +0 -6
- package/src/tools/ui-surface/definitions.ts +2 -2
- package/src/util/device-id.ts +28 -5
- package/src/util/platform.ts +6 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
- package/src/workspace/migrations/003-seed-device-id.ts +3 -4
- package/src/workspace/migrations/006-services-config.ts +5 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
- package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/top-level-renderer.ts +12 -0
- package/src/__tests__/asset-materialize-tool.test.ts +0 -523
- package/src/__tests__/asset-search-tool.test.ts +0 -536
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
- package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
- package/src/__tests__/media-visibility-policy.test.ts +0 -190
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
- package/src/daemon/media-visibility-policy.ts +0 -59
- package/src/tools/assets/materialize.ts +0 -248
- package/src/tools/assets/search.ts +0 -400
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Unwrap a Zod schema to reach its inner object shape, handling:
|
|
5
|
+
* - default/optional/nullable wrappers (innerType)
|
|
6
|
+
* - pipe/transform wrappers (in — the input side)
|
|
7
|
+
*/
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
function unwrapToShape(schema: any): any {
|
|
10
|
+
let current = schema;
|
|
11
|
+
while (current && !current.shape) {
|
|
12
|
+
const def = current._zod?.def;
|
|
13
|
+
if (!def) break;
|
|
14
|
+
// Pipe/transform: follow the input side to get the pre-transform schema
|
|
15
|
+
if (def.type === "pipe" && def.in) {
|
|
16
|
+
current = def.in;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
// Default/optional/nullable: follow innerType
|
|
20
|
+
if (def.innerType) {
|
|
21
|
+
current = def.innerType;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
return current;
|
|
27
|
+
}
|
|
28
|
+
|
|
3
29
|
/**
|
|
4
30
|
* Navigate a Zod schema by dotted path, unwrapping wrapper types
|
|
5
|
-
* (default, optional, nullable) to reach inner object shapes.
|
|
31
|
+
* (default, optional, nullable, pipe/transform) to reach inner object shapes.
|
|
6
32
|
* Returns the Zod schema at the given path, or null if the path is invalid.
|
|
7
33
|
*/
|
|
8
34
|
export function getSchemaAtPath(
|
|
@@ -13,12 +39,7 @@ export function getSchemaAtPath(
|
|
|
13
39
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
40
|
let current: any = schema;
|
|
15
41
|
for (const key of keys) {
|
|
16
|
-
|
|
17
|
-
while (current && !current.shape) {
|
|
18
|
-
const inner = current._zod?.def?.innerType;
|
|
19
|
-
if (!inner) break;
|
|
20
|
-
current = inner;
|
|
21
|
-
}
|
|
42
|
+
current = unwrapToShape(current);
|
|
22
43
|
if (!current || !current.shape) return null;
|
|
23
44
|
current = current.shape[key];
|
|
24
45
|
if (!current) return null;
|
package/src/config/schema.ts
CHANGED
|
@@ -38,6 +38,7 @@ export type { ElevenLabsConfig } from "./schemas/elevenlabs.js";
|
|
|
38
38
|
export {
|
|
39
39
|
DEFAULT_ELEVENLABS_VOICE_ID,
|
|
40
40
|
ElevenLabsConfigSchema,
|
|
41
|
+
VALID_CONVERSATION_TIMEOUTS,
|
|
41
42
|
} from "./schemas/elevenlabs.js";
|
|
42
43
|
export type { HeartbeatConfig } from "./schemas/heartbeat.js";
|
|
43
44
|
export { HeartbeatConfigSchema } from "./schemas/heartbeat.js";
|
|
@@ -321,6 +322,13 @@ export const AssistantConfigSchema = z
|
|
|
321
322
|
.boolean()
|
|
322
323
|
.default(true)
|
|
323
324
|
.describe("Whether to send diagnostic/crash reports"),
|
|
325
|
+
maxStepsPerSession: z
|
|
326
|
+
.number({ error: "maxStepsPerSession must be a number" })
|
|
327
|
+
.int("maxStepsPerSession must be an integer")
|
|
328
|
+
.min(1, "maxStepsPerSession must be >= 1")
|
|
329
|
+
.max(200, "maxStepsPerSession must be <= 200")
|
|
330
|
+
.default(50)
|
|
331
|
+
.describe("Maximum number of computer-use steps per session"),
|
|
324
332
|
})
|
|
325
333
|
.superRefine((config, ctx) => {
|
|
326
334
|
if (
|
|
@@ -5,6 +5,9 @@ import { z } from "zod";
|
|
|
5
5
|
// Mirrored in: clients/macos/.../OpenAIVoiceService.swift (defaultVoiceId)
|
|
6
6
|
export const DEFAULT_ELEVENLABS_VOICE_ID = "ZF6FPAbjXT4488VcRRnw";
|
|
7
7
|
|
|
8
|
+
/** Valid conversation timeout values (seconds). Shared with voice-config-update tool. */
|
|
9
|
+
export const VALID_CONVERSATION_TIMEOUTS = [5, 10, 15, 30, 60] as const;
|
|
10
|
+
|
|
8
11
|
export const ElevenLabsConfigSchema = z
|
|
9
12
|
.object({
|
|
10
13
|
voiceId: z
|
|
@@ -42,6 +45,21 @@ export const ElevenLabsConfigSchema = z
|
|
|
42
45
|
.describe(
|
|
43
46
|
"How closely the output matches the original voice — higher values increase similarity",
|
|
44
47
|
),
|
|
48
|
+
conversationTimeoutSeconds: z
|
|
49
|
+
.number({
|
|
50
|
+
error: "elevenlabs.conversationTimeoutSeconds must be a number",
|
|
51
|
+
})
|
|
52
|
+
.refine(
|
|
53
|
+
(v) =>
|
|
54
|
+
VALID_CONVERSATION_TIMEOUTS.includes(
|
|
55
|
+
v as (typeof VALID_CONVERSATION_TIMEOUTS)[number],
|
|
56
|
+
),
|
|
57
|
+
{
|
|
58
|
+
message: `elevenlabs.conversationTimeoutSeconds must be one of: ${VALID_CONVERSATION_TIMEOUTS.join(", ")}`,
|
|
59
|
+
},
|
|
60
|
+
)
|
|
61
|
+
.default(30)
|
|
62
|
+
.describe("Seconds of silence before voice conversation auto-ends"),
|
|
45
63
|
})
|
|
46
64
|
.describe("ElevenLabs text-to-speech configuration");
|
|
47
65
|
|
|
@@ -68,8 +68,10 @@ export const MemoryCleanupConfigSchema = z
|
|
|
68
68
|
.nonnegative(
|
|
69
69
|
"memory.cleanup.conversationRetentionDays must be non-negative",
|
|
70
70
|
)
|
|
71
|
-
.default(
|
|
72
|
-
.describe(
|
|
71
|
+
.default(0)
|
|
72
|
+
.describe(
|
|
73
|
+
"Number of days to retain conversation data before cleanup (0 disables pruning)",
|
|
74
|
+
),
|
|
73
75
|
})
|
|
74
76
|
.describe("Automatic memory cleanup and garbage collection settings");
|
|
75
77
|
|
|
@@ -20,15 +20,18 @@ export const VALID_WEB_SEARCH_PROVIDERS = [
|
|
|
20
20
|
"inference-provider-native",
|
|
21
21
|
] as const;
|
|
22
22
|
|
|
23
|
-
export const
|
|
23
|
+
export const BaseServiceSchema = z.object({
|
|
24
24
|
mode: ServiceModeSchema.default("your-own"),
|
|
25
|
+
});
|
|
26
|
+
export type BaseService = z.infer<typeof BaseServiceSchema>;
|
|
27
|
+
|
|
28
|
+
export const InferenceServiceSchema = BaseServiceSchema.extend({
|
|
25
29
|
provider: z.enum(VALID_INFERENCE_PROVIDERS).default("anthropic"),
|
|
26
30
|
model: z.string().default("claude-opus-4-6"),
|
|
27
31
|
});
|
|
28
32
|
export type InferenceService = z.infer<typeof InferenceServiceSchema>;
|
|
29
33
|
|
|
30
|
-
export const ImageGenerationServiceSchema =
|
|
31
|
-
mode: ServiceModeSchema.default("your-own"),
|
|
34
|
+
export const ImageGenerationServiceSchema = BaseServiceSchema.extend({
|
|
32
35
|
provider: z.enum(VALID_IMAGE_GEN_PROVIDERS).default("gemini"),
|
|
33
36
|
model: z.string().default("gemini-3.1-flash-image-preview"),
|
|
34
37
|
});
|
|
@@ -36,15 +39,14 @@ export type ImageGenerationService = z.infer<
|
|
|
36
39
|
typeof ImageGenerationServiceSchema
|
|
37
40
|
>;
|
|
38
41
|
|
|
39
|
-
export const WebSearchServiceSchema =
|
|
40
|
-
mode: ServiceModeSchema.default("your-own"),
|
|
42
|
+
export const WebSearchServiceSchema = BaseServiceSchema.extend({
|
|
41
43
|
provider: z
|
|
42
44
|
.enum(VALID_WEB_SEARCH_PROVIDERS)
|
|
43
45
|
.default("inference-provider-native"),
|
|
44
46
|
});
|
|
45
47
|
export type WebSearchService = z.infer<typeof WebSearchServiceSchema>;
|
|
46
48
|
|
|
47
|
-
export const GoogleOAuthServiceSchema =
|
|
49
|
+
export const GoogleOAuthServiceSchema = BaseServiceSchema.extend({
|
|
48
50
|
mode: ServiceModeSchema.default("managed"),
|
|
49
51
|
});
|
|
50
52
|
export type GoogleOAuthService = z.infer<typeof GoogleOAuthServiceSchema>;
|
|
@@ -295,21 +295,28 @@ function syncChannels(
|
|
|
295
295
|
.get();
|
|
296
296
|
|
|
297
297
|
if (existing) {
|
|
298
|
+
// Preserve guardian blocks: if the channel is blocked, do not overwrite
|
|
299
|
+
// its status/policy — mirrors the guard in the cross-contact reassignment
|
|
300
|
+
// path so a blocked channel cannot be unblocked via a same-contact sync.
|
|
301
|
+
const isBlocked = existing.status === "blocked";
|
|
302
|
+
|
|
298
303
|
const updateSet: Record<string, unknown> = {};
|
|
299
304
|
if (ch.isPrimary !== undefined) updateSet.isPrimary = ch.isPrimary;
|
|
300
305
|
if (ch.externalUserId !== undefined)
|
|
301
306
|
updateSet.externalUserId = ch.externalUserId;
|
|
302
307
|
if (ch.externalChatId !== undefined)
|
|
303
308
|
updateSet.externalChatId = ch.externalChatId;
|
|
304
|
-
if (
|
|
305
|
-
|
|
309
|
+
if (!isBlocked) {
|
|
310
|
+
if (ch.status !== undefined) updateSet.status = ch.status;
|
|
311
|
+
if (ch.policy !== undefined) updateSet.policy = ch.policy;
|
|
312
|
+
if (ch.revokedReason !== undefined)
|
|
313
|
+
updateSet.revokedReason = ch.revokedReason;
|
|
314
|
+
if (ch.blockedReason !== undefined)
|
|
315
|
+
updateSet.blockedReason = ch.blockedReason;
|
|
316
|
+
}
|
|
306
317
|
if (ch.verifiedAt !== undefined) updateSet.verifiedAt = ch.verifiedAt;
|
|
307
318
|
if (ch.verifiedVia !== undefined) updateSet.verifiedVia = ch.verifiedVia;
|
|
308
319
|
if (ch.inviteId !== undefined) updateSet.inviteId = ch.inviteId;
|
|
309
|
-
if (ch.revokedReason !== undefined)
|
|
310
|
-
updateSet.revokedReason = ch.revokedReason;
|
|
311
|
-
if (ch.blockedReason !== undefined)
|
|
312
|
-
updateSet.blockedReason = ch.blockedReason;
|
|
313
320
|
|
|
314
321
|
if (Object.keys(updateSet).length > 0) {
|
|
315
322
|
updateSet.updatedAt = now;
|
|
@@ -681,13 +681,24 @@ function countPersistedMessages(messages: Message[]): number {
|
|
|
681
681
|
}).length;
|
|
682
682
|
}
|
|
683
683
|
|
|
684
|
-
|
|
684
|
+
function isSystemNoticeBlock(block: ContentBlock): boolean {
|
|
685
|
+
if (block.type !== "text") return false;
|
|
686
|
+
const text = (block as { text?: string }).text ?? "";
|
|
687
|
+
return (
|
|
688
|
+
text.startsWith("<system_notice>") && text.endsWith("</system_notice>")
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/** A user message that contains ONLY tool_result blocks (no text or other content).
|
|
693
|
+
* System notice text blocks (retry nudges, progress checks) do not count as user content. */
|
|
685
694
|
function isToolResultOnly(message: Message): boolean {
|
|
686
695
|
return (
|
|
687
696
|
message.content.length > 0 &&
|
|
688
697
|
message.content.every(
|
|
689
698
|
(block) =>
|
|
690
|
-
block.type === "tool_result" ||
|
|
699
|
+
block.type === "tool_result" ||
|
|
700
|
+
block.type === "web_search_tool_result" ||
|
|
701
|
+
isSystemNoticeBlock(block),
|
|
691
702
|
)
|
|
692
703
|
);
|
|
693
704
|
}
|
|
@@ -15,11 +15,16 @@ import type {
|
|
|
15
15
|
} from "../channels/types.js";
|
|
16
16
|
import {
|
|
17
17
|
addMessage,
|
|
18
|
+
getConversation,
|
|
18
19
|
getMessageById,
|
|
19
20
|
provenanceFromTrustContext,
|
|
20
21
|
updateMessageContent,
|
|
21
22
|
} from "../memory/conversation-crud.js";
|
|
22
|
-
import {
|
|
23
|
+
import { syncMessageToDisk } from "../memory/conversation-disk-view.js";
|
|
24
|
+
import {
|
|
25
|
+
backfillMessageIdOnLogs,
|
|
26
|
+
recordRequestLog,
|
|
27
|
+
} from "../memory/llm-request-log-store.js";
|
|
23
28
|
import type { ContentBlock, ImageContent } from "../providers/types.js";
|
|
24
29
|
import type { DirectiveRequest } from "./assistant-attachments.js";
|
|
25
30
|
import {
|
|
@@ -48,6 +53,8 @@ export interface EventHandlerState {
|
|
|
48
53
|
llmCallStartedEmitted: boolean;
|
|
49
54
|
pendingDirectiveDisplayBuffer: string;
|
|
50
55
|
firstAssistantText: string;
|
|
56
|
+
/** Most recent resolved provider for the current exchange's usage accounting. */
|
|
57
|
+
exchangeProviderName: string | undefined;
|
|
51
58
|
exchangeInputTokens: number;
|
|
52
59
|
exchangeCacheCreationInputTokens: number;
|
|
53
60
|
exchangeCacheReadInputTokens: number;
|
|
@@ -114,6 +121,7 @@ export function createEventHandlerState(): EventHandlerState {
|
|
|
114
121
|
llmCallStartedEmitted: false,
|
|
115
122
|
pendingDirectiveDisplayBuffer: "",
|
|
116
123
|
firstAssistantText: "",
|
|
124
|
+
exchangeProviderName: undefined,
|
|
117
125
|
exchangeInputTokens: 0,
|
|
118
126
|
exchangeCacheCreationInputTokens: 0,
|
|
119
127
|
exchangeCacheReadInputTokens: 0,
|
|
@@ -167,6 +175,12 @@ export function emitLlmCallStartedIfNeeded(
|
|
|
167
175
|
);
|
|
168
176
|
}
|
|
169
177
|
|
|
178
|
+
// ── Client Payload Size Caps ─────────────────────────────────────────
|
|
179
|
+
// tool_input_delta streams accumulated JSON as tools run. For non-app
|
|
180
|
+
// tools the client discards it (extractCodePreview only handles app tools),
|
|
181
|
+
// so we skip forwarding entirely to avoid transport/decode overhead.
|
|
182
|
+
const APP_TOOL_NAMES = new Set(["app_create"]);
|
|
183
|
+
|
|
170
184
|
// ── Friendly Tool Names ──────────────────────────────────────────────
|
|
171
185
|
|
|
172
186
|
const TOOL_FRIENDLY_NAMES: Record<string, string> = {
|
|
@@ -183,11 +197,9 @@ const TOOL_FRIENDLY_NAMES: Record<string, string> = {
|
|
|
183
197
|
browser_scroll: "browser",
|
|
184
198
|
browser_wait: "browser",
|
|
185
199
|
app_create: "app",
|
|
186
|
-
|
|
200
|
+
app_refresh: "app refresh",
|
|
187
201
|
skill_load: "skill",
|
|
188
202
|
skill_execute: "skill",
|
|
189
|
-
app_file_edit: "app file",
|
|
190
|
-
app_file_write: "app file",
|
|
191
203
|
};
|
|
192
204
|
|
|
193
205
|
function friendlyToolName(name: string): string {
|
|
@@ -385,6 +397,10 @@ export function handleInputJsonDelta(
|
|
|
385
397
|
deps: EventHandlerDeps,
|
|
386
398
|
event: Extract<AgentEvent, { type: "input_json_delta" }>,
|
|
387
399
|
): void {
|
|
400
|
+
// Only forward input deltas for app tools — the client only uses this
|
|
401
|
+
// stream for app_create/app_update code previews. Non-app tools would
|
|
402
|
+
// send large cumulative JSON on every delta with no benefit.
|
|
403
|
+
if (!APP_TOOL_NAMES.has(event.toolName)) return;
|
|
388
404
|
deps.onEvent({
|
|
389
405
|
type: "tool_input_delta",
|
|
390
406
|
toolName: event.toolName,
|
|
@@ -627,12 +643,21 @@ export async function handleMessageComplete(
|
|
|
627
643
|
assistantMessageInterface:
|
|
628
644
|
deps.turnInterfaceContext.assistantMessageInterface,
|
|
629
645
|
};
|
|
630
|
-
await addMessage(
|
|
646
|
+
const toolResultMsg = await addMessage(
|
|
631
647
|
deps.ctx.conversationId,
|
|
632
648
|
"user",
|
|
633
649
|
JSON.stringify(toolResultBlocks),
|
|
634
650
|
toolResultMetadata,
|
|
635
651
|
);
|
|
652
|
+
// Sync tool-result user message to disk view
|
|
653
|
+
const convForToolResult = getConversation(deps.ctx.conversationId);
|
|
654
|
+
if (convForToolResult) {
|
|
655
|
+
syncMessageToDisk(
|
|
656
|
+
deps.ctx.conversationId,
|
|
657
|
+
toolResultMsg.id,
|
|
658
|
+
convForToolResult.createdAt,
|
|
659
|
+
);
|
|
660
|
+
}
|
|
636
661
|
for (const id of state.pendingToolResults.keys()) {
|
|
637
662
|
state.persistedToolUseIds.add(id);
|
|
638
663
|
}
|
|
@@ -697,6 +722,18 @@ export async function handleMessageComplete(
|
|
|
697
722
|
);
|
|
698
723
|
state.lastAssistantMessageId = assistantMsg.id;
|
|
699
724
|
|
|
725
|
+
// Backfill message_id on all LLM request logs from this turn.
|
|
726
|
+
// The agent loop is single-threaded per conversation, so all rows with
|
|
727
|
+
// message_id IS NULL belong to the current turn.
|
|
728
|
+
try {
|
|
729
|
+
backfillMessageIdOnLogs(deps.ctx.conversationId, assistantMsg.id);
|
|
730
|
+
} catch (err) {
|
|
731
|
+
deps.rlog.warn(
|
|
732
|
+
{ err },
|
|
733
|
+
"Failed to backfill message_id on LLM request logs (non-fatal)",
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
|
|
700
737
|
deps.ctx.currentTurnSurfaces = [];
|
|
701
738
|
|
|
702
739
|
// Emit trace event
|
|
@@ -724,6 +761,8 @@ export function handleUsage(
|
|
|
724
761
|
deps: EventHandlerDeps,
|
|
725
762
|
event: Extract<AgentEvent, { type: "usage" }>,
|
|
726
763
|
): void {
|
|
764
|
+
const providerName = event.actualProvider ?? deps.ctx.provider.name;
|
|
765
|
+
state.exchangeProviderName = providerName;
|
|
727
766
|
state.exchangeInputTokens += event.inputTokens;
|
|
728
767
|
state.exchangeCacheCreationInputTokens += event.cacheCreationInputTokens ?? 0;
|
|
729
768
|
state.exchangeCacheReadInputTokens += event.cacheReadInputTokens ?? 0;
|
|
@@ -739,6 +778,8 @@ export function handleUsage(
|
|
|
739
778
|
deps.ctx.conversationId,
|
|
740
779
|
JSON.stringify(event.rawRequest),
|
|
741
780
|
JSON.stringify(event.rawResponse),
|
|
781
|
+
undefined,
|
|
782
|
+
providerName,
|
|
742
783
|
);
|
|
743
784
|
} catch (err) {
|
|
744
785
|
deps.rlog.warn({ err }, "Failed to persist LLM request log (non-fatal)");
|
|
@@ -749,12 +790,12 @@ export function handleUsage(
|
|
|
749
790
|
|
|
750
791
|
deps.ctx.traceEmitter.emit(
|
|
751
792
|
"llm_call_finished",
|
|
752
|
-
`LLM call to ${
|
|
793
|
+
`LLM call to ${providerName} finished`,
|
|
753
794
|
{
|
|
754
795
|
requestId: deps.reqId,
|
|
755
796
|
status: "success",
|
|
756
797
|
attributes: {
|
|
757
|
-
provider:
|
|
798
|
+
provider: providerName,
|
|
758
799
|
model: event.model,
|
|
759
800
|
inputTokens: event.inputTokens,
|
|
760
801
|
outputTokens: event.outputTokens,
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
setSentryConversationContext,
|
|
33
33
|
} from "../instrument.js";
|
|
34
34
|
import { commitAppTurnChanges } from "../memory/app-git-service.js";
|
|
35
|
-
import { getApp, listAppFiles } from "../memory/app-store.js";
|
|
35
|
+
import { getApp, listAppFiles, resolveAppDir } from "../memory/app-store.js";
|
|
36
36
|
import {
|
|
37
37
|
addMessage,
|
|
38
38
|
deleteMessageById,
|
|
@@ -43,6 +43,10 @@ import {
|
|
|
43
43
|
updateConversationContextWindow,
|
|
44
44
|
updateConversationTitle,
|
|
45
45
|
} from "../memory/conversation-crud.js";
|
|
46
|
+
import {
|
|
47
|
+
rebuildConversationDiskViewFromDbState,
|
|
48
|
+
syncMessageToDisk,
|
|
49
|
+
} from "../memory/conversation-disk-view.js";
|
|
46
50
|
import {
|
|
47
51
|
isReplaceableTitle,
|
|
48
52
|
queueGenerateConversationTitle,
|
|
@@ -77,7 +81,6 @@ import {
|
|
|
77
81
|
} from "./conversation-agent-loop-handlers.js";
|
|
78
82
|
import {
|
|
79
83
|
approveHostAttachmentRead,
|
|
80
|
-
formatAttachmentWarnings,
|
|
81
84
|
resolveAssistantAttachments,
|
|
82
85
|
} from "./conversation-attachments.js";
|
|
83
86
|
import {
|
|
@@ -173,11 +176,9 @@ const TOOL_FRIENDLY_LABEL: Record<string, string> = {
|
|
|
173
176
|
browser_scroll: "Browser",
|
|
174
177
|
browser_wait: "Browser",
|
|
175
178
|
app_create: "Create App",
|
|
176
|
-
|
|
179
|
+
app_refresh: "Refresh App",
|
|
177
180
|
skill_load: "Load Skill",
|
|
178
181
|
skill_execute: "Run Skill Tool",
|
|
179
|
-
app_file_edit: "Edit App File",
|
|
180
|
-
app_file_write: "Write App File",
|
|
181
182
|
};
|
|
182
183
|
|
|
183
184
|
type GitServiceInitializer = {
|
|
@@ -604,6 +605,7 @@ export async function runAgentLoopImpl(
|
|
|
604
605
|
if (app) {
|
|
605
606
|
activeSurface.appId = app.id;
|
|
606
607
|
activeSurface.appName = app.name;
|
|
608
|
+
activeSurface.appDirName = resolveAppDir(app.id).dirName;
|
|
607
609
|
activeSurface.appSchemaJson = app.schemaJson;
|
|
608
610
|
activeSurface.appFiles = listAppFiles(app.id);
|
|
609
611
|
if (app.pages && Object.keys(app.pages).length > 0) {
|
|
@@ -801,7 +803,16 @@ export async function runAgentLoopImpl(
|
|
|
801
803
|
mode: currentInjectionMode,
|
|
802
804
|
});
|
|
803
805
|
|
|
804
|
-
|
|
806
|
+
// Re-estimate with injections included — step.estimatedTokens was
|
|
807
|
+
// computed on bare history (ctx.messages) and doesn't account for
|
|
808
|
+
// tokens added by runtime injections.
|
|
809
|
+
const postInjectionTokens = estimatePromptTokens(
|
|
810
|
+
runMessages,
|
|
811
|
+
ctx.systemPrompt,
|
|
812
|
+
{ providerName: ctx.provider.name, toolTokenBudget },
|
|
813
|
+
);
|
|
814
|
+
|
|
815
|
+
if (postInjectionTokens <= preflightBudget) break;
|
|
805
816
|
}
|
|
806
817
|
}
|
|
807
818
|
|
|
@@ -1173,6 +1184,7 @@ export async function runAgentLoopImpl(
|
|
|
1173
1184
|
preRepairMessages = runMessages;
|
|
1174
1185
|
preRunHistoryLength = runMessages.length;
|
|
1175
1186
|
state.contextTooLargeDetected = false;
|
|
1187
|
+
yieldedForBudget = false;
|
|
1176
1188
|
|
|
1177
1189
|
updatedHistory = await ctx.agentLoop.run(
|
|
1178
1190
|
runMessages,
|
|
@@ -1195,6 +1207,15 @@ export async function runAgentLoopImpl(
|
|
|
1195
1207
|
"Post-convergence rerun still yielded at checkpoint — continuing reduction",
|
|
1196
1208
|
);
|
|
1197
1209
|
state.contextTooLargeDetected = true;
|
|
1210
|
+
|
|
1211
|
+
// Fold rerun progress into ctx.messages so the next reducer
|
|
1212
|
+
// tier operates on up-to-date history instead of stale
|
|
1213
|
+
// pre-rerun messages.
|
|
1214
|
+
if (updatedHistory.length > preRunHistoryLength) {
|
|
1215
|
+
ctx.messages = stripInjectedContext(updatedHistory);
|
|
1216
|
+
preRepairMessages = updatedHistory;
|
|
1217
|
+
preRunHistoryLength = updatedHistory.length;
|
|
1218
|
+
}
|
|
1198
1219
|
}
|
|
1199
1220
|
}
|
|
1200
1221
|
|
|
@@ -1514,16 +1535,29 @@ export async function runAgentLoopImpl(
|
|
|
1514
1535
|
state.exchangeCacheCreationInputTokens,
|
|
1515
1536
|
state.exchangeCacheReadInputTokens,
|
|
1516
1537
|
collapseRawResponses(state.exchangeRawResponses),
|
|
1538
|
+
state.exchangeProviderName,
|
|
1517
1539
|
);
|
|
1518
1540
|
|
|
1519
1541
|
void getHookManager().trigger("post-message", {
|
|
1520
1542
|
conversationId: ctx.conversationId,
|
|
1521
1543
|
});
|
|
1522
1544
|
|
|
1545
|
+
const syncLastAssistantMessageToDisk = (): void => {
|
|
1546
|
+
if (!state.lastAssistantMessageId) return;
|
|
1547
|
+
const convForDisk = getConversation(ctx.conversationId);
|
|
1548
|
+
if (!convForDisk) return;
|
|
1549
|
+
syncMessageToDisk(
|
|
1550
|
+
ctx.conversationId,
|
|
1551
|
+
state.lastAssistantMessageId,
|
|
1552
|
+
convForDisk.createdAt,
|
|
1553
|
+
);
|
|
1554
|
+
};
|
|
1555
|
+
|
|
1523
1556
|
// Fast-path: when the user cancelled, skip expensive post-loop work
|
|
1524
1557
|
// (attachment resolution) and emit the cancellation event immediately
|
|
1525
1558
|
// so the client can re-enable the UI without delay.
|
|
1526
1559
|
if (abortController.signal.aborted) {
|
|
1560
|
+
syncLastAssistantMessageToDisk();
|
|
1527
1561
|
ctx.emitActivityState("idle", "generation_cancelled", "global", reqId);
|
|
1528
1562
|
ctx.traceEmitter.emit(
|
|
1529
1563
|
"generation_cancelled",
|
|
@@ -1559,17 +1593,7 @@ export async function runAgentLoopImpl(
|
|
|
1559
1593
|
|
|
1560
1594
|
ctx.lastAssistantAttachments = assistantAttachments;
|
|
1561
1595
|
ctx.lastAttachmentWarnings = attachmentResult.directiveWarnings;
|
|
1562
|
-
|
|
1563
|
-
const warningText = formatAttachmentWarnings(
|
|
1564
|
-
attachmentResult.directiveWarnings,
|
|
1565
|
-
);
|
|
1566
|
-
if (warningText) {
|
|
1567
|
-
onEvent({
|
|
1568
|
-
type: "assistant_text_delta",
|
|
1569
|
-
text: warningText,
|
|
1570
|
-
conversationId: ctx.conversationId,
|
|
1571
|
-
});
|
|
1572
|
-
}
|
|
1596
|
+
syncLastAssistantMessageToDisk();
|
|
1573
1597
|
|
|
1574
1598
|
// Re-check: the user may have cancelled during attachment resolution
|
|
1575
1599
|
if (abortController.signal.aborted) {
|
|
@@ -1604,6 +1628,9 @@ export async function runAgentLoopImpl(
|
|
|
1604
1628
|
...(emittedAttachments.length > 0
|
|
1605
1629
|
? { attachments: emittedAttachments }
|
|
1606
1630
|
: {}),
|
|
1631
|
+
...(ctx.lastAttachmentWarnings.length > 0
|
|
1632
|
+
? { attachmentWarnings: ctx.lastAttachmentWarnings }
|
|
1633
|
+
: {}),
|
|
1607
1634
|
...(state.lastAssistantMessageId
|
|
1608
1635
|
? { messageId: state.lastAssistantMessageId }
|
|
1609
1636
|
: {}),
|
|
@@ -1624,6 +1651,9 @@ export async function runAgentLoopImpl(
|
|
|
1624
1651
|
...(emittedAttachments.length > 0
|
|
1625
1652
|
? { attachments: emittedAttachments }
|
|
1626
1653
|
: {}),
|
|
1654
|
+
...(ctx.lastAttachmentWarnings.length > 0
|
|
1655
|
+
? { attachmentWarnings: ctx.lastAttachmentWarnings }
|
|
1656
|
+
: {}),
|
|
1627
1657
|
...(state.lastAssistantMessageId
|
|
1628
1658
|
? { messageId: state.lastAssistantMessageId }
|
|
1629
1659
|
: {}),
|
|
@@ -1739,7 +1769,13 @@ export async function runAgentLoopImpl(
|
|
|
1739
1769
|
ctx.commandIntent = undefined;
|
|
1740
1770
|
|
|
1741
1771
|
if (userMessageId) {
|
|
1742
|
-
consolidateAssistantMessages(
|
|
1772
|
+
const didMutateHistory = consolidateAssistantMessages(
|
|
1773
|
+
ctx.conversationId,
|
|
1774
|
+
userMessageId,
|
|
1775
|
+
);
|
|
1776
|
+
if (didMutateHistory) {
|
|
1777
|
+
rebuildConversationDiskViewFromDbState(ctx.conversationId);
|
|
1778
|
+
}
|
|
1743
1779
|
}
|
|
1744
1780
|
|
|
1745
1781
|
ctx.drainQueue(yieldedForHandoff ? "checkpoint_handoff" : "loop_complete");
|
|
@@ -1766,11 +1802,12 @@ function emitUsage(
|
|
|
1766
1802
|
cacheCreationInputTokens = 0,
|
|
1767
1803
|
cacheReadInputTokens = 0,
|
|
1768
1804
|
rawResponse?: unknown,
|
|
1805
|
+
providerName?: string,
|
|
1769
1806
|
): void {
|
|
1770
1807
|
recordUsage(
|
|
1771
1808
|
{
|
|
1772
1809
|
conversationId: ctx.conversationId,
|
|
1773
|
-
providerName: ctx.provider.name,
|
|
1810
|
+
providerName: providerName ?? ctx.provider.name,
|
|
1774
1811
|
usageStats: ctx.usageStats,
|
|
1775
1812
|
},
|
|
1776
1813
|
inputTokens,
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
attachInlineAttachmentToMessage,
|
|
2
3
|
AttachmentUploadError,
|
|
3
|
-
|
|
4
|
-
linkAttachmentToMessage,
|
|
4
|
+
getFilePathForAttachment,
|
|
5
5
|
setAttachmentThumbnail,
|
|
6
|
-
uploadAttachment,
|
|
7
|
-
uploadFileBackedAttachment,
|
|
8
|
-
writeAttachmentToDisk,
|
|
9
6
|
} from "../memory/attachments-store.js";
|
|
10
7
|
import {
|
|
11
8
|
check,
|
|
@@ -107,12 +104,6 @@ export async function approveHostAttachmentRead(
|
|
|
107
104
|
return isAllowDecision(response.decision);
|
|
108
105
|
}
|
|
109
106
|
|
|
110
|
-
export function formatAttachmentWarnings(warnings: string[]): string | null {
|
|
111
|
-
if (warnings.length === 0) return null;
|
|
112
|
-
const lines = warnings.map((warning) => `Attachment warning: ${warning}`);
|
|
113
|
-
return `\n\n${lines.join("\n")}`;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
107
|
export interface AttachmentResolutionResult {
|
|
117
108
|
assistantAttachments: AssistantAttachmentDraft[];
|
|
118
109
|
emittedAttachments: UserMessageAttachment[];
|
|
@@ -211,28 +202,16 @@ export async function resolveAssistantAttachments(
|
|
|
211
202
|
if (assistantAttachments.length > 0 && lastAssistantMessageId) {
|
|
212
203
|
for (let i = 0; i < assistantAttachments.length; i++) {
|
|
213
204
|
const draft = assistantAttachments[i];
|
|
214
|
-
const isFileBacked = draft.sizeBytes > FILE_BACKED_THRESHOLD_BYTES;
|
|
215
205
|
let stored;
|
|
216
|
-
let diskFilePath: string | undefined;
|
|
217
206
|
try {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
diskFilePath,
|
|
227
|
-
draft.sizeBytes,
|
|
228
|
-
);
|
|
229
|
-
} else {
|
|
230
|
-
stored = uploadAttachment(
|
|
231
|
-
draft.filename,
|
|
232
|
-
draft.mimeType,
|
|
233
|
-
draft.dataBase64,
|
|
234
|
-
);
|
|
235
|
-
}
|
|
207
|
+
stored = attachInlineAttachmentToMessage(
|
|
208
|
+
lastAssistantMessageId,
|
|
209
|
+
i,
|
|
210
|
+
draft.filename,
|
|
211
|
+
draft.mimeType,
|
|
212
|
+
draft.dataBase64,
|
|
213
|
+
{ skipSizeLimit: true },
|
|
214
|
+
);
|
|
236
215
|
} catch (err) {
|
|
237
216
|
if (err instanceof AttachmentUploadError) {
|
|
238
217
|
log.warn(
|
|
@@ -246,11 +225,11 @@ export async function resolveAssistantAttachments(
|
|
|
246
225
|
}
|
|
247
226
|
throw err;
|
|
248
227
|
}
|
|
249
|
-
linkAttachmentToMessage(lastAssistantMessageId, stored.id, i);
|
|
250
228
|
const isVideo = draft.mimeType.startsWith("video/");
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
229
|
+
// Only omit data for videos — they have an end-to-end lazy-load path
|
|
230
|
+
// via /v1/attachments/:id/content. Other types (images, PDFs) still need
|
|
231
|
+
// inline data for thumbnails, preview, and file-save in the client.
|
|
232
|
+
const omitData = isVideo && draft.dataBase64.length > MAX_INLINE_B64_SIZE;
|
|
254
233
|
|
|
255
234
|
// Generate and persist a thumbnail for video attachments.
|
|
256
235
|
let thumbnailData: string | undefined;
|
|
@@ -259,6 +238,7 @@ export async function resolveAssistantAttachments(
|
|
|
259
238
|
if (existing) {
|
|
260
239
|
thumbnailData = existing;
|
|
261
240
|
} else {
|
|
241
|
+
const diskFilePath = getFilePathForAttachment(stored.id);
|
|
262
242
|
const generated = diskFilePath
|
|
263
243
|
? await generateVideoThumbnailFromPath(diskFilePath)
|
|
264
244
|
: await generateVideoThumbnail(draft.dataBase64);
|
|
@@ -274,8 +254,9 @@ export async function resolveAssistantAttachments(
|
|
|
274
254
|
filename: draft.filename,
|
|
275
255
|
mimeType: draft.mimeType,
|
|
276
256
|
data: omitData ? "" : draft.dataBase64,
|
|
257
|
+
sourceType: draft.sourceType,
|
|
277
258
|
...(omitData ? { sizeBytes: draft.sizeBytes } : {}),
|
|
278
|
-
|
|
259
|
+
fileBacked: true,
|
|
279
260
|
...(thumbnailData ? { thumbnailData } : {}),
|
|
280
261
|
});
|
|
281
262
|
}
|
|
@@ -285,6 +266,7 @@ export async function resolveAssistantAttachments(
|
|
|
285
266
|
filename: draft.filename,
|
|
286
267
|
mimeType: draft.mimeType,
|
|
287
268
|
data: draft.dataBase64,
|
|
269
|
+
sourceType: draft.sourceType,
|
|
288
270
|
});
|
|
289
271
|
}
|
|
290
272
|
}
|