@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
|
@@ -4,13 +4,29 @@
|
|
|
4
4
|
* Explicit request kinds provide a stable contract between producers and
|
|
5
5
|
* notification rendering logic, avoiding implicit inference from incidental
|
|
6
6
|
* fields like `toolName`.
|
|
7
|
+
*
|
|
8
|
+
* Payload shapes are defined as Zod schemas — single source of truth for
|
|
9
|
+
* both runtime validation and TypeScript types.
|
|
10
|
+
* https://zod.dev/?id=basic-usage
|
|
7
11
|
*/
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
|
|
15
|
+
import { nonEmpty } from "./notification-utils.js";
|
|
16
|
+
|
|
17
|
+
// ── Schema primitives ──────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
export const GuardianQuestionRequestKindSchema = z.enum([
|
|
20
|
+
"pending_question",
|
|
21
|
+
"tool_approval",
|
|
22
|
+
"tool_grant_request",
|
|
23
|
+
"access_request",
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
export type GuardianQuestionRequestKind = z.infer<
|
|
27
|
+
typeof GuardianQuestionRequestKindSchema
|
|
28
|
+
>;
|
|
29
|
+
|
|
14
30
|
type GuardianQuestionInstructionMode = "approval" | "answer";
|
|
15
31
|
|
|
16
32
|
interface GuardianRequestKindModeConfig {
|
|
@@ -37,16 +53,82 @@ const REQUEST_KIND_MODE_CONFIG: Record<
|
|
|
37
53
|
},
|
|
38
54
|
};
|
|
39
55
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
56
|
+
// ── Zod schemas for guardian.question payloads ──────────────────────────
|
|
57
|
+
|
|
58
|
+
const GuardianQuestionPayloadBaseSchema = z.object({
|
|
59
|
+
requestId: z.string().min(1),
|
|
60
|
+
requestCode: z.string().min(1),
|
|
61
|
+
questionText: z.string().min(1),
|
|
62
|
+
/** Channel the request originated from. Set by producers but previously
|
|
63
|
+
* invisible to the type system (passed via index signature). */
|
|
64
|
+
sourceChannel: z.string().optional(),
|
|
65
|
+
/** Display name or identifier of the requester. */
|
|
66
|
+
requesterIdentifier: z.string().optional(),
|
|
67
|
+
/** External user ID of the requester (e.g. Slack user ID). */
|
|
68
|
+
requesterExternalUserId: z.string().optional(),
|
|
69
|
+
/** External chat ID of the requester. */
|
|
70
|
+
requesterChatId: z.string().nullable().optional(),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const PendingQuestionPayloadSchema =
|
|
74
|
+
GuardianQuestionPayloadBaseSchema.extend({
|
|
75
|
+
requestKind: z.literal("pending_question"),
|
|
76
|
+
callSessionId: z.string().min(1),
|
|
77
|
+
activeGuardianRequestCount: z.number(),
|
|
78
|
+
toolName: z.string().optional(),
|
|
79
|
+
});
|
|
45
80
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
81
|
+
export const ToolApprovalPayloadSchema =
|
|
82
|
+
GuardianQuestionPayloadBaseSchema.extend({
|
|
83
|
+
requestKind: z.literal("tool_approval"),
|
|
84
|
+
toolName: z.string().min(1),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export const ToolGrantPayloadSchema = GuardianQuestionPayloadBaseSchema.extend({
|
|
88
|
+
requestKind: z.literal("tool_grant_request"),
|
|
89
|
+
toolName: z.string().min(1),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export const AccessRequestGuardianPayloadSchema =
|
|
93
|
+
GuardianQuestionPayloadBaseSchema.extend({
|
|
94
|
+
requestKind: z.literal("access_request"),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export const GuardianQuestionPayloadSchema = z.discriminatedUnion(
|
|
98
|
+
"requestKind",
|
|
99
|
+
[
|
|
100
|
+
PendingQuestionPayloadSchema,
|
|
101
|
+
ToolApprovalPayloadSchema,
|
|
102
|
+
ToolGrantPayloadSchema,
|
|
103
|
+
AccessRequestGuardianPayloadSchema,
|
|
104
|
+
],
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Lenient schema for tool-approval rendering. Requires only `requestKind`
|
|
109
|
+
* (for mode detection) — everything else is optional. Handles partially
|
|
110
|
+
* constructed payloads that don't satisfy the strict discriminated union
|
|
111
|
+
* (e.g. missing `callSessionId` on a `pending_question` with `toolName`).
|
|
112
|
+
*
|
|
113
|
+
* Used by `buildToolApprovalSeedContentBlocks` which must degrade
|
|
114
|
+
* gracefully rather than refuse to render when optional card fields
|
|
115
|
+
* are absent.
|
|
116
|
+
*/
|
|
117
|
+
export const LenientToolApprovalPayloadSchema = z.object({
|
|
118
|
+
requestKind: GuardianQuestionRequestKindSchema,
|
|
119
|
+
requestId: z.string().nullable().optional(),
|
|
120
|
+
requestCode: z.string().nullable().optional(),
|
|
121
|
+
questionText: z.string().nullable().optional(),
|
|
122
|
+
toolName: z.string().nullable().optional(),
|
|
123
|
+
sourceChannel: z.string().nullable().optional(),
|
|
124
|
+
requesterIdentifier: z.string().nullable().optional(),
|
|
125
|
+
requesterExternalUserId: z.string().nullable().optional(),
|
|
126
|
+
requesterChatId: z.string().nullable().optional(),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
export type LenientToolApprovalPayload = z.infer<
|
|
130
|
+
typeof LenientToolApprovalPayloadSchema
|
|
131
|
+
>;
|
|
50
132
|
|
|
51
133
|
interface GuardianRequestModeInput {
|
|
52
134
|
kind: unknown;
|
|
@@ -105,136 +187,39 @@ const MODE_TEXT_CONFIG: Record<
|
|
|
105
187
|
},
|
|
106
188
|
};
|
|
107
189
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
export interface ToolGrantGuardianPayload extends GuardianQuestionPayloadBaseWithDiscriminator {
|
|
125
|
-
requestKind: "tool_grant_request";
|
|
126
|
-
toolName: string;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export interface AccessRequestGuardianPayload extends GuardianQuestionPayloadBaseWithDiscriminator {
|
|
130
|
-
requestKind: "access_request";
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export type GuardianQuestionPayload =
|
|
134
|
-
| PendingQuestionGuardianPayload
|
|
135
|
-
| ToolApprovalGuardianPayload
|
|
136
|
-
| ToolGrantGuardianPayload
|
|
137
|
-
| AccessRequestGuardianPayload;
|
|
190
|
+
// ── Derived TypeScript types ─────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
export type PendingQuestionGuardianPayload = z.infer<
|
|
193
|
+
typeof PendingQuestionPayloadSchema
|
|
194
|
+
>;
|
|
195
|
+
export type ToolApprovalGuardianPayload = z.infer<
|
|
196
|
+
typeof ToolApprovalPayloadSchema
|
|
197
|
+
>;
|
|
198
|
+
export type ToolGrantGuardianPayload = z.infer<typeof ToolGrantPayloadSchema>;
|
|
199
|
+
export type AccessRequestGuardianPayload = z.infer<
|
|
200
|
+
typeof AccessRequestGuardianPayloadSchema
|
|
201
|
+
>;
|
|
202
|
+
export type GuardianQuestionPayload = z.infer<
|
|
203
|
+
typeof GuardianQuestionPayloadSchema
|
|
204
|
+
>;
|
|
138
205
|
|
|
139
206
|
interface GuardianQuestionModeResolution {
|
|
140
207
|
mode: GuardianQuestionInstructionMode;
|
|
141
208
|
requestKind: GuardianQuestionRequestKind | null;
|
|
142
209
|
}
|
|
143
210
|
|
|
144
|
-
|
|
145
|
-
if (typeof value !== "string") return null;
|
|
146
|
-
const trimmed = value.trim();
|
|
147
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function parseGuardianQuestionRequestKind(
|
|
151
|
-
payload: Record<string, unknown>,
|
|
152
|
-
): GuardianQuestionRequestKind | null {
|
|
153
|
-
const raw = nonEmptyString(payload.requestKind);
|
|
154
|
-
if (!raw) return null;
|
|
155
|
-
|
|
156
|
-
switch (raw) {
|
|
157
|
-
case "pending_question":
|
|
158
|
-
case "tool_approval":
|
|
159
|
-
case "tool_grant_request":
|
|
160
|
-
case "access_request":
|
|
161
|
-
return raw;
|
|
162
|
-
default:
|
|
163
|
-
return null;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function parseBasePayload(
|
|
168
|
-
payload: Record<string, unknown>,
|
|
169
|
-
): GuardianQuestionPayloadBase | null {
|
|
170
|
-
const requestId = nonEmptyString(payload.requestId);
|
|
171
|
-
const requestCode = nonEmptyString(payload.requestCode);
|
|
172
|
-
const questionText = nonEmptyString(payload.questionText);
|
|
173
|
-
if (!requestId || !requestCode || !questionText) return null;
|
|
174
|
-
return { requestId, requestCode, questionText };
|
|
175
|
-
}
|
|
211
|
+
// ── Payload parsing ────────────────────────────────────────────────────
|
|
176
212
|
|
|
177
213
|
/**
|
|
178
|
-
* Parse a guardian.question context payload into a strict discriminated union
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* or when requestKind is absent/unknown.
|
|
214
|
+
* Parse a guardian.question context payload into a strict discriminated union
|
|
215
|
+
* using Zod validation. Returns null when the payload is missing required
|
|
216
|
+
* fields or has an unknown/missing requestKind.
|
|
182
217
|
*/
|
|
183
218
|
export function parseGuardianQuestionPayload(
|
|
184
219
|
payload: Record<string, unknown>,
|
|
185
220
|
): GuardianQuestionPayload | null {
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const base = parseBasePayload(payload);
|
|
190
|
-
if (!base) return null;
|
|
191
|
-
|
|
192
|
-
switch (requestKind) {
|
|
193
|
-
case "pending_question": {
|
|
194
|
-
const callSessionId = nonEmptyString(payload.callSessionId);
|
|
195
|
-
const activeGuardianRequestCount =
|
|
196
|
-
typeof payload.activeGuardianRequestCount === "number"
|
|
197
|
-
? payload.activeGuardianRequestCount
|
|
198
|
-
: undefined;
|
|
199
|
-
const toolName = nonEmptyString(payload.toolName);
|
|
200
|
-
if (
|
|
201
|
-
!callSessionId ||
|
|
202
|
-
activeGuardianRequestCount === undefined ||
|
|
203
|
-
Number.isNaN(activeGuardianRequestCount)
|
|
204
|
-
) {
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
207
|
-
const pendingQuestionPayload: PendingQuestionGuardianPayload = {
|
|
208
|
-
requestKind,
|
|
209
|
-
...base,
|
|
210
|
-
callSessionId,
|
|
211
|
-
activeGuardianRequestCount,
|
|
212
|
-
};
|
|
213
|
-
if (toolName) {
|
|
214
|
-
pendingQuestionPayload.toolName = toolName;
|
|
215
|
-
}
|
|
216
|
-
return {
|
|
217
|
-
...pendingQuestionPayload,
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
case "tool_approval":
|
|
221
|
-
case "tool_grant_request": {
|
|
222
|
-
const toolName = nonEmptyString(payload.toolName);
|
|
223
|
-
if (!toolName) return null;
|
|
224
|
-
return {
|
|
225
|
-
requestKind,
|
|
226
|
-
...base,
|
|
227
|
-
toolName,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
case "access_request":
|
|
231
|
-
return {
|
|
232
|
-
requestKind,
|
|
233
|
-
...base,
|
|
234
|
-
};
|
|
235
|
-
default:
|
|
236
|
-
return null;
|
|
237
|
-
}
|
|
221
|
+
const result = GuardianQuestionPayloadSchema.safeParse(payload);
|
|
222
|
+
return result.success ? result.data : null;
|
|
238
223
|
}
|
|
239
224
|
|
|
240
225
|
function resolveGuardianInstructionModeForRequestKind(
|
|
@@ -242,7 +227,7 @@ function resolveGuardianInstructionModeForRequestKind(
|
|
|
242
227
|
toolName?: string | null,
|
|
243
228
|
): GuardianQuestionInstructionMode {
|
|
244
229
|
const config = REQUEST_KIND_MODE_CONFIG[requestKind];
|
|
245
|
-
const normalizedToolName =
|
|
230
|
+
const normalizedToolName = nonEmpty(toolName ?? undefined) ?? null;
|
|
246
231
|
if (normalizedToolName && config.modeWhenToolNamePresent) {
|
|
247
232
|
return config.modeWhenToolNamePresent;
|
|
248
233
|
}
|
|
@@ -257,16 +242,14 @@ export function resolveGuardianInstructionModeFromFields(
|
|
|
257
242
|
requestKind: GuardianQuestionRequestKind;
|
|
258
243
|
mode: GuardianQuestionInstructionMode;
|
|
259
244
|
} | null {
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
});
|
|
263
|
-
if (!requestKind) return null;
|
|
245
|
+
const parsed = GuardianQuestionRequestKindSchema.safeParse(requestKindValue);
|
|
246
|
+
if (!parsed.success) return null;
|
|
264
247
|
|
|
265
248
|
return {
|
|
266
|
-
requestKind,
|
|
249
|
+
requestKind: parsed.data,
|
|
267
250
|
mode: resolveGuardianInstructionModeForRequestKind(
|
|
268
|
-
|
|
269
|
-
|
|
251
|
+
parsed.data,
|
|
252
|
+
typeof toolNameValue === "string" ? toolNameValue : null,
|
|
270
253
|
),
|
|
271
254
|
};
|
|
272
255
|
}
|
|
@@ -283,6 +266,23 @@ export function resolveGuardianInstructionModeForRequest(
|
|
|
283
266
|
return modeResolution.mode;
|
|
284
267
|
}
|
|
285
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Resolve instruction mode directly from a typed guardian question payload.
|
|
271
|
+
* Avoids re-parsing when the caller already holds a validated payload.
|
|
272
|
+
*/
|
|
273
|
+
export function resolveGuardianInstructionModeFromPayload(
|
|
274
|
+
payload: GuardianQuestionPayload,
|
|
275
|
+
): GuardianQuestionModeResolution {
|
|
276
|
+
const toolName = "toolName" in payload ? payload.toolName : undefined;
|
|
277
|
+
return {
|
|
278
|
+
mode: resolveGuardianInstructionModeForRequestKind(
|
|
279
|
+
payload.requestKind,
|
|
280
|
+
toolName ?? null,
|
|
281
|
+
),
|
|
282
|
+
requestKind: payload.requestKind,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
286
|
function getModeTextConfig(
|
|
287
287
|
mode: GuardianQuestionInstructionMode,
|
|
288
288
|
): GuardianModeTextConfig {
|
|
@@ -420,28 +420,18 @@ export function stripConflictingGuardianRequestInstructions(
|
|
|
420
420
|
}
|
|
421
421
|
|
|
422
422
|
/**
|
|
423
|
-
* Resolve guardian reply instruction mode from
|
|
423
|
+
* Resolve guardian reply instruction mode from a raw context payload.
|
|
424
424
|
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
* to "approval" mode.
|
|
425
|
+
* Attempts Zod-based parsing first. When that fails, falls back to
|
|
426
|
+
* field-level requestKind resolution. Defaults to "approval" mode
|
|
427
|
+
* when requestKind is missing or unknown.
|
|
429
428
|
*/
|
|
430
429
|
export function resolveGuardianQuestionInstructionMode(
|
|
431
430
|
payload: Record<string, unknown>,
|
|
432
431
|
): GuardianQuestionModeResolution {
|
|
433
432
|
const parsed = parseGuardianQuestionPayload(payload);
|
|
434
433
|
if (parsed) {
|
|
435
|
-
|
|
436
|
-
"toolName" in parsed ? parsed.toolName : null,
|
|
437
|
-
);
|
|
438
|
-
return {
|
|
439
|
-
mode: resolveGuardianInstructionModeForRequestKind(
|
|
440
|
-
parsed.requestKind,
|
|
441
|
-
parsedToolName,
|
|
442
|
-
),
|
|
443
|
-
requestKind: parsed.requestKind,
|
|
444
|
-
};
|
|
434
|
+
return resolveGuardianInstructionModeFromPayload(parsed);
|
|
445
435
|
}
|
|
446
436
|
|
|
447
437
|
const requestKindResolution = resolveGuardianInstructionModeFromFields(
|
|
@@ -22,7 +22,7 @@ import { getConversation } from "../memory/conversation-crud.js";
|
|
|
22
22
|
import { isBackgroundConversationType } from "../memory/conversation-types.js";
|
|
23
23
|
import { getLogger } from "../util/logger.js";
|
|
24
24
|
import { isConversationSeedSane } from "./conversation-seed-composer.js";
|
|
25
|
-
import { readPayloadString } from "./
|
|
25
|
+
import { readPayloadString } from "./notification-utils.js";
|
|
26
26
|
import type { NotificationSignal } from "./signal.js";
|
|
27
27
|
import type { NotificationDecision, RenderedChannelCopy } from "./types.js";
|
|
28
28
|
|
|
@@ -58,10 +58,8 @@ export async function writeHomeFeedItemForSignal(
|
|
|
58
58
|
decision: NotificationDecision,
|
|
59
59
|
fallbackConversationId?: string,
|
|
60
60
|
): Promise<FeedItem | null> {
|
|
61
|
-
const { mirror, sourceConversationId } =
|
|
62
|
-
signal,
|
|
63
|
-
fallbackConversationId,
|
|
64
|
-
);
|
|
61
|
+
const { mirror, sourceConversationId, sourceScheduleJobId } =
|
|
62
|
+
resolveHomeFeedMirror(signal, fallbackConversationId);
|
|
65
63
|
if (!mirror) return null;
|
|
66
64
|
|
|
67
65
|
const renderedCopy =
|
|
@@ -106,13 +104,28 @@ export async function writeHomeFeedItemForSignal(
|
|
|
106
104
|
|
|
107
105
|
const category = deriveCategory(signal);
|
|
108
106
|
const panelKind = deriveDetailPanelKind(signal);
|
|
109
|
-
|
|
107
|
+
|
|
108
|
+
const baseMetadata =
|
|
110
109
|
signal.contextPayload &&
|
|
111
110
|
typeof signal.contextPayload === "object" &&
|
|
112
111
|
!Array.isArray(signal.contextPayload)
|
|
113
|
-
? (signal.contextPayload as Record<string, unknown>)
|
|
112
|
+
? { ...(signal.contextPayload as Record<string, unknown>) }
|
|
114
113
|
: undefined;
|
|
115
114
|
|
|
115
|
+
// Link scheduled-run notifications back to their schedule. `notify`-mode
|
|
116
|
+
// jobs put `scheduleId` directly in the context payload; `execute`-mode (and
|
|
117
|
+
// other agent-backed) jobs only tag their conversation, so fall back to the
|
|
118
|
+
// source conversation's `scheduleJobId`.
|
|
119
|
+
const scheduleId =
|
|
120
|
+
readPayloadString(signal.contextPayload, "scheduleId") ??
|
|
121
|
+
sourceScheduleJobId ??
|
|
122
|
+
undefined;
|
|
123
|
+
|
|
124
|
+
const metadata =
|
|
125
|
+
scheduleId !== undefined
|
|
126
|
+
? { ...(baseMetadata ?? {}), scheduleId }
|
|
127
|
+
: baseMetadata;
|
|
128
|
+
|
|
116
129
|
const item: FeedItem = {
|
|
117
130
|
id: `notif:${signal.signalId}`,
|
|
118
131
|
type: "notification",
|
|
@@ -202,8 +215,11 @@ function resolveHomeFeedMirror(
|
|
|
202
215
|
): {
|
|
203
216
|
mirror: boolean;
|
|
204
217
|
sourceConversationId?: string;
|
|
218
|
+
sourceScheduleJobId?: string;
|
|
205
219
|
} {
|
|
206
|
-
let sourceRow:
|
|
220
|
+
let sourceRow:
|
|
221
|
+
| { conversationType?: string; scheduleJobId?: string | null }
|
|
222
|
+
| null = null;
|
|
207
223
|
if (signal.sourceContextId) {
|
|
208
224
|
try {
|
|
209
225
|
sourceRow = getConversation(signal.sourceContextId) ?? null;
|
|
@@ -219,15 +235,16 @@ function resolveHomeFeedMirror(
|
|
|
219
235
|
const sourceConversationId = sourceRow
|
|
220
236
|
? signal.sourceContextId
|
|
221
237
|
: fallbackConversationId;
|
|
238
|
+
const sourceScheduleJobId = sourceRow?.scheduleJobId ?? undefined;
|
|
222
239
|
|
|
223
240
|
if (signal.sourceChannel === "assistant_tool") {
|
|
224
|
-
return { mirror: true, sourceConversationId };
|
|
241
|
+
return { mirror: true, sourceConversationId, sourceScheduleJobId };
|
|
225
242
|
}
|
|
226
243
|
if (signal.attentionHints.isAsyncBackground) {
|
|
227
|
-
return { mirror: true, sourceConversationId };
|
|
244
|
+
return { mirror: true, sourceConversationId, sourceScheduleJobId };
|
|
228
245
|
}
|
|
229
246
|
if (isBackgroundConversationType(sourceRow?.conversationType)) {
|
|
230
|
-
return { mirror: true, sourceConversationId };
|
|
247
|
+
return { mirror: true, sourceConversationId, sourceScheduleJobId };
|
|
231
248
|
}
|
|
232
249
|
return { mirror: false };
|
|
233
250
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared leaf utilities for the notification pipeline.
|
|
3
|
+
*
|
|
4
|
+
* This module has no intra-pipeline imports — it depends only on Node
|
|
5
|
+
* stdlib — so any notification module can import it without creating
|
|
6
|
+
* circular dependencies.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ── String helpers ──────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
/** Return `value` trimmed, or `undefined` when blank/nullish. */
|
|
12
|
+
export function nonEmpty(value: string | null | undefined): string | undefined {
|
|
13
|
+
if (typeof value !== "string") return undefined;
|
|
14
|
+
const trimmed = value.trim();
|
|
15
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Safely read a string property from an unknown-typed payload object.
|
|
20
|
+
* Returns `undefined` when the payload is falsy, not an object, or the
|
|
21
|
+
* key does not hold a string value.
|
|
22
|
+
*/
|
|
23
|
+
export function readPayloadString(
|
|
24
|
+
payload: unknown,
|
|
25
|
+
key: string,
|
|
26
|
+
): string | undefined {
|
|
27
|
+
if (!payload || typeof payload !== "object") return undefined;
|
|
28
|
+
const value = (payload as Record<string, unknown>)[key];
|
|
29
|
+
return typeof value === "string" ? value : undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ── Sanitization ────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
const IDENTITY_FIELD_MAX_LENGTH = 120;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Sanitize an untrusted identity field for inclusion in notification copy.
|
|
38
|
+
*
|
|
39
|
+
* - Strips control characters (U+0000–U+001F, U+007F–U+009F) and newlines.
|
|
40
|
+
* - Clamps to 120 characters.
|
|
41
|
+
*/
|
|
42
|
+
export function sanitizeIdentityField(value: string): string {
|
|
43
|
+
const stripped = value.replace(/[\x00-\x1f\x7f-\x9f\r\n]+/g, " ").trim();
|
|
44
|
+
const clamped =
|
|
45
|
+
stripped.length > IDENTITY_FIELD_MAX_LENGTH
|
|
46
|
+
? stripped.slice(0, IDENTITY_FIELD_MAX_LENGTH) + "…"
|
|
47
|
+
: stripped;
|
|
48
|
+
return clamped;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const MESSAGE_PREVIEW_MAX_LENGTH = 200;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Sanitize an untrusted message preview for inclusion in notification copy.
|
|
55
|
+
*
|
|
56
|
+
* Same as {@link sanitizeIdentityField} but uses a higher length limit
|
|
57
|
+
* (200 chars) suited for message previews.
|
|
58
|
+
*/
|
|
59
|
+
export function sanitizeMessagePreview(value: string): string {
|
|
60
|
+
const stripped = value.replace(/[\x00-\x1f\x7f-\x9f\r\n]+/g, " ").trim();
|
|
61
|
+
const clamped =
|
|
62
|
+
stripped.length > MESSAGE_PREVIEW_MAX_LENGTH
|
|
63
|
+
? stripped.slice(0, MESSAGE_PREVIEW_MAX_LENGTH) + "…"
|
|
64
|
+
: stripped;
|
|
65
|
+
return clamped;
|
|
66
|
+
}
|
|
@@ -159,6 +159,12 @@ export interface AccessRequestContextPayload {
|
|
|
159
159
|
previousMemberStatus: string | null;
|
|
160
160
|
/** Preview of the requester's original message (first ~200 chars). */
|
|
161
161
|
messagePreview: string | null;
|
|
162
|
+
/** Slack-specific: user is from an external workspace (Slack Connect). */
|
|
163
|
+
isStranger?: boolean;
|
|
164
|
+
/** Slack-specific: user is a guest / restricted account. */
|
|
165
|
+
isRestricted?: boolean;
|
|
166
|
+
/** Slack message timestamp (e.g. "1234567890.123456") for permalink construction. */
|
|
167
|
+
messageTs?: string;
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
export interface GuardianChannelActivationPayload {
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic helpers for building guardian-facing tool-approval copy.
|
|
3
|
+
*
|
|
4
|
+
* Produces Surface card seed content blocks for `tool_approval`,
|
|
5
|
+
* `tool_grant_request`, and voice/call `pending_question` (with `toolName`)
|
|
6
|
+
* guardian questions, enabling Approve/Reject buttons in the Vellum in-app
|
|
7
|
+
* channel (web/macOS/iOS).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { buildApprovalCardBlocks } from "./approval-card-builder.js";
|
|
11
|
+
import {
|
|
12
|
+
buildGuardianRequestCodeInstruction,
|
|
13
|
+
type GuardianQuestionPayload,
|
|
14
|
+
type LenientToolApprovalPayload,
|
|
15
|
+
LenientToolApprovalPayloadSchema,
|
|
16
|
+
parseGuardianQuestionPayload,
|
|
17
|
+
resolveGuardianInstructionModeFromFields,
|
|
18
|
+
resolveGuardianInstructionModeFromPayload,
|
|
19
|
+
} from "./guardian-question-mode.js";
|
|
20
|
+
import { nonEmpty, sanitizeIdentityField } from "./notification-utils.js";
|
|
21
|
+
|
|
22
|
+
// ── Approval detection ──────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Determine whether a typed guardian.question payload represents a tool
|
|
26
|
+
* approval (as opposed to a free-text answer). Uses the canonical mode
|
|
27
|
+
* resolver in `guardian-question-mode.ts` — the single source of truth
|
|
28
|
+
* for the requestKind → instructionMode mapping.
|
|
29
|
+
*/
|
|
30
|
+
function isToolApprovalPayload(payload: GuardianQuestionPayload): boolean {
|
|
31
|
+
const { mode } = resolveGuardianInstructionModeFromPayload(payload);
|
|
32
|
+
return mode === "approval" && payload.requestKind !== "access_request";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Lenient approval detection for partially-constructed payloads that don't
|
|
37
|
+
* satisfy the strict discriminated union schema.
|
|
38
|
+
*/
|
|
39
|
+
function isLenientToolApproval(payload: LenientToolApprovalPayload): boolean {
|
|
40
|
+
const modeResolution = resolveGuardianInstructionModeFromFields(
|
|
41
|
+
payload.requestKind,
|
|
42
|
+
payload.toolName,
|
|
43
|
+
);
|
|
44
|
+
if (!modeResolution) return false;
|
|
45
|
+
return (
|
|
46
|
+
modeResolution.mode === "approval" &&
|
|
47
|
+
payload.requestKind !== "access_request"
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Seed content blocks (Surface-based rendering) ───────────────────────────
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build structured content blocks for a tool approval/grant notification seed
|
|
55
|
+
* message. Returns `null` when the payload does not represent a tool approval.
|
|
56
|
+
*
|
|
57
|
+
* Accepts both strict `GuardianQuestionPayload` (Zod-validated) and raw
|
|
58
|
+
* `Record<string, unknown>` payloads. For raw payloads, attempts strict
|
|
59
|
+
* parsing first, then falls back to lenient field extraction so cards
|
|
60
|
+
* still render when optional fields are absent.
|
|
61
|
+
*/
|
|
62
|
+
export function buildToolApprovalSeedContentBlocks(
|
|
63
|
+
payload: GuardianQuestionPayload,
|
|
64
|
+
): unknown[] | null;
|
|
65
|
+
export function buildToolApprovalSeedContentBlocks(
|
|
66
|
+
payload: Record<string, unknown>,
|
|
67
|
+
): unknown[] | null;
|
|
68
|
+
export function buildToolApprovalSeedContentBlocks(
|
|
69
|
+
payload: GuardianQuestionPayload | Record<string, unknown>,
|
|
70
|
+
): unknown[] | null {
|
|
71
|
+
// Try strict Zod parsing first (full discriminated union).
|
|
72
|
+
const strict = parseGuardianQuestionPayload(
|
|
73
|
+
payload as Record<string, unknown>,
|
|
74
|
+
);
|
|
75
|
+
if (strict) {
|
|
76
|
+
if (!isToolApprovalPayload(strict)) return null;
|
|
77
|
+
return buildCardFromFields(strict);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Fall back to lenient parsing — requires only `requestKind`.
|
|
81
|
+
const lenient = LenientToolApprovalPayloadSchema.safeParse(payload);
|
|
82
|
+
if (!lenient.success) return null;
|
|
83
|
+
if (!isLenientToolApproval(lenient.data)) return null;
|
|
84
|
+
return buildCardFromFields(lenient.data);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Card construction (shared between strict and lenient paths) ─────────────
|
|
88
|
+
|
|
89
|
+
function buildCardFromFields(
|
|
90
|
+
p: GuardianQuestionPayload | LenientToolApprovalPayload,
|
|
91
|
+
): unknown[] {
|
|
92
|
+
const toolName =
|
|
93
|
+
("toolName" in p ? nonEmpty(p.toolName) : undefined) ?? "unknown tool";
|
|
94
|
+
const rawRequester = nonEmpty(p.requesterIdentifier);
|
|
95
|
+
const requester = rawRequester
|
|
96
|
+
? sanitizeIdentityField(rawRequester)
|
|
97
|
+
: "Someone";
|
|
98
|
+
|
|
99
|
+
const isGrant = p.requestKind === "tool_grant_request";
|
|
100
|
+
|
|
101
|
+
const metadata: Array<{ label: string; value: string }> = [];
|
|
102
|
+
metadata.push({ label: "Tool", value: toolName });
|
|
103
|
+
const sourceChannel = nonEmpty(p.sourceChannel);
|
|
104
|
+
if (sourceChannel) {
|
|
105
|
+
metadata.push({ label: "Source", value: sourceChannel });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const body = p.questionText
|
|
109
|
+
? `> ${p.questionText}`
|
|
110
|
+
: "No additional context available.";
|
|
111
|
+
|
|
112
|
+
// Fallback text with request-code instructions for older clients.
|
|
113
|
+
const baseFallback =
|
|
114
|
+
p.questionText ?? `${requester} is requesting approval to use ${toolName}`;
|
|
115
|
+
let fallbackText = baseFallback;
|
|
116
|
+
const requestCode = nonEmpty(p.requestCode);
|
|
117
|
+
if (requestCode) {
|
|
118
|
+
const modeResolution = resolveGuardianInstructionModeFromFields(
|
|
119
|
+
p.requestKind,
|
|
120
|
+
"toolName" in p ? (p.toolName ?? undefined) : undefined,
|
|
121
|
+
);
|
|
122
|
+
const mode = modeResolution?.mode ?? "approval";
|
|
123
|
+
const instruction = buildGuardianRequestCodeInstruction(
|
|
124
|
+
requestCode.trim().toUpperCase(),
|
|
125
|
+
mode,
|
|
126
|
+
);
|
|
127
|
+
fallbackText = `${baseFallback}\n\n${instruction}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return buildApprovalCardBlocks({
|
|
131
|
+
surfaceIdPrefix: "tool-approval",
|
|
132
|
+
cardTitle: isGrant ? "Tool Grant Request" : "Tool Approval",
|
|
133
|
+
requesterName: requester,
|
|
134
|
+
subtitle: isGrant
|
|
135
|
+
? "Requesting permission to use this tool"
|
|
136
|
+
: "Requesting approval to run this tool",
|
|
137
|
+
body,
|
|
138
|
+
metadata,
|
|
139
|
+
requestId: nonEmpty(p.requestId),
|
|
140
|
+
fallbackText,
|
|
141
|
+
});
|
|
142
|
+
}
|