@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
package/src/subagent/manager.ts
CHANGED
|
@@ -38,16 +38,24 @@ import type { Message, TextContent } from "../providers/types.js";
|
|
|
38
38
|
import { createAbortReason } from "../util/abort-reasons.js";
|
|
39
39
|
import { getLogger } from "../util/logger.js";
|
|
40
40
|
import { getSandboxWorkingDir } from "../util/platform.js";
|
|
41
|
+
import { sleep } from "../util/retry.js";
|
|
41
42
|
import { injectMessageIntoParent } from "./notify.js";
|
|
43
|
+
import { isSubagentProgressEvent } from "./progress-events.js";
|
|
42
44
|
import {
|
|
45
|
+
DEFAULT_SUBAGENT_ROLE,
|
|
46
|
+
formatSubagentToolStats,
|
|
43
47
|
normalizeSubagentLabel,
|
|
44
48
|
settleUnsupervisedStatus,
|
|
45
49
|
SUBAGENT_LIMITS,
|
|
46
50
|
SUBAGENT_ROLE_REGISTRY,
|
|
47
51
|
type SubagentConfig,
|
|
52
|
+
subagentOutputContractText,
|
|
48
53
|
type SubagentRole,
|
|
54
|
+
type SubagentSpawnMode,
|
|
49
55
|
type SubagentState,
|
|
50
56
|
type SubagentStatus,
|
|
57
|
+
type SubagentToolStatsReading,
|
|
58
|
+
type SubagentToolStatsSummary,
|
|
51
59
|
TERMINAL_STATUSES,
|
|
52
60
|
} from "./types.js";
|
|
53
61
|
|
|
@@ -68,6 +76,21 @@ const SWEEP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
|
68
76
|
*/
|
|
69
77
|
const MAX_REHYDRATED_TERMINAL_RECORDS = 200;
|
|
70
78
|
|
|
79
|
+
/**
|
|
80
|
+
* How long {@link SubagentManager.settleQueuedTurns} waits for a queued
|
|
81
|
+
* follow-up turn before reporting the subagent as still moving.
|
|
82
|
+
*
|
|
83
|
+
* Sized for the drain handoff, not for the turn itself: the queue is taken
|
|
84
|
+
* some milliseconds before the processing lock, and a wait shorter than that
|
|
85
|
+
* gap would mistake a turn that is starting for one that already finished. A
|
|
86
|
+
* guidance turn that is genuinely mid-flight outlives any budget worth
|
|
87
|
+
* blocking the parent's tool call for, so it is reported as unfinished instead
|
|
88
|
+
* of waited out.
|
|
89
|
+
*/
|
|
90
|
+
const QUEUED_TURN_SETTLE_TIMEOUT_MS = 2_000;
|
|
91
|
+
/** Gap between queue observations while a queued turn is waiting to start. */
|
|
92
|
+
const QUEUED_TURN_POLL_MS = 25;
|
|
93
|
+
|
|
71
94
|
// ── Durable record → state mapping ─────────────────────────────────────
|
|
72
95
|
|
|
73
96
|
/**
|
|
@@ -81,6 +104,11 @@ const MAX_REHYDRATED_TERMINAL_RECORDS = 200;
|
|
|
81
104
|
* `SubagentConfig` fields that are not persisted (context, prompts, trust,
|
|
82
105
|
* profile overrides) are absent, so this shape answers lifecycle questions
|
|
83
106
|
* only.
|
|
107
|
+
*
|
|
108
|
+
* Tool-call counters are in-memory only and the row carries none, so a state
|
|
109
|
+
* built here never has {@link SubagentState.stats}. What that absence means is
|
|
110
|
+
* the manager's answer to give, not this shape's: see
|
|
111
|
+
* {@link SubagentManager.currentToolStats}.
|
|
84
112
|
*/
|
|
85
113
|
export function subagentStateFromRecord(rec: SubagentRecord): SubagentState {
|
|
86
114
|
return {
|
|
@@ -169,6 +197,18 @@ function extractFinalAssistantText(messages: Message[]): string {
|
|
|
169
197
|
return "";
|
|
170
198
|
}
|
|
171
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Snapshot the child conversation's live tool-call counters before the
|
|
202
|
+
* conversation is released. The `filesWritten` Set collapses to its size so
|
|
203
|
+
* nothing keeps a reference into the released conversation.
|
|
204
|
+
*/
|
|
205
|
+
function snapshotToolStats(
|
|
206
|
+
conversation: Conversation,
|
|
207
|
+
): SubagentToolStatsSummary {
|
|
208
|
+
const { calls, succeeded, filesWritten } = conversation.subagentToolStats;
|
|
209
|
+
return { calls, succeeded, filesWritten: filesWritten.size };
|
|
210
|
+
}
|
|
211
|
+
|
|
172
212
|
/**
|
|
173
213
|
* Pull the user-visible text out of a streaming delta event, or null for any
|
|
174
214
|
* other event type. Used by the synchronous `onText` tap to forward
|
|
@@ -191,12 +231,15 @@ export function buildSubagentSystemPrompt(
|
|
|
191
231
|
role: SubagentRole,
|
|
192
232
|
): string {
|
|
193
233
|
const roleConfig = SUBAGENT_ROLE_REGISTRY[role];
|
|
194
|
-
const sections: string[] = [
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
234
|
+
const sections: string[] = [roleConfig.systemPromptPreamble];
|
|
235
|
+
if (config.persona) {
|
|
236
|
+
sections.push(`- Persona: act as ${config.persona} for this task.`);
|
|
237
|
+
}
|
|
238
|
+
const contractText = subagentOutputContractText(config.outputContract);
|
|
239
|
+
if (contractText) {
|
|
240
|
+
sections.push(`- Output contract: ${contractText}`);
|
|
241
|
+
}
|
|
242
|
+
sections.push("", "## Your Task", config.objective);
|
|
200
243
|
if (config.context) {
|
|
201
244
|
sections.push("", "## Context from Parent", config.context);
|
|
202
245
|
}
|
|
@@ -207,6 +250,7 @@ export function buildSubagentSystemPrompt(
|
|
|
207
250
|
"- You cannot spawn nested subagents.",
|
|
208
251
|
"- Use notify_parent to report important findings, or if you are blocked.",
|
|
209
252
|
'- If the objective needs a capability your role\'s tools do not provide (for example, writing or editing a file, or running a command, with a read-only role), do NOT fabricate a completed result — call notify_parent with urgency "blocked", name the capability you lack (e.g. file_write), and stop.',
|
|
253
|
+
"- If a tool call fails, or a tool you expected is unavailable, report the failure verbatim and stop that line of work. Never simulate, reconstruct, or invent tool output you did not actually receive.",
|
|
210
254
|
);
|
|
211
255
|
return sections.join("\n");
|
|
212
256
|
}
|
|
@@ -234,6 +278,8 @@ export function buildSubagentTerminalMessage(opts: {
|
|
|
234
278
|
deferred?: boolean;
|
|
235
279
|
/** Tools the subagent attempted but that its role allowlist denied. */
|
|
236
280
|
deniedTools?: string[];
|
|
281
|
+
/** What the subagent actually ran, harvested when the run ended. */
|
|
282
|
+
stats?: SubagentToolStatsSummary;
|
|
237
283
|
}): string {
|
|
238
284
|
const {
|
|
239
285
|
label,
|
|
@@ -245,6 +291,7 @@ export function buildSubagentTerminalMessage(opts: {
|
|
|
245
291
|
error,
|
|
246
292
|
deferred,
|
|
247
293
|
deniedTools,
|
|
294
|
+
stats,
|
|
248
295
|
} = opts;
|
|
249
296
|
const prefix = isFork ? "Fork" : "Subagent";
|
|
250
297
|
|
|
@@ -254,9 +301,14 @@ export function buildSubagentTerminalMessage(opts: {
|
|
|
254
301
|
const many = (deniedTools?.length ?? 0) > 1;
|
|
255
302
|
const deniedNote =
|
|
256
303
|
deniedTools && deniedTools.length > 0
|
|
257
|
-
? `\n\nNote: this ${prefix.toLowerCase()} attempted ${deniedTools.join(", ")} but its role does not permit ${many ? "them" : "it"}. If the objective requires ${many ? "those" : "that"}, re-spawn with a role that includes ${many ? "them" : "it"} (e.g. \`
|
|
304
|
+
? `\n\nNote: this ${prefix.toLowerCase()} attempted ${deniedTools.join(", ")} but its role does not permit ${many ? "them" : "it"}. If the objective requires ${many ? "those" : "that"}, re-spawn with a role that includes ${many ? "them" : "it"} (e.g. \`builder\`).`
|
|
258
305
|
: "";
|
|
259
306
|
|
|
307
|
+
// Machine truth envelope: the counts the parent can check the synthesis
|
|
308
|
+
// above against. Absent when the run never reached the harvest, and on the
|
|
309
|
+
// deferred path, where the counters are still moving (see the caller).
|
|
310
|
+
const statsNote = stats ? `\n\n${formatSubagentToolStats(stats)}` : "";
|
|
311
|
+
|
|
260
312
|
if (outcome === "failed") {
|
|
261
313
|
return (
|
|
262
314
|
`[${prefix} "${label}" failed]\n\n` +
|
|
@@ -273,7 +325,8 @@ export function buildSubagentTerminalMessage(opts: {
|
|
|
273
325
|
(silent
|
|
274
326
|
? `(Use these findings internally; do not relay the raw ${prefix.toLowerCase()} output to the user.)`
|
|
275
327
|
: `(Incorporate this into your reply to the user as appropriate.)`) +
|
|
276
|
-
deniedNote
|
|
328
|
+
deniedNote +
|
|
329
|
+
statsNote
|
|
277
330
|
);
|
|
278
331
|
}
|
|
279
332
|
|
|
@@ -288,7 +341,8 @@ export function buildSubagentTerminalMessage(opts: {
|
|
|
288
341
|
`[${prefix} "${label}" completed]\n\n` +
|
|
289
342
|
`${reason}. Use subagent_read with subagent_id "${subagentId}"${lastN} for the latest output.` +
|
|
290
343
|
(silent ? ` Keep the result internal.` : ``) +
|
|
291
|
-
deniedNote
|
|
344
|
+
deniedNote +
|
|
345
|
+
statsNote
|
|
292
346
|
);
|
|
293
347
|
}
|
|
294
348
|
|
|
@@ -302,6 +356,14 @@ interface ManagedSubagent {
|
|
|
302
356
|
parentSendToClient: (msg: AssistantEvent) => void;
|
|
303
357
|
/** Epoch ms after which this terminal entry can be removed by the TTL sweep. */
|
|
304
358
|
retainedUntil?: number;
|
|
359
|
+
/**
|
|
360
|
+
* True for an entry the startup rehydration rebuilt from a durable row
|
|
361
|
+
* rather than a run this process executed. Such an entry sits in the manager
|
|
362
|
+
* exactly like a live one but can never have tool-call counters, so
|
|
363
|
+
* {@link SubagentManager.currentToolStats} reads this to tell "never
|
|
364
|
+
* measured here" apart from "not measured yet".
|
|
365
|
+
*/
|
|
366
|
+
rehydrated?: boolean;
|
|
305
367
|
/**
|
|
306
368
|
* Sticky monotonic flag: set to true when sendMessage enqueues a follow-up
|
|
307
369
|
* message while a run is in progress, and never cleared. Needed because the
|
|
@@ -331,6 +393,15 @@ interface ManagedSubagent {
|
|
|
331
393
|
* this callback IN ADDITION to the normal `subagent_event` envelope.
|
|
332
394
|
*/
|
|
333
395
|
onText?: (chunk: string) => void;
|
|
396
|
+
/**
|
|
397
|
+
* Optional liveness tap for the synchronous path. When set,
|
|
398
|
+
* `wrappedSendToClient` fires it once per event that shows the child is still
|
|
399
|
+
* moving (see {@link isSubagentProgressEvent}), which is a superset of the
|
|
400
|
+
* `onText` chunks: a subagent executing a tool streams no token but is not
|
|
401
|
+
* stalled. Callers that bound a synchronous run by an idle window re-arm it
|
|
402
|
+
* here.
|
|
403
|
+
*/
|
|
404
|
+
onProgress?: () => void;
|
|
334
405
|
}
|
|
335
406
|
|
|
336
407
|
export interface SubagentNotificationInfo {
|
|
@@ -413,7 +484,11 @@ export class SubagentManager {
|
|
|
413
484
|
private async setUpSubagent(
|
|
414
485
|
config: Omit<SubagentConfig, "id">,
|
|
415
486
|
parentSendToClient: (msg: AssistantEvent) => void,
|
|
416
|
-
opts?: {
|
|
487
|
+
opts?: {
|
|
488
|
+
synchronous?: boolean;
|
|
489
|
+
onText?: (chunk: string) => void;
|
|
490
|
+
onProgress?: () => void;
|
|
491
|
+
},
|
|
417
492
|
): Promise<{ subagentId: string; managed: ManagedSubagent }> {
|
|
418
493
|
// ── Limit checks ────────────────────────────────────────────────
|
|
419
494
|
|
|
@@ -428,22 +503,13 @@ export class SubagentManager {
|
|
|
428
503
|
}
|
|
429
504
|
|
|
430
505
|
// ── Resolve role ─────────────────────────────────────────────────
|
|
506
|
+
// A role is one of the three types, or absent for an internal caller that
|
|
507
|
+
// states no shape and runs on the default. The `subagent_spawn` tool
|
|
508
|
+
// resolves whatever the model wrote into a type before it gets here (see
|
|
509
|
+
// subagent/role-resolution.ts), so an unregistered value at this point is
|
|
510
|
+
// an internal caller's bug and is worth throwing over.
|
|
431
511
|
const isFork = config.fork === true;
|
|
432
|
-
const role: SubagentRole =
|
|
433
|
-
if (isFork && role !== "general") {
|
|
434
|
-
// A context-inheriting subagent normally keeps the parent's `general`
|
|
435
|
-
// role so its KV cache stays aligned with the parent conversation. An
|
|
436
|
-
// explicit non-general role opts out of that alignment on purpose
|
|
437
|
-
// (e.g. the advisor role running on a stronger profile), so honor it.
|
|
438
|
-
log.warn(
|
|
439
|
-
{
|
|
440
|
-
requestedRole: role,
|
|
441
|
-
parentConversationId: config.parentConversationId,
|
|
442
|
-
label: config.label,
|
|
443
|
-
},
|
|
444
|
-
"Fork requested with non-general role — caller opted out of parent KV-cache alignment",
|
|
445
|
-
);
|
|
446
|
-
}
|
|
512
|
+
const role: SubagentRole = config.role ?? DEFAULT_SUBAGENT_ROLE;
|
|
447
513
|
if (!SUBAGENT_ROLE_REGISTRY[role]) {
|
|
448
514
|
throw new Error(
|
|
449
515
|
`Invalid subagent role "${config.role}". Must be one of: ${Object.keys(SUBAGENT_ROLE_REGISTRY).join(", ")}`,
|
|
@@ -451,14 +517,29 @@ export class SubagentManager {
|
|
|
451
517
|
}
|
|
452
518
|
const roleConfig = SUBAGENT_ROLE_REGISTRY[role];
|
|
453
519
|
|
|
520
|
+
// ── Resolve spawn mode ───────────────────────────────────────────
|
|
521
|
+
// The spawning call site is the only layer that can tell an advisor
|
|
522
|
+
// consult or a live-voice continuation apart from a plain fork, so it
|
|
523
|
+
// declares its mode. The fallback is mechanical rather than NULL: a
|
|
524
|
+
// future call site that forgets still records honest context-inheritance
|
|
525
|
+
// shape instead of dropping out of the telemetry breakdown entirely.
|
|
526
|
+
const spawnMode: SubagentSpawnMode =
|
|
527
|
+
config.spawnMode ?? (isFork ? "fork" : "regular");
|
|
528
|
+
|
|
454
529
|
// ── Create conversation ─────────────────────────────────────────
|
|
455
530
|
const subagentId = uuid();
|
|
531
|
+
// `subagentRole` / `subagentSpawnMode` are stamped on the conversation
|
|
532
|
+
// row, not just the `subagents` row, because `subagents` rows are deleted
|
|
533
|
+
// on dispose while usage telemetry flushes on a watermark that can trail
|
|
534
|
+
// far behind. See migration 362.
|
|
456
535
|
const conversationRecord = await bootstrapConversation({
|
|
457
536
|
conversationType: "background",
|
|
458
537
|
source: "subagent",
|
|
459
538
|
origin: "subagent",
|
|
460
539
|
systemHint: `Subagent: ${config.label}`,
|
|
461
540
|
parentConversationId: config.parentConversationId,
|
|
541
|
+
subagentRole: role,
|
|
542
|
+
subagentSpawnMode: spawnMode,
|
|
462
543
|
});
|
|
463
544
|
|
|
464
545
|
// ── Build conversation dependencies ─────────────────────────────
|
|
@@ -555,6 +636,7 @@ export class SubagentManager {
|
|
|
555
636
|
parentSendToClient,
|
|
556
637
|
...(opts?.synchronous ? { synchronous: true } : {}),
|
|
557
638
|
...(opts?.onText ? { onText: opts.onText } : {}),
|
|
639
|
+
...(opts?.onProgress ? { onProgress: opts.onProgress } : {}),
|
|
558
640
|
};
|
|
559
641
|
|
|
560
642
|
// Wrap sendToClient to envelope all events with the subagent ID.
|
|
@@ -569,6 +651,11 @@ export class SubagentManager {
|
|
|
569
651
|
managed.onText(text);
|
|
570
652
|
}
|
|
571
653
|
}
|
|
654
|
+
// Liveness tap, separate from the text tap because tool activity is
|
|
655
|
+
// progress the caller must see and is not a chunk it can forward.
|
|
656
|
+
if (managed.onProgress && isSubagentProgressEvent(msg)) {
|
|
657
|
+
managed.onProgress();
|
|
658
|
+
}
|
|
572
659
|
managed.parentSendToClient({
|
|
573
660
|
type: "subagent_event",
|
|
574
661
|
subagentId,
|
|
@@ -589,10 +676,11 @@ export class SubagentManager {
|
|
|
589
676
|
// Records the parent at construction; drives isSubagent and notify
|
|
590
677
|
// routing from non-writable in-process state.
|
|
591
678
|
parentConversationId: config.parentConversationId,
|
|
592
|
-
// The advisor consult
|
|
593
|
-
// its guidance with provider-native web search when the
|
|
594
|
-
// provider supports it. This is a server tool the provider
|
|
595
|
-
//
|
|
679
|
+
// The advisor consult is scoped to read-only CLIENT tools and should
|
|
680
|
+
// also ground its guidance with provider-native web search when the
|
|
681
|
+
// resolved provider supports it. This is a server tool the provider
|
|
682
|
+
// runs itself, so no client tool is surfaced and the allowlist is
|
|
683
|
+
// unchanged.
|
|
596
684
|
// Other roles keep the default (no native search appended).
|
|
597
685
|
...(role === "advisor" ? { enableNativeWebSearch: true } : {}),
|
|
598
686
|
},
|
|
@@ -613,10 +701,17 @@ export class SubagentManager {
|
|
|
613
701
|
// wins over parent inheritance: a parent that stamps trust per-turn (the
|
|
614
702
|
// live-voice bridge) has already cleared it by the time a detached spawn
|
|
615
703
|
// reads it, so its spawner resolves trust itself.
|
|
704
|
+
//
|
|
705
|
+
// Inherit the parent's *turn* trust ahead of its conversation-level slot:
|
|
706
|
+
// the slot holds whichever actor sent most recently, so a spawn during one
|
|
707
|
+
// actor's turn would otherwise run under another's privileges.
|
|
708
|
+
const parentTurnTrust =
|
|
709
|
+
parentConversation?.currentTurnTrustContext ??
|
|
710
|
+
parentConversation?.trustContext;
|
|
616
711
|
if (config.trustContext) {
|
|
617
712
|
conversation.setTrustContext({ ...config.trustContext });
|
|
618
|
-
} else if (
|
|
619
|
-
conversation.setTrustContext({ ...
|
|
713
|
+
} else if (parentTurnTrust) {
|
|
714
|
+
conversation.setTrustContext({ ...parentTurnTrust });
|
|
620
715
|
}
|
|
621
716
|
const parentAuthContext = parentConversation?.getAuthContext();
|
|
622
717
|
if (parentAuthContext) {
|
|
@@ -645,18 +740,18 @@ export class SubagentManager {
|
|
|
645
740
|
conversation.hasSystemPromptOverride = true;
|
|
646
741
|
}
|
|
647
742
|
|
|
648
|
-
// Apply the role's tool allowlist when one is defined.
|
|
649
|
-
//
|
|
650
|
-
//
|
|
651
|
-
// allowlist applied like any other subagent.
|
|
743
|
+
// Apply the role's tool allowlist when one is defined. `builder` defines
|
|
744
|
+
// none, so it keeps the full surface its conversation projects; the scoped
|
|
745
|
+
// roles are filtered to their own list whether or not this is a fork.
|
|
652
746
|
if (roleConfig.allowedTools) {
|
|
653
747
|
conversation.setSubagentAllowedTools(new Set(roleConfig.allowedTools));
|
|
654
748
|
}
|
|
655
749
|
|
|
656
750
|
// A read-only subagent refuses side-effecting tools regardless of trust
|
|
657
|
-
// class; the executor gate rejects
|
|
658
|
-
//
|
|
659
|
-
|
|
751
|
+
// class; the executor gate rejects any such dispatch and they are kept off
|
|
752
|
+
// the model's tool surface. The role carries the gate so a caller cannot
|
|
753
|
+
// spawn a role past it, and a spawn can still ask for it on top.
|
|
754
|
+
if (config.denySideEffectTools || roleConfig.denySideEffects) {
|
|
660
755
|
conversation.setSubagentDenySideEffects(true);
|
|
661
756
|
}
|
|
662
757
|
|
|
@@ -745,12 +840,20 @@ export class SubagentManager {
|
|
|
745
840
|
async spawnAndAwait(
|
|
746
841
|
config: Omit<SubagentConfig, "id">,
|
|
747
842
|
parentSendToClient: (msg: AssistantEvent) => void,
|
|
748
|
-
opts?: {
|
|
843
|
+
opts?: {
|
|
844
|
+
signal?: AbortSignal;
|
|
845
|
+
onText?: (chunk: string) => void;
|
|
846
|
+
onProgress?: () => void;
|
|
847
|
+
},
|
|
749
848
|
): Promise<string> {
|
|
750
849
|
const { subagentId, managed } = await this.setUpSubagent(
|
|
751
850
|
config,
|
|
752
851
|
parentSendToClient,
|
|
753
|
-
{
|
|
852
|
+
{
|
|
853
|
+
synchronous: true,
|
|
854
|
+
...(opts?.onText ? { onText: opts.onText } : {}),
|
|
855
|
+
...(opts?.onProgress ? { onProgress: opts.onProgress } : {}),
|
|
856
|
+
},
|
|
754
857
|
);
|
|
755
858
|
|
|
756
859
|
// Wire the external signal to abort the child conversation. If the signal
|
|
@@ -863,13 +966,24 @@ export class SubagentManager {
|
|
|
863
966
|
// the generic "complete this task and return your findings" wrapper would
|
|
864
967
|
// fight that framing. The advisor's objective is already the bare advice
|
|
865
968
|
// request (`advisorRequestText()`), so it is sent uncontested.
|
|
969
|
+
//
|
|
970
|
+
// A fork's persona and output contract ride in this framing rather than
|
|
971
|
+
// the system prompt: the prompt is the parent's, inherited verbatim to
|
|
972
|
+
// keep the KV cache aligned, so the task message is the only place a
|
|
973
|
+
// fork-specific instruction can land.
|
|
866
974
|
const useForkFraming =
|
|
867
975
|
managed.state.isFork && managed.state.config.role !== "advisor";
|
|
976
|
+
const forkPersona = managed.state.config.persona;
|
|
977
|
+
const forkContract = subagentOutputContractText(
|
|
978
|
+
managed.state.config.outputContract,
|
|
979
|
+
);
|
|
868
980
|
const message = useForkFraming
|
|
869
981
|
? [
|
|
870
982
|
"⎯⎯⎯ FORK TASK ⎯⎯⎯",
|
|
871
983
|
"You have been forked from the parent conversation to execute a specific task.",
|
|
872
984
|
"The conversation above is context — do NOT continue it. Do NOT spawn sub-agents.",
|
|
985
|
+
...(forkPersona ? [`Act as ${forkPersona} for this task.`] : []),
|
|
986
|
+
...(forkContract ? [`Output contract: ${forkContract}`] : []),
|
|
873
987
|
"Complete this task directly and return only your findings:",
|
|
874
988
|
"",
|
|
875
989
|
objective,
|
|
@@ -887,6 +1001,11 @@ export class SubagentManager {
|
|
|
887
1001
|
...(managed.state.config.forceOverrideProfile
|
|
888
1002
|
? { forceOverrideProfile: true }
|
|
889
1003
|
: {}),
|
|
1004
|
+
// Stamp the child's usage with the firing that spawned it so schedule
|
|
1005
|
+
// cost reporting sees delegated spend.
|
|
1006
|
+
...(managed.state.config.cronRunId
|
|
1007
|
+
? { cronRunId: managed.state.config.cronRunId }
|
|
1008
|
+
: {}),
|
|
890
1009
|
});
|
|
891
1010
|
|
|
892
1011
|
// Agent loop completed successfully.
|
|
@@ -898,6 +1017,12 @@ export class SubagentManager {
|
|
|
898
1017
|
const deniedTools = [...conversation.subagentDeniedToolNames];
|
|
899
1018
|
// Copy usage stats from the conversation before sending status (which includes usage).
|
|
900
1019
|
managed.state.usage = { ...conversation.usageStats };
|
|
1020
|
+
// Same window for the tool-call counts: the terminal notification below
|
|
1021
|
+
// reads them off the state. This is the first reading, not necessarily
|
|
1022
|
+
// the last, since a follow-up turn queued during the run drains after
|
|
1023
|
+
// this returns and keeps counting into the same conversation. Later
|
|
1024
|
+
// readers go through `currentToolStats` for the settled numbers.
|
|
1025
|
+
managed.state.stats = snapshotToolStats(conversation);
|
|
901
1026
|
// Only update state + notify if still non-terminal (guards against abort race).
|
|
902
1027
|
if (!TERMINAL_STATUSES.has(managed.state.status)) {
|
|
903
1028
|
managed.state.completedAt = Date.now();
|
|
@@ -925,6 +1050,7 @@ export class SubagentManager {
|
|
|
925
1050
|
// Copy usage from the captured conversation reference — managed.conversation
|
|
926
1051
|
// may have been nulled by an external dispose() before catch runs.
|
|
927
1052
|
managed.state.usage = { ...conversation.usageStats };
|
|
1053
|
+
managed.state.stats = snapshotToolStats(conversation);
|
|
928
1054
|
|
|
929
1055
|
// Only update status if not already terminal (e.g. aborted).
|
|
930
1056
|
if (!TERMINAL_STATUSES.has(managed.state.status)) {
|
|
@@ -1167,9 +1293,19 @@ export class SubagentManager {
|
|
|
1167
1293
|
|
|
1168
1294
|
// ── Send message to subagent ──────────────────────────────────────────
|
|
1169
1295
|
|
|
1296
|
+
/**
|
|
1297
|
+
* Deliver a follow-up message to a live subagent.
|
|
1298
|
+
*
|
|
1299
|
+
* `opts.cronRunId` is the firing that produced THIS message, not the one the
|
|
1300
|
+
* subagent was spawned under: a continuation turn's spend belongs to the
|
|
1301
|
+
* firing that asked for it. Only the immediately-processed turn carries it,
|
|
1302
|
+
* since a queued message drains through the conversation's own queue, which
|
|
1303
|
+
* holds no per-message run options.
|
|
1304
|
+
*/
|
|
1170
1305
|
async sendMessage(
|
|
1171
1306
|
subagentId: string,
|
|
1172
1307
|
content: string,
|
|
1308
|
+
opts?: { cronRunId?: string | null },
|
|
1173
1309
|
): Promise<"sent" | "empty" | "not_found" | "terminal"> {
|
|
1174
1310
|
const trimmed = content?.trim();
|
|
1175
1311
|
if (!trimmed) {
|
|
@@ -1208,6 +1344,7 @@ export class SubagentManager {
|
|
|
1208
1344
|
...(managed.state.config.forceOverrideProfile
|
|
1209
1345
|
? { forceOverrideProfile: true }
|
|
1210
1346
|
: {}),
|
|
1347
|
+
...(opts?.cronRunId ? { cronRunId: opts.cronRunId } : {}),
|
|
1211
1348
|
})
|
|
1212
1349
|
.catch((err) => {
|
|
1213
1350
|
log.error({ subagentId, err }, "Subagent message processing failed");
|
|
@@ -1222,6 +1359,116 @@ export class SubagentManager {
|
|
|
1222
1359
|
return this.subagents.get(subagentId)?.state;
|
|
1223
1360
|
}
|
|
1224
1361
|
|
|
1362
|
+
/**
|
|
1363
|
+
* The subagent's tool-call counters, brought up to date first, or why there
|
|
1364
|
+
* are none (see {@link SubagentToolStatsReading}).
|
|
1365
|
+
*
|
|
1366
|
+
* `runSubagent` harvests when its awaited agent loop returns, but that is not
|
|
1367
|
+
* the end of the child's work: guidance queued during the run drains
|
|
1368
|
+
* afterwards, on the same conversation, and those calls land in the same
|
|
1369
|
+
* counters. So any read taken later re-reads them while the conversation is
|
|
1370
|
+
* still retained (see {@link refreshToolStats}), and the release freezes the
|
|
1371
|
+
* settled numbers. Readers that need the queued turn's calls included wait
|
|
1372
|
+
* for it first, via {@link settleQueuedTurns}.
|
|
1373
|
+
*
|
|
1374
|
+
* An id the manager does not hold is `unrecoverable` rather than unknown:
|
|
1375
|
+
* counters exist nowhere else, so no caller can ever obtain them, and the
|
|
1376
|
+
* only state a caller can be holding for such an id came from the durable
|
|
1377
|
+
* row.
|
|
1378
|
+
*/
|
|
1379
|
+
currentToolStats(subagentId: string): SubagentToolStatsReading {
|
|
1380
|
+
const managed = this.subagents.get(subagentId);
|
|
1381
|
+
if (!managed) {
|
|
1382
|
+
return { kind: "unrecoverable" };
|
|
1383
|
+
}
|
|
1384
|
+
this.refreshToolStats(managed);
|
|
1385
|
+
if (managed.state.stats) {
|
|
1386
|
+
return { kind: "counted", stats: managed.state.stats };
|
|
1387
|
+
}
|
|
1388
|
+
return managed.rehydrated
|
|
1389
|
+
? { kind: "unrecoverable" }
|
|
1390
|
+
: { kind: "unmeasured" };
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* Wait for a follow-up turn queued during the subagent's run to finish.
|
|
1395
|
+
*
|
|
1396
|
+
* `runSubagent` marks the subagent terminal as soon as its own agent loop
|
|
1397
|
+
* returns, and the parent is told to read from there. Guidance queued during
|
|
1398
|
+
* that run drains afterwards though, on the same retained conversation, so a
|
|
1399
|
+
* read taken in that window sees the transcript and the counters from before
|
|
1400
|
+
* the guidance landed and never comes back for the rest. Waiting here closes
|
|
1401
|
+
* the window.
|
|
1402
|
+
*
|
|
1403
|
+
* Resolves `true` once the retained conversation is idle with an empty
|
|
1404
|
+
* queue, and `false` when `timeoutMs` elapses first, so the reader always
|
|
1405
|
+
* gets an answer within a bound and can say the subagent is still moving
|
|
1406
|
+
* rather than pass a partial result off as final.
|
|
1407
|
+
*
|
|
1408
|
+
* `true` comes back immediately when nothing can still be running: no
|
|
1409
|
+
* manager entry, no retained conversation (a released one has its transcript
|
|
1410
|
+
* and counters frozen), or a run that never had anything queued.
|
|
1411
|
+
*
|
|
1412
|
+
* Idle is confirmed across two observations a poll apart. The drain takes
|
|
1413
|
+
* the queue before it takes the processing lock (`drainQueue` shifts the
|
|
1414
|
+
* message, then `drainSingleMessage` awaits slash resolution and the
|
|
1415
|
+
* user-message persist before `runAgentLoop` sets processing), so a single
|
|
1416
|
+
* look into that gap finds an empty queue and an unlocked conversation while
|
|
1417
|
+
* the turn is in fact starting.
|
|
1418
|
+
*/
|
|
1419
|
+
async settleQueuedTurns(
|
|
1420
|
+
subagentId: string,
|
|
1421
|
+
timeoutMs: number = QUEUED_TURN_SETTLE_TIMEOUT_MS,
|
|
1422
|
+
): Promise<boolean> {
|
|
1423
|
+
const managed = this.subagents.get(subagentId);
|
|
1424
|
+
const conversation = managed?.conversation;
|
|
1425
|
+
if (!managed || !conversation || managed.hadEnqueuedMessages !== true) {
|
|
1426
|
+
return true;
|
|
1427
|
+
}
|
|
1428
|
+
const deadline = Date.now() + timeoutMs;
|
|
1429
|
+
let idleObservations = 0;
|
|
1430
|
+
for (;;) {
|
|
1431
|
+
// A release during the wait (the TTL sweep, or a dispose) freezes
|
|
1432
|
+
// everything a reader can see, so there is nothing left to wait for.
|
|
1433
|
+
if (managed.conversation !== conversation) {
|
|
1434
|
+
return true;
|
|
1435
|
+
}
|
|
1436
|
+
const remainingMs = deadline - Date.now();
|
|
1437
|
+
if (remainingMs <= 0) {
|
|
1438
|
+
return false;
|
|
1439
|
+
}
|
|
1440
|
+
if (conversation.isProcessing()) {
|
|
1441
|
+
idleObservations = 0;
|
|
1442
|
+
await conversation.waitForIdle({ timeoutMs: remainingMs });
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1445
|
+
if (conversation.hasQueuedMessages()) {
|
|
1446
|
+
idleObservations = 0;
|
|
1447
|
+
await sleep(Math.min(QUEUED_TURN_POLL_MS, remainingMs));
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
idleObservations += 1;
|
|
1451
|
+
if (idleObservations >= 2) {
|
|
1452
|
+
return true;
|
|
1453
|
+
}
|
|
1454
|
+
await sleep(Math.min(QUEUED_TURN_POLL_MS, remainingMs));
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* Re-read the child conversation's live counters into its state.
|
|
1460
|
+
*
|
|
1461
|
+
* Only ever updates a harvest that already happened: a run that never
|
|
1462
|
+
* reached its harvest (aborted before the first turn, or still going) has
|
|
1463
|
+
* nothing measured to report, and a zero taken mid-flight would read as
|
|
1464
|
+
* "this subagent used no tools" rather than "not measured yet".
|
|
1465
|
+
*/
|
|
1466
|
+
private refreshToolStats(managed: ManagedSubagent): void {
|
|
1467
|
+
if (managed.conversation && managed.state.stats) {
|
|
1468
|
+
managed.state.stats = snapshotToolStats(managed.conversation);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1225
1472
|
getByLabel(
|
|
1226
1473
|
label: string,
|
|
1227
1474
|
parentConversationId: string,
|
|
@@ -1296,6 +1543,10 @@ export class SubagentManager {
|
|
|
1296
1543
|
if (!managed.conversation) {
|
|
1297
1544
|
return;
|
|
1298
1545
|
}
|
|
1546
|
+
// Last chance at the counters: on the deferred path this release happens
|
|
1547
|
+
// after the queued follow-up turn drained, so this is the reading that
|
|
1548
|
+
// includes it. Everything read after this point is this snapshot.
|
|
1549
|
+
this.refreshToolStats(managed);
|
|
1299
1550
|
const conversation = managed.conversation;
|
|
1300
1551
|
removeSubagentConversation(conversation.conversationId, conversation);
|
|
1301
1552
|
conversation.dispose();
|
|
@@ -1388,7 +1639,7 @@ export class SubagentManager {
|
|
|
1388
1639
|
conversationId: state.conversationId,
|
|
1389
1640
|
label: state.config.label,
|
|
1390
1641
|
objective: state.config.objective,
|
|
1391
|
-
role: state.config.role ??
|
|
1642
|
+
role: state.config.role ?? DEFAULT_SUBAGENT_ROLE,
|
|
1392
1643
|
isFork: state.isFork,
|
|
1393
1644
|
sendResultToUser: state.config.sendResultToUser ?? null,
|
|
1394
1645
|
parentToolUseId: state.config.parentToolUseId ?? null,
|
|
@@ -1454,6 +1705,7 @@ export class SubagentManager {
|
|
|
1454
1705
|
state,
|
|
1455
1706
|
parentSendToClient: () => {},
|
|
1456
1707
|
retainedUntil: now + TERMINAL_RETENTION_MS,
|
|
1708
|
+
rehydrated: true,
|
|
1457
1709
|
};
|
|
1458
1710
|
this.subagents.set(rec.id, managed);
|
|
1459
1711
|
|
|
@@ -1609,6 +1861,10 @@ export class SubagentManager {
|
|
|
1609
1861
|
? config.sendResultToUser !== true
|
|
1610
1862
|
: config.sendResultToUser === false;
|
|
1611
1863
|
|
|
1864
|
+
// A queued follow-up turn means the snapshot we hold is stale; defer to a
|
|
1865
|
+
// read pointer so the parent picks up the queued turn's output instead.
|
|
1866
|
+
const deferred = managed.hadEnqueuedMessages === true;
|
|
1867
|
+
|
|
1612
1868
|
const message = buildSubagentTerminalMessage({
|
|
1613
1869
|
label: config.label,
|
|
1614
1870
|
subagentId: config.id,
|
|
@@ -1617,10 +1873,14 @@ export class SubagentManager {
|
|
|
1617
1873
|
silent,
|
|
1618
1874
|
finalText,
|
|
1619
1875
|
error: managed.state.error,
|
|
1620
|
-
|
|
1621
|
-
// read pointer so the parent picks up the queued turn's output instead.
|
|
1622
|
-
deferred: managed.hadEnqueuedMessages === true,
|
|
1876
|
+
deferred,
|
|
1623
1877
|
deniedTools,
|
|
1878
|
+
// Same staleness applies to the counters, and worse: the queued turn has
|
|
1879
|
+
// not run yet at this point, so any number quoted here would under-report
|
|
1880
|
+
// it, permanently, in a message that is never rewritten. The deferred
|
|
1881
|
+
// message sends the parent to `subagent_read`, whose footer re-reads the
|
|
1882
|
+
// counters once the queued turn has actually landed.
|
|
1883
|
+
...(deferred ? {} : { stats: managed.state.stats }),
|
|
1624
1884
|
});
|
|
1625
1885
|
|
|
1626
1886
|
const notification: SubagentNotificationInfo = {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classify subagent events as evidence that a child run is still moving.
|
|
3
|
+
*
|
|
4
|
+
* A synchronous caller bounds its wait with an idle window, and the only thing
|
|
5
|
+
* that resets that window is a progress signal. Streamed tokens are the obvious
|
|
6
|
+
* one, but a subagent that calls a tool goes silent for the whole execution
|
|
7
|
+
* while producing no delta, so tool activity counts as well: the call being
|
|
8
|
+
* recognized, its input streaming in, its output streaming out, and its result
|
|
9
|
+
* landing. Without it, a healthy child reading a large file looks identical to
|
|
10
|
+
* a stalled one and is aborted before the model ever sees its tool result.
|
|
11
|
+
*
|
|
12
|
+
* Its own module so the classification can be unit-tested (and imported by a
|
|
13
|
+
* deadline test) without pulling in the manager and its Conversation graph.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { AssistantEvent } from "../api/index.js";
|
|
17
|
+
|
|
18
|
+
const PROGRESS_EVENT_TYPES: ReadonlySet<string> = new Set([
|
|
19
|
+
"assistant_text_delta",
|
|
20
|
+
"assistant_thinking_delta",
|
|
21
|
+
"tool_use_preview_start",
|
|
22
|
+
"tool_use_start",
|
|
23
|
+
"tool_input_delta",
|
|
24
|
+
"tool_output_chunk",
|
|
25
|
+
"tool_result",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether an event is evidence of forward progress in a child run. Pure, and
|
|
30
|
+
* deliberately an allowlist: a lifecycle or status event says nothing about
|
|
31
|
+
* whether work is happening, so an unknown event type never extends a deadline.
|
|
32
|
+
*/
|
|
33
|
+
export function isSubagentProgressEvent(msg: AssistantEvent): boolean {
|
|
34
|
+
return PROGRESS_EVENT_TYPES.has(msg.type);
|
|
35
|
+
}
|