@vellumai/assistant 0.10.2 → 0.10.3-dev.202606252046.9075fd5
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/bun.lock +20 -0
- package/docs/workspace-tools.md +42 -33
- package/eslint-rules/cli-no-daemon-internals.js +6 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +44 -0
- package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +28 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +41 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/openapi.yaml +196 -56
- package/package.json +4 -1
- package/scripts/test.sh +36 -15
- package/src/__tests__/actor-token-service.test.ts +36 -14
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
- package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
- package/src/__tests__/annotate-risk-options.test.ts +2 -0
- package/src/__tests__/approval-cascade.test.ts +2 -0
- package/src/__tests__/assistant-attachments.test.ts +42 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
- package/src/__tests__/btw-routes.test.ts +2 -0
- package/src/__tests__/build-persisted-content.test.ts +2 -0
- package/src/__tests__/call-controller.test.ts +19 -0
- package/src/__tests__/channel-approval-routes.test.ts +21 -26
- package/src/__tests__/channel-delivery-store.test.ts +28 -0
- package/src/__tests__/channel-guardian.test.ts +164 -78
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
- package/src/__tests__/channel-reply-delivery.test.ts +2 -0
- package/src/__tests__/code-search-tool.test.ts +585 -0
- package/src/__tests__/compaction-events.test.ts +2 -0
- package/src/__tests__/compaction-ledger-store.test.ts +128 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
- package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
- package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
- package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
- package/src/__tests__/config-loader-backfill.test.ts +123 -10
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
- package/src/__tests__/consult-deadline.test.ts +60 -0
- package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
- package/src/__tests__/contact-store-user-file.test.ts +7 -10
- package/src/__tests__/contacts-relay-reads.test.ts +19 -24
- package/src/__tests__/contacts-write.test.ts +0 -2
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +134 -0
- package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
- package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
- package/src/__tests__/conversation-fork-crud.test.ts +354 -24
- package/src/__tests__/conversation-history-web-search.test.ts +2 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
- package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
- package/src/__tests__/conversation-pairing.test.ts +2 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
- package/src/__tests__/conversation-process-callsite.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +91 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
- package/src/__tests__/conversation-slash-queue.test.ts +2 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-speed-override.test.ts +2 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +29 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -0
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
- package/src/__tests__/conversation-usage.test.ts +2 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
- package/src/__tests__/db-migration-rollback.test.ts +205 -171
- package/src/__tests__/db-test-helpers.ts +5 -4
- package/src/__tests__/delete-propagation.test.ts +5 -3
- package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
- package/src/__tests__/disk-pressure-guard.test.ts +41 -0
- package/src/__tests__/dm-backfill.test.ts +6 -4
- package/src/__tests__/dm-persistence.test.ts +2 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +7 -6
- package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
- package/src/__tests__/exploration-drift-hook.test.ts +3 -2
- package/src/__tests__/filing-service.test.ts +2 -0
- package/src/__tests__/guardian-binding-drift-heal.test.ts +104 -19
- package/src/__tests__/guardian-dispatch.test.ts +140 -1
- package/src/__tests__/guardian-expiry-notifier.test.ts +282 -0
- package/src/__tests__/guardian-outbound-http.test.ts +13 -0
- package/src/__tests__/guardian-routing-state.test.ts +6 -10
- package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
- package/src/__tests__/heartbeat-service.test.ts +2 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +46 -19
- package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
- package/src/__tests__/helpers/mock-logger.ts +1 -0
- package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
- package/src/__tests__/host-app-control-routes.test.ts +24 -30
- package/src/__tests__/host-bash-routes.test.ts +31 -41
- package/src/__tests__/host-browser-routes.test.ts +26 -32
- package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
- package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
- package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
- package/src/__tests__/http-user-message-parity.test.ts +290 -8
- package/src/__tests__/inbound-invite-redemption.test.ts +115 -10
- package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/invite-redemption-service.test.ts +471 -53
- package/src/__tests__/invite-routes-http.test.ts +34 -0
- package/src/__tests__/invite-service-ipc.test.ts +65 -2
- package/src/__tests__/llm-context-normalization.test.ts +105 -0
- package/src/__tests__/llm-request-log-error-payload.test.ts +71 -9
- package/src/__tests__/llm-usage-store.test.ts +25 -0
- package/src/__tests__/mcp-config-secret-boundary.test.ts +392 -0
- package/src/__tests__/mcp-health-check.test.ts +2 -1
- package/src/__tests__/media-stream-server-integration.test.ts +127 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
- package/src/__tests__/messaging-send-tool.test.ts +2 -0
- package/src/__tests__/migration-import-from-url.test.ts +2 -2
- package/src/__tests__/mtime-cache.test.ts +275 -5
- package/src/__tests__/native-web-search.test.ts +2 -0
- package/src/__tests__/non-member-access-request.test.ts +191 -17
- package/src/__tests__/notification-broadcaster.test.ts +4 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
- package/src/__tests__/notification-deep-link.test.ts +6 -0
- package/src/__tests__/notification-guardian-path.test.ts +19 -0
- package/src/__tests__/onboarding-persona-write.test.ts +52 -22
- package/src/__tests__/openai-provider.test.ts +22 -12
- package/src/__tests__/openai-responses-provider.test.ts +12 -2
- package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
- package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
- package/src/__tests__/persona-resolver.test.ts +75 -45
- package/src/__tests__/plugin-bootstrap.test.ts +21 -83
- package/src/__tests__/plugin-disabled-state.test.ts +190 -0
- package/src/__tests__/plugin-pipeline.test.ts +96 -0
- package/src/__tests__/plugin-route-contribution.test.ts +6 -19
- package/src/__tests__/plugin-tool-contribution.test.ts +5 -20
- package/src/__tests__/plugin-types.test.ts +2 -4
- package/src/__tests__/process-message-background-slack.test.ts +2 -0
- package/src/__tests__/process-message-display-content.test.ts +2 -0
- package/src/__tests__/provider-error-scenarios.test.ts +5 -4
- package/src/__tests__/provider-usage-tracking.test.ts +39 -0
- package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
- package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
- package/src/__tests__/reaction-persistence.test.ts +51 -4
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
- package/src/__tests__/registry.test.ts +4 -1
- package/src/__tests__/relay-server.test.ts +758 -32
- package/src/__tests__/runtime-attachment-metadata.test.ts +9 -12
- package/src/__tests__/secret-ingress-http.test.ts +14 -0
- package/src/__tests__/send-endpoint-busy.test.ts +30 -8
- package/src/__tests__/settings-routes.test.ts +32 -0
- package/src/__tests__/skills.test.ts +44 -0
- package/src/__tests__/slack-inbound-verification.test.ts +47 -2
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +79 -0
- package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
- package/src/__tests__/stt-hints.test.ts +49 -15
- package/src/__tests__/subagent-detail.test.ts +27 -0
- package/src/__tests__/subagent-disposal.test.ts +65 -0
- package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
- package/src/__tests__/subagent-notify-parent.test.ts +2 -0
- package/src/__tests__/subagent-role-registry.test.ts +24 -6
- package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
- package/src/__tests__/subagent-tools.test.ts +398 -1
- package/src/__tests__/suggestion-routes.test.ts +2 -0
- package/src/__tests__/thread-backfill.test.ts +3 -3
- package/src/__tests__/title-generate-hook.test.ts +2 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
- package/src/__tests__/tool-executor.test.ts +16 -11
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
- package/src/__tests__/tool-start-timestamp.test.ts +2 -0
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +12 -12
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
- package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
- package/src/__tests__/trusted-contact-verification.test.ts +79 -54
- package/src/__tests__/twilio-routes.test.ts +96 -0
- package/src/__tests__/ui-file-upload-surface.test.ts +86 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
- package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
- package/src/__tests__/voice-invite-redemption.test.ts +216 -20
- package/src/__tests__/web-search-backend-failure.test.ts +2 -0
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +208 -0
- package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +14 -35
- package/src/__tests__/workspace-tool-loader.test.ts +195 -2
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
- package/src/agent/loop-exclusive-tool.test.ts +154 -0
- package/src/agent/loop-native-web-search.test.ts +200 -0
- package/src/agent/loop.ts +164 -1
- package/src/api/constants/sse-replay.ts +41 -0
- package/src/api/events/conversation-notice.ts +26 -0
- package/src/api/index.ts +19 -1
- package/src/api/responses/llm-request-log-entry.ts +29 -0
- package/src/api/responses/subagent-detail.ts +17 -0
- package/src/api/surfaces.ts +39 -3
- package/src/approvals/guardian-channel-delivery.ts +30 -0
- package/src/approvals/guardian-expiry-notifier.ts +148 -0
- package/src/approvals/guardian-request-resolvers.ts +17 -15
- package/src/calls/__tests__/relay-setup-router.test.ts +267 -17
- package/src/calls/call-domain.ts +3 -3
- package/src/calls/guardian-dispatch.ts +14 -9
- package/src/calls/inbound-trust-reader.ts +24 -2
- package/src/calls/media-stream-server.ts +21 -0
- package/src/calls/relay-access-wait.ts +6 -6
- package/src/calls/relay-server.ts +188 -51
- package/src/calls/relay-setup-router.ts +47 -17
- package/src/calls/relay-verification.ts +4 -4
- package/src/calls/stt-hints.ts +9 -12
- package/src/calls/twilio-routes.ts +14 -4
- package/src/channels/types.ts +10 -20
- package/src/cli/commands/__tests__/cache.test.ts +8 -1
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/cache.ts +194 -181
- package/src/cli/commands/contacts.ts +10 -7
- package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
- package/src/cli/commands/db/status.ts +37 -1
- package/src/cli/commands/mcp.ts +252 -218
- package/src/cli/commands/memory/__tests__/worker.test.ts +432 -0
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/worker.ts +242 -0
- package/src/cli/commands/plugins.ts +125 -13
- package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
- package/src/cli/lib/__tests__/publish-plugin.test.ts +404 -0
- package/src/cli/lib/list-installed-plugins.ts +179 -1
- package/src/cli/lib/publish-plugin.ts +633 -0
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
- package/src/config/__tests__/sync-gated-profiles.test.ts +16 -10
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
- package/src/config/bundled-skills/subagent/SKILL.md +17 -2
- package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
- package/src/config/call-site-defaults.ts +0 -6
- package/src/config/feature-flag-registry.json +5 -13
- package/src/config/llm-resolver.ts +0 -3
- package/src/config/loader.ts +38 -5
- package/src/config/prune-seeded-callsite-defaults.ts +110 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +0 -7
- package/src/config/schemas/heartbeat.ts +2 -5
- package/src/config/schemas/llm.ts +3 -12
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/memory-v3.ts +7 -0
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/schemas/timeouts.ts +8 -0
- package/src/config/seed-inference-profiles.ts +30 -34
- package/src/config/skills.ts +27 -5
- package/src/config/sync-gated-profiles.ts +12 -16
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +128 -0
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
- package/src/contacts/__tests__/member-write-relay.test.ts +226 -0
- package/src/contacts/contact-store.ts +27 -237
- package/src/contacts/contacts-write.ts +18 -55
- package/src/contacts/gateway-channel-read.ts +51 -0
- package/src/contacts/guardian-delivery-reader.ts +223 -0
- package/src/contacts/member-write-relay.ts +183 -0
- package/src/contacts/types.ts +3 -15
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +35 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
- package/src/daemon/assistant-attachments.ts +27 -4
- package/src/daemon/conversation-agent-loop.ts +28 -0
- package/src/daemon/conversation-notices.ts +60 -0
- package/src/daemon/conversation-process.ts +35 -16
- package/src/daemon/conversation-surfaces.ts +111 -38
- package/src/daemon/conversation-tool-setup.ts +71 -30
- package/src/daemon/conversation.ts +23 -1
- package/src/daemon/disk-pressure-guard.ts +12 -2
- package/src/daemon/event-loop-watchdog.ts +28 -1
- package/src/daemon/external-plugins-bootstrap.ts +17 -41
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -2
- package/src/daemon/handlers/__tests__/config-channels.test.ts +220 -0
- package/src/daemon/handlers/config-a2a.ts +6 -14
- package/src/daemon/handlers/config-channels.ts +67 -26
- package/src/daemon/handlers/conversations.ts +77 -0
- package/src/daemon/lifecycle.ts +16 -0
- package/src/daemon/mcp-reload-service.ts +10 -0
- package/src/daemon/memory-v2-startup.test.ts +72 -0
- package/src/daemon/memory-v2-startup.ts +87 -19
- package/src/daemon/message-types/conversations.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +12 -12
- package/src/daemon/server.ts +0 -4
- package/src/daemon/shutdown-handlers.ts +20 -0
- package/src/daemon/tool-setup-types.ts +9 -0
- package/src/heartbeat/heartbeat-service.ts +5 -0
- package/src/home/relationship-state-writer.ts +5 -0
- package/src/hooks/hook-loader.ts +341 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
- package/src/ipc/assistant-server.ts +2 -2
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -0
- package/src/mcp/client.ts +15 -1
- package/src/mcp/mcp-auth-orchestrator.ts +6 -1
- package/src/mcp/mcp-header-store.ts +134 -0
- package/src/mcp/mcp-oauth-provider.ts +19 -8
- package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
- package/src/memory/__tests__/prompt-override.test.ts +192 -0
- package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
- package/src/memory/compaction-ledger-store.ts +107 -0
- package/src/memory/conversation-crud.ts +129 -61
- package/src/memory/db-connection.ts +22 -3
- package/src/memory/db-init.ts +37 -503
- package/src/memory/db-singleton.ts +6 -4
- package/src/memory/jobs-worker.ts +104 -0
- package/src/memory/llm-request-log-store.ts +26 -1
- package/src/memory/llm-usage-store.ts +48 -20
- package/src/memory/memory-retrospective-job.ts +14 -8
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +152 -56
- package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
- package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
- package/src/memory/migrations/302-create-compaction-events.ts +107 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +297 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
- package/src/memory/migrations/run-migrations.ts +106 -10
- package/src/memory/migrations/validate-migration-state.ts +105 -67
- package/src/memory/prompt-override.ts +129 -0
- package/src/memory/schema/contacts.ts +6 -2
- package/src/memory/schema/conversations.ts +37 -0
- package/src/memory/schema/infrastructure.ts +20 -0
- package/src/memory/steps.ts +1294 -0
- package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
- package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
- package/src/memory/v2/cli-command-store.ts +75 -38
- package/src/memory/v2/prompts/consolidation.ts +13 -82
- package/src/memory/v2/prompts/router.ts +21 -93
- package/src/memory/v2/skill-store.ts +68 -31
- package/src/memory/watchdog-events-store.ts +87 -0
- package/src/memory/worker-control.ts +204 -0
- package/src/memory/worker-process.ts +89 -0
- package/src/messaging/channel-binding-metadata.ts +31 -0
- package/src/messaging/provider-types.ts +8 -0
- package/src/messaging/providers/slack/binding-metadata.ts +60 -0
- package/src/notifications/__tests__/broadcaster.test.ts +8 -8
- package/src/notifications/__tests__/connected-channels.test.ts +86 -0
- package/src/notifications/__tests__/decision-engine.test.ts +78 -9
- package/src/notifications/__tests__/destination-resolver.test.ts +151 -0
- package/src/notifications/broadcaster.ts +8 -1
- package/src/notifications/decision-engine.ts +15 -7
- package/src/notifications/destination-resolver.ts +53 -25
- package/src/notifications/emit-signal.ts +34 -15
- package/src/onboarding/checkin-event.test.ts +222 -0
- package/src/onboarding/checkin-event.ts +321 -0
- package/src/onboarding/schedule-checkin.ts +190 -0
- package/src/permissions/question-prompter.test.ts +1 -1
- package/src/permissions/question-prompter.ts +7 -4
- package/src/plugin-api/index.ts +12 -12
- package/src/plugin-api/types.ts +12 -14
- package/src/plugin-api/vision-support.test.ts +28 -4
- package/src/plugin-api/vision-support.ts +66 -31
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/empty-response/hooks/stop.ts +2 -2
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +4 -3
- package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/history-repair/hooks/stop.ts +2 -2
- package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
- package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +12 -13
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +14 -22
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/image-recovery/hooks/stop.ts +2 -2
- package/src/plugins/defaults/index.ts +0 -35
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +33 -3
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +48 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +4 -8
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +43 -15
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
- package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +77 -13
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +12 -11
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +2 -2
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -2
- package/src/plugins/defaults/title-generate/hooks/stop.ts +2 -2
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +2 -2
- package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +2 -2
- package/src/plugins/disabled-state.ts +31 -0
- package/src/plugins/external-plugin-loader.ts +2 -2
- package/src/plugins/mtime-cache.ts +186 -330
- package/src/plugins/pipeline.ts +111 -13
- package/src/plugins/registry.ts +59 -16
- package/src/plugins/surface-import.ts +121 -0
- package/src/plugins/types.ts +7 -9
- package/src/prompts/persona-resolver.ts +43 -11
- package/src/providers/anthropic/client.ts +5 -0
- package/src/providers/call-site-routing.ts +45 -0
- package/src/providers/model-catalog.ts +16 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +321 -0
- package/src/providers/openai/api-error-normalization.ts +270 -0
- package/src/providers/openai/chat-completions-provider.ts +37 -83
- package/src/providers/openai/responses-provider.ts +50 -46
- package/src/providers/openrouter/client.ts +5 -0
- package/src/providers/provider-send-message.ts +10 -0
- package/src/providers/ratelimit.ts +10 -0
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +10 -0
- package/src/providers/types.ts +22 -0
- package/src/providers/usage-tracking.ts +10 -0
- package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +184 -0
- package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
- package/src/runtime/__tests__/local-principal-trust.test.ts +162 -0
- package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +402 -123
- package/src/runtime/access-request-helper.ts +18 -39
- package/src/runtime/actor-trust-resolver.ts +46 -19
- package/src/runtime/anchored-guardian.test.ts +109 -0
- package/src/runtime/anchored-guardian.ts +86 -0
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/assistant-stream-state.ts +9 -2
- package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
- package/src/runtime/auth/require-bound-guardian.ts +21 -11
- package/src/runtime/channel-verification-service.ts +56 -31
- package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
- package/src/runtime/guardian-vellum-migration.ts +69 -8
- package/src/runtime/invite-redemption-service.ts +213 -187
- package/src/runtime/local-actor-identity.test.ts +108 -0
- package/src/runtime/local-actor-identity.ts +85 -13
- package/src/runtime/local-principal-trust.ts +52 -0
- package/src/runtime/member-verdict-cache.ts +0 -0
- package/src/runtime/pending-interactions.ts +11 -1
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/contact-routes.test.ts +305 -0
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +92 -0
- package/src/runtime/routes/__tests__/http-adapter-actor-header.test.ts +129 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +188 -0
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +13 -5
- package/src/runtime/routes/channel-verification-routes.ts +3 -3
- package/src/runtime/routes/contact-routes.ts +48 -57
- package/src/runtime/routes/conversation-cli-routes.ts +4 -5
- package/src/runtime/routes/conversation-list-routes.ts +4 -7
- package/src/runtime/routes/conversation-query-routes.ts +72 -0
- package/src/runtime/routes/conversation-routes.ts +84 -85
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/global-search-routes.ts +3 -1
- package/src/runtime/routes/guardian-action-routes.ts +4 -5
- package/src/runtime/routes/host-app-control-routes.ts +5 -4
- package/src/runtime/routes/host-bash-routes.ts +5 -4
- package/src/runtime/routes/host-browser-routes.ts +9 -11
- package/src/runtime/routes/host-cu-routes.ts +5 -4
- package/src/runtime/routes/host-file-routes.ts +5 -4
- package/src/runtime/routes/host-transfer-routes.ts +6 -6
- package/src/runtime/routes/http-adapter.ts +16 -1
- package/src/runtime/routes/identity-routes.ts +3 -2
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -59
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +13 -5
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/llm-context-normalization.ts +83 -0
- package/src/runtime/routes/mcp-auth-routes.ts +171 -19
- package/src/runtime/routes/migration-rollback-routes.ts +4 -3
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
- package/src/runtime/routes/settings-routes.ts +8 -3
- package/src/runtime/routes/subagents-routes.ts +5 -0
- package/src/runtime/routes/surface-action-routes.ts +42 -56
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
- package/src/runtime/services/conversation-serializer.ts +13 -58
- package/src/runtime/tool-grant-request-helper.ts +3 -3
- package/src/runtime/trust-verdict-consumer.ts +111 -40
- package/src/runtime/verification-outbound-actions.ts +18 -18
- package/src/signals/user-message.ts +16 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
- package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
- package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +22 -32
- package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
- package/src/subagent/index.ts +1 -1
- package/src/subagent/manager.ts +254 -33
- package/src/subagent/types.ts +11 -4
- package/src/telemetry/types.ts +34 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +87 -3
- package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
- package/src/tools/ask-question/ask-question-tool.ts +13 -0
- package/src/tools/executor.ts +4 -4
- package/src/tools/filesystem/search.ts +543 -0
- package/src/tools/registry.ts +38 -0
- package/src/tools/shared/filesystem/path-policy.ts +12 -5
- package/src/tools/subagent/consult-deadline.ts +49 -0
- package/src/tools/subagent/spawn.ts +234 -5
- package/src/tools/tool-approval-handler.ts +1 -1
- package/src/tools/tool-defaults.ts +9 -2
- package/src/tools/tool-manifest.ts +3 -0
- package/src/tools/types.ts +131 -25
- package/src/tools/workspace-tools/loader.ts +348 -244
- package/src/util/errors.ts +26 -1
- package/src/util/logger.ts +9 -0
- package/src/util/platform.ts +19 -0
- package/src/util/telemetry-db-path.ts +24 -0
- package/src/workflows/library.test.ts +140 -0
- package/src/workflows/library.ts +82 -28
- package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
- package/src/workspace/migrations/031-drop-user-md.ts +232 -148
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +14 -66
- package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +134 -0
- package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
- package/src/daemon/workspace-tools-watcher.ts +0 -328
- package/src/memory/migrations/registry.ts +0 -573
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -153
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
- package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
- package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
- package/src/plugins/defaults/advisor/config.ts +0 -21
- package/src/plugins/defaults/advisor/consult.ts +0 -93
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
- package/src/plugins/defaults/advisor/package.json +0 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +0 -65
- package/src/providers/openai/__tests__/api-error-detail.test.ts +0 -120
package/src/subagent/manager.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { wrapWithCallSiteRouting } from "../providers/call-site-routing.js";
|
|
|
24
24
|
import { resolveDefaultProvider } from "../providers/connection-resolution.js";
|
|
25
25
|
import { RateLimitProvider } from "../providers/ratelimit.js";
|
|
26
26
|
import { listProviders } from "../providers/registry.js";
|
|
27
|
+
import type { Message, TextContent } from "../providers/types.js";
|
|
27
28
|
import { createAbortReason } from "../util/abort-reasons.js";
|
|
28
29
|
import { ProviderNotConfiguredError } from "../util/errors.js";
|
|
29
30
|
import { getLogger } from "../util/logger.js";
|
|
@@ -58,6 +59,38 @@ export function mergeSkillIds(
|
|
|
58
59
|
return [...new Set([...roleSkillIds, ...(configSkillIds ?? [])])];
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
// ── Final-text extraction helper ────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Concatenate the `text` blocks of the conversation's trailing assistant
|
|
66
|
+
* message. Used by `spawnAndAwait` to return the child's final synthesis to
|
|
67
|
+
* the awaiting caller. Returns an empty string when the conversation has no
|
|
68
|
+
* assistant message or the final assistant message carries no text blocks
|
|
69
|
+
* (e.g. it ended on a tool_use).
|
|
70
|
+
*/
|
|
71
|
+
function extractFinalAssistantText(messages: Message[]): string {
|
|
72
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
73
|
+
const message = messages[i];
|
|
74
|
+
if (message.role !== "assistant") continue;
|
|
75
|
+
return message.content
|
|
76
|
+
.filter((block): block is TextContent => block.type === "text")
|
|
77
|
+
.map((block) => block.text)
|
|
78
|
+
.join("");
|
|
79
|
+
}
|
|
80
|
+
return "";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Pull the user-visible text out of a streaming delta event, or null for any
|
|
85
|
+
* other event type. Used by the synchronous `onText` tap to forward
|
|
86
|
+
* `assistant_text_delta` / `assistant_thinking_delta` chunks to the caller.
|
|
87
|
+
*/
|
|
88
|
+
function extractDeltaText(msg: ServerMessage): string | null {
|
|
89
|
+
if (msg.type === "assistant_text_delta") return msg.text;
|
|
90
|
+
if (msg.type === "assistant_thinking_delta") return msg.thinking;
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
61
94
|
// ── Default subagent system prompt ──────────────────────────────────────
|
|
62
95
|
|
|
63
96
|
function buildSubagentSystemPrompt(
|
|
@@ -110,6 +143,19 @@ interface ManagedSubagent {
|
|
|
110
143
|
* the release to the TTL sweep rather than tearing down mid-drain.
|
|
111
144
|
*/
|
|
112
145
|
hadEnqueuedMessages?: boolean;
|
|
146
|
+
/**
|
|
147
|
+
* Set on the synchronous `spawnAndAwait` path. When true, `runSubagent`
|
|
148
|
+
* skips the terminal parent-injection (`notifyParentTerminal`) — the awaiting
|
|
149
|
+
* caller receives the child's final text directly, so re-injecting a
|
|
150
|
+
* "read the result" notification into the parent would be redundant noise.
|
|
151
|
+
*/
|
|
152
|
+
synchronous?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Optional text tap for the synchronous path. When set, `wrappedSendToClient`
|
|
155
|
+
* forwards each `assistant_text_delta` / `assistant_thinking_delta` chunk to
|
|
156
|
+
* this callback IN ADDITION to the normal `subagent_event` envelope.
|
|
157
|
+
*/
|
|
158
|
+
onText?: (chunk: string) => void;
|
|
113
159
|
}
|
|
114
160
|
|
|
115
161
|
export interface SubagentNotificationInfo {
|
|
@@ -121,6 +167,21 @@ export interface SubagentNotificationInfo {
|
|
|
121
167
|
objective?: string;
|
|
122
168
|
}
|
|
123
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Thrown by `spawnAndAwait` when the run is aborted (e.g. an external timeout)
|
|
172
|
+
* before reaching a terminal `completed` state. Carries `partialText` — the
|
|
173
|
+
* child's trailing assistant text captured at the moment of abort — so a caller
|
|
174
|
+
* that times out a long generation can still surface the partial result instead
|
|
175
|
+
* of discarding it. Extends `Error` with the same legacy message, so callers
|
|
176
|
+
* that only inspect `.message` keep working.
|
|
177
|
+
*/
|
|
178
|
+
export class SubagentAbortedError extends Error {
|
|
179
|
+
constructor(readonly partialText: string) {
|
|
180
|
+
super("Subagent run aborted before completion.");
|
|
181
|
+
this.name = "SubagentAbortedError";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
124
185
|
export class SubagentManager {
|
|
125
186
|
/** subagentId → ManagedSubagent */
|
|
126
187
|
private subagents = new Map<string, ManagedSubagent>();
|
|
@@ -145,6 +206,30 @@ export class SubagentManager {
|
|
|
145
206
|
config: Omit<SubagentConfig, "id">,
|
|
146
207
|
parentSendToClient: (msg: ServerMessage) => void,
|
|
147
208
|
): Promise<string> {
|
|
209
|
+
const { subagentId } = await this.setUpSubagent(config, parentSendToClient);
|
|
210
|
+
|
|
211
|
+
// ── Kick off the agent loop (fire-and-forget) ───────────────────
|
|
212
|
+
this.runSubagent(subagentId, config.objective).catch((err) => {
|
|
213
|
+
log.error({ subagentId, err }, "Subagent run failed unexpectedly");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
return subagentId;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── Internal: shared spawn setup ──────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Perform all spawn-time setup shared by `spawn` and `spawnAndAwait`:
|
|
223
|
+
* enforce the depth limit, resolve role/provider/system prompt, construct
|
|
224
|
+
* the child Conversation, register it, and emit the `subagent_spawned`
|
|
225
|
+
* event. Does NOT start the agent loop — the caller decides whether to run
|
|
226
|
+
* fire-and-forget (`spawn`) or awaited (`spawnAndAwait`).
|
|
227
|
+
*/
|
|
228
|
+
private async setUpSubagent(
|
|
229
|
+
config: Omit<SubagentConfig, "id">,
|
|
230
|
+
parentSendToClient: (msg: ServerMessage) => void,
|
|
231
|
+
opts?: { synchronous?: boolean; onText?: (chunk: string) => void },
|
|
232
|
+
): Promise<{ subagentId: string; managed: ManagedSubagent }> {
|
|
148
233
|
// ── Limit checks ────────────────────────────────────────────────
|
|
149
234
|
|
|
150
235
|
// Depth check: prevent subagents from spawning nested subagents.
|
|
@@ -159,17 +244,20 @@ export class SubagentManager {
|
|
|
159
244
|
|
|
160
245
|
// ── Resolve role ─────────────────────────────────────────────────
|
|
161
246
|
const isFork = config.fork === true;
|
|
162
|
-
|
|
247
|
+
const role: SubagentRole = (config.role as SubagentRole) ?? "general";
|
|
163
248
|
if (isFork && role !== "general") {
|
|
249
|
+
// A context-inheriting subagent normally keeps the parent's `general`
|
|
250
|
+
// role so its KV cache stays aligned with the parent conversation. An
|
|
251
|
+
// explicit non-general role opts out of that alignment on purpose
|
|
252
|
+
// (e.g. the advisor role running on a stronger profile), so honor it.
|
|
164
253
|
log.warn(
|
|
165
254
|
{
|
|
166
255
|
requestedRole: role,
|
|
167
256
|
parentConversationId: config.parentConversationId,
|
|
168
257
|
label: config.label,
|
|
169
258
|
},
|
|
170
|
-
"Fork requested with non-general role —
|
|
259
|
+
"Fork requested with non-general role — caller opted out of parent KV-cache alignment",
|
|
171
260
|
);
|
|
172
|
-
role = "general";
|
|
173
261
|
}
|
|
174
262
|
if (!SUBAGENT_ROLE_REGISTRY[role]) {
|
|
175
263
|
throw new Error(
|
|
@@ -219,19 +307,21 @@ export class SubagentManager {
|
|
|
219
307
|
|
|
220
308
|
let systemPrompt: string;
|
|
221
309
|
if (isFork) {
|
|
222
|
-
// Forks
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
310
|
+
// Forks default to the parent's system prompt verbatim — no subagent
|
|
311
|
+
// preamble — so the KV cache stays aligned with the parent. An explicit
|
|
312
|
+
// `systemPromptOverride` opts out of that alignment and takes precedence
|
|
313
|
+
// (e.g. the advisor role framing the inherited context as advice).
|
|
314
|
+
const resolved =
|
|
315
|
+
config.systemPromptOverride ??
|
|
316
|
+
config.parentSystemPrompt ??
|
|
317
|
+
parentConversation?.getCurrentSystemPrompt();
|
|
318
|
+
if (!resolved) {
|
|
319
|
+
throw new Error(
|
|
320
|
+
"Fork spawn requires a parent system prompt but neither config.parentSystemPrompt " +
|
|
321
|
+
"nor findConversation yielded one.",
|
|
322
|
+
);
|
|
234
323
|
}
|
|
324
|
+
systemPrompt = resolved;
|
|
235
325
|
} else {
|
|
236
326
|
systemPrompt =
|
|
237
327
|
config.systemPromptOverride ??
|
|
@@ -279,11 +369,20 @@ export class SubagentManager {
|
|
|
279
369
|
conversation: null! as Conversation,
|
|
280
370
|
state,
|
|
281
371
|
parentSendToClient,
|
|
372
|
+
...(opts?.synchronous ? { synchronous: true } : {}),
|
|
373
|
+
...(opts?.onText ? { onText: opts.onText } : {}),
|
|
282
374
|
};
|
|
283
375
|
|
|
284
376
|
// Wrap sendToClient to envelope all events with the subagent ID.
|
|
285
377
|
// Reads from managed.parentSendToClient so reconnects are picked up.
|
|
286
378
|
const wrappedSendToClient = (msg: ServerMessage): void => {
|
|
379
|
+
// Tap streaming text/thinking deltas for the synchronous caller (if any),
|
|
380
|
+
// in addition to the normal envelope below. Reads from managed.onText so
|
|
381
|
+
// the synchronous path can forward chunks without altering event routing.
|
|
382
|
+
if (managed.onText) {
|
|
383
|
+
const text = extractDeltaText(msg);
|
|
384
|
+
if (text) managed.onText(text);
|
|
385
|
+
}
|
|
287
386
|
managed.parentSendToClient({
|
|
288
387
|
type: "subagent_event",
|
|
289
388
|
subagentId,
|
|
@@ -298,7 +397,16 @@ export class SubagentManager {
|
|
|
298
397
|
systemPrompt,
|
|
299
398
|
wrappedSendToClient,
|
|
300
399
|
workingDir,
|
|
301
|
-
{
|
|
400
|
+
{
|
|
401
|
+
maxTokens,
|
|
402
|
+
cacheTtl: "5m",
|
|
403
|
+
// The advisor consult runs tool-less for CLIENT tools but should ground
|
|
404
|
+
// its guidance with provider-native web search when the resolved
|
|
405
|
+
// provider supports it. This is a server tool the provider runs itself,
|
|
406
|
+
// so it stays one-shot — no client tool surfaced, allowlist unchanged.
|
|
407
|
+
// Other roles keep the default (no native search appended).
|
|
408
|
+
...(role === "advisor" ? { enableNativeWebSearch: true } : {}),
|
|
409
|
+
},
|
|
302
410
|
);
|
|
303
411
|
|
|
304
412
|
// Mark conversation as having no direct client — it routes through parent.
|
|
@@ -325,16 +433,19 @@ export class SubagentManager {
|
|
|
325
433
|
conversation.setAssistantId(parentConversation.assistantId);
|
|
326
434
|
}
|
|
327
435
|
|
|
328
|
-
if (isFork) {
|
|
329
|
-
//
|
|
330
|
-
//
|
|
436
|
+
if (isFork && !config.systemPromptOverride) {
|
|
437
|
+
// A verbatim-prompt fork pins the parent's system prompt as-is, skipping
|
|
438
|
+
// the dynamic rebuild so the KV cache stays aligned with the parent. A
|
|
439
|
+
// fork that supplies its own override prompt opts out of that alignment,
|
|
440
|
+
// so leave `hasSystemPromptOverride` at its default.
|
|
331
441
|
conversation.hasSystemPromptOverride = true;
|
|
332
442
|
}
|
|
333
443
|
|
|
334
|
-
// Apply role
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
|
|
444
|
+
// Apply the role's tool allowlist when one is defined. The `general` role
|
|
445
|
+
// has `allowedTools: undefined`, so default forks (which keep the general
|
|
446
|
+
// role) are unaffected; a fork carrying an explicit role gets its
|
|
447
|
+
// allowlist applied like any other subagent.
|
|
448
|
+
if (roleConfig.allowedTools) {
|
|
338
449
|
conversation.setSubagentAllowedTools(new Set(roleConfig.allowedTools));
|
|
339
450
|
}
|
|
340
451
|
|
|
@@ -393,12 +504,65 @@ export class SubagentManager {
|
|
|
393
504
|
"Subagent spawned",
|
|
394
505
|
);
|
|
395
506
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
log.error({ subagentId, err }, "Subagent run failed unexpectedly");
|
|
399
|
-
});
|
|
507
|
+
return { subagentId, managed };
|
|
508
|
+
}
|
|
400
509
|
|
|
401
|
-
|
|
510
|
+
// ── Spawn and await (synchronous) ─────────────────────────────────────
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Spawn a subagent and AWAIT its run, resolving to the child's final
|
|
514
|
+
* assistant text. Unlike `spawn` (fire-and-forget), the caller blocks until
|
|
515
|
+
* the child reaches a terminal state and receives the text directly — so the
|
|
516
|
+
* terminal parent-injection (`notifyParentTerminal`) is skipped on this path.
|
|
517
|
+
*
|
|
518
|
+
* `opts.signal` aborts the underlying run when triggered (e.g. an external
|
|
519
|
+
* timeout). `opts.onText` receives each streaming text/thinking chunk in
|
|
520
|
+
* addition to the normal `subagent_event` envelope.
|
|
521
|
+
*/
|
|
522
|
+
async spawnAndAwait(
|
|
523
|
+
config: Omit<SubagentConfig, "id">,
|
|
524
|
+
parentSendToClient: (msg: ServerMessage) => void,
|
|
525
|
+
opts?: { signal?: AbortSignal; onText?: (chunk: string) => void },
|
|
526
|
+
): Promise<string> {
|
|
527
|
+
const { subagentId, managed } = await this.setUpSubagent(
|
|
528
|
+
config,
|
|
529
|
+
parentSendToClient,
|
|
530
|
+
{ synchronous: true, ...(opts?.onText ? { onText: opts.onText } : {}) },
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
// Wire the external signal to abort the child conversation. If the signal
|
|
534
|
+
// is already aborted, abort immediately so the run rejects promptly.
|
|
535
|
+
const signal = opts?.signal;
|
|
536
|
+
const onAbort = (): void => {
|
|
537
|
+
// Route through the manager abort path so the subagent is marked terminal
|
|
538
|
+
// ("aborted") and broadcast as such. A bare conversation.abort() leaves
|
|
539
|
+
// status non-terminal, so runSubagent's success branch would record the
|
|
540
|
+
// run as "completed" once runAgentLoop resolves the consumed cancellation.
|
|
541
|
+
// Suppress the parent notification: the awaiting caller observes the abort
|
|
542
|
+
// as a thrown rejection, so a "do NOT re-spawn" injection would be
|
|
543
|
+
// redundant noise.
|
|
544
|
+
this.abort(subagentId, managed.parentSendToClient, undefined, {
|
|
545
|
+
suppressNotification: true,
|
|
546
|
+
});
|
|
547
|
+
};
|
|
548
|
+
if (signal) {
|
|
549
|
+
if (signal.aborted) onAbort();
|
|
550
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
try {
|
|
554
|
+
const finalText = await this.runSubagent(subagentId, config.objective);
|
|
555
|
+
// Surface aborts as a rejection so the caller's timeout path is
|
|
556
|
+
// observable — but carry the partial text on the error so a caller that
|
|
557
|
+
// timed out a long generation (e.g. the advisor consult) can still
|
|
558
|
+
// surface what was produced instead of throwing it away.
|
|
559
|
+
if (signal?.aborted) {
|
|
560
|
+
throw new SubagentAbortedError(finalText);
|
|
561
|
+
}
|
|
562
|
+
return finalText;
|
|
563
|
+
} finally {
|
|
564
|
+
signal?.removeEventListener("abort", onAbort);
|
|
565
|
+
}
|
|
402
566
|
}
|
|
403
567
|
|
|
404
568
|
// ── Internal: run the subagent ────────────────────────────────────────
|
|
@@ -406,14 +570,31 @@ export class SubagentManager {
|
|
|
406
570
|
private async runSubagent(
|
|
407
571
|
subagentId: string,
|
|
408
572
|
objective: string,
|
|
409
|
-
): Promise<
|
|
573
|
+
): Promise<string> {
|
|
410
574
|
const managed = this.subagents.get(subagentId);
|
|
411
|
-
if (!managed) return;
|
|
575
|
+
if (!managed) return "";
|
|
412
576
|
|
|
413
577
|
// Capture the live conversation — it is non-null at this point because
|
|
414
578
|
// spawn() sets it before firing runSubagent.
|
|
415
579
|
const conversation = managed.conversation!;
|
|
416
580
|
|
|
581
|
+
// The child's trailing assistant text, captured after runAgentLoop resolves
|
|
582
|
+
// (before the `finally` releases the conversation). Returned to the
|
|
583
|
+
// synchronous `spawnAndAwait` caller; the fire-and-forget `spawn` caller
|
|
584
|
+
// ignores it.
|
|
585
|
+
let finalText = "";
|
|
586
|
+
|
|
587
|
+
// Aborted before the run started (e.g. an already-aborted signal on the
|
|
588
|
+
// synchronous spawnAndAwait path): the subagent is already terminal. Do not
|
|
589
|
+
// start the agent loop or reset status back to "running" — but still release
|
|
590
|
+
// the conversation, exactly as the post-run `finally` does for a terminal
|
|
591
|
+
// run. The loop never started, so no messages were enqueued; this matches
|
|
592
|
+
// the finally's non-deferred release branch.
|
|
593
|
+
if (TERMINAL_STATUSES.has(managed.state.status)) {
|
|
594
|
+
this.releaseConversation(managed);
|
|
595
|
+
return finalText;
|
|
596
|
+
}
|
|
597
|
+
|
|
417
598
|
// Read the current parent sender so reconnects are picked up.
|
|
418
599
|
const getSender = () => managed.parentSendToClient;
|
|
419
600
|
|
|
@@ -441,7 +622,16 @@ export class SubagentManager {
|
|
|
441
622
|
// the fork tends to continue the parent conversation instead of
|
|
442
623
|
// pivoting to the task — the inherited context is louder than a bare
|
|
443
624
|
// objective buried after 100k+ tokens of chat history.
|
|
444
|
-
|
|
625
|
+
//
|
|
626
|
+
// The advisor consult is the exception: it is a fork, but its
|
|
627
|
+
// `systemPromptOverride` already frames the inherited context as advice
|
|
628
|
+
// ("you are a senior advisor … do not write its final deliverable"), so
|
|
629
|
+
// the generic "complete this task and return your findings" wrapper would
|
|
630
|
+
// fight that framing. The advisor's objective is already the bare advice
|
|
631
|
+
// request (`advisorRequestText()`), so it is sent uncontested.
|
|
632
|
+
const useForkFraming =
|
|
633
|
+
managed.state.isFork && managed.state.config.role !== "advisor";
|
|
634
|
+
const message = useForkFraming
|
|
445
635
|
? [
|
|
446
636
|
"⎯⎯⎯ FORK TASK ⎯⎯⎯",
|
|
447
637
|
"You have been forked from the parent conversation to execute a specific task.",
|
|
@@ -466,6 +656,9 @@ export class SubagentManager {
|
|
|
466
656
|
});
|
|
467
657
|
|
|
468
658
|
// Agent loop completed successfully.
|
|
659
|
+
// Capture the trailing assistant text before any release nulls the
|
|
660
|
+
// conversation reference. The fire-and-forget caller ignores the return.
|
|
661
|
+
finalText = extractFinalAssistantText(conversation.messages);
|
|
469
662
|
// Copy usage stats from the conversation before sending status (which includes usage).
|
|
470
663
|
managed.state.usage = { ...conversation.usageStats };
|
|
471
664
|
// Only update state + notify if still non-terminal (guards against abort race).
|
|
@@ -476,7 +669,12 @@ export class SubagentManager {
|
|
|
476
669
|
log.info({ subagentId }, "Subagent completed");
|
|
477
670
|
|
|
478
671
|
// Notify the parent conversation so the LLM can call subagent_read.
|
|
479
|
-
|
|
672
|
+
// Skipped on the synchronous path — the awaiting caller receives the
|
|
673
|
+
// final text directly, so re-injecting a "read the result" prompt
|
|
674
|
+
// would be redundant noise in the parent.
|
|
675
|
+
if (!managed.synchronous) {
|
|
676
|
+
this.notifyParentTerminal(managed, "completed");
|
|
677
|
+
}
|
|
480
678
|
}
|
|
481
679
|
} catch (err) {
|
|
482
680
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -489,10 +687,22 @@ export class SubagentManager {
|
|
|
489
687
|
// Only update status if not already terminal (e.g. aborted).
|
|
490
688
|
if (!TERMINAL_STATUSES.has(managed.state.status)) {
|
|
491
689
|
this.setStatus(subagentId, "failed", getSender(), errorMsg);
|
|
492
|
-
|
|
690
|
+
// Skip terminal parent-injection on the synchronous path — the failure
|
|
691
|
+
// surfaces to the awaiting caller as a rejected promise instead.
|
|
692
|
+
if (!managed.synchronous) {
|
|
693
|
+
this.notifyParentTerminal(managed, "failed");
|
|
694
|
+
}
|
|
493
695
|
}
|
|
494
696
|
|
|
495
697
|
log.error({ subagentId, err }, "Subagent failed");
|
|
698
|
+
|
|
699
|
+
// Surface the failure to the synchronous caller. The fire-and-forget
|
|
700
|
+
// path has no awaiter, so re-throwing there only feeds the `.catch()`
|
|
701
|
+
// logger in `spawn` — harmless but noisy — so it is confined to the
|
|
702
|
+
// synchronous path.
|
|
703
|
+
if (managed.synchronous) {
|
|
704
|
+
throw err;
|
|
705
|
+
}
|
|
496
706
|
} finally {
|
|
497
707
|
// Release the heavyweight Conversation — output is already persisted in DB.
|
|
498
708
|
// drainQueue is async: it awaits buildPassthroughBatch (which awaits
|
|
@@ -516,6 +726,8 @@ export class SubagentManager {
|
|
|
516
726
|
this.releaseConversation(managed);
|
|
517
727
|
}
|
|
518
728
|
}
|
|
729
|
+
|
|
730
|
+
return finalText;
|
|
519
731
|
}
|
|
520
732
|
|
|
521
733
|
// ── Abort ─────────────────────────────────────────────────────────────
|
|
@@ -553,6 +765,15 @@ export class SubagentManager {
|
|
|
553
765
|
),
|
|
554
766
|
);
|
|
555
767
|
managed.state.completedAt = Date.now();
|
|
768
|
+
// Capture the conversation's latest usage before emitting the terminal
|
|
769
|
+
// status. `subagent_status_changed` ships `state.usage`, and the abort path
|
|
770
|
+
// (unlike the completion/failure paths, which sync at agent-loop exit) would
|
|
771
|
+
// otherwise send the {0,0,0} init usage — zeroing the client's token counts
|
|
772
|
+
// even though those tokens were already spent. `usageStats` accrues per LLM
|
|
773
|
+
// turn (see conversation-usage.ts), so this is the most recent total.
|
|
774
|
+
if (managed.conversation) {
|
|
775
|
+
managed.state.usage = { ...managed.conversation.usageStats };
|
|
776
|
+
}
|
|
556
777
|
if (parentSendToClient) {
|
|
557
778
|
// Route the status update through the stored parent sender so the
|
|
558
779
|
// owning conversation's UI chip updates, even when the abort comes from a
|
package/src/subagent/types.ts
CHANGED
|
@@ -116,7 +116,8 @@ export type SubagentRole =
|
|
|
116
116
|
| "researcher"
|
|
117
117
|
| "coder"
|
|
118
118
|
| "planner"
|
|
119
|
-
| "investigator"
|
|
119
|
+
| "investigator"
|
|
120
|
+
| "advisor";
|
|
120
121
|
|
|
121
122
|
export interface SubagentRoleConfig {
|
|
122
123
|
/**
|
|
@@ -180,7 +181,7 @@ export const SUBAGENT_ROLE_REGISTRY: Record<SubagentRole, SubagentRoleConfig> =
|
|
|
180
181
|
},
|
|
181
182
|
investigator: {
|
|
182
183
|
allowedTools: [
|
|
183
|
-
"
|
|
184
|
+
"code_search",
|
|
184
185
|
"file_read",
|
|
185
186
|
"file_list",
|
|
186
187
|
"web_search",
|
|
@@ -191,11 +192,17 @@ export const SUBAGENT_ROLE_REGISTRY: Record<SubagentRole, SubagentRoleConfig> =
|
|
|
191
192
|
skillIds: [],
|
|
192
193
|
systemPromptPreamble: [
|
|
193
194
|
"You are an investigation-focused subagent for root-cause analysis: debugging, log forensics, and tracing behavior across code.",
|
|
194
|
-
"
|
|
195
|
-
"Working method: read whole files instead of many small line-range slices; prefer broad
|
|
195
|
+
"You have read-only investigation tools only — there is no shell. Use code_search to search file contents across directories, file_list to enumerate paths, and file_read to read whole files and logs. You cannot modify files or system state.",
|
|
196
|
+
"Working method: read whole files instead of many small line-range slices; prefer broad code_search queries across a directory over one-symbol-at-a-time queries.",
|
|
196
197
|
"Send notify_parent (urgency 'important') as soon as each finding is confirmed, so progress survives interruption.",
|
|
197
198
|
"Your final message must be a compact root-cause report with these sections: Symptom, Root cause, Evidence (file:line references), Suggested fix, Open questions.",
|
|
198
199
|
"If you approach context limits, stop investigating and produce the report from what you have — a partial report delivered is worth more than a complete investigation lost.",
|
|
199
200
|
].join(" "),
|
|
200
201
|
},
|
|
202
|
+
advisor: {
|
|
203
|
+
allowedTools: [],
|
|
204
|
+
skillIds: [],
|
|
205
|
+
systemPromptPreamble:
|
|
206
|
+
"You are a read-only senior advisor consulted for a one-shot strategic review. Read the inherited conversation, then return focused, high-leverage guidance in a single response. You have no tools — you cannot search, read files, or run commands — so reason from the context you were given.",
|
|
207
|
+
},
|
|
201
208
|
};
|
package/src/telemetry/types.ts
CHANGED
|
@@ -391,6 +391,38 @@ export interface SkillLoadedTelemetryEvent extends ModelTelemetryEventBase {
|
|
|
391
391
|
conversation_id: string | null;
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
+
/**
|
|
395
|
+
* Watchdog health event — one per watchdog check firing. The daemon's
|
|
396
|
+
* watchdog observes liveness/health signals (event-loop block, stream-idle
|
|
397
|
+
* stalls, restarts, ...) and emits one event per check firing.
|
|
398
|
+
*
|
|
399
|
+
* Deliberately minimal and forward-compatible, mirroring the platform
|
|
400
|
+
* `WatchdogTelemetryEventSerializer`:
|
|
401
|
+
*
|
|
402
|
+
* - `check_name` — which watchdog check fired. Open string set (not a
|
|
403
|
+
* closed enum) so the daemon can introduce a new check without a
|
|
404
|
+
* coordinated serializer release; it is the primary group-by dimension
|
|
405
|
+
* downstream. The infrastructure admin chart filters this to
|
|
406
|
+
* `event_loop_blocked`.
|
|
407
|
+
* - `value` — the single measured magnitude for the check (block ms, idle
|
|
408
|
+
* ms, ...). Nullable: not every check carries a scalar. The platform
|
|
409
|
+
* coerces ints to float, so the daemon need not distinguish.
|
|
410
|
+
* - `detail` — open JSON bag for any extra fields the daemon attaches
|
|
411
|
+
* (reason codes, secondary numbers, a human message) without a
|
|
412
|
+
* platform-coordinated schema change. Null when the daemon attaches
|
|
413
|
+
* nothing. Bounded server-side (4096 bytes serialized); an oversize bag
|
|
414
|
+
* rejects only the single event, never the batch.
|
|
415
|
+
*
|
|
416
|
+
* Metadata only — no conversation content. Dedupe downstream on
|
|
417
|
+
* `daemon_event_id` (the daemon retries a batch on transient POST failure).
|
|
418
|
+
*/
|
|
419
|
+
export interface WatchdogTelemetryEvent extends TelemetryEventBase {
|
|
420
|
+
type: "watchdog";
|
|
421
|
+
check_name: string;
|
|
422
|
+
value: number | null;
|
|
423
|
+
detail: Record<string, unknown> | null;
|
|
424
|
+
}
|
|
425
|
+
|
|
394
426
|
/** Discriminated union of all telemetry event types. */
|
|
395
427
|
export type TelemetryEvent =
|
|
396
428
|
| LlmUsageTelemetryEvent
|
|
@@ -399,4 +431,5 @@ export type TelemetryEvent =
|
|
|
399
431
|
| OnboardingTelemetryEvent
|
|
400
432
|
| AuthFallbackTelemetryEvent
|
|
401
433
|
| ToolExecutedTelemetryEvent
|
|
402
|
-
| SkillLoadedTelemetryEvent
|
|
434
|
+
| SkillLoadedTelemetryEvent
|
|
435
|
+
| WatchdogTelemetryEvent;
|
|
@@ -1547,11 +1547,11 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1547
1547
|
// No HTTP call should have been made
|
|
1548
1548
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
1549
1549
|
|
|
1550
|
-
// All
|
|
1550
|
+
// All 8 timestamp watermarks should have been advanced, and all 8 ID
|
|
1551
1551
|
// watermarks pinned to the high-sorting sentinel (a truthy value keeps
|
|
1552
1552
|
// the compound-cursor branch active while closing its same-millisecond
|
|
1553
1553
|
// arm against opt-out rows).
|
|
1554
|
-
expect(mockSetMemoryCheckpoint).toHaveBeenCalledTimes(
|
|
1554
|
+
expect(mockSetMemoryCheckpoint).toHaveBeenCalledTimes(16);
|
|
1555
1555
|
|
|
1556
1556
|
const calls = mockSetMemoryCheckpoint.mock.calls;
|
|
1557
1557
|
const keys = calls.map((c) => c[0]);
|
|
@@ -1563,6 +1563,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1563
1563
|
"auth_fallback",
|
|
1564
1564
|
"tool_executed",
|
|
1565
1565
|
"skill_loaded",
|
|
1566
|
+
"watchdog",
|
|
1566
1567
|
];
|
|
1567
1568
|
for (const eventType of eventTypes) {
|
|
1568
1569
|
expect(keys).toContain(`telemetry:${eventType}:last_reported_at`);
|