@vellumai/assistant 0.11.2 → 0.11.3-staging.1
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-queue.test.ts +265 -1
- package/src/__tests__/conversation-retry-route.test.ts +3 -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-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-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-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 +216 -132
- 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/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 +296 -145
- 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 +85 -42
- package/src/daemon/conversation-process.ts +76 -28
- 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 +105 -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.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/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 +56 -8
- 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 +350 -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 +295 -42
- 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/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/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
|
},
|
|
@@ -645,18 +733,18 @@ export class SubagentManager {
|
|
|
645
733
|
conversation.hasSystemPromptOverride = true;
|
|
646
734
|
}
|
|
647
735
|
|
|
648
|
-
// Apply the role's tool allowlist when one is defined.
|
|
649
|
-
//
|
|
650
|
-
//
|
|
651
|
-
// allowlist applied like any other subagent.
|
|
736
|
+
// Apply the role's tool allowlist when one is defined. `builder` defines
|
|
737
|
+
// none, so it keeps the full surface its conversation projects; the scoped
|
|
738
|
+
// roles are filtered to their own list whether or not this is a fork.
|
|
652
739
|
if (roleConfig.allowedTools) {
|
|
653
740
|
conversation.setSubagentAllowedTools(new Set(roleConfig.allowedTools));
|
|
654
741
|
}
|
|
655
742
|
|
|
656
743
|
// A read-only subagent refuses side-effecting tools regardless of trust
|
|
657
|
-
// class; the executor gate rejects
|
|
658
|
-
//
|
|
659
|
-
|
|
744
|
+
// class; the executor gate rejects any such dispatch and they are kept off
|
|
745
|
+
// the model's tool surface. The role carries the gate so a caller cannot
|
|
746
|
+
// spawn a role past it, and a spawn can still ask for it on top.
|
|
747
|
+
if (config.denySideEffectTools || roleConfig.denySideEffects) {
|
|
660
748
|
conversation.setSubagentDenySideEffects(true);
|
|
661
749
|
}
|
|
662
750
|
|
|
@@ -745,12 +833,20 @@ export class SubagentManager {
|
|
|
745
833
|
async spawnAndAwait(
|
|
746
834
|
config: Omit<SubagentConfig, "id">,
|
|
747
835
|
parentSendToClient: (msg: AssistantEvent) => void,
|
|
748
|
-
opts?: {
|
|
836
|
+
opts?: {
|
|
837
|
+
signal?: AbortSignal;
|
|
838
|
+
onText?: (chunk: string) => void;
|
|
839
|
+
onProgress?: () => void;
|
|
840
|
+
},
|
|
749
841
|
): Promise<string> {
|
|
750
842
|
const { subagentId, managed } = await this.setUpSubagent(
|
|
751
843
|
config,
|
|
752
844
|
parentSendToClient,
|
|
753
|
-
{
|
|
845
|
+
{
|
|
846
|
+
synchronous: true,
|
|
847
|
+
...(opts?.onText ? { onText: opts.onText } : {}),
|
|
848
|
+
...(opts?.onProgress ? { onProgress: opts.onProgress } : {}),
|
|
849
|
+
},
|
|
754
850
|
);
|
|
755
851
|
|
|
756
852
|
// Wire the external signal to abort the child conversation. If the signal
|
|
@@ -863,13 +959,24 @@ export class SubagentManager {
|
|
|
863
959
|
// the generic "complete this task and return your findings" wrapper would
|
|
864
960
|
// fight that framing. The advisor's objective is already the bare advice
|
|
865
961
|
// request (`advisorRequestText()`), so it is sent uncontested.
|
|
962
|
+
//
|
|
963
|
+
// A fork's persona and output contract ride in this framing rather than
|
|
964
|
+
// the system prompt: the prompt is the parent's, inherited verbatim to
|
|
965
|
+
// keep the KV cache aligned, so the task message is the only place a
|
|
966
|
+
// fork-specific instruction can land.
|
|
866
967
|
const useForkFraming =
|
|
867
968
|
managed.state.isFork && managed.state.config.role !== "advisor";
|
|
969
|
+
const forkPersona = managed.state.config.persona;
|
|
970
|
+
const forkContract = subagentOutputContractText(
|
|
971
|
+
managed.state.config.outputContract,
|
|
972
|
+
);
|
|
868
973
|
const message = useForkFraming
|
|
869
974
|
? [
|
|
870
975
|
"⎯⎯⎯ FORK TASK ⎯⎯⎯",
|
|
871
976
|
"You have been forked from the parent conversation to execute a specific task.",
|
|
872
977
|
"The conversation above is context — do NOT continue it. Do NOT spawn sub-agents.",
|
|
978
|
+
...(forkPersona ? [`Act as ${forkPersona} for this task.`] : []),
|
|
979
|
+
...(forkContract ? [`Output contract: ${forkContract}`] : []),
|
|
873
980
|
"Complete this task directly and return only your findings:",
|
|
874
981
|
"",
|
|
875
982
|
objective,
|
|
@@ -887,6 +994,11 @@ export class SubagentManager {
|
|
|
887
994
|
...(managed.state.config.forceOverrideProfile
|
|
888
995
|
? { forceOverrideProfile: true }
|
|
889
996
|
: {}),
|
|
997
|
+
// Stamp the child's usage with the firing that spawned it so schedule
|
|
998
|
+
// cost reporting sees delegated spend.
|
|
999
|
+
...(managed.state.config.cronRunId
|
|
1000
|
+
? { cronRunId: managed.state.config.cronRunId }
|
|
1001
|
+
: {}),
|
|
890
1002
|
});
|
|
891
1003
|
|
|
892
1004
|
// Agent loop completed successfully.
|
|
@@ -898,6 +1010,12 @@ export class SubagentManager {
|
|
|
898
1010
|
const deniedTools = [...conversation.subagentDeniedToolNames];
|
|
899
1011
|
// Copy usage stats from the conversation before sending status (which includes usage).
|
|
900
1012
|
managed.state.usage = { ...conversation.usageStats };
|
|
1013
|
+
// Same window for the tool-call counts: the terminal notification below
|
|
1014
|
+
// reads them off the state. This is the first reading, not necessarily
|
|
1015
|
+
// the last, since a follow-up turn queued during the run drains after
|
|
1016
|
+
// this returns and keeps counting into the same conversation. Later
|
|
1017
|
+
// readers go through `currentToolStats` for the settled numbers.
|
|
1018
|
+
managed.state.stats = snapshotToolStats(conversation);
|
|
901
1019
|
// Only update state + notify if still non-terminal (guards against abort race).
|
|
902
1020
|
if (!TERMINAL_STATUSES.has(managed.state.status)) {
|
|
903
1021
|
managed.state.completedAt = Date.now();
|
|
@@ -925,6 +1043,7 @@ export class SubagentManager {
|
|
|
925
1043
|
// Copy usage from the captured conversation reference — managed.conversation
|
|
926
1044
|
// may have been nulled by an external dispose() before catch runs.
|
|
927
1045
|
managed.state.usage = { ...conversation.usageStats };
|
|
1046
|
+
managed.state.stats = snapshotToolStats(conversation);
|
|
928
1047
|
|
|
929
1048
|
// Only update status if not already terminal (e.g. aborted).
|
|
930
1049
|
if (!TERMINAL_STATUSES.has(managed.state.status)) {
|
|
@@ -1167,9 +1286,19 @@ export class SubagentManager {
|
|
|
1167
1286
|
|
|
1168
1287
|
// ── Send message to subagent ──────────────────────────────────────────
|
|
1169
1288
|
|
|
1289
|
+
/**
|
|
1290
|
+
* Deliver a follow-up message to a live subagent.
|
|
1291
|
+
*
|
|
1292
|
+
* `opts.cronRunId` is the firing that produced THIS message, not the one the
|
|
1293
|
+
* subagent was spawned under: a continuation turn's spend belongs to the
|
|
1294
|
+
* firing that asked for it. Only the immediately-processed turn carries it,
|
|
1295
|
+
* since a queued message drains through the conversation's own queue, which
|
|
1296
|
+
* holds no per-message run options.
|
|
1297
|
+
*/
|
|
1170
1298
|
async sendMessage(
|
|
1171
1299
|
subagentId: string,
|
|
1172
1300
|
content: string,
|
|
1301
|
+
opts?: { cronRunId?: string | null },
|
|
1173
1302
|
): Promise<"sent" | "empty" | "not_found" | "terminal"> {
|
|
1174
1303
|
const trimmed = content?.trim();
|
|
1175
1304
|
if (!trimmed) {
|
|
@@ -1208,6 +1337,7 @@ export class SubagentManager {
|
|
|
1208
1337
|
...(managed.state.config.forceOverrideProfile
|
|
1209
1338
|
? { forceOverrideProfile: true }
|
|
1210
1339
|
: {}),
|
|
1340
|
+
...(opts?.cronRunId ? { cronRunId: opts.cronRunId } : {}),
|
|
1211
1341
|
})
|
|
1212
1342
|
.catch((err) => {
|
|
1213
1343
|
log.error({ subagentId, err }, "Subagent message processing failed");
|
|
@@ -1222,6 +1352,116 @@ export class SubagentManager {
|
|
|
1222
1352
|
return this.subagents.get(subagentId)?.state;
|
|
1223
1353
|
}
|
|
1224
1354
|
|
|
1355
|
+
/**
|
|
1356
|
+
* The subagent's tool-call counters, brought up to date first, or why there
|
|
1357
|
+
* are none (see {@link SubagentToolStatsReading}).
|
|
1358
|
+
*
|
|
1359
|
+
* `runSubagent` harvests when its awaited agent loop returns, but that is not
|
|
1360
|
+
* the end of the child's work: guidance queued during the run drains
|
|
1361
|
+
* afterwards, on the same conversation, and those calls land in the same
|
|
1362
|
+
* counters. So any read taken later re-reads them while the conversation is
|
|
1363
|
+
* still retained (see {@link refreshToolStats}), and the release freezes the
|
|
1364
|
+
* settled numbers. Readers that need the queued turn's calls included wait
|
|
1365
|
+
* for it first, via {@link settleQueuedTurns}.
|
|
1366
|
+
*
|
|
1367
|
+
* An id the manager does not hold is `unrecoverable` rather than unknown:
|
|
1368
|
+
* counters exist nowhere else, so no caller can ever obtain them, and the
|
|
1369
|
+
* only state a caller can be holding for such an id came from the durable
|
|
1370
|
+
* row.
|
|
1371
|
+
*/
|
|
1372
|
+
currentToolStats(subagentId: string): SubagentToolStatsReading {
|
|
1373
|
+
const managed = this.subagents.get(subagentId);
|
|
1374
|
+
if (!managed) {
|
|
1375
|
+
return { kind: "unrecoverable" };
|
|
1376
|
+
}
|
|
1377
|
+
this.refreshToolStats(managed);
|
|
1378
|
+
if (managed.state.stats) {
|
|
1379
|
+
return { kind: "counted", stats: managed.state.stats };
|
|
1380
|
+
}
|
|
1381
|
+
return managed.rehydrated
|
|
1382
|
+
? { kind: "unrecoverable" }
|
|
1383
|
+
: { kind: "unmeasured" };
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
/**
|
|
1387
|
+
* Wait for a follow-up turn queued during the subagent's run to finish.
|
|
1388
|
+
*
|
|
1389
|
+
* `runSubagent` marks the subagent terminal as soon as its own agent loop
|
|
1390
|
+
* returns, and the parent is told to read from there. Guidance queued during
|
|
1391
|
+
* that run drains afterwards though, on the same retained conversation, so a
|
|
1392
|
+
* read taken in that window sees the transcript and the counters from before
|
|
1393
|
+
* the guidance landed and never comes back for the rest. Waiting here closes
|
|
1394
|
+
* the window.
|
|
1395
|
+
*
|
|
1396
|
+
* Resolves `true` once the retained conversation is idle with an empty
|
|
1397
|
+
* queue, and `false` when `timeoutMs` elapses first, so the reader always
|
|
1398
|
+
* gets an answer within a bound and can say the subagent is still moving
|
|
1399
|
+
* rather than pass a partial result off as final.
|
|
1400
|
+
*
|
|
1401
|
+
* `true` comes back immediately when nothing can still be running: no
|
|
1402
|
+
* manager entry, no retained conversation (a released one has its transcript
|
|
1403
|
+
* and counters frozen), or a run that never had anything queued.
|
|
1404
|
+
*
|
|
1405
|
+
* Idle is confirmed across two observations a poll apart. The drain takes
|
|
1406
|
+
* the queue before it takes the processing lock (`drainQueue` shifts the
|
|
1407
|
+
* message, then `drainSingleMessage` awaits slash resolution and the
|
|
1408
|
+
* user-message persist before `runAgentLoop` sets processing), so a single
|
|
1409
|
+
* look into that gap finds an empty queue and an unlocked conversation while
|
|
1410
|
+
* the turn is in fact starting.
|
|
1411
|
+
*/
|
|
1412
|
+
async settleQueuedTurns(
|
|
1413
|
+
subagentId: string,
|
|
1414
|
+
timeoutMs: number = QUEUED_TURN_SETTLE_TIMEOUT_MS,
|
|
1415
|
+
): Promise<boolean> {
|
|
1416
|
+
const managed = this.subagents.get(subagentId);
|
|
1417
|
+
const conversation = managed?.conversation;
|
|
1418
|
+
if (!managed || !conversation || managed.hadEnqueuedMessages !== true) {
|
|
1419
|
+
return true;
|
|
1420
|
+
}
|
|
1421
|
+
const deadline = Date.now() + timeoutMs;
|
|
1422
|
+
let idleObservations = 0;
|
|
1423
|
+
for (;;) {
|
|
1424
|
+
// A release during the wait (the TTL sweep, or a dispose) freezes
|
|
1425
|
+
// everything a reader can see, so there is nothing left to wait for.
|
|
1426
|
+
if (managed.conversation !== conversation) {
|
|
1427
|
+
return true;
|
|
1428
|
+
}
|
|
1429
|
+
const remainingMs = deadline - Date.now();
|
|
1430
|
+
if (remainingMs <= 0) {
|
|
1431
|
+
return false;
|
|
1432
|
+
}
|
|
1433
|
+
if (conversation.isProcessing()) {
|
|
1434
|
+
idleObservations = 0;
|
|
1435
|
+
await conversation.waitForIdle({ timeoutMs: remainingMs });
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
if (conversation.hasQueuedMessages()) {
|
|
1439
|
+
idleObservations = 0;
|
|
1440
|
+
await sleep(Math.min(QUEUED_TURN_POLL_MS, remainingMs));
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
idleObservations += 1;
|
|
1444
|
+
if (idleObservations >= 2) {
|
|
1445
|
+
return true;
|
|
1446
|
+
}
|
|
1447
|
+
await sleep(Math.min(QUEUED_TURN_POLL_MS, remainingMs));
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* Re-read the child conversation's live counters into its state.
|
|
1453
|
+
*
|
|
1454
|
+
* Only ever updates a harvest that already happened: a run that never
|
|
1455
|
+
* reached its harvest (aborted before the first turn, or still going) has
|
|
1456
|
+
* nothing measured to report, and a zero taken mid-flight would read as
|
|
1457
|
+
* "this subagent used no tools" rather than "not measured yet".
|
|
1458
|
+
*/
|
|
1459
|
+
private refreshToolStats(managed: ManagedSubagent): void {
|
|
1460
|
+
if (managed.conversation && managed.state.stats) {
|
|
1461
|
+
managed.state.stats = snapshotToolStats(managed.conversation);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1225
1465
|
getByLabel(
|
|
1226
1466
|
label: string,
|
|
1227
1467
|
parentConversationId: string,
|
|
@@ -1296,6 +1536,10 @@ export class SubagentManager {
|
|
|
1296
1536
|
if (!managed.conversation) {
|
|
1297
1537
|
return;
|
|
1298
1538
|
}
|
|
1539
|
+
// Last chance at the counters: on the deferred path this release happens
|
|
1540
|
+
// after the queued follow-up turn drained, so this is the reading that
|
|
1541
|
+
// includes it. Everything read after this point is this snapshot.
|
|
1542
|
+
this.refreshToolStats(managed);
|
|
1299
1543
|
const conversation = managed.conversation;
|
|
1300
1544
|
removeSubagentConversation(conversation.conversationId, conversation);
|
|
1301
1545
|
conversation.dispose();
|
|
@@ -1388,7 +1632,7 @@ export class SubagentManager {
|
|
|
1388
1632
|
conversationId: state.conversationId,
|
|
1389
1633
|
label: state.config.label,
|
|
1390
1634
|
objective: state.config.objective,
|
|
1391
|
-
role: state.config.role ??
|
|
1635
|
+
role: state.config.role ?? DEFAULT_SUBAGENT_ROLE,
|
|
1392
1636
|
isFork: state.isFork,
|
|
1393
1637
|
sendResultToUser: state.config.sendResultToUser ?? null,
|
|
1394
1638
|
parentToolUseId: state.config.parentToolUseId ?? null,
|
|
@@ -1454,6 +1698,7 @@ export class SubagentManager {
|
|
|
1454
1698
|
state,
|
|
1455
1699
|
parentSendToClient: () => {},
|
|
1456
1700
|
retainedUntil: now + TERMINAL_RETENTION_MS,
|
|
1701
|
+
rehydrated: true,
|
|
1457
1702
|
};
|
|
1458
1703
|
this.subagents.set(rec.id, managed);
|
|
1459
1704
|
|
|
@@ -1609,6 +1854,10 @@ export class SubagentManager {
|
|
|
1609
1854
|
? config.sendResultToUser !== true
|
|
1610
1855
|
: config.sendResultToUser === false;
|
|
1611
1856
|
|
|
1857
|
+
// A queued follow-up turn means the snapshot we hold is stale; defer to a
|
|
1858
|
+
// read pointer so the parent picks up the queued turn's output instead.
|
|
1859
|
+
const deferred = managed.hadEnqueuedMessages === true;
|
|
1860
|
+
|
|
1612
1861
|
const message = buildSubagentTerminalMessage({
|
|
1613
1862
|
label: config.label,
|
|
1614
1863
|
subagentId: config.id,
|
|
@@ -1617,10 +1866,14 @@ export class SubagentManager {
|
|
|
1617
1866
|
silent,
|
|
1618
1867
|
finalText,
|
|
1619
1868
|
error: managed.state.error,
|
|
1620
|
-
|
|
1621
|
-
// read pointer so the parent picks up the queued turn's output instead.
|
|
1622
|
-
deferred: managed.hadEnqueuedMessages === true,
|
|
1869
|
+
deferred,
|
|
1623
1870
|
deniedTools,
|
|
1871
|
+
// Same staleness applies to the counters, and worse: the queued turn has
|
|
1872
|
+
// not run yet at this point, so any number quoted here would under-report
|
|
1873
|
+
// it, permanently, in a message that is never rewritten. The deferred
|
|
1874
|
+
// message sends the parent to `subagent_read`, whose footer re-reads the
|
|
1875
|
+
// counters once the queued turn has actually landed.
|
|
1876
|
+
...(deferred ? {} : { stats: managed.state.stats }),
|
|
1624
1877
|
});
|
|
1625
1878
|
|
|
1626
1879
|
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
|
+
}
|