@vellumai/assistant 0.8.12 → 0.9.0-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/AGENTS.md +0 -14
- package/ARCHITECTURE.md +45 -45
- package/README.md +1 -1
- package/bun.lock +200 -154
- package/docs/architecture/integrations.md +3 -3
- package/docs/architecture/memory.md +2 -2
- package/docs/architecture/security.md +10 -10
- package/docs/runbook-trusted-contacts.md +12 -12
- package/docs/skills.md +6 -6
- package/docs/workflows-testing.md +221 -0
- package/docs/workflows.md +510 -0
- package/examples/plugins/echo/README.md +5 -5
- package/knip.json +2 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +105 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +12 -0
- package/openapi.yaml +7197 -5708
- package/package.json +8 -4
- package/scripts/generate-openapi.ts +66 -114
- package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
- package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
- package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
- package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
- package/src/__tests__/app-compiler.test.ts +7 -1
- package/src/__tests__/app-dir-path-guard.test.ts +27 -3
- package/src/__tests__/app-executors.test.ts +43 -0
- package/src/__tests__/approval-cascade.test.ts +0 -5
- package/src/__tests__/approval-routes-http.test.ts +91 -0
- package/src/__tests__/assistant-stream-state.test.ts +107 -0
- package/src/__tests__/browser-fill-credential.test.ts +3 -3
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
- package/src/__tests__/compaction-events.test.ts +63 -7
- package/src/__tests__/compaction-trail-store.test.ts +74 -1
- package/src/__tests__/compaction.benchmark.test.ts +63 -41
- package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
- package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
- package/src/__tests__/conversation-history-web-search.test.ts +7 -0
- package/src/__tests__/conversation-process-callsite.test.ts +0 -5
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
- package/src/__tests__/conversation-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
- package/src/__tests__/conversation-speed-override.test.ts +0 -5
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +97 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-usage.test.ts +2 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
- package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
- package/src/__tests__/credential-broker-server-use.test.ts +2 -2
- package/src/__tests__/credential-broker.test.ts +1 -1
- package/src/__tests__/credential-prompt-route.test.ts +417 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault.test.ts +37 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
- package/src/__tests__/dynamic-page-surface.test.ts +219 -0
- package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
- package/src/__tests__/gateway-flag-listener.test.ts +24 -7
- package/src/__tests__/guardian-action-sweep.test.ts +56 -219
- package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
- package/src/__tests__/llm-resolver.test.ts +115 -0
- package/src/__tests__/managed-profile-guard.test.ts +6 -5
- package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
- package/src/__tests__/media-generate-image.test.ts +20 -9
- package/src/__tests__/mock-gateway-ipc.ts +23 -0
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/normalize-onboarding.test.ts +26 -0
- package/src/__tests__/notification-decision-strategy.test.ts +4 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
- package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
- package/src/__tests__/require-fresh-approval.test.ts +425 -1
- package/src/__tests__/resolve-app-id.test.ts +56 -0
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
- package/src/__tests__/schedule-routes.test.ts +257 -4
- package/src/__tests__/schedule-store.test.ts +60 -0
- package/src/__tests__/schedule-tools.test.ts +247 -2
- package/src/__tests__/skill-execute-input.test.ts +85 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
- package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
- package/src/__tests__/task-scheduler.test.ts +299 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
- package/src/__tests__/tool-result-spool.test.ts +3 -1
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
- package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
- package/src/agent/compaction-circuit.ts +11 -0
- package/src/agent/loop.ts +181 -12
- package/src/api/constants/call-sites.ts +12 -0
- package/src/api/events/assistant-thinking-delta.ts +10 -0
- package/src/api/events/usage-update.ts +7 -0
- package/src/api/index.ts +4 -1
- package/src/api/responses/memory-v3-selection-log.ts +18 -11
- package/src/approvals/approval-primitive.ts +2 -2
- package/src/background-wake/background-wake-routes.test.ts +5 -2
- package/src/bundler/compiler-tools.ts +1 -1
- package/src/bundler/package-resolver.ts +0 -1
- package/src/calls/call-domain.ts +1 -1
- package/src/calls/guardian-action-sweep.ts +16 -93
- package/src/cli/AGENTS.md +4 -0
- package/src/cli/commands/__tests__/schedules.test.ts +430 -1
- package/src/cli/commands/credentials.ts +28 -24
- package/src/cli/commands/image-generation.ts +23 -9
- package/src/cli/commands/notifications.ts +1 -1
- package/src/cli/commands/plugins.ts +89 -46
- package/src/cli/commands/schedules.ts +384 -11
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
- package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
- package/src/cli/lib/inspect-plugin.ts +62 -1
- package/src/cli/lib/install-from-github.ts +52 -5
- package/src/cli/lib/upgrade-plugin.ts +18 -0
- package/src/config/__tests__/workflows-schema.test.ts +60 -0
- package/src/config/bundled-skills/acp/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +6 -1
- package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +7 -1
- package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +7 -1
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +10 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
- package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
- package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
- package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
- package/src/config/bundled-skills/schedule/SKILL.md +7 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
- package/src/config/bundled-skills/workflows/SKILL.md +214 -0
- package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
- package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
- package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
- package/src/config/bundled-tool-registry.ts +14 -2
- package/src/config/call-site-defaults.ts +5 -0
- package/src/config/feature-flag-registry.json +12 -4
- package/src/config/llm-context-resolution.ts +8 -0
- package/src/config/llm-resolver.ts +30 -0
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
- package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
- package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
- package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/heartbeat.ts +4 -1
- package/src/config/schemas/llm.ts +33 -26
- package/src/config/schemas/memory-retrospective.ts +19 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/services.ts +5 -2
- package/src/config/schemas/workflows.ts +42 -0
- package/src/config/skills.ts +3 -3
- package/src/context/compactor.ts +273 -39
- package/src/context/post-turn-tool-result-truncation.ts +23 -6
- package/src/context/tool-result-spool.ts +12 -17
- package/src/credential-execution/executable-discovery.ts +1 -1
- package/src/credential-execution/process-manager.ts +37 -3
- package/src/credential-execution/prompted-credential.ts +205 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
- package/src/daemon/conversation-process.ts +11 -2
- package/src/daemon/conversation-surfaces.ts +167 -3
- package/src/daemon/conversation-tool-setup.ts +103 -26
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +115 -11
- package/src/daemon/handlers/shared.ts +26 -14
- package/src/daemon/host-cu-proxy.ts +15 -12
- package/src/daemon/host-file-proxy.ts +15 -12
- package/src/daemon/host-transfer-proxy.ts +30 -24
- package/src/daemon/lifecycle.ts +40 -3
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/messages.ts +2 -10
- package/src/daemon/message-types/workflows.ts +49 -0
- package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
- package/src/daemon/process-message.ts +6 -0
- package/src/daemon/tool-setup-types.ts +57 -0
- package/src/daemon/wake-conversation-ops.ts +18 -0
- package/src/heartbeat/heartbeat-run-store.ts +8 -2
- package/src/home/feed-types.ts +1 -1
- package/src/ipc/gateway-flag-listener.ts +28 -6
- package/src/mcp/mcp-auth-state.ts +8 -20
- package/src/media/__tests__/image-models.test.ts +57 -0
- package/src/media/image-models.ts +66 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
- package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
- package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
- package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
- package/src/memory/auto-analysis-enqueue.ts +14 -1
- package/src/memory/compaction-log-store-clickhouse.ts +6 -4
- package/src/memory/conversation-crud.ts +9 -2
- package/src/memory/conversation-disk-view.ts +1 -1
- package/src/memory/conversation-queries.ts +22 -7
- package/src/memory/db-init.ts +20 -0
- package/src/memory/db-maintenance.ts +16 -0
- package/src/memory/embedding-runtime-manager.ts +1 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
- package/src/memory/llm-request-log-source-local.ts +19 -1
- package/src/memory/llm-request-log-source.ts +35 -6
- package/src/memory/llm-request-log-store.ts +90 -2
- package/src/memory/memory-retrospective-constants.ts +9 -0
- package/src/memory/memory-retrospective-enqueue.ts +3 -6
- package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
- package/src/memory/memory-retrospective-job.ts +500 -208
- package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
- package/src/memory/memory-retrospective-state.ts +85 -2
- package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
- package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
- package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
- package/src/memory/migrations/284-workflow-runs.ts +51 -0
- package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
- package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
- package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
- package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
- package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
- package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
- package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/preloaded-apps.ts +116 -0
- package/src/memory/schema/infrastructure.ts +4 -0
- package/src/memory/schema/memory-core.ts +4 -0
- package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
- package/src/memory/v2/__tests__/page-store.test.ts +13 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -6
- package/src/memory/v2/page-store.ts +4 -3
- package/src/memory/v2/qdrant.ts +42 -3
- package/src/memory/v2/types.ts +16 -10
- package/src/messaging/draft-store.ts +1 -1
- package/src/notifications/access-request-copy.ts +200 -113
- package/src/notifications/adapters/slack.ts +250 -111
- package/src/notifications/adapters/telegram.ts +7 -44
- package/src/notifications/approval-card-builder.ts +93 -0
- package/src/notifications/broadcaster.ts +74 -0
- package/src/notifications/conversation-pairing.ts +8 -6
- package/src/notifications/copy-composer.ts +32 -26
- package/src/notifications/decision-engine.ts +59 -7
- package/src/notifications/guardian-question-mode.ts +145 -155
- package/src/notifications/home-feed-side-effect.ts +28 -11
- package/src/notifications/notification-utils.ts +66 -0
- package/src/notifications/signal.ts +6 -0
- package/src/notifications/tool-approval-copy.ts +142 -0
- package/src/notifications/types.ts +19 -0
- package/src/permissions/threshold.ts +11 -0
- package/src/plugin-api/types.ts +16 -4
- package/src/plugins/defaults/compaction/window-manager.ts +44 -0
- package/src/plugins/defaults/index.ts +46 -0
- package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
- package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
- package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
- package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
- package/src/prompts/__tests__/system-prompt.test.ts +100 -1
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
- package/src/prompts/normalize-onboarding.ts +2 -0
- package/src/prompts/persona-resolver.ts +3 -0
- package/src/prompts/system-prompt.ts +51 -2
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
- package/src/prompts/templates/system-sections.ts +8 -3
- package/src/providers/call-site-routing.ts +6 -3
- package/src/providers/fireworks/client.ts +3 -0
- package/src/providers/inference/auth.ts +52 -46
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
- package/src/providers/openai/chat-completions-provider.ts +47 -9
- package/src/providers/openai/coerce-object-args.ts +104 -0
- package/src/providers/retry.ts +8 -5
- package/src/providers/types.ts +10 -0
- package/src/runtime/__tests__/agent-wake.test.ts +629 -7
- package/src/runtime/access-request-helper.ts +16 -9
- package/src/runtime/agent-wake.ts +302 -51
- package/src/runtime/assistant-stream-state.ts +141 -8
- package/src/runtime/background-job-runner.ts +9 -0
- package/src/runtime/channel-approval-types.ts +1 -0
- package/src/runtime/channel-invite-transports/telegram.ts +6 -5
- package/src/runtime/channel-invite-transports/voice.ts +2 -2
- package/src/runtime/channel-invite-types.ts +4 -2
- package/src/runtime/channel-retry-sweep.ts +19 -41
- package/src/runtime/finalize-event-delivery.ts +72 -0
- package/src/runtime/guardian-action-message-composer.ts +0 -54
- package/src/runtime/http-server.ts +6 -14
- package/src/runtime/http-types.ts +0 -1
- package/src/runtime/message-composer-types.ts +0 -9
- package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
- package/src/runtime/middleware/auth.ts +27 -3
- package/src/runtime/middleware/rate-limiter.ts +28 -1
- package/src/runtime/migrations/vbundle-builder.ts +6 -5
- package/src/runtime/pending-interactions.ts +20 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
- package/src/runtime/routes/approval-routes.ts +35 -8
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
- package/src/runtime/routes/btw-routes.ts +37 -1
- package/src/runtime/routes/channel-delivery-routes.ts +11 -7
- package/src/runtime/routes/channel-route-definitions.ts +3 -0
- package/src/runtime/routes/channel-route-shared.ts +3 -1
- package/src/runtime/routes/consolidation-routes.ts +17 -13
- package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
- package/src/runtime/routes/conversation-list-routes.ts +41 -4
- package/src/runtime/routes/conversation-query-routes.ts +196 -11
- package/src/runtime/routes/conversation-routes.ts +15 -1
- package/src/runtime/routes/credential-prompt-routes.ts +39 -17
- package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
- package/src/runtime/routes/heartbeat-routes.ts +18 -13
- package/src/runtime/routes/image-generation-routes.ts +20 -2
- package/src/runtime/routes/inbound-message-handler.ts +13 -12
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +32 -31
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +7 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -21
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +6 -15
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +1 -1
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/log-export/AGENTS.md +1 -1
- package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
- package/src/runtime/routes/migration-routes.ts +5 -9
- package/src/runtime/routes/plugins-routes.ts +26 -0
- package/src/runtime/routes/ps-routes.ts +10 -8
- package/src/runtime/routes/retrospective-routes.ts +235 -0
- package/src/runtime/routes/runs-pagination.ts +75 -0
- package/src/runtime/routes/schedule-routes.ts +367 -59
- package/src/runtime/routes/sounds-config-routes.ts +239 -0
- package/src/runtime/routes/surface-action-routes.ts +84 -4
- package/src/runtime/routes/workflow-routes.test.ts +372 -0
- package/src/runtime/routes/workflow-routes.ts +363 -0
- package/src/runtime/routes/workspace-routes.test.ts +61 -1
- package/src/runtime/routes/workspace-routes.ts +26 -1
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
- package/src/runtime/services/analyze-conversation.ts +26 -13
- package/src/schedule/inference-profile.ts +28 -0
- package/src/schedule/schedule-store.ts +152 -4
- package/src/schedule/scheduler-types.ts +6 -0
- package/src/schedule/scheduler.ts +96 -0
- package/src/security/secret-allowlist.ts +1 -1
- package/src/skills/path-classifier.ts +1 -1
- package/src/tools/apps/executors.ts +23 -0
- package/src/tools/apps/resolve-app-id.ts +42 -0
- package/src/tools/browser/browser-execution.ts +9 -11
- package/src/tools/credentials/broker.ts +4 -4
- package/src/tools/credentials/vault.ts +26 -137
- package/src/tools/executor.ts +69 -0
- package/src/tools/flag-gated-tools.test.ts +76 -0
- package/src/tools/permission-checker.ts +8 -1
- package/src/tools/registry.ts +51 -0
- package/src/tools/schedule/create.ts +77 -1
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +73 -1
- package/src/tools/skills/execute.ts +56 -0
- package/src/tools/terminal/shell.ts +1 -1
- package/src/tools/ui-surface/definitions.ts +91 -2
- package/src/tools/workflows/manage-workflows.ts +183 -0
- package/src/tools/workflows/run-workflow.test.ts +442 -0
- package/src/tools/workflows/run-workflow.ts +88 -0
- package/src/types/onboarding-context.ts +2 -0
- package/src/usage/attribution.ts +24 -0
- package/src/util/canonicalize-identity.ts +12 -3
- package/src/util/platform.ts +17 -17
- package/src/watcher/__tests__/engine.test.ts +24 -0
- package/src/watcher/__tests__/telemetry.test.ts +135 -0
- package/src/watcher/engine.ts +7 -0
- package/src/watcher/telemetry.ts +74 -0
- package/src/workflows/capabilities.test.ts +365 -0
- package/src/workflows/capabilities.ts +359 -0
- package/src/workflows/deterministic-stringify.ts +27 -0
- package/src/workflows/engine-integration.test.ts +656 -0
- package/src/workflows/engine.test.ts +1144 -0
- package/src/workflows/engine.ts +1078 -0
- package/src/workflows/fanout-load.test.ts +168 -0
- package/src/workflows/journal-store.test.ts +369 -0
- package/src/workflows/journal-store.ts +470 -0
- package/src/workflows/leaf-runner.test.ts +704 -0
- package/src/workflows/leaf-runner.ts +589 -0
- package/src/workflows/library.test.ts +134 -0
- package/src/workflows/library.ts +124 -0
- package/src/workflows/run-manager.test.ts +711 -0
- package/src/workflows/run-manager.ts +593 -0
- package/src/workflows/sandbox-escape.test.ts +339 -0
- package/src/workflows/sandbox.test.ts +251 -0
- package/src/workflows/sandbox.ts +447 -0
- package/src/workspace/adaptive-thinking-repair.ts +33 -11
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
- package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
- package/src/workspace/migrations/031-drop-user-md.ts +1 -4
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
- package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
- package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
- package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
- package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
- package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/workspace/migrations/runner.ts +1 -1
- package/tsconfig.json +1 -1
- package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
- package/src/__tests__/guardian-action-store.test.ts +0 -106
- package/src/daemon/guardian-action-generators.ts +0 -71
- package/src/memory/guardian-action-store.ts +0 -484
- package/src/runtime/guardian-action-grant-minter.ts +0 -150
|
@@ -32,6 +32,21 @@ export const TRUNCATION_MARKER = "\u2014 full result:";
|
|
|
32
32
|
*/
|
|
33
33
|
export const TRUNCATION_EXEMPT_TOOLS = new Set<string>(["skill_load"]);
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* File-read tools that page spooled `.tool-results/` content back into context.
|
|
37
|
+
* `file_read` reads inside the workspace; `host_file_read` reads anywhere on the
|
|
38
|
+
* host. Both are (a) exempt from the result-time spool pass — re-stubbing a read
|
|
39
|
+
* of a spooled file would hand back another stub, so the content could never be
|
|
40
|
+
* paged back at all — and (b) recognized by {@link derefToolResultReReads}, so a
|
|
41
|
+
* re-read of a spooled file collapses to a stub instead of duplicating content
|
|
42
|
+
* already in context. Sharing one set keeps the two passes from drifting if a
|
|
43
|
+
* new file-read tool is added.
|
|
44
|
+
*/
|
|
45
|
+
export const FILE_READ_TOOL_NAMES = new Set<string>([
|
|
46
|
+
"file_read",
|
|
47
|
+
"host_file_read",
|
|
48
|
+
]);
|
|
49
|
+
|
|
35
50
|
/**
|
|
36
51
|
* Build a map of tool_use_id -> originating tool name by walking the tool_use
|
|
37
52
|
* blocks in assistant messages. A tool_result only carries `tool_use_id`, so
|
|
@@ -170,16 +185,18 @@ export const REREAD_STUB =
|
|
|
170
185
|
* Deduplicate re-reads of saved tool results.
|
|
171
186
|
*
|
|
172
187
|
* When `postTurnTruncateToolResults` truncates a large result, it saves the full
|
|
173
|
-
* content to a `.tool-results/` file. If the model later calls `file_read`
|
|
174
|
-
* saved file, the result is a second copy of content
|
|
175
|
-
* is already in context. This function detects
|
|
176
|
-
* tool_result content with a short stub to
|
|
188
|
+
* content to a `.tool-results/` file. If the model later calls `file_read` or
|
|
189
|
+
* `host_file_read` on that saved file, the result is a second copy of content
|
|
190
|
+
* whose truncated prefix/suffix is already in context. This function detects
|
|
191
|
+
* those re-reads and replaces their tool_result content with a short stub to
|
|
192
|
+
* avoid duplication.
|
|
177
193
|
*/
|
|
178
194
|
export function derefToolResultReReads(messages: Message[]): {
|
|
179
195
|
messages: Message[];
|
|
180
196
|
dereferencedCount: number;
|
|
181
197
|
} {
|
|
182
|
-
// Build a set of tool_use_ids that are
|
|
198
|
+
// Build a set of tool_use_ids that are file-read calls (file_read or
|
|
199
|
+
// host_file_read) targeting .tool-results/ paths.
|
|
183
200
|
const reReadToolUseIds = new Set<string>();
|
|
184
201
|
|
|
185
202
|
for (const msg of messages) {
|
|
@@ -187,7 +204,7 @@ export function derefToolResultReReads(messages: Message[]): {
|
|
|
187
204
|
for (const block of msg.content) {
|
|
188
205
|
if (block.type !== "tool_use") continue;
|
|
189
206
|
const tu = block as ToolUseContent;
|
|
190
|
-
if (tu.name
|
|
207
|
+
if (!FILE_READ_TOOL_NAMES.has(tu.name)) continue;
|
|
191
208
|
const filePath = tu.input.path ?? tu.input.file_path;
|
|
192
209
|
if (typeof filePath !== "string") continue;
|
|
193
210
|
if (filePath.includes(`/${TOOL_RESULT_DIR}/`)) {
|
|
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import type { ContentBlock, ToolResultContent } from "../providers/types.js";
|
|
5
5
|
import {
|
|
6
6
|
buildTruncatedContent,
|
|
7
|
+
FILE_READ_TOOL_NAMES,
|
|
7
8
|
getToolResultFilePath,
|
|
8
9
|
isTruncationEligible,
|
|
9
10
|
TOOL_RESULT_DIR,
|
|
@@ -18,27 +19,21 @@ import {
|
|
|
18
19
|
const AX_TREE_TAG = "<ax-tree>";
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
const RESULT_TIME_STUB_EXEMPT_TOOLS = new Set<string>(["file_read"]);
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Whether a tool result is eligible for the result-time spool/stub pass:
|
|
33
|
-
* the post-turn pass's shared rules plus the AX-tree and `file_read`
|
|
34
|
-
* exemptions above.
|
|
22
|
+
* Whether a tool result is eligible for the result-time spool/stub pass: the
|
|
23
|
+
* post-turn pass's shared rules plus the AX-tree exemption, minus the file-read
|
|
24
|
+
* tools ({@link FILE_READ_TOOL_NAMES}). The file-read tools are the model's only
|
|
25
|
+
* way to page spooled content back into context: stubbing a read of a
|
|
26
|
+
* `.tool-results/` file would spool a fresh copy and hand back another stub, so
|
|
27
|
+
* oversized content could never be read at all. Explicit reads are therefore
|
|
28
|
+
* honored in full; the post-turn pass still truncates them at turn end, after
|
|
29
|
+
* the model has consumed the content.
|
|
35
30
|
*/
|
|
36
31
|
function isSpoolEligible(
|
|
37
32
|
tr: ToolResultContent,
|
|
38
33
|
toolName: string | undefined,
|
|
39
34
|
): boolean {
|
|
40
35
|
if (!isTruncationEligible(tr, toolName)) return false;
|
|
41
|
-
if (toolName !== undefined &&
|
|
36
|
+
if (toolName !== undefined && FILE_READ_TOOL_NAMES.has(toolName)) {
|
|
42
37
|
return false;
|
|
43
38
|
}
|
|
44
39
|
if (tr.content.includes(AX_TREE_TAG)) return false;
|
|
@@ -56,8 +51,8 @@ function isSpoolEligible(
|
|
|
56
51
|
* rewriting an earlier message between calls would invalidate the cache from
|
|
57
52
|
* that point on every iteration. The model still gets the head/tail preview
|
|
58
53
|
* plus the on-disk path, so it can page the full content back in with
|
|
59
|
-
* `file_read` (whose results are exempt from this pass)
|
|
60
|
-
* needs it.
|
|
54
|
+
* `file_read` or `host_file_read` (whose results are exempt from this pass)
|
|
55
|
+
* when it actually needs it.
|
|
61
56
|
*
|
|
62
57
|
* Uses the same file paths, stub bytes, and eligibility rules as
|
|
63
58
|
* `postTurnTruncateToolResults`, whose `TRUNCATION_MARKER` guard then skips
|
|
@@ -63,7 +63,7 @@ function getManagedBootstrapSocketPath(): string {
|
|
|
63
63
|
* in order. The first existing path wins.
|
|
64
64
|
*
|
|
65
65
|
* Only paths outside the sandbox working directory are eligible.
|
|
66
|
-
* `getDataDir()` (under
|
|
66
|
+
* `getDataDir()` (under `$VELLUM_WORKSPACE_DIR/data`) was previously included
|
|
67
67
|
* but is inside the sandbox write boundary, so a sandboxed tool could plant
|
|
68
68
|
* a malicious binary there. Removed to close the sandbox-escape vector.
|
|
69
69
|
*
|
|
@@ -327,6 +327,8 @@ function createStdioTransport(proc: Subprocess): CesTransport {
|
|
|
327
327
|
const decoder = new TextDecoder();
|
|
328
328
|
|
|
329
329
|
void (async () => {
|
|
330
|
+
let endReason: "eof" | "error" = "eof";
|
|
331
|
+
let readError: unknown;
|
|
330
332
|
try {
|
|
331
333
|
while (true) {
|
|
332
334
|
const { value, done } = await reader.read();
|
|
@@ -344,17 +346,49 @@ function createStdioTransport(proc: Subprocess): CesTransport {
|
|
|
344
346
|
}
|
|
345
347
|
}
|
|
346
348
|
}
|
|
347
|
-
} catch {
|
|
348
|
-
|
|
349
|
+
} catch (err) {
|
|
350
|
+
endReason = "error";
|
|
351
|
+
readError = err;
|
|
349
352
|
} finally {
|
|
353
|
+
// Preserve existing semantics: the transport reports dead the moment
|
|
354
|
+
// the stdout stream ends, regardless of why.
|
|
350
355
|
alive = false;
|
|
356
|
+
|
|
357
|
+
// DIAGNOSTIC (observation only). The reconnection path (secure-keys.ts)
|
|
358
|
+
// bounces CES whenever isAlive() goes false. This classifies WHY the
|
|
359
|
+
// transport died: a real process exit, or a stdout stream that
|
|
360
|
+
// ended/errored while the process is STILL RUNNING — the spurious case
|
|
361
|
+
// that needlessly restarts a healthy CES. Let `proc.exited` settle one
|
|
362
|
+
// tick so a near-simultaneous real exit isn't misread as spurious, then
|
|
363
|
+
// snapshot the exit code (`null` = still running).
|
|
364
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
365
|
+
const exitCode = proc.exitCode;
|
|
366
|
+
if (exitCode === null) {
|
|
367
|
+
log.warn(
|
|
368
|
+
{
|
|
369
|
+
pid: proc.pid,
|
|
370
|
+
endReason,
|
|
371
|
+
err: readError instanceof Error ? readError.message : readError,
|
|
372
|
+
},
|
|
373
|
+
"CES stdio transport: stdout stream ended while the process is still alive (spurious transport death)",
|
|
374
|
+
);
|
|
375
|
+
} else {
|
|
376
|
+
log.debug(
|
|
377
|
+
{ pid: proc.pid, endReason, exitCode },
|
|
378
|
+
"CES stdio transport: stdout stream ended after process exit",
|
|
379
|
+
);
|
|
380
|
+
}
|
|
351
381
|
}
|
|
352
382
|
})();
|
|
353
383
|
}
|
|
354
384
|
|
|
355
385
|
// Track process exit
|
|
356
|
-
proc.exited.then(() => {
|
|
386
|
+
void proc.exited.then((exitCode) => {
|
|
357
387
|
alive = false;
|
|
388
|
+
log.info(
|
|
389
|
+
{ pid: proc.pid, exitCode },
|
|
390
|
+
"CES stdio transport: process exited",
|
|
391
|
+
);
|
|
358
392
|
});
|
|
359
393
|
|
|
360
394
|
return {
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistence for credentials collected through a secure prompt.
|
|
3
|
+
*
|
|
4
|
+
* A prompted credential can be delivered in three shapes:
|
|
5
|
+
* - one-time send: injected into the broker for the next operation, never
|
|
6
|
+
* written to the vault;
|
|
7
|
+
* - a Slack channel token: routed through the Slack channel config so the
|
|
8
|
+
* channel actually connects, not just stored;
|
|
9
|
+
* - any other credential: written to secure storage.
|
|
10
|
+
*
|
|
11
|
+
* Both the `credential_store` tool's prompt action and the CLI
|
|
12
|
+
* `credentials prompt` route share this logic so the two entry points behave
|
|
13
|
+
* identically.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { getConfig } from "../config/loader.js";
|
|
17
|
+
import {
|
|
18
|
+
setSlackChannelConfig,
|
|
19
|
+
type SlackChannelConfigResult,
|
|
20
|
+
} from "../daemon/handlers/config-slack-channel.js";
|
|
21
|
+
import { syncManualTokenConnection } from "../oauth/manual-token-connection.js";
|
|
22
|
+
import type { SecretDelivery } from "../permissions/secret-prompter.js";
|
|
23
|
+
import { credentialKey } from "../security/credential-key.js";
|
|
24
|
+
import { setSecureKeyAsync } from "../security/secure-keys.js";
|
|
25
|
+
import { credentialBroker } from "../tools/credentials/broker.js";
|
|
26
|
+
import {
|
|
27
|
+
getCredentialMetadata,
|
|
28
|
+
upsertCredentialMetadata,
|
|
29
|
+
} from "../tools/credentials/metadata-store.js";
|
|
30
|
+
import type { CredentialInjectionTemplate } from "../tools/credentials/policy-types.js";
|
|
31
|
+
import { getLogger } from "../util/logger.js";
|
|
32
|
+
|
|
33
|
+
const log = getLogger("prompted-credential");
|
|
34
|
+
|
|
35
|
+
/** Slack channel credentials connect the channel rather than just storing a value. */
|
|
36
|
+
export function isSlackChannelCredential(
|
|
37
|
+
service: string,
|
|
38
|
+
field: string,
|
|
39
|
+
): field is "bot_token" | "app_token" | "user_token" {
|
|
40
|
+
return (
|
|
41
|
+
service === "slack_channel" &&
|
|
42
|
+
(field === "bot_token" || field === "app_token" || field === "user_token")
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function storeSlackChannelCredential(
|
|
47
|
+
field: "bot_token" | "app_token" | "user_token",
|
|
48
|
+
value: string,
|
|
49
|
+
): Promise<SlackChannelConfigResult> {
|
|
50
|
+
if (field === "bot_token") {
|
|
51
|
+
return setSlackChannelConfig(value, undefined);
|
|
52
|
+
}
|
|
53
|
+
if (field === "app_token") {
|
|
54
|
+
return setSlackChannelConfig(undefined, value);
|
|
55
|
+
}
|
|
56
|
+
return setSlackChannelConfig(undefined, undefined, value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function formatSlackChannelStatus(
|
|
60
|
+
result: SlackChannelConfigResult,
|
|
61
|
+
): string {
|
|
62
|
+
if (result.connected) {
|
|
63
|
+
const teamLabel = result.teamName ?? "Slack";
|
|
64
|
+
const botLabel = result.botUsername ? ` (@${result.botUsername})` : "";
|
|
65
|
+
return ` Slack channel connected to ${teamLabel}${botLabel}.`;
|
|
66
|
+
}
|
|
67
|
+
if (result.warning) {
|
|
68
|
+
return ` ${result.warning}`;
|
|
69
|
+
}
|
|
70
|
+
return "";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Usage policy carried alongside a prompted credential.
|
|
75
|
+
*
|
|
76
|
+
* `allowedTools` / `allowedDomains` are forwarded to the metadata store as-is:
|
|
77
|
+
* `undefined` leaves an existing credential's list untouched and defaults to
|
|
78
|
+
* deny-all (empty) when creating a new credential, while an explicit empty
|
|
79
|
+
* array sets a deny-all policy. This matches the store's partial-update
|
|
80
|
+
* contract, so re-prompting a credential without policy flags preserves the
|
|
81
|
+
* policy it was originally stored with.
|
|
82
|
+
*/
|
|
83
|
+
interface PromptedCredentialPolicy {
|
|
84
|
+
allowedTools?: string[];
|
|
85
|
+
allowedDomains?: string[];
|
|
86
|
+
usageDescription?: string;
|
|
87
|
+
injectionTemplates?: CredentialInjectionTemplate[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Outcome of persisting a prompted credential.
|
|
92
|
+
*
|
|
93
|
+
* `error` carries a human-readable message (without an "Error:" prefix);
|
|
94
|
+
* `stored` carries the Slack connection result when the value was a Slack
|
|
95
|
+
* channel token.
|
|
96
|
+
*/
|
|
97
|
+
type PersistPromptedCredentialResult =
|
|
98
|
+
| { outcome: "transient" }
|
|
99
|
+
| { outcome: "stored"; slackChannel?: SlackChannelConfigResult }
|
|
100
|
+
| { outcome: "error"; message: string };
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Persist a credential value obtained from a secure prompt.
|
|
104
|
+
*
|
|
105
|
+
* The caller is responsible for issuing the prompt and handling a missing
|
|
106
|
+
* value (cancellation / unsupported channel); this function owns everything
|
|
107
|
+
* that happens once a value is in hand.
|
|
108
|
+
*/
|
|
109
|
+
export async function persistPromptedCredential(args: {
|
|
110
|
+
service: string;
|
|
111
|
+
field: string;
|
|
112
|
+
value: string;
|
|
113
|
+
delivery: SecretDelivery;
|
|
114
|
+
policy: PromptedCredentialPolicy;
|
|
115
|
+
}): Promise<PersistPromptedCredentialResult> {
|
|
116
|
+
const { service, field, value, delivery, policy } = args;
|
|
117
|
+
|
|
118
|
+
if (delivery === "transient_send") {
|
|
119
|
+
if (isSlackChannelCredential(service, field)) {
|
|
120
|
+
return {
|
|
121
|
+
outcome: "error",
|
|
122
|
+
message:
|
|
123
|
+
"Slack channel credentials must be saved to secure storage. Re-run the secure prompt and choose to store the token.",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const config = getConfig();
|
|
127
|
+
if (!config.secretDetection.allowOneTimeSend) {
|
|
128
|
+
log.warn(
|
|
129
|
+
{ service, field },
|
|
130
|
+
"One-time send requested but not enabled in config",
|
|
131
|
+
);
|
|
132
|
+
return {
|
|
133
|
+
outcome: "error",
|
|
134
|
+
message:
|
|
135
|
+
"one-time send is not enabled. Set secretDetection.allowOneTimeSend to true in config.",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// Ensure metadata exists so broker policy checks work, but don't overwrite
|
|
139
|
+
// an existing record - a stored credential's policy must not be silently
|
|
140
|
+
// replaced by the transient prompt's policy. Metadata is written before the
|
|
141
|
+
// transient value so no dangling value can fail policy checks.
|
|
142
|
+
if (!getCredentialMetadata(service, field)) {
|
|
143
|
+
try {
|
|
144
|
+
upsertCredentialMetadata(service, field, {
|
|
145
|
+
allowedTools: policy.allowedTools,
|
|
146
|
+
allowedDomains: policy.allowedDomains,
|
|
147
|
+
usageDescription: policy.usageDescription,
|
|
148
|
+
injectionTemplates: policy.injectionTemplates,
|
|
149
|
+
});
|
|
150
|
+
} catch (err) {
|
|
151
|
+
log.error(
|
|
152
|
+
{ service, field, err },
|
|
153
|
+
"metadata write failed for transient credential",
|
|
154
|
+
);
|
|
155
|
+
return {
|
|
156
|
+
outcome: "error",
|
|
157
|
+
message: `failed to write credential metadata for ${service}/${field}; the one-time value was discarded.`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
credentialBroker.injectTransient(service, field, value);
|
|
162
|
+
log.info(
|
|
163
|
+
{ service, field, delivery: "transient_send" },
|
|
164
|
+
"One-time secret delivery used",
|
|
165
|
+
);
|
|
166
|
+
return { outcome: "transient" };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let slackChannel: SlackChannelConfigResult | undefined;
|
|
170
|
+
if (isSlackChannelCredential(service, field)) {
|
|
171
|
+
slackChannel = await storeSlackChannelCredential(field, value);
|
|
172
|
+
if (!slackChannel.success) {
|
|
173
|
+
return {
|
|
174
|
+
outcome: "error",
|
|
175
|
+
message: slackChannel.error ?? "failed to configure Slack channel",
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
const key = credentialKey(service, field);
|
|
180
|
+
const ok = await setSecureKeyAsync(key, value);
|
|
181
|
+
if (!ok) {
|
|
182
|
+
return { outcome: "error", message: "failed to store credential" };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
upsertCredentialMetadata(service, field, {
|
|
188
|
+
allowedTools: policy.allowedTools,
|
|
189
|
+
allowedDomains: policy.allowedDomains,
|
|
190
|
+
usageDescription: policy.usageDescription,
|
|
191
|
+
injectionTemplates: policy.injectionTemplates,
|
|
192
|
+
});
|
|
193
|
+
} catch (err) {
|
|
194
|
+
log.warn(
|
|
195
|
+
{ service, field, err },
|
|
196
|
+
"metadata write failed after storing credential",
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!isSlackChannelCredential(service, field)) {
|
|
201
|
+
await syncManualTokenConnection(service);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return { outcome: "stored", slackChannel };
|
|
205
|
+
}
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
writeSlackMetadata,
|
|
52
52
|
} from "../messaging/providers/slack/message-metadata.js";
|
|
53
53
|
import type { ContextWindowResult } from "../plugins/defaults/compaction/window-manager.js";
|
|
54
|
+
import { backfillMemoryV3SelectionMessageId } from "../plugins/defaults/memory-v3-shadow/shadow-plugin.js";
|
|
54
55
|
import type {
|
|
55
56
|
ContentBlock,
|
|
56
57
|
ImageContent,
|
|
@@ -867,6 +868,7 @@ function handleThinkingDelta(
|
|
|
867
868
|
thinking: event.thinking,
|
|
868
869
|
conversationId: deps.ctx.conversationId,
|
|
869
870
|
messageId: state.lastAssistantMessageId,
|
|
871
|
+
timestampMs: Date.now(),
|
|
870
872
|
});
|
|
871
873
|
// Mirror thinking into the same running view as text so the debounced
|
|
872
874
|
// partial flush persists it mid-turn -- long reasoning streams survive a
|
|
@@ -1945,6 +1947,18 @@ export async function handleMessageComplete(
|
|
|
1945
1947
|
);
|
|
1946
1948
|
}
|
|
1947
1949
|
|
|
1950
|
+
try {
|
|
1951
|
+
backfillMemoryV3SelectionMessageId(
|
|
1952
|
+
deps.ctx.conversationId,
|
|
1953
|
+
assistantMessageId,
|
|
1954
|
+
);
|
|
1955
|
+
} catch (err) {
|
|
1956
|
+
deps.rlog.warn(
|
|
1957
|
+
{ err },
|
|
1958
|
+
"Failed to backfill memory v3 selection messageId (non-fatal)",
|
|
1959
|
+
);
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1948
1962
|
deps.ctx.currentTurnSurfaces = [];
|
|
1949
1963
|
|
|
1950
1964
|
// Emit trace event. Char count is computed from the cleaned +
|
|
@@ -222,8 +222,7 @@ async function buildPassthroughBatch(
|
|
|
222
222
|
// otherwise diverge.
|
|
223
223
|
if (candIf?.userMessageInterface !== headInterface?.userMessageInterface)
|
|
224
224
|
break;
|
|
225
|
-
if (candidate.sourceActorPrincipalId !== head.sourceActorPrincipalId)
|
|
226
|
-
break;
|
|
225
|
+
if (candidate.sourceActorPrincipalId !== head.sourceActorPrincipalId) break;
|
|
227
226
|
if (classifySlash(candidate.content) !== "passthrough") break;
|
|
228
227
|
if (
|
|
229
228
|
resolveVerificationSessionIntent(candidate.content).kind ===
|
|
@@ -1358,6 +1357,12 @@ export interface ProcessMessageOptions {
|
|
|
1358
1357
|
currentPage?: string;
|
|
1359
1358
|
isInteractive?: boolean;
|
|
1360
1359
|
callSite?: LLMCallSite;
|
|
1360
|
+
/**
|
|
1361
|
+
* Optional ad-hoc inference-profile override applied to every LLM call
|
|
1362
|
+
* this turn issues (e.g. a schedule's pinned profile). Forwarded to
|
|
1363
|
+
* {@link Conversation.runAgentLoop}.
|
|
1364
|
+
*/
|
|
1365
|
+
overrideProfile?: string;
|
|
1361
1366
|
displayContent?: string;
|
|
1362
1367
|
}
|
|
1363
1368
|
|
|
@@ -1380,6 +1385,7 @@ export async function processMessage(
|
|
|
1380
1385
|
currentPage,
|
|
1381
1386
|
isInteractive,
|
|
1382
1387
|
callSite,
|
|
1388
|
+
overrideProfile,
|
|
1383
1389
|
displayContent,
|
|
1384
1390
|
} = options;
|
|
1385
1391
|
await conversation.ensureActorScopedHistory();
|
|
@@ -1852,11 +1858,14 @@ export async function processMessage(
|
|
|
1852
1858
|
isUserMessage?: boolean;
|
|
1853
1859
|
titleText?: string;
|
|
1854
1860
|
callSite?: LLMCallSite;
|
|
1861
|
+
overrideProfile?: string;
|
|
1855
1862
|
} = { isUserMessage: true };
|
|
1856
1863
|
if (isInteractive !== undefined) loopOptions.isInteractive = isInteractive;
|
|
1857
1864
|
if (agentLoopContent !== resolvedContent)
|
|
1858
1865
|
loopOptions.titleText = resolvedContent;
|
|
1859
1866
|
if (callSite !== undefined) loopOptions.callSite = callSite;
|
|
1867
|
+
if (overrideProfile !== undefined)
|
|
1868
|
+
loopOptions.overrideProfile = overrideProfile;
|
|
1860
1869
|
|
|
1861
1870
|
await conversation.runAgentLoop(agentLoopContent, userMessageId, {
|
|
1862
1871
|
...loopOptions,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getAppDirPath,
|
|
8
8
|
getAppPreview,
|
|
9
9
|
isMultifileApp,
|
|
10
|
+
listAppsByConversation,
|
|
10
11
|
resolveAppDir,
|
|
11
12
|
resolveEffectiveAppHtml,
|
|
12
13
|
updateApp,
|
|
@@ -179,6 +180,18 @@ export function flushSurfaceDataPersist(surfaceId: string): void {
|
|
|
179
180
|
persistSurfaceData(pending.conversationId, surfaceId, pending.data);
|
|
180
181
|
}
|
|
181
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Discard (without writing) any pending debounced persist for `surfaceId`.
|
|
185
|
+
* Called on dismissal so an in-flight `ui_update` snapshot cannot land after
|
|
186
|
+
* the surface block has been removed.
|
|
187
|
+
*/
|
|
188
|
+
export function cancelSurfaceDataPersist(surfaceId: string): void {
|
|
189
|
+
const pending = pendingSurfacePersists.get(surfaceId);
|
|
190
|
+
if (!pending) return;
|
|
191
|
+
clearTimeout(pending.timer);
|
|
192
|
+
pendingSurfacePersists.delete(surfaceId);
|
|
193
|
+
}
|
|
194
|
+
|
|
182
195
|
/**
|
|
183
196
|
* Cancel all pending debounced persists. Called on conversation
|
|
184
197
|
* teardown to avoid timers firing against torn-down state.
|
|
@@ -277,8 +290,133 @@ export function markSurfaceCompleted(
|
|
|
277
290
|
log.warn({ err, surfaceId }, "Failed to persist surface completion to DB");
|
|
278
291
|
}
|
|
279
292
|
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Remove a `ui_surface` content block from history so a passively dismissed
|
|
296
|
+
* surface does not survive a reload. The live client drops a dismissed surface
|
|
297
|
+
* entirely; this converges persisted state with that behaviour. Cancels any
|
|
298
|
+
* pending debounced data persist first so a late `ui_update` snapshot cannot
|
|
299
|
+
* re-add the block, then strips the block from in-memory messages and the DB.
|
|
300
|
+
*/
|
|
301
|
+
export function removeSurfaceBlock(
|
|
302
|
+
ctx: { conversationId: string; messages?: Array<{ content: unknown }> },
|
|
303
|
+
surfaceId: string,
|
|
304
|
+
): void {
|
|
305
|
+
cancelSurfaceDataPersist(surfaceId);
|
|
306
|
+
|
|
307
|
+
if (ctx.messages) {
|
|
308
|
+
for (let i = ctx.messages.length - 1; i >= 0; i--) {
|
|
309
|
+
const msg = ctx.messages[i];
|
|
310
|
+
if (!Array.isArray(msg.content)) continue;
|
|
311
|
+
const idx = msg.content.findIndex((block) => {
|
|
312
|
+
const b = block as Record<string, unknown>;
|
|
313
|
+
return b.type === "ui_surface" && b.surfaceId === surfaceId;
|
|
314
|
+
});
|
|
315
|
+
if (idx !== -1) {
|
|
316
|
+
msg.content.splice(idx, 1);
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const rows = getMessages(ctx.conversationId);
|
|
324
|
+
for (let r = rows.length - 1; r >= 0; r--) {
|
|
325
|
+
let parsed: unknown[];
|
|
326
|
+
try {
|
|
327
|
+
const result = JSON.parse(rows[r].content);
|
|
328
|
+
if (!Array.isArray(result)) continue;
|
|
329
|
+
parsed = result;
|
|
330
|
+
} catch {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const idx = parsed.findIndex((pb) => {
|
|
334
|
+
const rb = pb as Record<string, unknown>;
|
|
335
|
+
return rb.type === "ui_surface" && rb.surfaceId === surfaceId;
|
|
336
|
+
});
|
|
337
|
+
if (idx !== -1) {
|
|
338
|
+
parsed.splice(idx, 1);
|
|
339
|
+
updateMessageContent(rows[r].id, JSON.stringify(parsed));
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
} catch (err) {
|
|
344
|
+
log.warn({ err, surfaceId }, "Failed to remove dismissed surface from DB");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
280
347
|
const TASK_PROGRESS_TEMPLATE_FIELDS = ["title", "status", "steps"] as const;
|
|
281
348
|
|
|
349
|
+
const TASK_PROGRESS_CARD_STATUSES = new Set([
|
|
350
|
+
"in_progress",
|
|
351
|
+
"completed",
|
|
352
|
+
"failed",
|
|
353
|
+
]);
|
|
354
|
+
const TASK_PROGRESS_STEP_STATUSES = new Set([
|
|
355
|
+
"pending",
|
|
356
|
+
"in_progress",
|
|
357
|
+
"completed",
|
|
358
|
+
"failed",
|
|
359
|
+
]);
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Coerce a model-supplied `steps` value into a renderable array. Drops
|
|
363
|
+
* non-object and label-less entries, accepts `title` as a `label` alias, and
|
|
364
|
+
* defaults a missing/invalid per-step status to "pending". Returns `[]` for a
|
|
365
|
+
* missing or non-array input so an indeterminate card still renders.
|
|
366
|
+
*/
|
|
367
|
+
function normalizeTaskProgressSteps(
|
|
368
|
+
value: unknown,
|
|
369
|
+
): Array<Record<string, unknown>> {
|
|
370
|
+
if (!Array.isArray(value)) return [];
|
|
371
|
+
return value
|
|
372
|
+
.filter((step): step is Record<string, unknown> => isPlainObject(step))
|
|
373
|
+
.map((step) => {
|
|
374
|
+
const label =
|
|
375
|
+
typeof step.label === "string"
|
|
376
|
+
? step.label
|
|
377
|
+
: typeof step.title === "string"
|
|
378
|
+
? step.title
|
|
379
|
+
: "";
|
|
380
|
+
const status =
|
|
381
|
+
typeof step.status === "string" &&
|
|
382
|
+
TASK_PROGRESS_STEP_STATUSES.has(step.status)
|
|
383
|
+
? step.status
|
|
384
|
+
: "pending";
|
|
385
|
+
return { ...step, label, status };
|
|
386
|
+
})
|
|
387
|
+
.filter((step) => (step.label as string).trim().length > 0);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Guarantee a task_progress card reaches the client with a well-formed
|
|
392
|
+
* `templateData` object so a coarse or indeterminate attempt (missing steps,
|
|
393
|
+
* missing status) renders instead of being silently dropped. Fills only
|
|
394
|
+
* missing fields — a fully-specified card is left intact.
|
|
395
|
+
*/
|
|
396
|
+
function ensureTaskProgressTemplateData(
|
|
397
|
+
normalized: Record<string, unknown>,
|
|
398
|
+
): void {
|
|
399
|
+
const templateData: Record<string, unknown> = isPlainObject(
|
|
400
|
+
normalized.templateData,
|
|
401
|
+
)
|
|
402
|
+
? { ...normalized.templateData }
|
|
403
|
+
: {};
|
|
404
|
+
if (
|
|
405
|
+
typeof templateData.title !== "string" &&
|
|
406
|
+
typeof normalized.title === "string"
|
|
407
|
+
) {
|
|
408
|
+
templateData.title = normalized.title;
|
|
409
|
+
}
|
|
410
|
+
if (
|
|
411
|
+
typeof templateData.status !== "string" ||
|
|
412
|
+
!TASK_PROGRESS_CARD_STATUSES.has(templateData.status)
|
|
413
|
+
) {
|
|
414
|
+
templateData.status = "in_progress";
|
|
415
|
+
}
|
|
416
|
+
templateData.steps = normalizeTaskProgressSteps(templateData.steps);
|
|
417
|
+
normalized.templateData = templateData;
|
|
418
|
+
}
|
|
419
|
+
|
|
282
420
|
/**
|
|
283
421
|
* Migrate dynamic_page fields from the top-level tool input into `data`.
|
|
284
422
|
*
|
|
@@ -365,6 +503,10 @@ function normalizeCardShowData(
|
|
|
365
503
|
normalized.body = "";
|
|
366
504
|
}
|
|
367
505
|
|
|
506
|
+
if (normalized.template === "task_progress") {
|
|
507
|
+
ensureTaskProgressTemplateData(normalized);
|
|
508
|
+
}
|
|
509
|
+
|
|
368
510
|
return normalized as unknown as CardSurfaceData;
|
|
369
511
|
}
|
|
370
512
|
|
|
@@ -2783,6 +2925,15 @@ export async function surfaceProxyResolver(
|
|
|
2783
2925
|
conversationId: ctx.conversationId,
|
|
2784
2926
|
surfaceId,
|
|
2785
2927
|
});
|
|
2928
|
+
// The live client drops a dismissed surface entirely. Mirror that in
|
|
2929
|
+
// persisted state: pull it from the pending turn snapshot (appended to
|
|
2930
|
+
// the message at turn completion) and strip any already-persisted block,
|
|
2931
|
+
// so a reload does not resurrect a half-finished progress card.
|
|
2932
|
+
const turnIdx = ctx.currentTurnSurfaces.findIndex(
|
|
2933
|
+
(s) => s.surfaceId === surfaceId,
|
|
2934
|
+
);
|
|
2935
|
+
if (turnIdx !== -1) ctx.currentTurnSurfaces.splice(turnIdx, 1);
|
|
2936
|
+
removeSurfaceBlock(ctx, surfaceId);
|
|
2786
2937
|
}
|
|
2787
2938
|
ctx.pendingSurfaceActions.delete(surfaceId);
|
|
2788
2939
|
ctx.surfaceState.delete(surfaceId);
|
|
@@ -2796,11 +2947,24 @@ export async function surfaceProxyResolver(
|
|
|
2796
2947
|
}
|
|
2797
2948
|
|
|
2798
2949
|
if (toolName === "app_open") {
|
|
2799
|
-
|
|
2950
|
+
// Weaker models routinely omit app_id even though the active app is in
|
|
2951
|
+
// context. Fall back to the conversation's most-recently-updated app
|
|
2952
|
+
// rather than failing with "Invalid ID: undefined".
|
|
2953
|
+
let appId = input.app_id as string;
|
|
2954
|
+
if (typeof appId !== "string" || appId.trim().length === 0) {
|
|
2955
|
+
appId = listAppsByConversation(ctx.conversationId)[0]?.id ?? "";
|
|
2956
|
+
}
|
|
2800
2957
|
const preview = input.preview as DynamicPageSurfaceData["preview"];
|
|
2801
2958
|
const openMode = input.open_mode as string | undefined;
|
|
2802
|
-
const app = getApp(appId);
|
|
2803
|
-
if (!app)
|
|
2959
|
+
const app = appId ? getApp(appId) : null;
|
|
2960
|
+
if (!app) {
|
|
2961
|
+
return {
|
|
2962
|
+
content: appId
|
|
2963
|
+
? `App not found: ${appId}`
|
|
2964
|
+
: "app_id is required and no active app exists in this conversation. Call app_create first, or pass app_id explicitly.",
|
|
2965
|
+
isError: true,
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2804
2968
|
|
|
2805
2969
|
// Track conversation association (best-effort — failures must not break open flow).
|
|
2806
2970
|
try {
|