@vellumai/assistant 0.11.2 → 0.11.3-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/ARCHITECTURE.md +58 -3
- package/docs/trusted-contact-access.md +9 -5
- package/docs/vellum-doctor.md +195 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
- package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +19 -2
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
- package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
- package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
- package/node_modules/@vellumai/environments/src/index.ts +17 -6
- package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
- package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +4 -0
- package/node_modules/@vellumai/gateway-client/src/verification-session-contract.ts +91 -6
- package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
- package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
- package/node_modules/@vellumai/ipc-server-utils/src/index.ts +2 -0
- package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +15 -1
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +17 -2
- package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +16 -2
- package/openapi.yaml +589 -30
- package/package.json +1 -1
- package/src/__tests__/agent-image-optimize.test.ts +4 -4
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +113 -66
- package/src/__tests__/agent-loop-override-profile.test.ts +38 -48
- package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +166 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
- package/src/__tests__/answered-question-persistence.test.ts +240 -0
- package/src/__tests__/anthropic-provider.test.ts +165 -30
- package/src/__tests__/app-control-flow.test.ts +9 -8
- package/src/__tests__/assistant-event-hub.test.ts +48 -0
- package/src/__tests__/attachments-store-heic-normalize.test.ts +16 -12
- package/src/__tests__/attachments-store.test.ts +130 -68
- package/src/__tests__/attachments.test.ts +36 -36
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/call-start-guardian-guard.test.ts +18 -4
- package/src/__tests__/canned-reply-release.test.ts +86 -0
- package/src/__tests__/channel-guardian.test.ts +59 -29
- package/src/__tests__/channel-readiness-telegram-remote.test.ts +154 -0
- package/src/__tests__/channel-setup-panel-ack.test.ts +6 -5
- package/src/__tests__/client-os-metadata-persistence.test.ts +103 -4
- package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
- package/src/__tests__/consult-deadline.test.ts +65 -0
- package/src/__tests__/conversation-agent-loop.test.ts +438 -2
- package/src/__tests__/conversation-disk-view-integration.test.ts +5 -1
- package/src/__tests__/conversation-disk-view.test.ts +4 -4
- package/src/__tests__/conversation-error.test.ts +22 -3
- package/src/__tests__/conversation-fork-crud.test.ts +5 -1
- package/src/__tests__/conversation-fork-referential.test.ts +352 -0
- package/src/__tests__/conversation-fork-retrospective.test.ts +5 -1
- package/src/__tests__/conversation-group-tools.test.ts +32 -2
- package/src/__tests__/conversation-inference-profile-route.test.ts +27 -1
- package/src/__tests__/conversation-initial-prompt.test.ts +51 -22
- package/src/__tests__/conversation-key-store-bootstrap-cleanup.test.ts +119 -0
- package/src/__tests__/conversation-key-store-origin.test.ts +105 -0
- package/src/__tests__/conversation-list-recency-ordering.test.ts +104 -0
- package/src/__tests__/conversation-origin-at-creation.test.ts +153 -0
- package/src/__tests__/conversation-origin-channel-filter.test.ts +95 -0
- package/src/__tests__/conversation-placement-promotion.test.ts +172 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +123 -0
- package/src/__tests__/conversation-queue.test.ts +433 -1
- package/src/__tests__/conversation-retry-route.test.ts +3 -1
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +64 -1
- package/src/__tests__/conversation-store-ephemeral.test.ts +4 -0
- package/src/__tests__/conversation-store.test.ts +26 -10
- package/src/__tests__/conversation-summarize-route.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +15 -6
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -5
- package/src/__tests__/conversation-surfaces-app-control.test.ts +12 -11
- package/src/__tests__/conversation-surfaces-app-open.test.ts +11 -5
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +5 -4
- package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +967 -0
- package/src/__tests__/conversation-surfaces-persisted-info.test.ts +312 -0
- package/src/__tests__/conversation-surfaces-queued-emit.test.ts +6 -5
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -8
- package/src/__tests__/conversation-surfaces-standalone.test.ts +21 -11
- package/src/__tests__/conversation-surfaces-state-update.test.ts +7 -11
- package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -6
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +11 -6
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +9 -6
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +70 -7
- package/src/__tests__/conversation-wait-for-idle.test.ts +6 -11
- package/src/__tests__/credential-prompt-route.test.ts +57 -7
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +89 -28
- package/src/__tests__/custom-group-listing-visibility.test.ts +1 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -1
- package/src/__tests__/discord-access-request-privacy.test.ts +366 -0
- package/src/__tests__/discord-requester-notice-privacy.test.ts +87 -0
- package/src/__tests__/disk-pressure-guard.test.ts +55 -55
- package/src/__tests__/disk-pressure-routes.test.ts +10 -10
- package/src/__tests__/disk-pressure-tools.test.ts +2 -2
- package/src/__tests__/disk-usage.test.ts +35 -20
- package/src/__tests__/document-sync-tags.test.ts +379 -0
- package/src/__tests__/document-update-default-surface.test.ts +28 -0
- package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
- package/src/__tests__/emit-signal-routing-intent.test.ts +3 -4
- package/src/__tests__/ensure-conversation-exists.test.ts +8 -5
- package/src/__tests__/events-dev-bypass-actor.test.ts +183 -1
- package/src/__tests__/evict-conversations-for-reload.test.ts +90 -0
- package/src/__tests__/file-list-tool.test.ts +12 -10
- package/src/__tests__/file-ops-service.test.ts +151 -44
- package/src/__tests__/file-read-container-boundary.test.ts +137 -0
- package/src/__tests__/file-read-tool.test.ts +21 -9
- package/src/__tests__/file-url-path-guard.test.ts +83 -0
- package/src/__tests__/filesystem-tools.test.ts +82 -66
- package/src/__tests__/guardian-outbound-http.test.ts +8 -8
- package/src/__tests__/helpers/gateway-verification-sessions-stub.ts +14 -1
- package/src/__tests__/helpers/hub-clients.ts +42 -0
- package/src/__tests__/helpers/mock-conversation.ts +46 -0
- package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +61 -57
- package/src/__tests__/history-repair.test.ts +112 -12
- package/src/__tests__/host-bash-proxy.test.ts +10 -5
- package/src/__tests__/host-bash-routes.test.ts +67 -0
- package/src/__tests__/host-browser-proxy.test.ts +2 -2
- package/src/__tests__/host-cu-proxy.test.ts +3 -7
- package/src/__tests__/host-file-proxy.test.ts +3 -7
- package/src/__tests__/host-file-read-tool.test.ts +5 -1
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +6 -4
- package/src/__tests__/http-conversation-lineage.test.ts +2 -5
- package/src/__tests__/http-user-message-parity.test.ts +44 -0
- package/src/__tests__/identity-routes.test.ts +13 -13
- package/src/__tests__/image-conversion.test.ts +29 -26
- package/src/__tests__/image-source-path-reinject.test.ts +2 -2
- package/src/__tests__/inference-profile-session-handler.test.ts +95 -0
- package/src/__tests__/inference-profile-session-ipc.test.ts +25 -0
- package/src/__tests__/introduction-card-resolver.test.ts +6 -1
- package/src/__tests__/list-messages-attachments.test.ts +56 -32
- package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
- package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
- package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
- package/src/__tests__/list-messages-page-latest.test.ts +61 -61
- package/src/__tests__/list-messages-provider-error.test.ts +6 -6
- package/src/__tests__/list-messages-queued.test.ts +49 -6
- package/src/__tests__/list-messages-system-card.test.ts +4 -4
- package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
- package/src/__tests__/llm-resolver-override-or-default.test.ts +141 -0
- package/src/__tests__/llm-usage-store.test.ts +55 -0
- package/src/__tests__/manual-token-reconciliation.test.ts +45 -0
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +133 -0
- package/src/__tests__/media-stream-server-integration.test.ts +36 -0
- package/src/__tests__/migration-export-http.test.ts +1 -1
- package/src/__tests__/migration-import-commit-http.test.ts +1 -1
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
- package/src/__tests__/migration-validate-http.test.ts +1 -1
- package/src/__tests__/mtime-cache.test.ts +65 -3
- package/src/__tests__/notification-platform-adapter.test.ts +104 -0
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
- package/src/__tests__/oauth-provider-profiles.test.ts +4 -2
- package/src/__tests__/oauth-provider-seed-logos.test.ts +65 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +17 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/openai-responses-prompt-cache.test.ts +5 -2
- package/src/__tests__/path-policy.test.ts +164 -1
- package/src/__tests__/persist-media-references.test.ts +2 -2
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
- package/src/__tests__/persist-unsendable-image.test.ts +23 -23
- package/src/__tests__/persona-resolver.test.ts +154 -0
- package/src/__tests__/plugin-api-webhook-url.test.ts +170 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -0
- package/src/__tests__/processing-flag-persist-failure.test.ts +15 -10
- package/src/__tests__/profile-availability-incomplete.test.ts +113 -0
- package/src/__tests__/profiler-routes.test.ts +13 -13
- package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +593 -0
- package/src/__tests__/proxy-approval-callback.test.ts +3 -3
- package/src/__tests__/queued-message-cancel-and-steer.test.ts +189 -0
- package/src/__tests__/queued-message-steer-actor-scoping.test.ts +164 -0
- package/src/__tests__/resolve-trust-class.test.ts +20 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -1
- package/src/__tests__/run-due-schedules.test.ts +174 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +166 -0
- package/src/__tests__/schedule-routes.test.ts +730 -8
- package/src/__tests__/schedule-store.test.ts +540 -0
- package/src/__tests__/schedule-tools.test.ts +103 -0
- package/src/__tests__/scheduler-wake.test.ts +5 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +29 -1
- package/src/__tests__/secure-keys.test.ts +91 -0
- package/src/__tests__/server-history-render.test.ts +97 -7
- package/src/__tests__/size-guard.test.ts +8 -8
- package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
- package/src/__tests__/skill-tool-factory.test.ts +247 -0
- package/src/__tests__/skills-install-staging.test.ts +15 -6
- package/src/__tests__/slack-edit-ordering-characterization.test.ts +103 -0
- package/src/__tests__/slack-inbound-verification.test.ts +4 -1
- package/src/__tests__/slack-mention-binding-invariance.test.ts +103 -0
- package/src/__tests__/slack-mention-provider-leak-audit.test.ts +135 -0
- package/src/__tests__/starter-task-flow.test.ts +5 -4
- package/src/__tests__/steer-on-enqueue-question.test.ts +3 -2
- package/src/__tests__/stt-language-catalog-parity.test.ts +38 -0
- package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
- package/src/__tests__/subagent-cron-run-attribution.test.ts +301 -0
- package/src/__tests__/subagent-disposal.test.ts +10 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
- package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
- package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
- package/src/__tests__/subagent-manager-notify.test.ts +148 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -1
- package/src/__tests__/subagent-persistence.test.ts +141 -0
- package/src/__tests__/subagent-role-registry.test.ts +172 -63
- package/src/__tests__/subagent-role-resolution.test.ts +88 -0
- package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
- package/src/__tests__/subagent-terminal-message.test.ts +88 -1
- package/src/__tests__/subagent-tool-gate-mode.test.ts +225 -10
- package/src/__tests__/subagent-tools.test.ts +1901 -108
- package/src/__tests__/surface-completion-compaction-boundary.test.ts +124 -0
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +298 -0
- package/src/__tests__/system-prompt.test.ts +82 -7
- package/src/__tests__/tool-grant-request-escalation.test.ts +40 -1
- package/src/__tests__/tool-result-spool.test.ts +74 -18
- package/src/__tests__/tool-side-effects-documents.test.ts +174 -0
- package/src/__tests__/tool-side-effects-hook-failures.test.ts +129 -0
- package/src/__tests__/tools-audio-read.test.ts +10 -10
- package/src/__tests__/tools-get-route.test.ts +85 -12
- package/src/__tests__/tools-list-cli.test.ts +171 -0
- package/src/__tests__/top-level-renderer.test.ts +54 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +68 -1
- package/src/__tests__/trusted-contact-multichannel.test.ts +4 -1
- package/src/__tests__/turn-tail-chain.test.ts +135 -0
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +6 -4
- package/src/__tests__/ui-visual-surface.test.ts +5 -4
- package/src/__tests__/ui-work-result-surface.test.ts +5 -4
- package/src/__tests__/unified-turn-context-visible-app.test.ts +7 -8
- package/src/__tests__/usage-routes.test.ts +62 -0
- package/src/__tests__/verification-outbound-delivery.test.ts +176 -0
- package/src/__tests__/visible-app-context.test.ts +20 -4
- package/src/__tests__/web-search-history.test.ts +96 -0
- package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +6 -6
- package/src/__tests__/workspace-migration-140-repair-seed-pinned-memory-v3-live.test.ts +330 -0
- package/src/acp/failure-error.ts +3 -5
- package/src/agent/attachments.ts +26 -24
- package/src/agent/history-repair/history-repair.ts +27 -26
- package/src/agent/image-optimize.ts +6 -6
- package/src/agent/loop.ts +40 -22
- package/src/agent/message-types.ts +3 -3
- package/src/api/constants/document-tools.ts +55 -0
- package/src/api/events/question-answered.ts +53 -0
- package/src/api/events/tool-result.ts +7 -0
- package/src/api/index.ts +13 -0
- package/src/api/responses/conversation-message.ts +11 -0
- package/src/api/surfaces-oauth-connect.test.ts +96 -0
- package/src/api/surfaces.ts +36 -8
- package/src/approvals/guardian-channel-delivery.ts +83 -0
- package/src/approvals/guardian-expiry-notifier.ts +13 -11
- package/src/approvals/guardian-request-resolvers.ts +222 -135
- package/src/calls/__tests__/voice-session-bridge.test.ts +50 -0
- package/src/calls/media-stream-server.ts +9 -1
- package/src/calls/voice-session-bridge.ts +38 -0
- package/src/channels/__tests__/gateway-verification-sessions.test.ts +3 -3
- package/src/channels/config.ts +7 -2
- package/src/channels/gateway-verification-sessions.ts +7 -2
- package/src/channels/types.ts +7 -1
- package/src/cli/commands/__tests__/cache.test.ts +7 -4
- package/src/cli/commands/__tests__/cli-test-harness.ts +82 -0
- package/src/cli/commands/__tests__/conversations-wake.test.ts +51 -6
- package/src/cli/commands/__tests__/inference-models.test.ts +14 -0
- package/src/cli/commands/__tests__/inference-profiles.test.ts +44 -1
- package/src/cli/commands/__tests__/inference-providers.test.ts +7 -0
- package/src/cli/commands/__tests__/plugins.test.ts +692 -0
- package/src/cli/commands/__tests__/schedules.test.ts +136 -37
- package/src/cli/commands/conversations.ts +7 -3
- package/src/cli/commands/credentials.help.ts +7 -0
- package/src/cli/commands/inference-profiles.ts +9 -3
- package/src/cli/commands/inference.help.ts +16 -2
- package/src/cli/commands/mcp.help.ts +4 -2
- package/src/cli/commands/memory/memory-retrospective.ts +18 -0
- package/src/cli/commands/notifications.ts +3 -6
- package/src/cli/commands/oauth/connect-surface-guidance.test.ts +16 -0
- package/src/cli/commands/oauth/connect-surface-guidance.ts +25 -7
- package/src/cli/commands/oauth/connect.test.ts +26 -1
- package/src/cli/commands/oauth/connect.ts +4 -1
- package/src/cli/commands/oauth/index.help.ts +3 -1
- package/src/cli/commands/oauth/providers.ts +3 -0
- package/src/cli/commands/plugins.help.ts +25 -2
- package/src/cli/commands/plugins.ts +289 -10
- package/src/cli/commands/schedules.help.ts +14 -12
- package/src/cli/commands/schedules.ts +19 -5
- package/src/cli/commands/tools.help.ts +4 -2
- package/src/cli/commands/tools.ts +38 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +1 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +79 -0
- package/src/cli/lib/__tests__/install-from-platform.test.ts +47 -1
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +86 -7
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +122 -0
- package/src/cli/lib/install-from-github.ts +61 -0
- package/src/cli/lib/install-from-platform.ts +13 -2
- package/src/cli/lib/plugin-surfaces.ts +124 -4
- package/src/cli/lib/upgrade-plugin.ts +18 -0
- package/src/cli.ts +1 -1
- package/src/config/__tests__/deployment-context-defaults.test.ts +73 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +40 -0
- package/src/config/__tests__/plugin-updates-schema.test.ts +43 -0
- package/src/config/__tests__/profile-tool-support.test.ts +131 -0
- package/src/config/bundled-skills/AGENTS.md +1 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +3 -3
- package/src/config/bundled-skills/schedule/SKILL.md +5 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -0
- package/src/config/bundled-skills/subagent/SKILL.md +53 -22
- package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
- package/src/config/bundled-skills/visualize/SKILL.md +1 -1
- package/src/config/default-profile-catalog.ts +6 -2
- package/src/config/feature-flag-registry.json +25 -17
- package/src/config/llm-resolver.ts +45 -15
- package/src/config/loader.ts +70 -1
- package/src/config/profile-tool-support.ts +110 -0
- package/src/config/sanitize-for-transfer.ts +11 -0
- package/src/config/schema.ts +4 -0
- package/src/config/schemas/__tests__/stt.test.ts +20 -5
- package/src/config/schemas/channels.ts +13 -0
- package/src/config/schemas/memory-retrospective.ts +25 -0
- package/src/config/schemas/plugin-updates.ts +55 -0
- package/src/config/schemas/services.ts +8 -4
- package/src/config/schemas/stt.ts +17 -5
- package/src/config/schemas/timeouts.ts +1 -1
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +16 -0
- package/src/contacts/guardian-delivery-reader.ts +29 -2
- package/src/context/compactor.ts +4 -4
- package/src/context/post-turn-tool-result-truncation.ts +32 -16
- package/src/context/strip-injections.ts +42 -0
- package/src/context/tool-result-spool.ts +32 -18
- package/src/credential-execution/ces-runtime.ts +3 -0
- package/src/credential-execution/client.ts +22 -1
- package/src/credential-execution/process-manager.test.ts +99 -0
- package/src/credential-execution/process-manager.ts +62 -16
- package/src/daemon/__tests__/abort-null-controller.test.ts +141 -5
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +8 -12
- package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
- package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +2 -4
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +20 -29
- package/src/daemon/channel-ui-capability.ts +4 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +117 -61
- package/src/daemon/conversation-agent-loop.ts +318 -148
- package/src/daemon/conversation-attachments.ts +1 -1
- package/src/daemon/conversation-error.ts +8 -1
- package/src/daemon/conversation-initial-prompt.ts +16 -38
- package/src/daemon/conversation-lifecycle.ts +154 -32
- package/src/daemon/conversation-messaging.ts +106 -43
- package/src/daemon/conversation-process.ts +131 -33
- package/src/daemon/conversation-queue-manager.ts +10 -0
- package/src/daemon/conversation-runtime-assembly.ts +9 -4
- package/src/daemon/conversation-store.ts +52 -3
- package/src/daemon/conversation-surface-state.ts +9 -0
- package/src/daemon/conversation-surfaces.ts +403 -210
- package/src/daemon/conversation-tool-setup.ts +56 -7
- package/src/daemon/conversation-turn-finalize.ts +134 -89
- package/src/daemon/conversation.ts +111 -27
- package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
- package/src/daemon/disk-pressure-guard.ts +3 -3
- package/src/daemon/doordash-steps.ts +5 -5
- package/src/daemon/external-plugins-bootstrap.ts +7 -1
- package/src/daemon/handlers/config-channels.ts +48 -5
- package/src/daemon/handlers/conversation-history.ts +1 -1
- package/src/daemon/handlers/conversations.ts +62 -17
- package/src/daemon/handlers/shared.ts +12 -0
- package/src/daemon/handlers/skills.ts +11 -1
- package/src/daemon/identity-helpers.ts +38 -10
- package/src/daemon/install-assistant-command.ts +322 -0
- package/src/daemon/lifecycle.ts +14 -3
- package/src/daemon/message-types/conversations.ts +6 -7
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/persist-media-references.ts +23 -14
- package/src/daemon/process-message.ts +6 -6
- package/src/daemon/tool-setup-types.ts +19 -122
- package/src/daemon/tool-side-effects.ts +17 -94
- package/src/daemon/trust-context-types.ts +39 -0
- package/src/daemon/trust-context.ts +14 -6
- package/src/daemon/turn-tail-chain.ts +73 -0
- package/src/daemon/web-search-history.ts +47 -33
- package/src/heartbeat/heartbeat-service.ts +6 -6
- package/src/ipc/__tests__/socket-path.test.ts +21 -0
- package/src/ipc/assistant-server.ts +17 -10
- package/src/ipc/cli-client.ts +10 -1
- package/src/ipc/routes/__tests__/documents-sync-ipc-routes.test.ts +56 -0
- package/src/ipc/routes/documents-sync-ipc-routes.ts +42 -0
- package/src/ipc/socket-cleanup.ts +9 -7
- package/src/ipc/socket-path.ts +4 -96
- package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
- package/src/live-voice/__tests__/live-voice-attach-image.test.ts +209 -0
- package/src/live-voice/__tests__/live-voice-connection.test.ts +20 -0
- package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
- package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +265 -0
- package/src/live-voice/__tests__/live-voice-stt.test.ts +6 -1
- package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
- package/src/live-voice/__tests__/protocol.test.ts +52 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +3 -0
- package/src/live-voice/live-voice-archive.ts +13 -13
- package/src/live-voice/live-voice-connection.ts +7 -1
- package/src/live-voice/live-voice-photo.ts +169 -0
- package/src/live-voice/live-voice-session.ts +108 -4
- package/src/live-voice/protocol.ts +89 -1
- package/src/mcp/client.ts +10 -0
- package/src/messaging/providers/__tests__/callback-routing.test.ts +5 -1
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +66 -4
- package/src/messaging/providers/callback-routing.ts +1 -0
- package/src/messaging/providers/discord/api.ts +312 -0
- package/src/messaging/providers/discord/dm-delivery.test.ts +204 -0
- package/src/messaging/providers/discord/render.test.ts +204 -0
- package/src/messaging/providers/discord/render.ts +195 -0
- package/src/messaging/providers/discord/send.test.ts +179 -0
- package/src/messaging/providers/discord/send.ts +212 -0
- package/src/messaging/providers/discord/transport.ts +81 -0
- package/src/messaging/providers/index.ts +3 -1
- package/src/messaging/providers/retry-policy.test.ts +247 -0
- package/src/messaging/providers/retry-policy.ts +206 -0
- package/src/messaging/providers/slack/mention-source.test.ts +285 -0
- package/src/messaging/providers/slack/mention-source.ts +287 -0
- package/src/messaging/providers/telegram-bot/api.ts +47 -130
- package/src/messaging/providers/whatsapp/api.ts +47 -136
- package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
- package/src/monitoring/__tests__/plugin-auto-update.test.ts +332 -0
- package/src/monitoring/db-integrity-sample.ts +6 -1
- package/src/monitoring/plugin-auto-update.ts +359 -0
- package/src/monitoring/resource-sampler.ts +21 -5
- package/src/monitoring/worker.ts +13 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +375 -2
- package/src/notifications/__tests__/broadcaster.test.ts +37 -1
- package/src/notifications/__tests__/copy-composer.test.ts +165 -0
- package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +205 -0
- package/src/notifications/__tests__/notification-utils.test.ts +249 -0
- package/src/notifications/adapters/platform.ts +31 -2
- package/src/notifications/assistant-reply-producer.ts +79 -32
- package/src/notifications/broadcaster.ts +30 -10
- package/src/notifications/copy-composer.ts +69 -0
- package/src/notifications/emit-signal.ts +82 -15
- package/src/notifications/events-store.ts +10 -2
- package/src/notifications/home-feed-side-effect.ts +6 -3
- package/src/notifications/notification-utils.ts +173 -5
- package/src/notifications/signal.ts +12 -0
- package/src/oauth/AGENTS.md +4 -2
- package/src/oauth/byo-connection.ts +6 -1
- package/src/oauth/credential-token-resolver.ts +3 -10
- package/src/oauth/manual-token-connection.ts +29 -64
- package/src/oauth/manual-token-providers.ts +62 -0
- package/src/oauth/provider-serializer.ts +13 -0
- package/src/oauth/seed-providers.ts +62 -11
- package/src/onboarding/onboarding-events-store.ts +91 -1
- package/src/permissions/question-prompter.test.ts +4 -0
- package/src/permissions/question-prompter.ts +21 -4
- package/src/persistence/__tests__/conversation-lineage.test.ts +248 -0
- package/src/persistence/attachments-store.ts +15 -15
- package/src/persistence/conversation-attention-store.ts +30 -10
- package/src/persistence/conversation-bootstrap.ts +17 -0
- package/src/persistence/conversation-crud.ts +363 -97
- package/src/persistence/conversation-group-migration.ts +1 -1
- package/src/persistence/conversation-key-store.ts +38 -10
- package/src/persistence/conversation-lineage.ts +226 -0
- package/src/persistence/conversation-plugin-facade.ts +28 -3
- package/src/persistence/conversation-queries.ts +207 -33
- package/src/persistence/conversation-types.ts +107 -1
- package/src/persistence/jobs-store.ts +32 -8
- package/src/persistence/llm-usage-store.ts +22 -0
- package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +168 -0
- package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +76 -0
- package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
- package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
- package/src/persistence/migrations/363-backfill-schedule-inference-profile.test.ts +229 -0
- package/src/persistence/migrations/363-backfill-schedule-inference-profile.ts +127 -0
- package/src/persistence/migrations/364-add-schedule-source-key.test.ts +120 -0
- package/src/persistence/migrations/364-add-schedule-source-key.ts +43 -0
- package/src/persistence/migrations/365-add-conversation-fork-strategy.ts +27 -0
- package/src/persistence/schema/conversations.ts +32 -0
- package/src/persistence/schema/infrastructure.ts +3 -0
- package/src/persistence/steps.ts +30 -0
- package/src/persistence/subagent-store.ts +148 -6
- package/src/platform/managed-speech.test.ts +20 -0
- package/src/platform/managed-speech.ts +9 -0
- package/src/plugin-api/index.ts +8 -0
- package/src/plugin-api/webhook-url.ts +151 -0
- package/src/plugins/__tests__/installed-plugin-dirs.test.ts +82 -0
- package/src/plugins/__tests__/source-fingerprint.test.ts +5 -1
- package/src/plugins/collect-source-versions.ts +4 -27
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
- package/src/plugins/defaults/exploration-drift/package.json +1 -1
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
- package/src/plugins/defaults/image-recovery/recover.ts +67 -57
- package/src/plugins/defaults/index.ts +1 -1
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +64 -18
- package/src/plugins/defaults/memory/__tests__/jobs-worker-outcome-contract.test.ts +242 -0
- package/src/plugins/defaults/memory/__tests__/jobs-worker-retrospective-sweep.test.ts +24 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +48 -3
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +511 -19
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +39 -3
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +493 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +5 -4
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -1
- package/src/plugins/defaults/memory/job-handlers.ts +87 -5
- package/src/plugins/defaults/memory/jobs-worker.ts +60 -6
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +11 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +29 -0
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +342 -60
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +23 -3
- package/src/plugins/defaults/memory/substrate/consolidation-lock.ts +9 -4
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +7 -0
- package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
- package/src/plugins/defaults/memory/v2/router.ts +8 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +1 -2
- package/src/plugins/external-plugin-loader.ts +17 -8
- package/src/plugins/installed-plugin-dirs.ts +101 -0
- package/src/plugins/mtime-cache.ts +34 -22
- package/src/plugins/types.ts +69 -8
- package/src/prompts/persona-resolver.ts +6 -2
- package/src/prompts/system-prompt.ts +72 -17
- package/src/providers/__tests__/inference.test.ts +40 -0
- package/src/providers/__tests__/preflight-resolved-config.test.ts +58 -1
- package/src/providers/__tests__/retry-callsite.test.ts +67 -0
- package/src/providers/__tests__/vellum-connection-routing.test.ts +42 -11
- package/src/providers/anthropic/client.ts +112 -95
- package/src/providers/connection-resolution.ts +35 -1
- package/src/providers/gemini/client.ts +5 -5
- package/src/providers/inference/adapter-factory.ts +16 -10
- package/src/providers/inference/auth.ts +16 -0
- package/src/providers/inference/connection-availability.ts +164 -12
- package/src/providers/inference/connections.ts +25 -1
- package/src/providers/media-resolve.ts +21 -11
- package/src/providers/model-catalog.ts +15 -0
- package/src/providers/openai/chat-completions-provider.ts +4 -4
- package/src/providers/openai/responses-provider.ts +12 -25
- package/src/providers/registry.ts +9 -4
- package/src/providers/retry.ts +25 -0
- package/src/providers/server-tool-pairing.ts +134 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +187 -6
- package/src/providers/speech-to-text/deepgram.ts +1 -0
- package/src/providers/speech-to-text/resolve.ts +80 -10
- package/src/providers/speech-to-text/vellum-managed.ts +7 -1
- package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +3 -1
- package/src/providers/types.ts +26 -6
- package/src/providers/vellum-model-routing.ts +6 -7
- package/src/runtime/AGENTS.md +2 -0
- package/src/runtime/__tests__/agent-wake.test.ts +16 -16
- package/src/runtime/__tests__/background-job-runner.test.ts +0 -144
- package/src/runtime/__tests__/desktop-presence.test.ts +162 -0
- package/src/runtime/agent-wake.ts +63 -24
- package/src/runtime/assistant-event-hub.ts +87 -12
- package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -0
- package/src/runtime/auth/__tests__/scopes.test.ts +20 -0
- package/src/runtime/auth/same-actor.ts +56 -8
- package/src/runtime/auth/scopes.ts +12 -0
- package/src/runtime/auth/types.ts +2 -0
- package/src/runtime/background-job-runner.ts +5 -27
- package/src/runtime/channel-readiness-service.ts +59 -3
- package/src/runtime/channel-readiness-types.ts +18 -0
- package/src/runtime/desktop-presence.ts +69 -0
- package/src/runtime/http-server.ts +12 -0
- package/src/runtime/local-actor-identity.ts +11 -9
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/client-routes.test.ts +186 -14
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +547 -1
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +162 -7
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +14 -10
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +340 -3
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +28 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +173 -1
- package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +20 -0
- package/src/runtime/routes/__tests__/skill-history-route.test.ts +116 -0
- package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +60 -2
- package/src/runtime/routes/archive-utils.ts +16 -12
- package/src/runtime/routes/attachment-routes.ts +13 -6
- package/src/runtime/routes/canned-reply-release.ts +54 -0
- package/src/runtime/routes/channel-verification-routes.ts +2 -27
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +5 -0
- package/src/runtime/routes/client-routes.ts +114 -2
- package/src/runtime/routes/conversation-cli-routes.ts +10 -12
- package/src/runtime/routes/conversation-list-routes.ts +59 -10
- package/src/runtime/routes/conversation-management-routes.ts +47 -18
- package/src/runtime/routes/conversation-query-routes.ts +32 -53
- package/src/runtime/routes/conversation-routes.ts +360 -293
- package/src/runtime/routes/credential-prompt-routes.ts +23 -2
- package/src/runtime/routes/debug-routes.ts +4 -1
- package/src/runtime/routes/default-provider-routes.ts +7 -11
- package/src/runtime/routes/documents-routes.ts +44 -1
- package/src/runtime/routes/events-routes.ts +28 -1
- package/src/runtime/routes/host-app-control-routes.ts +2 -0
- package/src/runtime/routes/host-bash-routes.ts +2 -0
- package/src/runtime/routes/host-browser-routes.ts +2 -0
- package/src/runtime/routes/host-cu-routes.ts +2 -0
- package/src/runtime/routes/host-file-routes.ts +2 -0
- package/src/runtime/routes/host-transfer-routes.ts +4 -0
- package/src/runtime/routes/identity-routes.ts +6 -6
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +14 -0
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +14 -11
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -17
- package/src/runtime/routes/inference-profile-availability-guard.ts +171 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +26 -2
- package/src/runtime/routes/inference-profile-session-routes.ts +2 -1
- package/src/runtime/routes/inference-profiles-routes.ts +152 -73
- package/src/runtime/routes/inference-provider-connection-routes.ts +0 -16
- package/src/runtime/routes/llm-call-sites-routes.ts +13 -0
- package/src/runtime/routes/log-export-routes.ts +1 -1
- package/src/runtime/routes/notification-routes.ts +0 -14
- package/src/runtime/routes/oauth-providers.ts +1 -0
- package/src/runtime/routes/plugins-routes.ts +106 -1
- package/src/runtime/routes/profiler-routes.ts +4 -2
- package/src/runtime/routes/question-routes.ts +28 -10
- package/src/runtime/routes/retrospective-routes.ts +9 -3
- package/src/runtime/routes/schedule-routes.ts +210 -12
- package/src/runtime/routes/secret-routes.ts +40 -23
- package/src/runtime/routes/settings-routes.ts +41 -13
- package/src/runtime/routes/skills-routes.ts +79 -0
- package/src/runtime/routes/ui-snapshot-routes.ts +1 -0
- package/src/runtime/routes/wake-conversation-routes.ts +23 -0
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
- package/src/runtime/services/conversation-serializer.ts +24 -9
- package/src/runtime/sync/documents-sidecar-publish.test.ts +101 -0
- package/src/runtime/sync/resource-sync-events.ts +79 -1
- package/src/runtime/sync/worker-daemon-notify.test.ts +34 -0
- package/src/runtime/sync/worker-daemon-notify.ts +44 -6
- package/src/runtime/verification-outbound-actions.ts +250 -222
- package/src/runtime/verification-templates.ts +41 -30
- package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +614 -0
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1066 -0
- package/src/schedule/inference-profile.ts +64 -3
- package/src/schedule/plugin-schedule-declarations.ts +518 -0
- package/src/schedule/plugin-schedule-reconciler.ts +605 -0
- package/src/schedule/plugin-schedules-gate.ts +23 -0
- package/src/schedule/schedule-store.ts +520 -26
- package/src/schedule/scheduler.ts +38 -0
- package/src/security/credential-backend.ts +51 -4
- package/src/security/encrypted-store.ts +64 -42
- package/src/security/secure-keys.ts +55 -7
- package/src/skills/catalog-install.ts +20 -7
- package/src/skills/inline-command-runner.ts +2 -7
- package/src/skills/skill-history.test.ts +269 -0
- package/src/skills/skill-history.ts +250 -0
- package/src/skills/skillssh-registry.ts +1 -1
- package/src/stt/daemon-batch-transcriber.ts +8 -6
- package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
- package/src/subagent/consult-prompt.ts +3 -1
- package/src/subagent/consult-transcript.ts +1 -1
- package/src/subagent/manager.ts +304 -44
- package/src/subagent/progress-events.ts +35 -0
- package/src/subagent/role-resolution.ts +88 -0
- package/src/subagent/types.ts +302 -67
- package/src/subagent/validate-allowlists.ts +3 -4
- package/src/telegram/__tests__/webhook-health.test.ts +73 -1
- package/src/telegram/webhook-health.ts +101 -3
- package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
- package/src/telemetry/activation-funnel.ts +6 -1
- package/src/telemetry/live-voice-funnel.ts +75 -0
- package/src/telemetry/telemetry-event-sources.ts +6 -0
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire.generated.ts +2 -0
- package/src/telemetry/types.ts +23 -4
- package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
- package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
- package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
- package/src/tools/ask-question/ask-question-tool.ts +56 -2
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/calls/call-start.ts +8 -5
- package/src/tools/conversation-groups/move_to_group.ts +10 -12
- package/src/tools/document/document-tool.ts +11 -4
- package/src/tools/execution-timeout.ts +9 -2
- package/src/tools/executor.ts +7 -6
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +7 -13
- package/src/tools/filesystem/search.ts +23 -21
- package/src/tools/filesystem/write.ts +4 -1
- package/src/tools/host-filesystem/edit.ts +7 -2
- package/src/tools/host-filesystem/read.ts +21 -8
- package/src/tools/host-filesystem/write.ts +10 -2
- package/src/tools/host-terminal/host-shell.ts +2 -2
- package/src/tools/mcp/mcp-tool-factory.ts +25 -1
- package/src/tools/permission-checker.ts +8 -3
- package/src/tools/schedule/delete.ts +13 -1
- package/src/tools/schedule/list.ts +14 -3
- package/src/tools/schedule/update.ts +56 -33
- package/src/tools/shared/filesystem/audio-read.ts +6 -4
- package/src/tools/shared/filesystem/file-ops-service.ts +176 -116
- package/src/tools/shared/filesystem/image-read.ts +12 -12
- package/src/tools/shared/filesystem/path-policy.ts +70 -5
- package/src/tools/shared/filesystem/size-guard.ts +6 -6
- package/src/tools/shared/input-misuse.ts +72 -0
- package/src/tools/skills/scaffold-managed.ts +82 -0
- package/src/tools/skills/skill-tool-factory.ts +14 -1
- package/src/tools/subagent/consult-deadline.ts +12 -7
- package/src/tools/subagent/message.ts +3 -1
- package/src/tools/subagent/read.ts +66 -4
- package/src/tools/subagent/spawn.ts +500 -78
- package/src/tools/tool-approval-handler.ts +45 -4
- package/src/tools/types.ts +22 -3
- package/src/tools/ui-surface/surface-shape-docs.ts +1 -1
- package/src/tools/ui-surface/visual-validation.ts +2 -0
- package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
- package/src/usage/subagent-attribution.ts +108 -0
- package/src/util/__tests__/sqlite-retry.test.ts +112 -0
- package/src/util/abort-reasons.ts +16 -0
- package/src/util/ansi.ts +44 -0
- package/src/util/disk-usage.ts +17 -13
- package/src/util/image-conversion.ts +20 -13
- package/src/util/schedule-source-key.ts +23 -0
- package/src/util/worker-process.ts +7 -1
- package/src/workspace/git-service.ts +33 -9
- package/src/workspace/migrations/140-repair-seed-pinned-memory-v3-live.ts +260 -0
- package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +111 -0
- package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +126 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/top-level-renderer.ts +15 -2
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -357
- package/src/daemon/install-symlink.ts +0 -207
- package/src/notifications/deferred-emit.ts +0 -147
|
@@ -2,13 +2,16 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
import type { AssistantEvent } from "../../api/index.js";
|
|
4
4
|
import { validateInferenceProfileKey } from "../../config/inference-profile-validation.js";
|
|
5
|
-
import { resolveDefaultProfileKey } from "../../config/llm-resolver.js";
|
|
6
5
|
import { getConfig } from "../../config/loader.js";
|
|
6
|
+
import { profileSupportsTools } from "../../config/profile-tool-support.js";
|
|
7
7
|
import { findConversation } from "../../daemon/conversation-registry.js";
|
|
8
|
+
import { getMessages } from "../../persistence/conversation-crud.js";
|
|
8
9
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
countRecentSimilarSpawns,
|
|
11
|
+
normalizeSpawnObjective,
|
|
12
|
+
type RecentSimilarSpawns,
|
|
13
|
+
type SimilarSpawnTally,
|
|
14
|
+
} from "../../persistence/subagent-store.js";
|
|
12
15
|
import type { ContentBlock, Message } from "../../providers/types.js";
|
|
13
16
|
import { buildAdvisorContext } from "../../subagent/consult-context.js";
|
|
14
17
|
import {
|
|
@@ -20,7 +23,15 @@ import {
|
|
|
20
23
|
getSubagentManager,
|
|
21
24
|
SubagentAbortedError,
|
|
22
25
|
} from "../../subagent/index.js";
|
|
23
|
-
import
|
|
26
|
+
import {
|
|
27
|
+
type ResolvedSubagentRole,
|
|
28
|
+
resolveSubagentRole,
|
|
29
|
+
} from "../../subagent/role-resolution.js";
|
|
30
|
+
import {
|
|
31
|
+
SUBAGENT_OUTPUT_CONTRACTS,
|
|
32
|
+
type SubagentOutputContract,
|
|
33
|
+
type SubagentRole,
|
|
34
|
+
} from "../../subagent/types.js";
|
|
24
35
|
import { getLogger } from "../../util/logger.js";
|
|
25
36
|
import {
|
|
26
37
|
invalidToolInputResult,
|
|
@@ -33,11 +44,12 @@ const log = getLogger("subagent-spawn");
|
|
|
33
44
|
|
|
34
45
|
/**
|
|
35
46
|
* Idle ceiling on a single advisor consult: abort only after this much time
|
|
36
|
-
* passes with NO
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* time-to-first-token
|
|
47
|
+
* passes with NO sign of forward progress, meaning neither a streamed token
|
|
48
|
+
* (thinking or text) nor tool activity. A reasoning advisor profile streams its
|
|
49
|
+
* reasoning while it works, so a fixed wall-clock ceiling would kill it
|
|
50
|
+
* mid-thought; an idle window instead fires only when the consult is genuinely
|
|
51
|
+
* stalled (or never starts). Generous enough to also span time-to-first-token
|
|
52
|
+
* over a large inherited transcript.
|
|
41
53
|
*/
|
|
42
54
|
const ADVISOR_IDLE_TIMEOUT_MS = 60_000;
|
|
43
55
|
|
|
@@ -49,6 +61,62 @@ const ADVISOR_IDLE_TIMEOUT_MS = 60_000;
|
|
|
49
61
|
*/
|
|
50
62
|
const ADVISOR_MAX_TIMEOUT_MS = 300_000;
|
|
51
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Tool calls a single advisor consult may make before it is stopped and asked
|
|
66
|
+
* to answer with what it has.
|
|
67
|
+
*
|
|
68
|
+
* The consult BLOCKS the user-facing turn, and every tool call it makes
|
|
69
|
+
* re-arms the idle window, so a reading advisor is otherwise bounded only by
|
|
70
|
+
* the absolute backstop: five minutes of silence in the chat. A consult is
|
|
71
|
+
* meant to check a decisive fact or two, not to survey a codebase, so a handful
|
|
72
|
+
* of reads is the whole budget; past it the guidance written so far is worth
|
|
73
|
+
* more to the caller than the reads it was still queuing.
|
|
74
|
+
*/
|
|
75
|
+
const ADVISOR_MAX_TOOL_CALLS = 8;
|
|
76
|
+
|
|
77
|
+
/** How far back the repeat-spawn guard looks for near-identical runs. */
|
|
78
|
+
const LOOP_GUARD_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Near-identical runs one conversation may complete inside the window before
|
|
82
|
+
* the next spawn is held for confirmation. A couple of repeats is normal work;
|
|
83
|
+
* a fourth completed run of one objective in a single conversation is a loop.
|
|
84
|
+
* Runs that failed, were aborted, or were interrupted never count, so a retry
|
|
85
|
+
* after a bad run is not what trips this.
|
|
86
|
+
*/
|
|
87
|
+
const LOOP_GUARD_CONVERSATION_LIMIT = 3;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Near-identical runs the whole assistant may complete inside the window,
|
|
91
|
+
* counted the same way. Higher than the per-conversation limit so the same
|
|
92
|
+
* audit asked for in a few separate chats still goes through, while a standing
|
|
93
|
+
* re-run of one objective does not.
|
|
94
|
+
*/
|
|
95
|
+
const LOOP_GUARD_ASSISTANT_LIMIT = 10;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Near-identical runs one conversation may have IN FLIGHT before the next
|
|
99
|
+
* spawn is held. Lower than the completed limits because nothing has come back
|
|
100
|
+
* yet: a burst of copies fired before any of them finishes is the runaway shape
|
|
101
|
+
* the guard exists for, and the completed counts are blind to it for as long as
|
|
102
|
+
* the burst lasts.
|
|
103
|
+
*/
|
|
104
|
+
const LOOP_GUARD_IN_FLIGHT_CONVERSATION_LIMIT = 2;
|
|
105
|
+
|
|
106
|
+
/** The same ceiling assistant-wide, scaled like the completed limits. */
|
|
107
|
+
const LOOP_GUARD_IN_FLIGHT_ASSISTANT_LIMIT = 4;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The tier a `verdict` spawn runs on when it names no `inference_profile`.
|
|
111
|
+
*
|
|
112
|
+
* Checking a criterion against evidence that already exists is mechanical:
|
|
113
|
+
* find the file, read the value, say PASS or FAIL. A premium model buys
|
|
114
|
+
* nothing there, and completion checks are frequent enough that paying
|
|
115
|
+
* investigation rates for them dominates delegated spend, so the contract that
|
|
116
|
+
* makes a spawn a check also picks the tier the check is worth.
|
|
117
|
+
*/
|
|
118
|
+
const VERDICT_PROFILE_KEY = "cost-optimized";
|
|
119
|
+
|
|
52
120
|
/**
|
|
53
121
|
* Model-input schema, `safeParse`d at the top of {@link executeSubagentSpawn}.
|
|
54
122
|
* Same in-tool pattern and TOOLS.json drift guard as the other bundled-skill
|
|
@@ -56,15 +124,16 @@ const ADVISOR_MAX_TIMEOUT_MS = 300_000;
|
|
|
56
124
|
* framework.
|
|
57
125
|
*
|
|
58
126
|
* `fork` / `send_result_to_user` (deliberate `=== true` / `!== false`
|
|
59
|
-
* coercions) and `role` (
|
|
60
|
-
*
|
|
61
|
-
* UNDECLARED — loose passthrough.
|
|
127
|
+
* coercions) and `role` (any string resolves, see `resolveSubagentRole`) are
|
|
128
|
+
* deliberately UNDECLARED: loose passthrough.
|
|
62
129
|
*/
|
|
63
130
|
export const subagentSpawnInputSchema = z.looseObject({
|
|
64
131
|
label: nullAsOmitted(z.string()),
|
|
65
132
|
objective: nullAsOmitted(z.string()),
|
|
66
133
|
context: nullAsOmitted(z.string()),
|
|
67
134
|
inference_profile: z.string().optional(),
|
|
135
|
+
confirm_repeat: nullAsOmitted(z.boolean()),
|
|
136
|
+
output_contract: nullAsOmitted(z.enum(SUBAGENT_OUTPUT_CONTRACTS)),
|
|
68
137
|
});
|
|
69
138
|
|
|
70
139
|
export async function executeSubagentSpawn(
|
|
@@ -81,8 +150,13 @@ export async function executeSubagentSpawn(
|
|
|
81
150
|
const objective = parsed.objective;
|
|
82
151
|
const extraContext = parsed.context;
|
|
83
152
|
const fork = input.fork === true;
|
|
84
|
-
const
|
|
153
|
+
const requestedRole =
|
|
154
|
+
typeof input.role === "string" && input.role.trim().length > 0
|
|
155
|
+
? input.role
|
|
156
|
+
: undefined;
|
|
157
|
+
const resolvedRole = resolveSubagentRole(requestedRole);
|
|
85
158
|
const inferenceProfile = parsed.inference_profile;
|
|
159
|
+
const outputContract = parsed.output_contract;
|
|
86
160
|
|
|
87
161
|
// For fork mode, sendResultToUser defaults to false unless explicitly set to true.
|
|
88
162
|
// For regular mode, sendResultToUser defaults to true (existing behavior).
|
|
@@ -97,6 +171,11 @@ export async function executeSubagentSpawn(
|
|
|
97
171
|
};
|
|
98
172
|
}
|
|
99
173
|
|
|
174
|
+
const contractError = outputContractError(outputContract, resolvedRole.role);
|
|
175
|
+
if (contractError) {
|
|
176
|
+
return { content: contractError, isError: true };
|
|
177
|
+
}
|
|
178
|
+
|
|
100
179
|
let requestedOverrideProfile: string | undefined;
|
|
101
180
|
let forceOverrideProfile = false;
|
|
102
181
|
if (inferenceProfile !== undefined) {
|
|
@@ -122,10 +201,10 @@ export async function executeSubagentSpawn(
|
|
|
122
201
|
};
|
|
123
202
|
}
|
|
124
203
|
|
|
125
|
-
// ── Advisor role: synchronous,
|
|
204
|
+
// ── Advisor role: synchronous, read-only, stronger-model consult ──
|
|
126
205
|
// Branch before the fire-and-forget path: the advisor blocks on the run and
|
|
127
206
|
// returns its guidance as the tool result in the same turn.
|
|
128
|
-
if (role === "advisor") {
|
|
207
|
+
if (resolvedRole.role === "advisor") {
|
|
129
208
|
return runAdvisorConsult({
|
|
130
209
|
context,
|
|
131
210
|
label,
|
|
@@ -135,6 +214,21 @@ export async function executeSubagentSpawn(
|
|
|
135
214
|
});
|
|
136
215
|
}
|
|
137
216
|
|
|
217
|
+
// ── Repeat-spawn guard ───────────────────────────────────────────
|
|
218
|
+
// Re-running an objective that already completed several times in the last
|
|
219
|
+
// day buys the same answer twice, so the guard hands the repetition back to
|
|
220
|
+
// the caller with what it has already cost. It never blocks: `confirm_repeat`
|
|
221
|
+
// always spawns, and the advisor consult returned above is never guarded.
|
|
222
|
+
if (parsed.confirm_repeat !== true) {
|
|
223
|
+
const guardResult = repeatSpawnGuardResult(
|
|
224
|
+
context.conversationId,
|
|
225
|
+
objective,
|
|
226
|
+
);
|
|
227
|
+
if (guardResult) {
|
|
228
|
+
return guardResult;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
138
232
|
// ── Fork mode: resolve parent context ────────────────────────────
|
|
139
233
|
let forkFields:
|
|
140
234
|
| {
|
|
@@ -165,43 +259,52 @@ export async function executeSubagentSpawn(
|
|
|
165
259
|
};
|
|
166
260
|
}
|
|
167
261
|
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
262
|
+
// A subagent resolves the `subagentSpawn` call site's own default profile.
|
|
263
|
+
// A profile pinned on a conversation is a choice about that conversation,
|
|
264
|
+
// not about the work it delegates, and carrying it into a child would apply
|
|
265
|
+
// the pinned model's price and its tool-calling reliability to every spawn
|
|
266
|
+
// the turn makes.
|
|
173
267
|
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
// call site's own winning default (a `llm.callSites.<callSite>` pin or the
|
|
182
|
-
// catalog intent, e.g. `cost-optimized`) for a heartbeat/background
|
|
183
|
-
// invoker (`resolveDefaultProfileKey` runs the same winner selection as
|
|
184
|
-
// dispatch, minus a per-turn override).
|
|
268
|
+
// Landing on the call site's own profile means passing NO override: the
|
|
269
|
+
// child already runs its loop under `callSite: "subagentSpawn"`, so the
|
|
270
|
+
// resolver picks that profile on its own. Passing it explicitly would select
|
|
271
|
+
// the same model but register as an override, and `resolveUsageAttribution`
|
|
272
|
+
// classifies an override winner as `conversation`, which would file every
|
|
273
|
+
// subagent's spend under a pin the caller never set and break the call-site
|
|
274
|
+
// breakdown this telemetry exists for.
|
|
185
275
|
//
|
|
186
|
-
// An explicit `
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
// calls outside an agent-loop turn.
|
|
276
|
+
// An explicit `inference_profile` argument keeps `forceOverrideProfile` and
|
|
277
|
+
// wins outright, unless the catalog states it cannot call tools, in which
|
|
278
|
+
// case it falls back to the same default with a note on the result.
|
|
279
|
+
const config = getConfig();
|
|
280
|
+
const llm = config.llm;
|
|
281
|
+
|
|
282
|
+
let profileNote: string | undefined;
|
|
194
283
|
let inheritedOverrideProfile = requestedOverrideProfile;
|
|
195
284
|
if (inheritedOverrideProfile === undefined) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
285
|
+
if (outputContract === "verdict") {
|
|
286
|
+
// A verdict is a check rather than an investigation, so it takes the
|
|
287
|
+
// cheap tier. The tier is this tool's preset, not a caller's choice, and
|
|
288
|
+
// an override outranks a call site's own profile under single-winner
|
|
289
|
+
// resolution, so the preset is only applied when the user pinned
|
|
290
|
+
// nothing: with an explicit `llm.callSites.subagentSpawn` profile the
|
|
291
|
+
// field is left unset and that pin wins. An `inference_profile` argument
|
|
292
|
+
// beats both and never enters this branch at all.
|
|
293
|
+
inheritedOverrideProfile =
|
|
294
|
+
llm.callSites?.subagentSpawn?.profile == null
|
|
295
|
+
? VERDICT_PROFILE_KEY
|
|
296
|
+
: undefined;
|
|
297
|
+
}
|
|
298
|
+
// Every other spawn leaves this unset, which is what lands it on the
|
|
299
|
+
// subagentSpawn default rather than anything the parent turn was pinned to.
|
|
300
|
+
} else if (profileSupportsTools(inheritedOverrideProfile, config) === false) {
|
|
301
|
+
profileNote = `requested profile "${inheritedOverrideProfile}" is not verified for tool calling; ran on the default profile instead.`;
|
|
302
|
+
inheritedOverrideProfile = undefined;
|
|
303
|
+
forceOverrideProfile = false;
|
|
203
304
|
}
|
|
204
305
|
|
|
306
|
+
const roleNote = resolvedRoleNote(resolvedRole);
|
|
307
|
+
|
|
205
308
|
try {
|
|
206
309
|
const subagentId = await manager.spawn(
|
|
207
310
|
{
|
|
@@ -210,13 +313,28 @@ export async function executeSubagentSpawn(
|
|
|
210
313
|
objective,
|
|
211
314
|
context: extraContext,
|
|
212
315
|
sendResultToUser,
|
|
213
|
-
//
|
|
214
|
-
// role
|
|
215
|
-
|
|
316
|
+
// The resolved type is passed for every spawn, fork included: a fork
|
|
317
|
+
// that named no role resolves to `builder`, which imposes no
|
|
318
|
+
// allowlist, so it keeps the parent's tool surface, which is what the
|
|
319
|
+
// system prompt it inherits describes. The advisor is special-cased
|
|
320
|
+
// earlier via runAdvisorConsult, not here.
|
|
321
|
+
role: resolvedRole.role,
|
|
322
|
+
...(resolvedRole.personaText
|
|
323
|
+
? { persona: resolvedRole.personaText }
|
|
324
|
+
: {}),
|
|
325
|
+
...(outputContract ? { outputContract } : {}),
|
|
326
|
+
// Declare the spawn mode so delegated LLM spend is separable in
|
|
327
|
+
// telemetry: every variety shares `llm_call_site = "subagentSpawn"`,
|
|
328
|
+
// and a fork's inherited transcript costs very differently from a
|
|
329
|
+
// fresh objective-only spawn.
|
|
330
|
+
spawnMode: fork ? "fork" : "regular",
|
|
216
331
|
...(inheritedOverrideProfile
|
|
217
332
|
? { overrideProfile: inheritedOverrideProfile }
|
|
218
333
|
: {}),
|
|
219
334
|
...(forceOverrideProfile ? { forceOverrideProfile: true } : {}),
|
|
335
|
+
// Delegated work belongs to the firing that triggered the invoking
|
|
336
|
+
// turn, so the child's usage rows carry the parent's stamp.
|
|
337
|
+
...(context.cronRunId ? { cronRunId: context.cronRunId } : {}),
|
|
220
338
|
...(context.toolUseId ? { parentToolUseId: context.toolUseId } : {}),
|
|
221
339
|
...forkFields,
|
|
222
340
|
},
|
|
@@ -228,7 +346,10 @@ export async function executeSubagentSpawn(
|
|
|
228
346
|
subagentId,
|
|
229
347
|
label,
|
|
230
348
|
status: "pending",
|
|
349
|
+
role: resolvedRole.role,
|
|
231
350
|
...(fork ? { isFork: true } : {}),
|
|
351
|
+
...(profileNote ? { note: profileNote } : {}),
|
|
352
|
+
...(roleNote ? { roleNote } : {}),
|
|
232
353
|
message: fork
|
|
233
354
|
? `Forked subagent "${label}" spawned with full parent context. You will be notified automatically when it completes or fails - do NOT poll subagent_status. Continue the conversation normally.`
|
|
234
355
|
: `Subagent "${label}" spawned. You will be notified automatically when it completes or fails - do NOT poll subagent_status. Continue the conversation normally.`,
|
|
@@ -241,6 +362,181 @@ export async function executeSubagentSpawn(
|
|
|
241
362
|
}
|
|
242
363
|
}
|
|
243
364
|
|
|
365
|
+
// ── Output contract ──────────────────────────────────────────────────
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Why the requested `output_contract` cannot run under the type this spawn
|
|
369
|
+
* resolved to, or `undefined` when the pairing is fine.
|
|
370
|
+
*
|
|
371
|
+
* The two non-default contracts each need a capability only one type has: a
|
|
372
|
+
* verdict is a claim about what already exists, which is the read-only
|
|
373
|
+
* researcher's job, and an artifact has to be written, which only a builder
|
|
374
|
+
* can do. The advisor takes no contract at all: it is a blocking consult that
|
|
375
|
+
* returns guidance in its own framing, and its child never sees a built system
|
|
376
|
+
* prompt or fork framing to render one into. That includes an explicit
|
|
377
|
+
* `report`, which is checked against the advisor before it is waved through as
|
|
378
|
+
* the default everywhere else: the caller asked for a shape the consult cannot
|
|
379
|
+
* produce, and only an omitted contract means it never asked.
|
|
380
|
+
*
|
|
381
|
+
* Mismatches are rejected rather than coerced. Silently promoting a verdict
|
|
382
|
+
* spawn to a builder would hand out write access the caller never asked for,
|
|
383
|
+
* and silently demoting one to a report would return prose where the caller
|
|
384
|
+
* expected pass/fail. Either way the caller is the only one who can say which
|
|
385
|
+
* half of its request was the mistake.
|
|
386
|
+
*/
|
|
387
|
+
function outputContractError(
|
|
388
|
+
contract: SubagentOutputContract | undefined,
|
|
389
|
+
role: SubagentRole,
|
|
390
|
+
): string | undefined {
|
|
391
|
+
if (contract === undefined) {
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
if (role === "advisor") {
|
|
395
|
+
return (
|
|
396
|
+
`output_contract "${contract}" does not apply to the advisor: it is a blocking consult that returns guidance in its own shape. ` +
|
|
397
|
+
'Drop output_contract, or spawn role "researcher" with output_contract "verdict" to have work checked.'
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
if (contract === "report") {
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
if (contract === "verdict" && role !== "researcher") {
|
|
404
|
+
return (
|
|
405
|
+
`output_contract "verdict" is only available to researcher-typed subagents, and this spawn resolved to "${role}". ` +
|
|
406
|
+
'Spawn it with role "researcher" to get an evidence-backed PASS/FAIL check, or drop output_contract to get a report.'
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
if (contract === "artifact" && role !== "builder") {
|
|
410
|
+
return (
|
|
411
|
+
`output_contract "artifact" is only available to builder-typed subagents, and this spawn resolved to "${role}". ` +
|
|
412
|
+
'Spawn it with role "builder" so it can actually produce the artifact, or drop output_contract to get a report.'
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
return undefined;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ── Role resolution ──────────────────────────────────────────────────
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* What to tell the parent about how its `role` was read, or `undefined` when
|
|
422
|
+
* the requested role was a type name and nothing needs saying.
|
|
423
|
+
*
|
|
424
|
+
* The parent chose the role, so a silent substitution is the one outcome to
|
|
425
|
+
* avoid: an alias and a persona fallback both change what the child can do,
|
|
426
|
+
* and only the parent can decide whether that is what it wanted.
|
|
427
|
+
*/
|
|
428
|
+
function resolvedRoleNote(resolved: ResolvedSubagentRole): string | undefined {
|
|
429
|
+
if (resolved.alias) {
|
|
430
|
+
return `Role "${resolved.alias}" is an older name for "${resolved.role}"; the subagent runs with the ${resolved.role} toolset.`;
|
|
431
|
+
}
|
|
432
|
+
if (resolved.personaText) {
|
|
433
|
+
return (
|
|
434
|
+
`Role "${resolved.personaText}" is not a subagent type, so it was used as a persona and the subagent runs as a researcher: ` +
|
|
435
|
+
'read-only, no shell. If the task has to write files or run commands, re-spawn it with role "builder".'
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
return undefined;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// ── Repeat-spawn guard ───────────────────────────────────────────────
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* The result to return instead of spawning when this objective has already run
|
|
445
|
+
* too often inside {@link LOOP_GUARD_WINDOW_MS}, or `undefined` when the spawn
|
|
446
|
+
* should proceed.
|
|
447
|
+
*
|
|
448
|
+
* Two shapes of repetition are held, and they are told apart because the caller
|
|
449
|
+
* has to do something different about each. Completed runs mean an answer
|
|
450
|
+
* exists, so the caller is pointed at it. In-flight runs mean copies of this
|
|
451
|
+
* work are executing right now with nothing to show yet, which is the runaway
|
|
452
|
+
* loop the guard is for: a burst issued faster than anything can finish is
|
|
453
|
+
* invisible to the completed counts for its whole duration. Runs that ended
|
|
454
|
+
* without an answer count as neither, so a retry after a failure is never held.
|
|
455
|
+
*
|
|
456
|
+
* Not an error: the caller is being handed what its earlier runs produced and
|
|
457
|
+
* cost, and can still spawn by passing `confirm_repeat: true`. The spawn
|
|
458
|
+
* history can only advise, so a failed read lets the spawn through.
|
|
459
|
+
*/
|
|
460
|
+
function repeatSpawnGuardResult(
|
|
461
|
+
parentConversationId: string,
|
|
462
|
+
objective: string,
|
|
463
|
+
): ToolExecutionResult | undefined {
|
|
464
|
+
let recent: RecentSimilarSpawns;
|
|
465
|
+
try {
|
|
466
|
+
recent = countRecentSimilarSpawns({
|
|
467
|
+
parentConversationId,
|
|
468
|
+
normalizedObjective: normalizeSpawnObjective(objective),
|
|
469
|
+
sinceMs: Date.now() - LOOP_GUARD_WINDOW_MS,
|
|
470
|
+
});
|
|
471
|
+
} catch (err) {
|
|
472
|
+
log.warn(
|
|
473
|
+
{ err, conversationId: parentConversationId },
|
|
474
|
+
"Repeat-spawn guard could not read spawn history",
|
|
475
|
+
);
|
|
476
|
+
return undefined;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// The spawn being asked for is itself part of the limit, so a window whose
|
|
480
|
+
// runs already fill the allowance is what trips the guard. Completed runs are
|
|
481
|
+
// checked first: when both shapes are present, an answer that already exists
|
|
482
|
+
// is the more actionable thing to hand back.
|
|
483
|
+
let scope: string;
|
|
484
|
+
let tally: SimilarSpawnTally;
|
|
485
|
+
if (recent.conversation.count >= LOOP_GUARD_CONVERSATION_LIMIT) {
|
|
486
|
+
scope = "in this conversation";
|
|
487
|
+
tally = recent.conversation;
|
|
488
|
+
} else if (recent.assistant.count >= LOOP_GUARD_ASSISTANT_LIMIT) {
|
|
489
|
+
scope = "across this assistant";
|
|
490
|
+
tally = recent.assistant;
|
|
491
|
+
} else if (
|
|
492
|
+
recent.conversation.inFlight >= LOOP_GUARD_IN_FLIGHT_CONVERSATION_LIMIT
|
|
493
|
+
) {
|
|
494
|
+
return inFlightGuardResult(
|
|
495
|
+
recent.conversation.inFlight,
|
|
496
|
+
"this conversation",
|
|
497
|
+
);
|
|
498
|
+
} else if (
|
|
499
|
+
recent.assistant.inFlight >= LOOP_GUARD_IN_FLIGHT_ASSISTANT_LIMIT
|
|
500
|
+
) {
|
|
501
|
+
return inFlightGuardResult(recent.assistant.inFlight, "this assistant");
|
|
502
|
+
} else {
|
|
503
|
+
return undefined;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const hours = LOOP_GUARD_WINDOW_MS / 3_600_000;
|
|
507
|
+
const cost =
|
|
508
|
+
tally.estimatedCost > 0
|
|
509
|
+
? `, at a total cost of about $${tally.estimatedCost.toFixed(2)}`
|
|
510
|
+
: "";
|
|
511
|
+
return {
|
|
512
|
+
content:
|
|
513
|
+
`${tally.count} near-identical subagents already completed ${scope} in the last ${hours} hours${cost}. ` +
|
|
514
|
+
"Repeating an objective rarely returns a different answer: read what the earlier run produced with subagent_read, " +
|
|
515
|
+
"or narrow the objective to what is actually still missing. " +
|
|
516
|
+
"If the repetition is intentional, call subagent_spawn again with confirm_repeat: true.",
|
|
517
|
+
isError: false,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* The result handed back when copies of this objective are still executing.
|
|
523
|
+
* Says explicitly that there is nothing to read yet, so the caller waits for
|
|
524
|
+
* the running work instead of being sent after results that do not exist.
|
|
525
|
+
*/
|
|
526
|
+
function inFlightGuardResult(
|
|
527
|
+
inFlight: number,
|
|
528
|
+
scope: string,
|
|
529
|
+
): ToolExecutionResult {
|
|
530
|
+
return {
|
|
531
|
+
content:
|
|
532
|
+
`${inFlight} near-identical subagents are already running in ${scope}, and none of them has returned yet. ` +
|
|
533
|
+
"Another copy would repeat work that is in progress: wait for the running ones to report back, " +
|
|
534
|
+
"or narrow the objective to the part they are not covering. " +
|
|
535
|
+
"If the repetition is intentional, call subagent_spawn again with confirm_repeat: true.",
|
|
536
|
+
isError: false,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
244
540
|
// ── Advisor consult ──────────────────────────────────────────────────
|
|
245
541
|
|
|
246
542
|
/**
|
|
@@ -248,15 +544,20 @@ export async function executeSubagentSpawn(
|
|
|
248
544
|
* consult and return its guidance as the tool result.
|
|
249
545
|
*
|
|
250
546
|
* Inherits the parent transcript (sanitized), frames it as advice via
|
|
251
|
-
* `buildAdvisorSystem`, runs
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
* calls the
|
|
547
|
+
* `buildAdvisorSystem`, and runs on `llm.advisorProfile` (unless the caller
|
|
548
|
+
* passed an explicit `inference_profile`) under both the advisor role allowlist
|
|
549
|
+
* and `denySideEffectTools`, so the only tools it can reach are the first-party
|
|
550
|
+
* built-in readers. It is bounded on two axes, because the consult holds up the
|
|
551
|
+
* user-facing turn while it runs: a progress-aware deadline (an idle window,
|
|
552
|
+
* `ADVISOR_IDLE_TIMEOUT_MS`, reset on every streamed token and every tool event
|
|
553
|
+
* so a reasoning or reading model isn't killed mid-consult, plus an absolute
|
|
554
|
+
* `ADVISOR_MAX_TIMEOUT_MS` backstop), and a ceiling of `ADVISOR_MAX_TOOL_CALLS`
|
|
555
|
+
* tool calls, since tool activity re-arms the idle window and a reading consult
|
|
556
|
+
* would otherwise be bounded only by the backstop. Whichever ceiling is hit, the
|
|
557
|
+
* partial guidance produced so far is recovered and returned with a note saying
|
|
558
|
+
* what cut it short, rather than discarded. Degrades to a benign non-error
|
|
559
|
+
* notice on any other failure (including the depth-limit rejection when a
|
|
560
|
+
* subagent itself calls the advisor).
|
|
260
561
|
*/
|
|
261
562
|
async function runAdvisorConsult(args: {
|
|
262
563
|
context: ToolContext;
|
|
@@ -269,6 +570,11 @@ async function runAdvisorConsult(args: {
|
|
|
269
570
|
const { context, label, objective, sendToClient, requestedOverrideProfile } =
|
|
270
571
|
args;
|
|
271
572
|
|
|
573
|
+
/** Set when the consult is stopped for reading past its tool ceiling. */
|
|
574
|
+
let stoppedForToolCap = false;
|
|
575
|
+
/** Appended to the guidance when the consult did not run as asked for. */
|
|
576
|
+
let profileNote: string | undefined;
|
|
577
|
+
|
|
272
578
|
try {
|
|
273
579
|
const parentConversation = findConversation(context.conversationId);
|
|
274
580
|
if (!parentConversation) {
|
|
@@ -313,24 +619,76 @@ async function runAdvisorConsult(args: {
|
|
|
313
619
|
|
|
314
620
|
// Default to the stronger advisor profile when the caller did not pin one;
|
|
315
621
|
// an explicit `inference_profile` wins (already forced upstream).
|
|
316
|
-
const
|
|
317
|
-
|
|
622
|
+
const config = getConfig();
|
|
623
|
+
let overrideProfile = requestedOverrideProfile ?? config.llm.advisorProfile;
|
|
624
|
+
// The advisor carries read tools, so a profile the catalog states cannot
|
|
625
|
+
// call them is handed a surface it can never use and answers from the
|
|
626
|
+
// transcript alone. Fall back to the call site's own default and say so
|
|
627
|
+
// alongside the guidance, the way a regular spawn reports it. The check is
|
|
628
|
+
// unconditional, matching the tools it protects, and only a catalog `false`
|
|
629
|
+
// redirects, so a model the catalog has never heard of is left alone.
|
|
630
|
+
if (
|
|
631
|
+
overrideProfile !== undefined &&
|
|
632
|
+
profileSupportsTools(overrideProfile, config) === false
|
|
633
|
+
) {
|
|
634
|
+
profileNote = `profile "${overrideProfile}" is not verified for tool calling; the advisor ran on the default profile instead.`;
|
|
635
|
+
// Clearing this is what lands the consult on the subagentSpawn default:
|
|
636
|
+
// the child runs under that call site, so the resolver picks it anyway.
|
|
637
|
+
// Naming it would select the same model but register as an override,
|
|
638
|
+
// which usage attribution reports as a conversation pin, filing a consult
|
|
639
|
+
// that merely fell back against a pin nobody set.
|
|
640
|
+
overrideProfile = undefined;
|
|
641
|
+
}
|
|
318
642
|
const forceOverrideProfile = overrideProfile !== undefined;
|
|
319
643
|
|
|
320
|
-
// Progress-aware deadline: reset on every
|
|
321
|
-
// isn't killed mid-thought, with an absolute backstop.
|
|
322
|
-
// caller's own signal.
|
|
644
|
+
// Progress-aware deadline: reset on every sign of forward progress so the
|
|
645
|
+
// consult isn't killed mid-thought or mid-read, with an absolute backstop.
|
|
646
|
+
// Combine it with the caller's own signal.
|
|
323
647
|
const deadline = createConsultDeadline({
|
|
324
648
|
idleMs: ADVISOR_IDLE_TIMEOUT_MS,
|
|
325
649
|
maxMs: ADVISOR_MAX_TIMEOUT_MS,
|
|
326
650
|
});
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
651
|
+
// Tool ceiling, on its own controller so the abort is attributable: the
|
|
652
|
+
// consult is stopped for reading too much, which reads back to the caller
|
|
653
|
+
// differently from running out of time.
|
|
654
|
+
const toolCap = new AbortController();
|
|
655
|
+
const signal = AbortSignal.any(
|
|
656
|
+
context.signal
|
|
657
|
+
? [context.signal, deadline.signal, toolCap.signal]
|
|
658
|
+
: [deadline.signal, toolCap.signal],
|
|
659
|
+
);
|
|
660
|
+
// Count the child's tool calls off its own event stream. Each executed call
|
|
661
|
+
// emits exactly one `tool_use_start`, enveloped by the manager as a
|
|
662
|
+
// `subagent_event` on its way to the parent's client. The budget is spent
|
|
663
|
+
// in full before the abort fires: the call that trips it is the one past
|
|
664
|
+
// `ADVISOR_MAX_TOOL_CALLS`, so the consult keeps every result inside the
|
|
665
|
+
// budget and loses only the call it was starting.
|
|
666
|
+
let toolCalls = 0;
|
|
667
|
+
const countingSendToClient = (msg: AssistantEvent): void => {
|
|
668
|
+
sendToClient(msg);
|
|
669
|
+
if (!isAdvisorToolCallEvent(msg) || toolCap.signal.aborted) {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
toolCalls += 1;
|
|
673
|
+
if (toolCalls > ADVISOR_MAX_TOOL_CALLS) {
|
|
674
|
+
stoppedForToolCap = true;
|
|
675
|
+
log.warn(
|
|
676
|
+
{ conversationId: context.conversationId, toolCalls },
|
|
677
|
+
"Advisor consult exceeded its tool ceiling; returning guidance so far",
|
|
678
|
+
);
|
|
679
|
+
toolCap.abort();
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
// Streamed tokens AND tool activity both count as progress. A consult that
|
|
683
|
+
// opens a file emits no token while the read runs, so token-only progress
|
|
684
|
+
// would let the idle window lapse on a healthy advisor and abort it before
|
|
685
|
+
// the model ever sees its tool result. The absolute backstop is untouched,
|
|
686
|
+
// so a stalled tool cannot extend the consult past `ADVISOR_MAX_TIMEOUT_MS`.
|
|
687
|
+
const onProgress = (): void => {
|
|
333
688
|
deadline.recordProgress();
|
|
689
|
+
};
|
|
690
|
+
// Streamed chunks additionally forward to the caller's stream sink.
|
|
691
|
+
const onText = (chunk: string): void => {
|
|
334
692
|
context.onOutput?.(chunk);
|
|
335
693
|
};
|
|
336
694
|
|
|
@@ -349,39 +707,74 @@ async function runAdvisorConsult(args: {
|
|
|
349
707
|
sendResultToUser: false,
|
|
350
708
|
role: "advisor",
|
|
351
709
|
fork: true,
|
|
710
|
+
// The advisor's read-only guarantee cannot rest on tool NAMES. A
|
|
711
|
+
// workspace tool may register under `file_read` (registerWorkspaceTools
|
|
712
|
+
// stashes the built-in and installs its own implementation), and a
|
|
713
|
+
// name-only role allowlist would hand the advisor that implementation
|
|
714
|
+
// to execute. The owner-aware read-only gate that closes this comes
|
|
715
|
+
// from `denySideEffects` on the advisor's registry entry, so every
|
|
716
|
+
// path that projects the role applies it, this spawn and the
|
|
717
|
+
// `tools list --agent advisor` preview alike.
|
|
718
|
+
//
|
|
719
|
+
// The advisor is a ROLE, not an `LLMCallSiteEnum` value, so its usage
|
|
720
|
+
// lands under `subagentSpawn` like any other subagent. This is what
|
|
721
|
+
// makes advisor consults separable from regular forks in telemetry.
|
|
722
|
+
spawnMode: "advisor_consult",
|
|
352
723
|
parentMessages: sanitizedMessages,
|
|
353
724
|
systemPromptOverride: buildAdvisorSystem(parentSystemPrompt),
|
|
354
725
|
...(overrideProfile ? { overrideProfile } : {}),
|
|
355
726
|
...(forceOverrideProfile ? { forceOverrideProfile: true } : {}),
|
|
727
|
+
// A consult is delegated work of the invoking turn, so its spend
|
|
728
|
+
// attributes to the same firing.
|
|
729
|
+
...(context.cronRunId ? { cronRunId: context.cronRunId } : {}),
|
|
356
730
|
...(context.toolUseId ? { parentToolUseId: context.toolUseId } : {}),
|
|
357
731
|
},
|
|
358
|
-
|
|
359
|
-
{ signal, onText },
|
|
732
|
+
countingSendToClient,
|
|
733
|
+
{ signal, onText, onProgress },
|
|
360
734
|
);
|
|
361
735
|
|
|
362
736
|
const trimmed = advice.trim();
|
|
363
737
|
return {
|
|
364
|
-
content:
|
|
738
|
+
content: withAdvisorNote(
|
|
365
739
|
trimmed.length > 0 ? trimmed : "(advisor returned no guidance)",
|
|
740
|
+
profileNote,
|
|
741
|
+
),
|
|
366
742
|
isError: false,
|
|
367
743
|
};
|
|
368
744
|
} finally {
|
|
369
745
|
deadline.dispose();
|
|
370
746
|
}
|
|
371
747
|
} catch (err) {
|
|
372
|
-
//
|
|
748
|
+
// Cut short mid-generation: salvage whatever guidance the advisor had
|
|
373
749
|
// written rather than throwing it away. Partial strategic advice is far
|
|
374
750
|
// more useful to the agent than an "unavailable" notice — especially on a
|
|
375
|
-
// slow reasoning profile that needs most of the window to think.
|
|
751
|
+
// slow reasoning profile that needs most of the window to think. The note
|
|
752
|
+
// names which ceiling stopped it, because "it ran out of time" and "it was
|
|
753
|
+
// still reading" call for different follow-ups from the caller.
|
|
376
754
|
if (err instanceof SubagentAbortedError) {
|
|
377
755
|
const partial = err.partialText.trim();
|
|
378
756
|
if (partial.length > 0) {
|
|
379
757
|
log.warn(
|
|
380
|
-
{ conversationId: context.conversationId },
|
|
381
|
-
"Advisor consult
|
|
758
|
+
{ conversationId: context.conversationId, stoppedForToolCap },
|
|
759
|
+
"Advisor consult was cut short; returning partial guidance",
|
|
382
760
|
);
|
|
383
761
|
return {
|
|
384
|
-
content:
|
|
762
|
+
content: withAdvisorNote(
|
|
763
|
+
partial,
|
|
764
|
+
stoppedForToolCap
|
|
765
|
+
? `The advisor used its full budget of ${ADVISOR_MAX_TOOL_CALLS} tool calls and answered with what it had read, so the guidance above may be incomplete.`
|
|
766
|
+
: "The advisor reached its time limit while still writing, so the guidance above may be cut off.",
|
|
767
|
+
profileNote,
|
|
768
|
+
),
|
|
769
|
+
isError: false,
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
if (stoppedForToolCap) {
|
|
773
|
+
return {
|
|
774
|
+
content: withAdvisorNote(
|
|
775
|
+
`(advisor used its full budget of ${ADVISOR_MAX_TOOL_CALLS} tool calls without writing any guidance: narrow the question, or point it at the specific file or decision you want checked)`,
|
|
776
|
+
profileNote,
|
|
777
|
+
),
|
|
385
778
|
isError: false,
|
|
386
779
|
};
|
|
387
780
|
}
|
|
@@ -396,6 +789,35 @@ async function runAdvisorConsult(args: {
|
|
|
396
789
|
}
|
|
397
790
|
}
|
|
398
791
|
|
|
792
|
+
/**
|
|
793
|
+
* Whether a parent-bound event is a child tool call starting. The manager
|
|
794
|
+
* envelopes every child event as `subagent_event`, so the consult reads the
|
|
795
|
+
* inner event to see what the advisor is doing.
|
|
796
|
+
*/
|
|
797
|
+
function isAdvisorToolCallEvent(msg: AssistantEvent): boolean {
|
|
798
|
+
if (msg.type !== "subagent_event") {
|
|
799
|
+
return false;
|
|
800
|
+
}
|
|
801
|
+
const inner = (msg as { event?: { type?: string } }).event;
|
|
802
|
+
return inner?.type === "tool_use_start";
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* The guidance with any notes about how the consult actually ran appended
|
|
807
|
+
* below it. Notes are italicized asides so the guidance itself stays the
|
|
808
|
+
* result; nothing is appended when there is nothing to say.
|
|
809
|
+
*/
|
|
810
|
+
function withAdvisorNote(
|
|
811
|
+
guidance: string,
|
|
812
|
+
...notes: (string | undefined)[]
|
|
813
|
+
): string {
|
|
814
|
+
const present = notes.filter((note): note is string => Boolean(note));
|
|
815
|
+
if (present.length === 0) {
|
|
816
|
+
return guidance;
|
|
817
|
+
}
|
|
818
|
+
return `${guidance}\n\n${present.map((note) => `_(${note})_`).join("\n")}`;
|
|
819
|
+
}
|
|
820
|
+
|
|
399
821
|
/**
|
|
400
822
|
* Append the in-flight assistant turn (persisted this turn before the advisor
|
|
401
823
|
* tool ran) to an in-memory message snapshot, unless the snapshot already ends
|