@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
package/src/agent/loop.ts
CHANGED
|
@@ -1028,6 +1028,10 @@ export class AgentLoop {
|
|
|
1028
1028
|
// rejection means the repair could not recover, so the error surfaces
|
|
1029
1029
|
// instead of looping. Turn-scoped, so each turn recovers afresh.
|
|
1030
1030
|
let orderingRepairAttempted = false;
|
|
1031
|
+
// One resume per turn after a call dies mid-generation: a second
|
|
1032
|
+
// interruption means re-issuing is not getting through, so the error
|
|
1033
|
+
// surfaces instead of looping.
|
|
1034
|
+
let interruptedCallResumed = false;
|
|
1031
1035
|
let lastLlmCallTime = 0;
|
|
1032
1036
|
let exitReason: ExitReason | null = null;
|
|
1033
1037
|
// Armed at the end of a tool-use iteration so the budget gate runs at the
|
|
@@ -1176,6 +1180,12 @@ export class AgentLoop {
|
|
|
1176
1180
|
// elsewhere in the turn body (tool execution, the success-path stop
|
|
1177
1181
|
// chain, post-model-call hooks) must not re-enter the stop chain.
|
|
1178
1182
|
let providerCallError: unknown;
|
|
1183
|
+
// Set once the model streams its first token on this iteration's call.
|
|
1184
|
+
// Two readers: latency instrumentation stamps time-to-first-token off
|
|
1185
|
+
// its rising edge, and the outer catch reads it to tell a refused
|
|
1186
|
+
// request from a generation that died mid-flight. Declared here so that
|
|
1187
|
+
// catch can reach it.
|
|
1188
|
+
let streamedTokens = false;
|
|
1179
1189
|
|
|
1180
1190
|
try {
|
|
1181
1191
|
// ── Pre-call budget gate ─────────────────────────────────────
|
|
@@ -1527,11 +1537,6 @@ export class AgentLoop {
|
|
|
1527
1537
|
// the client would otherwise see nothing.
|
|
1528
1538
|
let streamedVisibleText = false;
|
|
1529
1539
|
|
|
1530
|
-
// Latency instrumentation: stamp the first streamed token (thinking or
|
|
1531
|
-
// text) of THIS call exactly once, so each per-call segment carries its
|
|
1532
|
-
// own time-to-first-token. Reset per provider call.
|
|
1533
|
-
let firstTokenMarked = false;
|
|
1534
|
-
|
|
1535
1540
|
// The `onEvent` wrapping below applies sensitive-output placeholder
|
|
1536
1541
|
// substitution to streamed text while forwarding every other event
|
|
1537
1542
|
// type through unchanged.
|
|
@@ -1541,10 +1546,10 @@ export class AgentLoop {
|
|
|
1541
1546
|
config: providerConfig,
|
|
1542
1547
|
onEvent: (event) => {
|
|
1543
1548
|
if (
|
|
1544
|
-
!
|
|
1549
|
+
!streamedTokens &&
|
|
1545
1550
|
(event.type === "thinking_delta" || event.type === "text_delta")
|
|
1546
1551
|
) {
|
|
1547
|
-
|
|
1552
|
+
streamedTokens = true;
|
|
1548
1553
|
latencyTracker?.markFirstToken(
|
|
1549
1554
|
event.type === "thinking_delta" ? "thinking" : "text",
|
|
1550
1555
|
);
|
|
@@ -2498,6 +2503,30 @@ export class AgentLoop {
|
|
|
2498
2503
|
newMessagesStart = history.length;
|
|
2499
2504
|
continue;
|
|
2500
2505
|
}
|
|
2506
|
+
|
|
2507
|
+
// Last: the call died mid-generation. `streamedTokens` means the
|
|
2508
|
+
// provider accepted the request and started producing before it
|
|
2509
|
+
// failed, so the request is fine and re-issuing it as-is is the
|
|
2510
|
+
// recovery — nothing to repair, which is why no branch above claims
|
|
2511
|
+
// it. A rejection that streamed nothing is the opposite case (the
|
|
2512
|
+
// provider refused the request), and an identical resend would be
|
|
2513
|
+
// refused identically, so those surface. Main-agent turns only:
|
|
2514
|
+
// background call sites answer to callers that handle their own
|
|
2515
|
+
// failures.
|
|
2516
|
+
if (
|
|
2517
|
+
streamedTokens &&
|
|
2518
|
+
!interruptedCallResumed &&
|
|
2519
|
+
callSite === "mainAgent" &&
|
|
2520
|
+
postModelCallContinues < MAX_POST_MODEL_CALL_CONTINUES
|
|
2521
|
+
) {
|
|
2522
|
+
interruptedCallResumed = true;
|
|
2523
|
+
postModelCallContinues++;
|
|
2524
|
+
rlog.warn(
|
|
2525
|
+
{ turn: toolUseTurns, messageCount: history.length, err },
|
|
2526
|
+
"Model call died mid-generation, resuming the turn",
|
|
2527
|
+
);
|
|
2528
|
+
continue;
|
|
2529
|
+
}
|
|
2501
2530
|
}
|
|
2502
2531
|
|
|
2503
2532
|
rlog.error(
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `context_window_usage` SSE event.
|
|
3
|
+
*
|
|
4
|
+
* Server → client push of a conversation's current context-window usage,
|
|
5
|
+
* measured with the provider's own tokenizer. Emitted by user-initiated
|
|
6
|
+
* compaction (`/compact`, "summarize up to here"), which recomputes the
|
|
7
|
+
* count outside any turn and so has no `usage_update` to carry it. Clients
|
|
8
|
+
* apply it to the context-window indicator, which otherwise only refreshes
|
|
9
|
+
* on the next LLM call.
|
|
10
|
+
*
|
|
11
|
+
* `tokens` and `maxTokens` are the same figures the compaction result card
|
|
12
|
+
* reports, so the indicator and the card always agree.
|
|
13
|
+
*
|
|
14
|
+
* Canonical wire-contract source. Daemon code imports the type directly
|
|
15
|
+
* from this file; external consumers import via `@vellumai/assistant-api`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
|
|
20
|
+
export const ContextWindowUsageEventSchema = z.object({
|
|
21
|
+
type: z.literal("context_window_usage"),
|
|
22
|
+
conversationId: z.string(),
|
|
23
|
+
/** Prompt tokens the conversation's context currently occupies. */
|
|
24
|
+
tokens: z.number(),
|
|
25
|
+
/** Input-token ceiling of the resolved context window. */
|
|
26
|
+
maxTokens: z.number(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export type ContextWindowUsageEvent = z.infer<
|
|
30
|
+
typeof ContextWindowUsageEventSchema
|
|
31
|
+
>;
|
|
@@ -23,9 +23,17 @@ export const NotificationIntentEventSchema = z.object({
|
|
|
23
23
|
title: z.string(),
|
|
24
24
|
body: z.string(),
|
|
25
25
|
deliveryId: z.string().optional(),
|
|
26
|
+
correlationId: z.string().optional(),
|
|
26
27
|
deepLinkMetadata: z.record(z.string(), z.unknown()).optional(),
|
|
27
28
|
targetGuardianPrincipalId: z.string().optional(),
|
|
28
29
|
silent: z.boolean().optional(),
|
|
30
|
+
/**
|
|
31
|
+
* True when the platform (APNs) channel accepted a remote push for
|
|
32
|
+
* this delivery, so native clients can avoid double-bannering.
|
|
33
|
+
*/
|
|
34
|
+
remotePushDispatched: z.boolean().optional(),
|
|
35
|
+
/** Native platforms that accepted this remote push. */
|
|
36
|
+
remotePushPlatforms: z.array(z.enum(["ios", "android"])).optional(),
|
|
29
37
|
});
|
|
30
38
|
|
|
31
39
|
export type NotificationIntentEvent = z.infer<
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ui_surface_pending` SSE event.
|
|
3
|
+
*
|
|
4
|
+
* Server → client notice that a `ui_show` call whose input is still streaming
|
|
5
|
+
* will produce a surface of `surfaceType`, so the client can hold a
|
|
6
|
+
* placeholder for it. Authoring a `visual` fragment is the longest input
|
|
7
|
+
* stream the model produces, and until the call closes there is nothing on
|
|
8
|
+
* screen at all: the `ui_show` chip is suppressed (the surface renders in its
|
|
9
|
+
* place) and the surface itself does not exist yet.
|
|
10
|
+
*
|
|
11
|
+
* The daemon emits this at most once per tool call, as soon as the
|
|
12
|
+
* accumulated tool input names a surface type worth a placeholder. It carries
|
|
13
|
+
* no input: the fragment is large and the client only needs to know that one
|
|
14
|
+
* is coming.
|
|
15
|
+
*
|
|
16
|
+
* `toolUseId` is the id of the producing `ui_show` call, the correlation key
|
|
17
|
+
* the client uses to retire the placeholder when the call resolves.
|
|
18
|
+
* `messageId` is the assistant row the call belongs to when the daemon has
|
|
19
|
+
* already opened one.
|
|
20
|
+
*
|
|
21
|
+
* Canonical wire-contract source. Daemon code imports the type directly from
|
|
22
|
+
* this file; external consumers import via `@vellumai/assistant-api`.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { z } from "zod";
|
|
26
|
+
|
|
27
|
+
export const UISurfacePendingEventSchema = z.object({
|
|
28
|
+
type: z.literal("ui_surface_pending"),
|
|
29
|
+
conversationId: z.string(),
|
|
30
|
+
surfaceType: z.literal("visual"),
|
|
31
|
+
toolUseId: z.string(),
|
|
32
|
+
messageId: z.string().optional(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export type UISurfacePendingEvent = z.infer<typeof UISurfacePendingEventSchema>;
|
package/src/api/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { ConfirmationStateChangedEventSchema } from "./events/confirmation-state
|
|
|
25
25
|
import { ContactRequestEventSchema } from "./events/contact-request.js";
|
|
26
26
|
import { ContactsChangedEventSchema } from "./events/contacts-changed.js";
|
|
27
27
|
import { ContextCompactedEventSchema } from "./events/context-compacted.js";
|
|
28
|
+
import { ContextWindowUsageEventSchema } from "./events/context-window-usage.js";
|
|
28
29
|
import { ConversationErrorEventSchema } from "./events/conversation-error.js";
|
|
29
30
|
import { ConversationInferenceProfileUpdatedEventSchema } from "./events/conversation-inference-profile-updated.js";
|
|
30
31
|
import { ConversationListInvalidatedEventSchema } from "./events/conversation-list-invalidated.js";
|
|
@@ -119,6 +120,7 @@ import { ToolUsePreviewStartEventSchema } from "./events/tool-use-preview-start.
|
|
|
119
120
|
import { ToolUseStartEventSchema } from "./events/tool-use-start.js";
|
|
120
121
|
import { UISurfaceCompleteEventSchema } from "./events/ui-surface-complete.js";
|
|
121
122
|
import { UISurfaceDismissEventSchema } from "./events/ui-surface-dismiss.js";
|
|
123
|
+
import { UISurfacePendingEventSchema } from "./events/ui-surface-pending.js";
|
|
122
124
|
import { UISurfaceShowEventSchema } from "./events/ui-surface-show.js";
|
|
123
125
|
import { UISurfaceUndoResultEventSchema } from "./events/ui-surface-undo-result.js";
|
|
124
126
|
import { UISurfaceUpdateEventSchema } from "./events/ui-surface-update.js";
|
|
@@ -270,6 +272,10 @@ export {
|
|
|
270
272
|
type ContextCompactedEvent,
|
|
271
273
|
ContextCompactedEventSchema,
|
|
272
274
|
} from "./events/context-compacted.js";
|
|
275
|
+
export {
|
|
276
|
+
type ContextWindowUsageEvent,
|
|
277
|
+
ContextWindowUsageEventSchema,
|
|
278
|
+
} from "./events/context-window-usage.js";
|
|
273
279
|
export {
|
|
274
280
|
type ConversationErrorCode,
|
|
275
281
|
ConversationErrorCodeSchema,
|
|
@@ -607,6 +613,10 @@ export {
|
|
|
607
613
|
type UISurfaceDismissEvent,
|
|
608
614
|
UISurfaceDismissEventSchema,
|
|
609
615
|
} from "./events/ui-surface-dismiss.js";
|
|
616
|
+
export {
|
|
617
|
+
type UISurfacePendingEvent,
|
|
618
|
+
UISurfacePendingEventSchema,
|
|
619
|
+
} from "./events/ui-surface-pending.js";
|
|
610
620
|
export {
|
|
611
621
|
type SurfaceAction,
|
|
612
622
|
SurfaceActionSchema,
|
|
@@ -836,6 +846,8 @@ export {
|
|
|
836
846
|
TableRowSchema,
|
|
837
847
|
type TableSurfaceData,
|
|
838
848
|
TableSurfaceDataSchema,
|
|
849
|
+
type VisualSurfaceData,
|
|
850
|
+
VisualSurfaceDataSchema,
|
|
839
851
|
type WorkResultDiff,
|
|
840
852
|
WorkResultDiffSchema,
|
|
841
853
|
type WorkResultItem,
|
|
@@ -895,6 +907,7 @@ export const AssistantEventSchema = z.discriminatedUnion("type", [
|
|
|
895
907
|
ContactRequestEventSchema,
|
|
896
908
|
ContactsChangedEventSchema,
|
|
897
909
|
ContextCompactedEventSchema,
|
|
910
|
+
ContextWindowUsageEventSchema,
|
|
898
911
|
ConversationErrorEventSchema,
|
|
899
912
|
ConversationInferenceProfileUpdatedEventSchema,
|
|
900
913
|
ConversationListInvalidatedEventSchema,
|
|
@@ -973,6 +986,7 @@ export const AssistantEventSchema = z.discriminatedUnion("type", [
|
|
|
973
986
|
ToolUseStartEventSchema,
|
|
974
987
|
UISurfaceCompleteEventSchema,
|
|
975
988
|
UISurfaceDismissEventSchema,
|
|
989
|
+
UISurfacePendingEventSchema,
|
|
976
990
|
UISurfaceShowEventSchema,
|
|
977
991
|
UISurfaceUndoResultEventSchema,
|
|
978
992
|
UISurfaceUpdateEventSchema,
|
|
@@ -415,7 +415,14 @@ export type ConversationSurfaceBlock = z.infer<
|
|
|
415
415
|
typeof ConversationSurfaceBlockSchema
|
|
416
416
|
>;
|
|
417
417
|
|
|
418
|
-
/**
|
|
418
|
+
/**
|
|
419
|
+
* A vellum attachment projection (no provider analog).
|
|
420
|
+
*
|
|
421
|
+
* The inlined `attachment` is a metadata-only positional reference:
|
|
422
|
+
* `data`/`thumbnailData` are never populated here. The payload ships once,
|
|
423
|
+
* on the message's flat `attachments` array (joinable by `attachment.id`),
|
|
424
|
+
* or on demand via the attachment content endpoint.
|
|
425
|
+
*/
|
|
419
426
|
export const ConversationAttachmentBlockSchema = z.object({
|
|
420
427
|
type: z.literal("attachment"),
|
|
421
428
|
attachment: ConversationMessageAttachmentSchema,
|
|
@@ -491,10 +498,13 @@ export const ConversationMessageSchema = z.object({
|
|
|
491
498
|
/** Display timestamp as an ISO-8601 string. */
|
|
492
499
|
timestamp: z.string(),
|
|
493
500
|
/**
|
|
494
|
-
* Flat list of attachment metadata for the row
|
|
495
|
-
*
|
|
501
|
+
* Flat list of attachment metadata for the row, and the sole carrier of
|
|
502
|
+
* inline attachment payloads: image rows populate `data`/`thumbnailData`
|
|
503
|
+
* here, while the `attachment` blocks in `contentBlocks` stay metadata-only
|
|
504
|
+
* references joinable by id. Not supersedable by `contentBlocks` for a
|
|
505
|
+
* second reason: `renderHistoryContent` emits an `attachment` content
|
|
496
506
|
* block only for file-block refs with an inline placement, so orphan rows
|
|
497
|
-
* (unmatched ids, count mismatch, no DB rows
|
|
507
|
+
* (unmatched ids, count mismatch, no DB rows; see `alignAttachments`) ship
|
|
498
508
|
* here alone. Kept non-deprecated until `contentBlocks` reaches attachment
|
|
499
509
|
* parity, so clients that migrate off the positional arrays don't drop those
|
|
500
510
|
* chips.
|
|
@@ -586,6 +596,17 @@ export const ConversationMessageSchema = z.object({
|
|
|
586
596
|
* system notices — no avatar, no persona bubble — and never group them
|
|
587
597
|
* with adjacent assistant turns. */
|
|
588
598
|
systemCard: z.boolean().optional(),
|
|
599
|
+
/** Present when this assistant row is a daemon-persisted provider-failure
|
|
600
|
+
* notice (`metadata.messageKind === "provider_error"`); clients may render
|
|
601
|
+
* a themed card instead of a persona bubble. `code` is the stable
|
|
602
|
+
* classified error code (e.g. `"PROVIDER_BILLING"`), `category` the
|
|
603
|
+
* classified category (e.g. `"credits_exhausted"`). */
|
|
604
|
+
providerError: z
|
|
605
|
+
.object({
|
|
606
|
+
code: z.string().optional(),
|
|
607
|
+
category: z.string().optional(),
|
|
608
|
+
})
|
|
609
|
+
.optional(),
|
|
589
610
|
slackMessage: ConversationSlackMessageSchema.optional(),
|
|
590
611
|
/**
|
|
591
612
|
* Queue state for a user message that is still waiting in the daemon's
|
package/src/api/surfaces.ts
CHANGED
|
@@ -54,6 +54,38 @@ export function coerceSurfaceDataRecord(
|
|
|
54
54
|
return {};
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Describe why a `data` payload the caller double-encoded as a JSON object or
|
|
59
|
+
* array literal could not be decoded, or null when it decoded fine.
|
|
60
|
+
*
|
|
61
|
+
* {@link coerceSurfaceDataRecord} collapses an undecodable string to `{}`, which
|
|
62
|
+
* every downstream shape check then reads as "no fields were sent". Callers pair
|
|
63
|
+
* the two so a rejection names the encoding failure instead of blaming whichever
|
|
64
|
+
* field happened to be looked up first.
|
|
65
|
+
*
|
|
66
|
+
* Scoped to strings that open with `{` or `[`: those are an object payload that
|
|
67
|
+
* lost a quote or a brace on the way through. A string that never resembled one
|
|
68
|
+
* (`"delete it?"`) is a different mistake, and the surface's own shape doc says
|
|
69
|
+
* more about it than a parse error would.
|
|
70
|
+
*/
|
|
71
|
+
export function describeSurfaceDataStringParseFailure(
|
|
72
|
+
value: unknown,
|
|
73
|
+
): string | null {
|
|
74
|
+
if (typeof value !== "string") {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const trimmed = value.trimStart();
|
|
78
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
JSON.parse(value);
|
|
83
|
+
return null;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return error instanceof Error ? error.message : String(error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
57
89
|
/** Optional string that drops (rather than rejects on) a non-string value. */
|
|
58
90
|
const tolerantString = () => z.string().optional().catch(undefined);
|
|
59
91
|
|
|
@@ -458,6 +490,59 @@ export const WorkResultSurfaceDataSchema = z.object({
|
|
|
458
490
|
});
|
|
459
491
|
export type WorkResultSurfaceData = z.infer<typeof WorkResultSurfaceDataSchema>;
|
|
460
492
|
|
|
493
|
+
/**
|
|
494
|
+
* Inline visual: a self-contained html fragment rendered in a sandboxed
|
|
495
|
+
* frame. Model-invokable via `ui_show`, which validates the fragment (no
|
|
496
|
+
* external resources, design-token styling) before emitting it — see
|
|
497
|
+
* `tools/ui-surface/visual-validation.ts`.
|
|
498
|
+
*/
|
|
499
|
+
export const VisualSurfaceDataSchema = z.object({
|
|
500
|
+
/** The visual's markup. Load-bearing: empty renders a blank box. */
|
|
501
|
+
html: z.string().catch(""),
|
|
502
|
+
/** Preferred render height in px. */
|
|
503
|
+
height: tolerantNumber(),
|
|
504
|
+
});
|
|
505
|
+
export type VisualSurfaceData = z.infer<typeof VisualSurfaceDataSchema>;
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Bounds for the caller's initial height estimate, in CSS pixels. The maximum
|
|
509
|
+
* matches the client renderer's clamp (`visual-surface.tsx`), so an estimate
|
|
510
|
+
* the frame would clip never reaches the wire.
|
|
511
|
+
*/
|
|
512
|
+
const VISUAL_MIN_HEIGHT = 80;
|
|
513
|
+
const VISUAL_MAX_HEIGHT = 1400;
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Normalize a visual `ui_show` payload: recover `html`/`height` the model
|
|
517
|
+
* placed at the top level of the tool input instead of nesting inside `data`,
|
|
518
|
+
* clamp the height estimate, then parse through the canonical schema. Shared
|
|
519
|
+
* by the tool's validation guard and the daemon resolver so both layers see
|
|
520
|
+
* the same fragment.
|
|
521
|
+
*/
|
|
522
|
+
export function normalizeVisualShowData(
|
|
523
|
+
input: Record<string, unknown>,
|
|
524
|
+
data: Record<string, unknown>,
|
|
525
|
+
): VisualSurfaceData {
|
|
526
|
+
const normalized: Record<string, unknown> = { ...data };
|
|
527
|
+
if (typeof normalized.html !== "string" && typeof input.html === "string") {
|
|
528
|
+
normalized.html = input.html;
|
|
529
|
+
}
|
|
530
|
+
if (normalized.height == null && input.height != null) {
|
|
531
|
+
normalized.height = input.height;
|
|
532
|
+
}
|
|
533
|
+
const parsed = VisualSurfaceDataSchema.parse(normalized);
|
|
534
|
+
if (parsed.height === undefined || parsed.height <= 0) {
|
|
535
|
+
return { html: parsed.html };
|
|
536
|
+
}
|
|
537
|
+
return {
|
|
538
|
+
html: parsed.html,
|
|
539
|
+
height: Math.min(
|
|
540
|
+
VISUAL_MAX_HEIGHT,
|
|
541
|
+
Math.max(VISUAL_MIN_HEIGHT, Math.round(parsed.height)),
|
|
542
|
+
),
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
461
546
|
// === Surface type registry ===
|
|
462
547
|
|
|
463
548
|
export const SURFACE_TYPES = [
|
|
@@ -477,6 +562,7 @@ export const SURFACE_TYPES = [
|
|
|
477
562
|
"work_result",
|
|
478
563
|
"skill_card",
|
|
479
564
|
"call_summary",
|
|
565
|
+
"visual",
|
|
480
566
|
] as const;
|
|
481
567
|
|
|
482
568
|
export const SurfaceTypeSchema = z.enum(SURFACE_TYPES);
|
|
@@ -534,7 +620,8 @@ export type SurfaceData =
|
|
|
534
620
|
| DynamicPageSurfaceData
|
|
535
621
|
| FileUploadSurfaceData
|
|
536
622
|
| DocumentPreviewSurfaceData
|
|
537
|
-
| WorkResultSurfaceData
|
|
623
|
+
| WorkResultSurfaceData
|
|
624
|
+
| VisualSurfaceData;
|
|
538
625
|
|
|
539
626
|
/**
|
|
540
627
|
* Per-type `data` payload shapes, keyed by surface type. This is the
|
|
@@ -568,6 +655,7 @@ export interface SurfaceDataByType {
|
|
|
568
655
|
work_result: WorkResultSurfaceData;
|
|
569
656
|
skill_card: Record<string, unknown>;
|
|
570
657
|
call_summary: Record<string, unknown>;
|
|
658
|
+
visual: VisualSurfaceData;
|
|
571
659
|
}
|
|
572
660
|
|
|
573
661
|
/** Any surface `data` payload, including the opaque (non-renderable) types. */
|
|
@@ -600,6 +688,7 @@ export const SURFACE_DATA_SCHEMAS: {
|
|
|
600
688
|
work_result: WorkResultSurfaceDataSchema,
|
|
601
689
|
skill_card: z.record(z.string(), z.unknown()),
|
|
602
690
|
call_summary: z.record(z.string(), z.unknown()),
|
|
691
|
+
visual: VisualSurfaceDataSchema,
|
|
603
692
|
};
|
|
604
693
|
|
|
605
694
|
/**
|
|
@@ -32,39 +32,16 @@ import {
|
|
|
32
32
|
markSurfaceCompleted,
|
|
33
33
|
} from "../daemon/conversation-surfaces.js";
|
|
34
34
|
import { withdrawSlackApprovalCard } from "../messaging/providers/slack/withdraw.js";
|
|
35
|
+
import { withdrawTelegramApprovalCard } from "../messaging/providers/telegram-bot/withdraw.js";
|
|
35
36
|
import { approvalCardSurfaceId } from "../notifications/approval-card-data.js";
|
|
36
37
|
import {
|
|
37
38
|
type ApprovalAction,
|
|
38
|
-
|
|
39
|
-
PARK_STATUS_LABEL,
|
|
39
|
+
resolveDecisionStatusWord,
|
|
40
40
|
} from "../runtime/channel-approval-types.js";
|
|
41
41
|
import { getLogger } from "../util/logger.js";
|
|
42
42
|
|
|
43
43
|
const log = getLogger("guardian-card-withdrawal");
|
|
44
44
|
|
|
45
|
-
/** Completion-summary label shown on an in-app card for a resolved request. */
|
|
46
|
-
const SURFACE_STATUS_LABELS: Partial<Record<GuardianRequestStatus, string>> = {
|
|
47
|
-
approved: "Approved",
|
|
48
|
-
denied: "Denied",
|
|
49
|
-
expired: "Expired",
|
|
50
|
-
cancelled: "Cancelled",
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* The completion-summary label for a resolved card. A `denied` status reached by
|
|
55
|
-
* a park action reads as the neutral {@link PARK_STATUS_LABEL} rather than
|
|
56
|
-
* "Denied" — a parked contact was neither trusted nor kept out.
|
|
57
|
-
*/
|
|
58
|
-
function resolveStatusLabel(
|
|
59
|
-
status: GuardianRequestStatus,
|
|
60
|
-
decidedAction: ApprovalAction | undefined,
|
|
61
|
-
): string {
|
|
62
|
-
if (status === "denied" && isParkAction(decidedAction)) {
|
|
63
|
-
return PARK_STATUS_LABEL;
|
|
64
|
-
}
|
|
65
|
-
return SURFACE_STATUS_LABELS[status] ?? "Resolved";
|
|
66
|
-
}
|
|
67
|
-
|
|
68
45
|
/** The request fields withdrawal reads — structural subset of the wire row. */
|
|
69
46
|
export interface WithdrawableGuardianRequest {
|
|
70
47
|
id: string;
|
|
@@ -92,10 +69,20 @@ export interface WithdrawGuardianCardsParams {
|
|
|
92
69
|
* The action the guardian took, when the terminal status came from a decision
|
|
93
70
|
* (omitted for the expiry sweep). A `denied` status can mean either a neutral
|
|
94
71
|
* park (`leave_unverified`) or an active rejection (`block`/`reject`); the
|
|
95
|
-
* action disambiguates them so a park renders neutrally as
|
|
96
|
-
* {@link
|
|
72
|
+
* action disambiguates them so a park renders neutrally as the park label
|
|
73
|
+
* (see {@link resolveDecisionStatusWord}) instead of "Denied".
|
|
97
74
|
*/
|
|
98
75
|
decidedAction?: ApprovalAction;
|
|
76
|
+
/**
|
|
77
|
+
* True when the deciding flow delivers the resolver's own guardian-facing
|
|
78
|
+
* reply on the origin channel (the resolver returned `guardianReplyText`).
|
|
79
|
+
* Telegram's quoted status reply is suppressed only when the origin chat is
|
|
80
|
+
* Telegram AND that richer reply is coming; most resolvers (tool grants,
|
|
81
|
+
* tool approvals, questions) reply to the requester, not the guardian, so
|
|
82
|
+
* without this flag the withdrawal's status reply is the only durable
|
|
83
|
+
* outcome the guardian's chat gets.
|
|
84
|
+
*/
|
|
85
|
+
hasOriginGuardianReply?: boolean;
|
|
99
86
|
}
|
|
100
87
|
|
|
101
88
|
/**
|
|
@@ -105,7 +92,13 @@ export interface WithdrawGuardianCardsParams {
|
|
|
105
92
|
export async function withdrawGuardianRequestCards(
|
|
106
93
|
params: WithdrawGuardianCardsParams,
|
|
107
94
|
): Promise<void> {
|
|
108
|
-
const {
|
|
95
|
+
const {
|
|
96
|
+
request,
|
|
97
|
+
status,
|
|
98
|
+
originChannel,
|
|
99
|
+
decidedAction,
|
|
100
|
+
hasOriginGuardianReply,
|
|
101
|
+
} = params;
|
|
109
102
|
|
|
110
103
|
let deliveries: GuardianRequestDeliveryWire[];
|
|
111
104
|
try {
|
|
@@ -130,9 +123,17 @@ export async function withdrawGuardianRequestCards(
|
|
|
130
123
|
);
|
|
131
124
|
} else if (delivery.destinationChannel === "slack") {
|
|
132
125
|
await withdrawSlackCard(request, delivery, status, decidedAction);
|
|
126
|
+
} else if (delivery.destinationChannel === "telegram") {
|
|
127
|
+
await withdrawTelegramCard(
|
|
128
|
+
delivery,
|
|
129
|
+
status,
|
|
130
|
+
originChannel,
|
|
131
|
+
decidedAction,
|
|
132
|
+
hasOriginGuardianReply ?? false,
|
|
133
|
+
);
|
|
133
134
|
}
|
|
134
|
-
//
|
|
135
|
-
//
|
|
135
|
+
// WhatsApp direct delivery can't edit a message in place (it would
|
|
136
|
+
// post a new one), so its stale clicks are left to the existing
|
|
136
137
|
// "already resolved" reply until in-place edit support lands.
|
|
137
138
|
} catch (err) {
|
|
138
139
|
log.warn(
|
|
@@ -175,7 +176,7 @@ function withdrawVellumCard(
|
|
|
175
176
|
if (!surfaceId) {
|
|
176
177
|
return;
|
|
177
178
|
}
|
|
178
|
-
const summary =
|
|
179
|
+
const summary = resolveDecisionStatusWord(status, decidedAction);
|
|
179
180
|
if (originChannel === "vellum") {
|
|
180
181
|
markSurfaceCompleted(
|
|
181
182
|
{ conversationId: delivery.destinationConversationId },
|
|
@@ -214,3 +215,37 @@ async function withdrawSlackCard(
|
|
|
214
215
|
decidedAtMs: request.updatedAt,
|
|
215
216
|
});
|
|
216
217
|
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Withdraw the Telegram approval card: remove its inline keyboard in place
|
|
221
|
+
* and post a silent reply quoting the card with the terminal outcome.
|
|
222
|
+
* Telegram bots cannot re-read a message, so unlike Slack the outcome rides
|
|
223
|
+
* a quoted reply rather than an in-message edit; the card's own text is left
|
|
224
|
+
* untouched for the audit trail.
|
|
225
|
+
*
|
|
226
|
+
* The status reply is suppressed only when the decision was made on Telegram
|
|
227
|
+
* AND its flow delivers the resolver's own guardian-facing reply there
|
|
228
|
+
* (`hasOriginGuardianReply`), where a second notice would read as a
|
|
229
|
+
* duplicate. Most resolvers reply to the requester, not the guardian, so a
|
|
230
|
+
* Telegram-origin decision usually still needs this reply as its durable
|
|
231
|
+
* outcome. No-ops when the channel-native message id was not captured at
|
|
232
|
+
* delivery time.
|
|
233
|
+
*/
|
|
234
|
+
async function withdrawTelegramCard(
|
|
235
|
+
delivery: GuardianRequestDeliveryWire,
|
|
236
|
+
status: GuardianRequestStatus,
|
|
237
|
+
originChannel: string | undefined,
|
|
238
|
+
decidedAction: ApprovalAction | undefined,
|
|
239
|
+
hasOriginGuardianReply: boolean,
|
|
240
|
+
): Promise<void> {
|
|
241
|
+
if (!delivery.destinationChatId || !delivery.destinationMessageId) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
await withdrawTelegramApprovalCard({
|
|
245
|
+
chatId: delivery.destinationChatId,
|
|
246
|
+
messageId: delivery.destinationMessageId,
|
|
247
|
+
status,
|
|
248
|
+
...(decidedAction ? { decidedAction } : {}),
|
|
249
|
+
postStatusReply: !(originChannel === "telegram" && hasOriginGuardianReply),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
@@ -532,6 +532,10 @@ export async function applyGuardianDecision(
|
|
|
532
532
|
status: targetStatus,
|
|
533
533
|
originChannel: actorContext.channel,
|
|
534
534
|
decidedAction: cardAction,
|
|
535
|
+
// Tells the Telegram projection whether the origin chat is about to get
|
|
536
|
+
// the resolver's own guardian-facing reply (delivered by our caller), so
|
|
537
|
+
// it can skip its quoted status reply instead of duplicating it.
|
|
538
|
+
hasOriginGuardianReply: Boolean(resolverReplyText),
|
|
535
539
|
}).catch((err) => {
|
|
536
540
|
log.warn(
|
|
537
541
|
{ err, requestId },
|