@vellumai/assistant 0.11.1 → 0.11.2-staging.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/Dockerfile +1 -3
- package/README.md +1 -1
- package/eslint.config.mjs +28 -8
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/slack-text/src/index.test.ts +48 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +25 -6
- package/openapi.yaml +292 -5
- package/package.json +1 -1
- package/src/__tests__/agent-loop-resume-interrupted.test.ts +223 -0
- package/src/__tests__/byok-default-profile-ensure.test.ts +43 -0
- package/src/__tests__/cli-logger-boundary-guard.test.ts +87 -0
- package/src/__tests__/compaction-events.test.ts +139 -2
- package/src/__tests__/config-loader-backfill.test.ts +35 -3
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +30 -0
- package/src/__tests__/conversation-agent-loop.test.ts +180 -7
- package/src/__tests__/conversation-error.test.ts +211 -4
- package/src/__tests__/conversation-options-turn-scoped-transport.test.ts +64 -0
- package/src/__tests__/conversation-queue.test.ts +126 -0
- package/src/__tests__/conversation-retry-route.test.ts +46 -7
- package/src/__tests__/conversation-slash.test.ts +4 -2
- package/src/__tests__/conversation-summarize-route.test.ts +7 -1
- package/src/__tests__/conversation-summarize-up-to.test.ts +71 -5
- package/src/__tests__/document-create-dedupe.test.ts +2 -1
- package/src/__tests__/document-find-replace.test.ts +2 -1
- package/src/__tests__/document-tool-security.test.ts +2 -1
- package/src/__tests__/document-update-default-surface.test.ts +2 -1
- package/src/__tests__/document-workspace-file.test.ts +467 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +290 -49
- package/src/__tests__/guardian-card-withdrawal.test.ts +91 -3
- package/src/__tests__/http-user-message-parity.test.ts +66 -0
- package/src/__tests__/list-messages-attachments.test.ts +87 -0
- package/src/__tests__/list-messages-provider-error.test.ts +143 -0
- package/src/__tests__/list-messages-system-card.test.ts +100 -0
- package/src/__tests__/llm-resolver.test.ts +19 -9
- package/src/__tests__/managed-profile-guard.test.ts +23 -0
- package/src/__tests__/notification-platform-adapter.test.ts +130 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +6 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +45 -0
- package/src/__tests__/plugin-api-model-profiles.test.ts +10 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
- package/src/__tests__/provider-error-scenarios.test.ts +140 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +95 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +66 -3
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +209 -0
- package/src/__tests__/skills.test.ts +27 -0
- package/src/__tests__/slack-channels-routes.test.ts +0 -2
- package/src/__tests__/slack-share-routes.test.ts +0 -3
- package/src/__tests__/slack-users-routes.test.ts +0 -2
- package/src/__tests__/subagent-tools.test.ts +11 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +58 -0
- package/src/__tests__/tool-result-spool.test.ts +5 -4
- package/src/__tests__/turn-boundary-resolution.test.ts +53 -0
- package/src/__tests__/turn-events-store.test.ts +26 -0
- package/src/__tests__/ui-visual-surface.test.ts +695 -0
- package/src/__tests__/unified-turn-context-visible-app.test.ts +99 -0
- package/src/__tests__/visible-app-context.test.ts +189 -0
- package/src/__tests__/workspace-git-service.test.ts +173 -33
- package/src/__tests__/workspace-migration-137-repair-retired-fireworks-minimax-model-id.test.ts +157 -0
- package/src/__tests__/workspace-migration-138-backfill-home-feed-titles.test.ts +373 -0
- package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +137 -0
- package/src/agent/loop.ts +36 -7
- package/src/api/events/context-window-usage.ts +31 -0
- package/src/api/events/notification-intent.ts +8 -0
- package/src/api/events/ui-surface-pending.ts +35 -0
- package/src/api/index.ts +14 -0
- package/src/api/responses/conversation-message.ts +25 -4
- package/src/api/surfaces.ts +90 -1
- package/src/approvals/guardian-card-withdrawal.ts +66 -31
- package/src/approvals/guardian-decision-primitive.ts +4 -0
- package/src/calls/__tests__/call-setup-router.test.ts +156 -26
- package/src/calls/__tests__/voice-session-bridge.test.ts +201 -12
- package/src/calls/call-setup-router.ts +80 -36
- package/src/calls/voice-session-bridge.ts +173 -26
- package/src/cli/commands/inference.help.ts +3 -3
- package/src/cli/commands/notifications.help.ts +3 -2
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +42 -128
- package/src/cli/commands/platform/__tests__/credits.test.ts +9 -78
- package/src/cli/commands/platform/__tests__/helpers.ts +90 -0
- package/src/cli/commands/platform/__tests__/invoices.test.ts +238 -0
- package/src/cli/commands/platform/__tests__/plans.test.ts +9 -88
- package/src/cli/commands/platform/__tests__/status.test.ts +12 -87
- package/src/cli/commands/platform/__tests__/subscription.test.ts +9 -86
- package/src/cli/commands/platform/index.help.ts +92 -0
- package/src/cli/commands/platform/index.ts +7 -0
- package/src/cli/commands/platform/invoices.ts +132 -0
- package/src/cli/commands/usage.help.ts +1 -1
- package/src/cli/lib/list-installed-plugins.ts +2 -1
- package/src/config/__tests__/default-profile-catalog.test.ts +7 -10
- package/src/config/__tests__/deployment-context-defaults.test.ts +27 -5
- package/src/config/assistant-feature-flags.ts +7 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -2
- package/src/config/bundled-skills/subagent/SKILL.md +3 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +3 -3
- package/src/config/bundled-skills/visualize/SKILL.md +163 -0
- package/src/config/call-site-defaults.ts +3 -2
- package/src/config/default-profile-catalog.ts +75 -70
- package/src/config/default-profile-names.ts +13 -28
- package/src/config/env-registry.ts +1 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/llm-resolver.ts +21 -5
- package/src/config/loader.ts +18 -11
- package/src/config/schemas/llm.ts +1 -9
- package/src/config/schemas/memory-retrospective.ts +9 -0
- package/src/config/schemas/monitoring.ts +28 -2
- package/src/config/schemas/workspace-git.ts +15 -0
- package/src/config/seed-inference-profiles.ts +19 -5
- package/src/context/post-turn-tool-result-truncation.ts +2 -2
- package/src/conversations/__tests__/message-consolidation.test.ts +54 -0
- package/src/conversations/message-consolidation.ts +17 -15
- package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +182 -0
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +181 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +90 -7
- package/src/daemon/conversation-agent-loop.ts +107 -22
- package/src/daemon/conversation-error.ts +169 -48
- package/src/daemon/conversation-messaging.ts +87 -4
- package/src/daemon/conversation-process.ts +57 -45
- package/src/daemon/conversation-runtime-assembly.ts +57 -0
- package/src/daemon/conversation-store.ts +36 -1
- package/src/daemon/conversation-surfaces.ts +68 -6
- package/src/daemon/conversation-turn-finalize.ts +76 -18
- package/src/daemon/conversation.ts +122 -27
- package/src/daemon/lifecycle.ts +9 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/surfaces.ts +3 -0
- package/src/documents/document-store.ts +247 -10
- package/src/home/__tests__/feed-types.test.ts +43 -8
- package/src/home/__tests__/feed-writer.test.ts +59 -0
- package/src/home/feed-types.ts +34 -7
- package/src/home/feed-writer.ts +15 -6
- package/src/live-voice/__tests__/activity-label.test.ts +95 -0
- package/src/live-voice/__tests__/live-activity-reporter.test.ts +86 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +310 -25
- package/src/live-voice/__tests__/live-voice-events.test.ts +4 -4
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +8 -4
- package/src/live-voice/__tests__/live-voice-vad.test.ts +9 -1
- package/src/live-voice/activity-label.ts +169 -0
- package/src/live-voice/live-activity-reporter.ts +35 -6
- package/src/live-voice/live-voice-session.ts +282 -41
- package/src/live-voice/protocol.ts +35 -0
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +84 -2
- package/src/messaging/providers/slack/adapter.ts +86 -19
- package/src/messaging/providers/slack/api.test.ts +85 -1
- package/src/messaging/providers/slack/api.ts +121 -288
- package/src/messaging/providers/slack/client.ts +20 -251
- package/src/messaging/providers/slack/send.test.ts +4 -9
- package/src/messaging/providers/slack/send.ts +1 -1
- package/src/messaging/providers/slack/types.ts +5 -0
- package/src/messaging/providers/slack/web-api-transport.test.ts +181 -0
- package/src/messaging/providers/slack/web-api-transport.ts +367 -0
- package/src/messaging/providers/slack/withdraw.ts +6 -14
- package/src/messaging/providers/telegram-bot/send.test.ts +31 -1
- package/src/messaging/providers/telegram-bot/send.ts +23 -2
- package/src/messaging/providers/telegram-bot/withdraw.test.ts +152 -0
- package/src/messaging/providers/telegram-bot/withdraw.ts +166 -0
- package/src/monitoring/__tests__/db-integrity-sample.test.ts +18 -4
- package/src/monitoring/__tests__/file-descriptors.test.ts +144 -0
- package/src/monitoring/file-descriptors.ts +262 -0
- package/src/monitoring/process-memory.ts +4 -11
- package/src/monitoring/resource-sampler.ts +5 -0
- package/src/monitoring/worker.ts +12 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +740 -0
- package/src/notifications/__tests__/broadcaster.test.ts +347 -7
- package/src/notifications/__tests__/copy-composer.test.ts +53 -3
- package/src/notifications/__tests__/decision-engine.test.ts +349 -29
- package/src/notifications/__tests__/deterministic-checks.test.ts +21 -0
- package/src/notifications/__tests__/edit-notification.test.ts +330 -0
- package/src/notifications/__tests__/guardian-delivery-recorder.test.ts +71 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +170 -12
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/adapters/platform.ts +90 -14
- package/src/notifications/adapters/telegram.ts +6 -4
- package/src/notifications/assistant-reply-producer.ts +219 -0
- package/src/notifications/broadcaster.ts +523 -317
- package/src/notifications/copy-composer.ts +27 -6
- package/src/notifications/decision-engine.ts +154 -77
- package/src/notifications/deterministic-checks.ts +6 -7
- package/src/notifications/edit-notification.ts +8 -4
- package/src/notifications/emit-signal.ts +49 -16
- package/src/notifications/guardian-delivery-recorder.ts +8 -2
- package/src/notifications/home-feed-side-effect.ts +43 -6
- package/src/notifications/notification-utils.ts +39 -4
- package/src/notifications/signal.ts +10 -0
- package/src/notifications/types.ts +17 -0
- package/src/persistence/bookmark-crud.ts +18 -9
- package/src/persistence/conversation-attention-store.ts +26 -0
- package/src/persistence/conversation-crud.ts +79 -32
- package/src/persistence/conversation-queries.ts +30 -15
- package/src/persistence/conversation-title-service.ts +5 -170
- package/src/persistence/conversation-types.ts +192 -1
- package/src/persistence/db-init.ts +14 -1
- package/src/persistence/db-maintenance.ts +5 -4
- package/src/persistence/embeddings/__tests__/plugin-index-qdrant-init.test.ts +219 -0
- package/src/persistence/embeddings/__tests__/plugin-index.test.ts +22 -0
- package/src/persistence/embeddings/__tests__/worker-script-version.test.ts +76 -0
- package/src/persistence/embeddings/embedding-local.ts +2 -1
- package/src/persistence/embeddings/embedding-runtime-manager.ts +29 -5
- package/src/persistence/embeddings/plugin-index.ts +84 -12
- package/src/persistence/migrations/360-add-document-workspace-path.test.ts +110 -0
- package/src/persistence/migrations/360-add-document-workspace-path.ts +39 -0
- package/src/persistence/planner-statistics.ts +14 -0
- package/src/persistence/schema/documents.ts +26 -11
- package/src/persistence/steps.ts +2 -0
- package/src/platform/client.test.ts +172 -2
- package/src/platform/client.ts +178 -55
- package/src/plugin-api/conversation-turn.ts +13 -0
- package/src/plugin-api/model-profiles.test.ts +6 -2
- package/src/plugins/defaults/memory/__tests__/bookmark-crud.test.ts +34 -0
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +22 -0
- package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +11 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +199 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +98 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +105 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +24 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +524 -0
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +1 -1
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +9 -4
- package/src/plugins/defaults/memory/host-utils.ts +5 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +105 -1
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +64 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +36 -1
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +12 -3
- package/src/plugins/defaults/memory/src/__tests__/memory-v2-simulate-route.test.ts +1 -0
- package/src/plugins/defaults/memory/substrate/__tests__/skill-store.test.ts +195 -1
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +1 -1
- package/src/plugins/defaults/memory/substrate/skill-store.ts +167 -29
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +131 -0
- package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +1 -0
- package/src/plugins/defaults/memory/v2/activation-log-store.ts +4 -0
- package/src/plugins/defaults/memory/v2/injection.ts +48 -3
- package/src/plugins/defaults/memory/v2/rerank-local.ts +6 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -1
- package/src/plugins/defaults/platform-hosted/routes/reengage.ts +1 -1
- package/src/plugins/defaults/turn-context/injectors.ts +1 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +26 -0
- package/src/plugins/types.ts +17 -0
- package/src/prompts/templates/system-sections.ts +2 -2
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +43 -2
- package/src/providers/__tests__/vellum-mismatch-routing.test.ts +8 -0
- package/src/providers/call-site-routing.ts +77 -14
- package/src/providers/connection-resolution.ts +42 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +127 -1
- package/src/providers/inference/adapter-factory.ts +70 -12
- package/src/providers/model-catalog.ts +0 -11
- package/src/providers/model-intents.ts +11 -0
- package/src/providers/registry.ts +8 -0
- package/src/providers/retry.ts +85 -17
- package/src/providers/types.ts +8 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/agent-wake.ts +132 -9
- package/src/runtime/channel-approval-types.ts +25 -0
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +2 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +11 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +121 -9
- package/src/runtime/routes/__tests__/platform-invoice-routes.test.ts +392 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +7 -0
- package/src/runtime/routes/__tests__/workspace-commit-routes.test.ts +63 -0
- package/src/runtime/routes/consolidation-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +18 -6
- package/src/runtime/routes/conversation-query-routes.ts +14 -23
- package/src/runtime/routes/conversation-routes.ts +113 -8
- package/src/runtime/routes/credential-routes.ts +1 -5
- package/src/runtime/routes/documents-routes.ts +207 -12
- package/src/runtime/routes/identity-routes.ts +3 -93
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +31 -1
- package/src/runtime/routes/inbound-stages/admission-policy.ts +18 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +52 -3
- package/src/runtime/routes/notification-routes.ts +4 -1
- package/src/runtime/routes/platform-routes.ts +238 -3
- package/src/runtime/routes/playground/guard.ts +1 -2
- package/src/runtime/routes/slack-channel-routes.ts +2 -4
- package/src/runtime/routes/workspace-commit-routes.ts +49 -2
- package/src/runtime/routes/workspace-routes.ts +11 -12
- package/src/runtime/routes/workspace-utils.ts +49 -2
- package/src/runtime/services/conversation-serializer.ts +2 -4
- package/src/subagent/__tests__/consult-context-gating.test.ts +98 -0
- package/src/subagent/__tests__/consult-context-skills.test.ts +82 -0
- package/src/subagent/__tests__/consult-context.test.ts +84 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +36 -0
- package/src/subagent/consult-context.ts +410 -0
- package/src/subagent/consult-prompt.ts +38 -5
- package/src/subagent/manager.ts +9 -4
- package/src/subagent/types.ts +8 -0
- package/src/telemetry/telemetry-event-sources.ts +7 -0
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire.generated.ts +1 -0
- package/src/telemetry/turn-events-store.ts +30 -1
- package/src/telemetry/types.ts +24 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/tools/browser/pinned-tabs.ts +3 -1
- package/src/tools/subagent/spawn.ts +23 -1
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/ui-surface/definitions.ts +39 -1
- package/src/tools/ui-surface/surface-shape-docs.ts +38 -4
- package/src/tools/ui-surface/visual-validation.ts +787 -0
- package/src/tools/workflows/run-workflow.test.ts +1 -0
- package/src/util/__tests__/short-title.test.ts +229 -0
- package/src/util/__tests__/worker-compute.test.ts +65 -0
- package/src/util/cgroup-cpu.ts +93 -0
- package/src/util/errors.ts +27 -0
- package/src/util/process-tree.ts +19 -0
- package/src/util/short-title.ts +189 -0
- package/src/util/worker-compute.ts +83 -0
- package/src/workspace/byok-default-profile-ensure.ts +25 -15
- package/src/workspace/git-service.ts +79 -30
- package/src/workspace/migrations/137-repair-retired-fireworks-minimax-model-id.ts +131 -0
- package/src/workspace/migrations/138-backfill-home-feed-titles.ts +179 -0
- package/src/workspace/migrations/139-clear-renamed-cost-profile-label.ts +92 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/plugins/defaults/memory/substrate/constants.ts +0 -8
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
getAppDirPath,
|
|
14
14
|
listAppFiles,
|
|
15
15
|
resolveAppDir,
|
|
16
|
+
resolveAppSource,
|
|
16
17
|
} from "../apps/app-store.js";
|
|
17
18
|
import { type ChannelId, parseInterfaceId } from "../channels/types.js";
|
|
18
19
|
import { resolveDefaultProfileForProvider } from "../config/default-profile-catalog.js";
|
|
@@ -433,6 +434,54 @@ export function buildActiveDocuments(conversationId: string): Array<{
|
|
|
433
434
|
: null;
|
|
434
435
|
}
|
|
435
436
|
|
|
437
|
+
/**
|
|
438
|
+
* Resolves the app the client reported as on-screen into the summary the
|
|
439
|
+
* `unified-turn-context` injector renders as the `visible_app:` line. Returns
|
|
440
|
+
* `null` when no app is in view or the id no longer resolves to an app (e.g.
|
|
441
|
+
* it was deleted while open).
|
|
442
|
+
*
|
|
443
|
+
* Resolution goes through `resolveAppSource` rather than `getApp` so both id
|
|
444
|
+
* shapes the viewer can hold are covered: workspace apps (opaque id) and
|
|
445
|
+
* plugin-bundled apps (`plugins~<plugin>~<app>`), which the apps list and open
|
|
446
|
+
* routes serve alongside them. Plugin apps carry their owning plugin so the
|
|
447
|
+
* rendered line can say the source belongs to a plugin rather than reading as
|
|
448
|
+
* an ordinary sandbox app to rewrite.
|
|
449
|
+
*
|
|
450
|
+
* Both identifiers come back: a workspace app's `appId` is an opaque UUID and
|
|
451
|
+
* the readable handle is its `slug` (the directory stem, frozen at creation).
|
|
452
|
+
* The app tools key off the id, so the line carries it for tool calls and the
|
|
453
|
+
* slug for everything a human or the model would recognize.
|
|
454
|
+
*/
|
|
455
|
+
export function buildVisibleAppContext(appId: string | undefined): {
|
|
456
|
+
appId: string;
|
|
457
|
+
name: string;
|
|
458
|
+
slug: string;
|
|
459
|
+
sourceDir: string;
|
|
460
|
+
pluginName?: string;
|
|
461
|
+
} | null {
|
|
462
|
+
if (!appId) {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
try {
|
|
466
|
+
const source = resolveAppSource(appId);
|
|
467
|
+
if (!source) {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
appId: source.id,
|
|
472
|
+
name: source.name,
|
|
473
|
+
slug: source.dirName,
|
|
474
|
+
sourceDir: source.sourceDir,
|
|
475
|
+
...(source.origin.kind === "plugin"
|
|
476
|
+
? { pluginName: source.origin.pluginName }
|
|
477
|
+
: {}),
|
|
478
|
+
};
|
|
479
|
+
} catch {
|
|
480
|
+
// Malformed id (traversal-shaped, empty): treat as no app in view.
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
436
485
|
const MAX_CONTEXT_LENGTH = 100_000;
|
|
437
486
|
|
|
438
487
|
function truncateHtml(html: string, budget: number): string {
|
|
@@ -2195,6 +2244,13 @@ export async function applyRuntimeInjections(
|
|
|
2195
2244
|
// `clientOs`) so a queued message from another surface can't perturb the
|
|
2196
2245
|
// in-flight turn — same anti-race pattern as `clientTimezone`.
|
|
2197
2246
|
const clientOs = liveConversation?.currentTurnClientOs ?? undefined;
|
|
2247
|
+
// The app the client has on screen (app viewer or the app-editing split),
|
|
2248
|
+
// resolved to its name and source directory so the assistant can act on it
|
|
2249
|
+
// without asking the user which app they mean. Frozen per turn like
|
|
2250
|
+
// `clientOs` for the same anti-race reason.
|
|
2251
|
+
const visibleApp = buildVisibleAppContext(
|
|
2252
|
+
liveConversation?.currentTurnVisibleAppId,
|
|
2253
|
+
);
|
|
2198
2254
|
const channelName = liveConversation
|
|
2199
2255
|
? (liveConversation.currentTurnChannelContext?.userMessageChannel ??
|
|
2200
2256
|
liveConversation.originChannel ??
|
|
@@ -2304,6 +2360,7 @@ export async function applyRuntimeInjections(
|
|
|
2304
2360
|
timestamp,
|
|
2305
2361
|
interfaceName,
|
|
2306
2362
|
clientOs,
|
|
2363
|
+
visibleApp,
|
|
2307
2364
|
channelName,
|
|
2308
2365
|
actorContext: options.actorContext,
|
|
2309
2366
|
configuredUserTimezone,
|
|
@@ -51,6 +51,30 @@ import { buildTransportHints } from "./transport-hints.js";
|
|
|
51
51
|
|
|
52
52
|
const conversationOptions = new Map<string, ConversationCreateOptions>();
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Drops the transport fields that describe what the client had on screen for a
|
|
56
|
+
* single message rather than for the conversation as a whole.
|
|
57
|
+
*
|
|
58
|
+
* `conversationOptions` is a durable map: a rebuilt conversation (evicted or
|
|
59
|
+
* gone stale) re-applies whatever transport was stored the last time anyone
|
|
60
|
+
* touched it. View state must not survive that, or a scheduled wake hours
|
|
61
|
+
* later would resurrect an app the user has since closed and assert it is on
|
|
62
|
+
* screen. The current call still applies the full transport to the live
|
|
63
|
+
* conversation, so only the persisted copy is trimmed.
|
|
64
|
+
*/
|
|
65
|
+
export function withoutTurnScopedTransport(
|
|
66
|
+
options: ConversationCreateOptions,
|
|
67
|
+
): ConversationCreateOptions {
|
|
68
|
+
const transport = options.transport;
|
|
69
|
+
if (!transport || transport.visibleAppId === undefined) {
|
|
70
|
+
return options;
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
...options,
|
|
74
|
+
transport: { ...transport, visibleAppId: undefined },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
54
78
|
export function mergeConversationOptions(
|
|
55
79
|
conversationId: string,
|
|
56
80
|
patch: Partial<ConversationCreateOptions>,
|
|
@@ -86,6 +110,7 @@ function applyTransportMetadata(
|
|
|
86
110
|
conversation.applyHostEnvFromTransport(transport);
|
|
87
111
|
conversation.applyClientTimezoneFromTransport(transport);
|
|
88
112
|
conversation.applyClientOsFromTransport(transport);
|
|
113
|
+
conversation.applyVisibleAppFromTransport(transport);
|
|
89
114
|
}
|
|
90
115
|
|
|
91
116
|
/**
|
|
@@ -110,7 +135,10 @@ export async function getOrCreateConversation(
|
|
|
110
135
|
...persistentOptions
|
|
111
136
|
} = options ?? {};
|
|
112
137
|
if (Object.values(persistentOptions).some((v) => v !== undefined)) {
|
|
113
|
-
mergeConversationOptions(
|
|
138
|
+
mergeConversationOptions(
|
|
139
|
+
conversationId,
|
|
140
|
+
withoutTurnScopedTransport(persistentOptions),
|
|
141
|
+
);
|
|
114
142
|
}
|
|
115
143
|
|
|
116
144
|
if (
|
|
@@ -221,6 +249,13 @@ export async function getOrCreateConversation(
|
|
|
221
249
|
await newConversation.ensureActorScopedHistory();
|
|
222
250
|
}
|
|
223
251
|
applyTransportMetadata(newConversation, storedOptions);
|
|
252
|
+
// The stored transport is stripped of view state, so a rebuild driven by
|
|
253
|
+
// a live send takes the app on screen from THIS call. A rebuild with no
|
|
254
|
+
// inbound transport (a scheduled wake, a background follow-up) correctly
|
|
255
|
+
// leaves it unset rather than inheriting whatever was open last time.
|
|
256
|
+
if (options?.transport) {
|
|
257
|
+
newConversation.applyVisibleAppFromTransport(options.transport);
|
|
258
|
+
}
|
|
224
259
|
setConversation(conversationId, newConversation);
|
|
225
260
|
return newConversation;
|
|
226
261
|
})();
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
isKnownSurfaceType,
|
|
14
14
|
MODEL_INVOKABLE_SURFACE_TYPES,
|
|
15
15
|
normalizeCopyBlockShowData,
|
|
16
|
+
normalizeVisualShowData,
|
|
16
17
|
OAuthConnectSurfaceDataSchema,
|
|
17
18
|
safeParseSurfaceData,
|
|
18
19
|
SurfaceTypeSchema,
|
|
@@ -98,6 +99,33 @@ import type { HostAppControlInput } from "./message-types/host-app-control.js";
|
|
|
98
99
|
import type { UserMessageAttachment } from "./message-types/shared.js";
|
|
99
100
|
import type { TrustContext } from "./trust-context-types.js";
|
|
100
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Prefix of the synthetic user-message text this module writes when a surface
|
|
104
|
+
* action has no custom prompt of its own (`[User action on app: ...]`,
|
|
105
|
+
* `[User action on <surface_type> surface: ...]`).
|
|
106
|
+
*
|
|
107
|
+
* Machine-authored, so turns carrying it must be stamped `scripted` and kept
|
|
108
|
+
* out of activation counts. Deliberately the SAME anchor the analytics
|
|
109
|
+
* classifier matches on (`stg_telemetry__scripted_turn.sql`, "Synthetic
|
|
110
|
+
* UI-surface action events"): the two signals are compared against each other
|
|
111
|
+
* by the `assert_scripted_signals_agree` dbt test, so if this string ever
|
|
112
|
+
* changes, that model's anchor has to change with it or the test fires.
|
|
113
|
+
*/
|
|
114
|
+
const SYNTHETIC_SURFACE_ACTION_PREFIX = "[User action on ";
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* True when `content` is the synthetic fallback text above rather than a
|
|
118
|
+
* custom prompt supplied by the surface.
|
|
119
|
+
*
|
|
120
|
+
* Only the synthetic form is scripted. A surface that supplies its own prompt
|
|
121
|
+
* is treated as a real turn, because that is what the existing analytics
|
|
122
|
+
* classifier does. Widening this to every surface action would silently move
|
|
123
|
+
* activation beyond the bug being fixed.
|
|
124
|
+
*/
|
|
125
|
+
function isSyntheticSurfaceActionContent(content: string): boolean {
|
|
126
|
+
return content.startsWith(SYNTHETIC_SURFACE_ACTION_PREFIX);
|
|
127
|
+
}
|
|
128
|
+
|
|
101
129
|
const log = getLogger("conversation-surfaces");
|
|
102
130
|
|
|
103
131
|
// Tolerant variant of SurfaceActionSchema for parsing raw model output.
|
|
@@ -112,6 +140,16 @@ const ModelActionSchema = SurfaceActionSchema.extend({
|
|
|
112
140
|
|
|
113
141
|
const MAX_UNDO_DEPTH = 10;
|
|
114
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Pending surface types that do not hold the one-interactive-surface-at-a-time
|
|
145
|
+
* lock. Both render content the user reads rather than a question they must
|
|
146
|
+
* answer, so a live one must not block the next surface.
|
|
147
|
+
*/
|
|
148
|
+
const NON_BLOCKING_PENDING_SURFACE_TYPES = new Set<SurfaceType>([
|
|
149
|
+
"dynamic_page",
|
|
150
|
+
"visual",
|
|
151
|
+
]);
|
|
152
|
+
|
|
115
153
|
/**
|
|
116
154
|
* Debounce window for persisting `ui_surface_update` data back to the
|
|
117
155
|
* message row. Surfaces typically receive bursts of updates (e.g. a
|
|
@@ -2012,6 +2050,9 @@ export async function handleSurfaceAction(
|
|
|
2012
2050
|
activeSurfaceId: surfaceId,
|
|
2013
2051
|
displayContent,
|
|
2014
2052
|
sourceActorPrincipalId,
|
|
2053
|
+
// Rides the metadata bag rather than a typed option: the queue
|
|
2054
|
+
// round-trips `metadata` but not `PersistMessageOptions`.
|
|
2055
|
+
metadata: { scripted: isSyntheticSurfaceActionContent(content) },
|
|
2015
2056
|
});
|
|
2016
2057
|
|
|
2017
2058
|
if (result.rejected) {
|
|
@@ -2069,6 +2110,7 @@ export async function handleSurfaceAction(
|
|
|
2069
2110
|
activeSurfaceId: surfaceId,
|
|
2070
2111
|
displayContent,
|
|
2071
2112
|
sourceActorPrincipalId,
|
|
2113
|
+
scripted: isSyntheticSurfaceActionContent(content),
|
|
2072
2114
|
})
|
|
2073
2115
|
.catch((err) => {
|
|
2074
2116
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -2254,6 +2296,9 @@ export async function handleSurfaceAction(
|
|
|
2254
2296
|
activeSurfaceId: surfaceId,
|
|
2255
2297
|
displayContent,
|
|
2256
2298
|
sourceActorPrincipalId,
|
|
2299
|
+
// Rides the metadata bag rather than a typed option: the queue
|
|
2300
|
+
// round-trips `metadata` but not `PersistMessageOptions`.
|
|
2301
|
+
metadata: { scripted: isSyntheticSurfaceActionContent(content) },
|
|
2257
2302
|
});
|
|
2258
2303
|
if (result.rejected) {
|
|
2259
2304
|
ctx.surfaceActionRequestIds.delete(requestId);
|
|
@@ -2336,6 +2381,7 @@ export async function handleSurfaceAction(
|
|
|
2336
2381
|
activeSurfaceId: surfaceId,
|
|
2337
2382
|
displayContent,
|
|
2338
2383
|
sourceActorPrincipalId,
|
|
2384
|
+
scripted: isSyntheticSurfaceActionContent(content),
|
|
2339
2385
|
})
|
|
2340
2386
|
.catch((err) => {
|
|
2341
2387
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -2992,9 +3038,9 @@ export async function surfaceProxyResolver(
|
|
|
2992
3038
|
}
|
|
2993
3039
|
|
|
2994
3040
|
// Every surface type parses through its canonical schema. Card,
|
|
2995
|
-
// copy_block, and
|
|
2996
|
-
// recover fields the model placed at the top level of the tool
|
|
2997
|
-
// the rest go straight through `safeParseSurfaceData`. Choice and
|
|
3041
|
+
// copy_block, dynamic_page, and visual first run bespoke normalizers
|
|
3042
|
+
// that recover fields the model placed at the top level of the tool
|
|
3043
|
+
// input; the rest go straight through `safeParseSurfaceData`. Choice and
|
|
2998
3044
|
// oauth_connect parse into named bindings so their content guards below
|
|
2999
3045
|
// read typed data instead of re-narrowing the union.
|
|
3000
3046
|
const cardData =
|
|
@@ -3026,7 +3072,12 @@ export async function surfaceProxyResolver(
|
|
|
3026
3072
|
surfaceType,
|
|
3027
3073
|
data: normalizeDynamicPageShowData(input, rawData),
|
|
3028
3074
|
}
|
|
3029
|
-
:
|
|
3075
|
+
: surfaceType === "visual"
|
|
3076
|
+
? {
|
|
3077
|
+
surfaceType,
|
|
3078
|
+
data: normalizeVisualShowData(input, rawData),
|
|
3079
|
+
}
|
|
3080
|
+
: parseShowPairOrThrow(surfaceType, rawData);
|
|
3030
3081
|
// Parse actions through the schema instead of typecasting raw model output.
|
|
3031
3082
|
// The model may place actions inside `data` instead of the top-level
|
|
3032
3083
|
// `actions` param — recover them so they aren't silently dropped.
|
|
@@ -3103,10 +3154,12 @@ export async function surfaceProxyResolver(
|
|
|
3103
3154
|
|
|
3104
3155
|
// Only one non-persistent interactive surface at a time. If another
|
|
3105
3156
|
// surface is already awaiting user input, reject this one so the LLM
|
|
3106
|
-
// presents surfaces sequentially.
|
|
3157
|
+
// presents surfaces sequentially. dynamic_page and visual are rendered
|
|
3158
|
+
// content rather than a question posed to the user, so a pending one
|
|
3159
|
+
// never blocks the next surface.
|
|
3107
3160
|
if (awaitAction) {
|
|
3108
3161
|
const hasExistingPending = [...ctx.pendingSurfaceActions.values()].some(
|
|
3109
|
-
(entry) => entry.surfaceType
|
|
3162
|
+
(entry) => !NON_BLOCKING_PENDING_SURFACE_TYPES.has(entry.surfaceType),
|
|
3110
3163
|
);
|
|
3111
3164
|
if (hasExistingPending) {
|
|
3112
3165
|
return {
|
|
@@ -3215,6 +3268,15 @@ export async function surfaceProxyResolver(
|
|
|
3215
3268
|
yieldToUser: true,
|
|
3216
3269
|
};
|
|
3217
3270
|
}
|
|
3271
|
+
if (surfaceType === "visual") {
|
|
3272
|
+
return {
|
|
3273
|
+
content: JSON.stringify({
|
|
3274
|
+
surfaceId,
|
|
3275
|
+
note: "The visual is now visible inline in the chat. Continue your response in prose and do not describe or restate what the visual shows. To replace it, call ui_dismiss with this surfaceId and show a new one.",
|
|
3276
|
+
}),
|
|
3277
|
+
isError: false,
|
|
3278
|
+
};
|
|
3279
|
+
}
|
|
3218
3280
|
return { content: JSON.stringify({ surfaceId }), isError: false };
|
|
3219
3281
|
}
|
|
3220
3282
|
|
|
@@ -22,8 +22,10 @@ import {
|
|
|
22
22
|
derefToolResultReReads,
|
|
23
23
|
postTurnTruncateToolResults,
|
|
24
24
|
} from "../context/post-turn-tool-result-truncation.js";
|
|
25
|
+
import { emitAssistantReplyNotification } from "../notifications/assistant-reply-producer.js";
|
|
25
26
|
import { projectAssistantMessage } from "../persistence/conversation-attention-store.js";
|
|
26
27
|
import {
|
|
28
|
+
type ConversationRow,
|
|
27
29
|
getConversation,
|
|
28
30
|
getMessageById,
|
|
29
31
|
parseMessageMetadata,
|
|
@@ -142,12 +144,44 @@ export async function runDeferredTurnTail(params: {
|
|
|
142
144
|
state: TurnTailState;
|
|
143
145
|
rlog: pino.Logger;
|
|
144
146
|
generationCompletedAt: number;
|
|
147
|
+
/**
|
|
148
|
+
* True only for a `message_complete` turn that produced a genuine reply. A
|
|
149
|
+
* handed-off generation is not the end of the run, a cancelled one has no
|
|
150
|
+
* final reply, and a provider-error turn's only assistant row is the
|
|
151
|
+
* synthetic error text.
|
|
152
|
+
*/
|
|
153
|
+
turnCompleted: boolean;
|
|
154
|
+
/** Id of the row that opened this turn, for the notification producer. */
|
|
155
|
+
userMessageId: string | undefined;
|
|
156
|
+
/**
|
|
157
|
+
* True when this turn's reply streams to the app and nowhere else, for the
|
|
158
|
+
* notification producer's delivery-surface gates.
|
|
159
|
+
*/
|
|
160
|
+
replyDeliveredInAppOnly?: boolean;
|
|
145
161
|
}): Promise<void> {
|
|
146
|
-
const {
|
|
162
|
+
const {
|
|
163
|
+
ctx,
|
|
164
|
+
state,
|
|
165
|
+
rlog,
|
|
166
|
+
generationCompletedAt,
|
|
167
|
+
turnCompleted,
|
|
168
|
+
userMessageId,
|
|
169
|
+
replyDeliveredInAppOnly,
|
|
170
|
+
} = params;
|
|
147
171
|
const tailStartedAt = Date.now();
|
|
148
172
|
|
|
173
|
+
// Conversation row for the notification producer below. Guarded like every
|
|
174
|
+
// step below, because this runs after the terminal SSE and a SQLite hiccup
|
|
175
|
+
// must not escape into the loop's outer catch.
|
|
176
|
+
let conversation: ConversationRow | null = null;
|
|
177
|
+
try {
|
|
178
|
+
conversation = getConversation(ctx.conversationId);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
rlog.warn({ err }, "Failed to read conversation for end-of-turn work");
|
|
181
|
+
}
|
|
182
|
+
|
|
149
183
|
// Per-message memory/attention finalize side-effects deferred from
|
|
150
|
-
// `handleMessageComplete
|
|
184
|
+
// `handleMessageComplete`: one closure per assistant row produced this turn,
|
|
151
185
|
// in production order.
|
|
152
186
|
for (const effect of state.deferredFinalizeEffects) {
|
|
153
187
|
try {
|
|
@@ -157,16 +191,44 @@ export async function runDeferredTurnTail(params: {
|
|
|
157
191
|
}
|
|
158
192
|
}
|
|
159
193
|
|
|
194
|
+
// Notify about a finished reply the user is no longer looking at. Ordered
|
|
195
|
+
// after the loop above so the producer's unseen check reads the attention
|
|
196
|
+
// cursor this turn's last row just projected. Not awaited: the pipeline
|
|
197
|
+
// awaits the platform adapter's HTTP dispatch, which retries with backoff,
|
|
198
|
+
// and nothing below here depends on the emit. The producer never rejects.
|
|
199
|
+
if (turnCompleted && state.lastAssistantMessageId) {
|
|
200
|
+
void emitAssistantReplyNotification({
|
|
201
|
+
conversationId: ctx.conversationId,
|
|
202
|
+
assistantMessageId: state.lastAssistantMessageId,
|
|
203
|
+
userMessageId,
|
|
204
|
+
...(replyDeliveredInAppOnly ? { replyDeliveredInAppOnly: true } : {}),
|
|
205
|
+
rlog,
|
|
206
|
+
conversation,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Fresh row for the two filesystem steps below. The awaited work above is
|
|
211
|
+
// long enough for the user to delete the conversation, and the row read at
|
|
212
|
+
// the top of the tail stays truthy across that deletion, so spooling or
|
|
213
|
+
// mirroring against it would rebuild a deleted conversation's directory from
|
|
214
|
+
// in-memory history. A row that is gone skips both steps exactly as an
|
|
215
|
+
// unreadable one does.
|
|
216
|
+
let liveConversation: ConversationRow | null = null;
|
|
217
|
+
try {
|
|
218
|
+
liveConversation = getConversation(ctx.conversationId);
|
|
219
|
+
} catch (err) {
|
|
220
|
+
rlog.warn({ err }, "Failed to re-read conversation for end-of-turn work");
|
|
221
|
+
}
|
|
222
|
+
|
|
160
223
|
// Post-turn tool-result truncation: spool oversized results to disk and
|
|
161
224
|
// replace their in-context content with a stub + pointer, shrinking the next
|
|
162
225
|
// turn's context. Rewrites only the in-memory history, so it has no bearing on
|
|
163
226
|
// the reply already delivered to the client.
|
|
164
227
|
try {
|
|
165
|
-
|
|
166
|
-
if (conv) {
|
|
228
|
+
if (liveConversation) {
|
|
167
229
|
const convDir = getResolvedConversationDirPath(
|
|
168
230
|
ctx.conversationId,
|
|
169
|
-
|
|
231
|
+
liveConversation.createdAt,
|
|
170
232
|
);
|
|
171
233
|
const { messages: derefMessages, dereferencedCount } =
|
|
172
234
|
derefToolResultReReads(ctx.messages);
|
|
@@ -199,20 +261,16 @@ export async function runDeferredTurnTail(params: {
|
|
|
199
261
|
}
|
|
200
262
|
|
|
201
263
|
// Mirror the final assistant row into the JSONL disk view. Guarded like the
|
|
202
|
-
// steps above: this runs AFTER the terminal SSE, so a throw here
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
// client already saw complete.
|
|
264
|
+
// steps above: this runs AFTER the terminal SSE, so a throw here must not
|
|
265
|
+
// escape into the loop's outer catch and emit a second, contradictory
|
|
266
|
+
// terminal event for a turn the client already saw complete.
|
|
206
267
|
try {
|
|
207
|
-
if (state.lastAssistantMessageId) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
convForDisk.createdAt,
|
|
214
|
-
);
|
|
215
|
-
}
|
|
268
|
+
if (state.lastAssistantMessageId && liveConversation) {
|
|
269
|
+
syncMessageToDisk(
|
|
270
|
+
ctx.conversationId,
|
|
271
|
+
state.lastAssistantMessageId,
|
|
272
|
+
liveConversation.createdAt,
|
|
273
|
+
);
|
|
216
274
|
}
|
|
217
275
|
} catch (err) {
|
|
218
276
|
rlog.warn({ err }, "Failed to sync assistant message to disk (non-fatal)");
|
|
@@ -622,6 +622,23 @@ export class Conversation {
|
|
|
622
622
|
* @internal
|
|
623
623
|
*/
|
|
624
624
|
currentTurnClientOs?: string;
|
|
625
|
+
/**
|
|
626
|
+
* @internal
|
|
627
|
+
* Id of the app the client currently has open on screen, reported by the
|
|
628
|
+
* client on each message. Drives the per-turn `visible_app:` context line so
|
|
629
|
+
* the assistant can resolve "the app" to what the user is looking at. This
|
|
630
|
+
* is the LIVE value; the assembly reads the frozen
|
|
631
|
+
* {@link currentTurnVisibleAppId}.
|
|
632
|
+
*/
|
|
633
|
+
visibleAppId?: string;
|
|
634
|
+
/**
|
|
635
|
+
* Per-turn frozen copy of {@link visibleAppId}, captured by the agent loop at
|
|
636
|
+
* turn start for the same reason as {@link currentTurnClientOs}: a queued
|
|
637
|
+
* message sent from a different view re-applies transport metadata before it
|
|
638
|
+
* is enqueued, and must not swap the app under the in-flight turn.
|
|
639
|
+
* @internal
|
|
640
|
+
*/
|
|
641
|
+
currentTurnVisibleAppId?: string;
|
|
625
642
|
/**
|
|
626
643
|
* Per-turn temporal snapshot frozen by the agent loop and read by
|
|
627
644
|
* `applyRuntimeInjections` to build the `<turn_context>` timezone-mismatch
|
|
@@ -1957,26 +1974,64 @@ export class Conversation {
|
|
|
1957
1974
|
}
|
|
1958
1975
|
}
|
|
1959
1976
|
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1977
|
+
/**
|
|
1978
|
+
* Push the conversation's current context-window usage to clients so the
|
|
1979
|
+
* context-window indicator matches the numbers a user-initiated compaction
|
|
1980
|
+
* card reports. Turn-driven compaction needs no push: the turn's own
|
|
1981
|
+
* `usage_update` carries the post-compaction count.
|
|
1982
|
+
*
|
|
1983
|
+
* Defaults to the conversation's own sender, the channel `emitActivityState`
|
|
1984
|
+
* and `context_compacted` already use, so a queued `/compact` reaches the
|
|
1985
|
+
* same client its result card does. Routes that resolve a conversation
|
|
1986
|
+
* outside the send path never wire that sender and pass their own `onEvent`.
|
|
1987
|
+
*/
|
|
1988
|
+
private emitContextWindowUsage(
|
|
1989
|
+
tokens: number,
|
|
1990
|
+
maxTokens: number,
|
|
1991
|
+
onEvent?: (msg: AssistantEvent) => void,
|
|
1992
|
+
): void {
|
|
1993
|
+
try {
|
|
1994
|
+
(onEvent ?? this.sendToClient)({
|
|
1995
|
+
type: "context_window_usage",
|
|
1996
|
+
conversationId: this.conversationId,
|
|
1997
|
+
tokens,
|
|
1998
|
+
maxTokens,
|
|
1999
|
+
});
|
|
2000
|
+
} catch (err) {
|
|
2001
|
+
log.warn(
|
|
2002
|
+
{ err, conversationId: this.conversationId },
|
|
2003
|
+
"sendToClient threw in emitContextWindowUsage",
|
|
2004
|
+
);
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
/**
|
|
2009
|
+
* Run a user-initiated compaction (`run`), reporting its before/after with
|
|
2010
|
+
* the provider's real tokenizer (count_tokens) rather than the local chars/4
|
|
2011
|
+
* estimate the compaction pipeline runs internally (it under-counts by ~25%
|
|
2012
|
+
* on typical histories), and pushing the resulting count to clients.
|
|
2013
|
+
* `calculateTokens` falls back to that estimate when the provider has no
|
|
2014
|
+
* count endpoint or the count call fails, so behavior degrades gracefully.
|
|
2015
|
+
*
|
|
2016
|
+
* Only the *displayed* numbers are overridden: the compaction log and
|
|
2017
|
+
* circuit-breaker accounting inside `runCompaction` keep the estimate-based
|
|
2018
|
+
* figures, leaving calibration and historical logs untouched.
|
|
2019
|
+
*
|
|
2020
|
+
* `run` must leave the compacted history applied to `this.messages`, which
|
|
2021
|
+
* every `runCompaction` path does. `onEvent` overrides the sink the usage
|
|
2022
|
+
* push goes to.
|
|
2023
|
+
*/
|
|
2024
|
+
private async runUserCompaction(
|
|
2025
|
+
run: () => Promise<ContextWindowResult>,
|
|
2026
|
+
onEvent?: (msg: AssistantEvent) => void,
|
|
2027
|
+
): Promise<ContextWindowResult> {
|
|
1972
2028
|
const before = await this.calculateTokens(this.messages);
|
|
1973
|
-
const result = await
|
|
1974
|
-
//
|
|
1975
|
-
// place, so after a successful compaction this re-counts the new history;
|
|
1976
|
-
// a no-op leaves the context unchanged, so before === after.
|
|
2029
|
+
const result = await run();
|
|
2030
|
+
// A no-op leaves the context unchanged, so before === after.
|
|
1977
2031
|
const after = result.compacted
|
|
1978
2032
|
? await this.calculateTokens(this.messages)
|
|
1979
2033
|
: before;
|
|
2034
|
+
this.emitContextWindowUsage(after, result.maxInputTokens, onEvent);
|
|
1980
2035
|
return {
|
|
1981
2036
|
...result,
|
|
1982
2037
|
previousEstimatedInputTokens: before,
|
|
@@ -1984,6 +2039,20 @@ export class Conversation {
|
|
|
1984
2039
|
};
|
|
1985
2040
|
}
|
|
1986
2041
|
|
|
2042
|
+
/**
|
|
2043
|
+
* `/compact`. `onEvent` is the sink for the context-window usage push, and
|
|
2044
|
+
* callers pass whatever sink they render the result card through: the queue
|
|
2045
|
+
* drain carries the queued item's own `onEvent`, and `sendToClient` is reset
|
|
2046
|
+
* to a no-op once an interactive turn finishes (`process-message.ts`), so a
|
|
2047
|
+
* `/compact` draining behind that turn would otherwise push into nothing
|
|
2048
|
+
* while its card still reaches the client.
|
|
2049
|
+
*/
|
|
2050
|
+
async forceCompact(
|
|
2051
|
+
onEvent?: (msg: AssistantEvent) => void,
|
|
2052
|
+
): Promise<ContextWindowResult> {
|
|
2053
|
+
return this.runUserCompaction(() => this.runCompaction(true), onEvent);
|
|
2054
|
+
}
|
|
2055
|
+
|
|
1987
2056
|
/**
|
|
1988
2057
|
* "Summarize up to here": summarize everything before the turn containing
|
|
1989
2058
|
* `beforeMessageId`, keeping that turn and everything after it verbatim.
|
|
@@ -1996,9 +2065,15 @@ export class Conversation {
|
|
|
1996
2065
|
* `resolveMetaSlashCommand`. Throws {@link UserError} (messages are
|
|
1997
2066
|
* user-facing) when the boundary cannot be resolved or the row→history
|
|
1998
2067
|
* index mapping cannot be verified.
|
|
2068
|
+
*
|
|
2069
|
+
* `onEvent` is the sink for the context-window usage push. The management
|
|
2070
|
+
* route that owns this action resolves its conversation outside the send
|
|
2071
|
+
* path, so the instance can still hold the store's no-op sender; it passes
|
|
2072
|
+
* the same broadcast path its result card goes out on.
|
|
1999
2073
|
*/
|
|
2000
2074
|
async summarizeUpToMessage(
|
|
2001
2075
|
beforeMessageId: string,
|
|
2076
|
+
onEvent?: (msg: AssistantEvent) => void,
|
|
2002
2077
|
): Promise<ContextWindowResult> {
|
|
2003
2078
|
const priorTrustContext = this.trustContext;
|
|
2004
2079
|
if (!resolveCapabilities(priorTrustContext?.trustClass).canAccessMemory) {
|
|
@@ -2078,17 +2153,22 @@ export class Conversation {
|
|
|
2078
2153
|
firstRowByHistoryIndex[historyIndex] = rowIndex;
|
|
2079
2154
|
}
|
|
2080
2155
|
}
|
|
2081
|
-
return await this.
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2156
|
+
return await this.runUserCompaction(
|
|
2157
|
+
() =>
|
|
2158
|
+
this.runCompaction(true, undefined, {
|
|
2159
|
+
fixedTailStartIndex: tailIndex,
|
|
2160
|
+
// When repair merged preceding continuation rows into the
|
|
2161
|
+
// boundary's message, the row boundary retreats to the message's
|
|
2162
|
+
// first contributing row: the summary call reads
|
|
2163
|
+
// messages[0..tailIndex), which excludes the merged rows' content,
|
|
2164
|
+
// so the image manifest and watermarks must not treat them as
|
|
2165
|
+
// summarized.
|
|
2166
|
+
fixedBoundaryRowIndex:
|
|
2167
|
+
firstRowByHistoryIndex[tailIndex] ?? boundaryRowIndex,
|
|
2168
|
+
fixedBoundaryRowView: { rows, firstRowByHistoryIndex },
|
|
2169
|
+
}),
|
|
2170
|
+
onEvent,
|
|
2171
|
+
);
|
|
2092
2172
|
} finally {
|
|
2093
2173
|
// Only undo the temporary guardian context this method installed. If
|
|
2094
2174
|
// trustContext was legitimately updated at an `await` boundary, the
|
|
@@ -2398,6 +2478,10 @@ export class Conversation {
|
|
|
2398
2478
|
this.clientOs = transport.clientOs ?? undefined;
|
|
2399
2479
|
}
|
|
2400
2480
|
|
|
2481
|
+
applyVisibleAppFromTransport(transport: ConversationTransportMetadata): void {
|
|
2482
|
+
this.visibleAppId = transport.visibleAppId ?? undefined;
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2401
2485
|
setAssistantId(assistantId: string | null): void {
|
|
2402
2486
|
this.assistantId = assistantId ?? undefined;
|
|
2403
2487
|
}
|
|
@@ -2471,6 +2555,17 @@ export class Conversation {
|
|
|
2471
2555
|
titleText?: string;
|
|
2472
2556
|
/** See {@link runAgentLoopImpl} — hidden machine-signal turn marker. */
|
|
2473
2557
|
isHiddenPrompt?: boolean;
|
|
2558
|
+
/**
|
|
2559
|
+
* See {@link runAgentLoopImpl}: the row the end-of-turn reply
|
|
2560
|
+
* notification treats as the prompt this turn answers.
|
|
2561
|
+
*/
|
|
2562
|
+
notifyUserMessageId?: string;
|
|
2563
|
+
/**
|
|
2564
|
+
* See {@link runAgentLoopImpl}: this run's reply streams to the app
|
|
2565
|
+
* alone, so the reply notification ignores the initiating row's
|
|
2566
|
+
* channel/voice delivery markers.
|
|
2567
|
+
*/
|
|
2568
|
+
replyDeliveredInAppOnly?: boolean;
|
|
2474
2569
|
callSite?: LLMCallSite;
|
|
2475
2570
|
/**
|
|
2476
2571
|
* Optional ad-hoc inference-profile override applied to every LLM call
|
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -28,6 +28,7 @@ import { initializeDb } from "../persistence/db-init.js";
|
|
|
28
28
|
import { startEmbeddingRuntimeManager } from "../persistence/embeddings/embedding-backend.js";
|
|
29
29
|
import { maybeEnqueueLexicalBackfillOnUpgrade } from "../persistence/job-handlers/message-lexical-backfill.js";
|
|
30
30
|
import { clearLifecycleQuiesce } from "../persistence/lifecycle-quiesce.js";
|
|
31
|
+
import { isPlatformClientConfigured } from "../platform/client.js";
|
|
31
32
|
import { startConsentRefresh } from "../platform/consent-cache.js";
|
|
32
33
|
import { syncWorkspaceIdentityToPlatform } from "../platform/sync-identity.js";
|
|
33
34
|
import { ensurePromptFiles } from "../prompts/system-prompt.js";
|
|
@@ -198,6 +199,14 @@ export async function runDaemon(): Promise<void> {
|
|
|
198
199
|
// failure is non-fatal — the daemon falls back to IPC-only operation.
|
|
199
200
|
await startRuntimeHttpServer();
|
|
200
201
|
|
|
202
|
+
// Warms the configured-probe cache (credential reads only, no DB). Fired
|
|
203
|
+
// immediately after the transport binds, ahead of DB init and readiness,
|
|
204
|
+
// so an urgent signal arriving the moment requests are admitted is not
|
|
205
|
+
// decided on an empty cache.
|
|
206
|
+
void isPlatformClientConfigured().catch((err) =>
|
|
207
|
+
log.warn({ err }, "Platform configured-probe warmup failed"),
|
|
208
|
+
);
|
|
209
|
+
|
|
201
210
|
// Pre-populate feature flag overrides so subsequent sync
|
|
202
211
|
// isAssistantFeatureFlagEnabled() calls have data. Fired non-blocking
|
|
203
212
|
// so a slow or unreachable gateway doesn't delay daemon startup (the
|