@vellumai/assistant 0.9.0 → 0.10.0-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +18 -34
- package/bun.lock +7 -8
- package/docs/activation-funnel-telemetry.md +28 -22
- package/docs/architecture/security.md +29 -28
- package/docs/stt-provider-onboarding.md +3 -5
- package/docs/workflows-testing.md +13 -44
- package/docs/workflows.md +3 -5
- package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
- package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
- package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
- package/openapi.yaml +976 -63
- package/package.json +2 -1
- package/scripts/sync-llm-catalog.ts +6 -15
- package/scripts/sync-web-search-catalog.ts +3 -11
- package/src/__tests__/access-request-card-view.test.ts +98 -0
- package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
- package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
- package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
- package/src/__tests__/app-compiler.test.ts +15 -1
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
- package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
- package/src/__tests__/avatar-identity-sync.test.ts +2 -27
- package/src/__tests__/btw-routes.test.ts +6 -8
- package/src/__tests__/call-pointer-messages.test.ts +28 -0
- package/src/__tests__/cancel-clears-processing.test.ts +89 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -4
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
- package/src/__tests__/checker.test.ts +0 -3
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
- package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
- package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
- package/src/__tests__/config-loader-backfill.test.ts +268 -27
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/config-watcher.test.ts +0 -18
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +0 -6
- package/src/__tests__/contacts-tools.test.ts +29 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +58 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-lifecycle.test.ts +7 -9
- package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
- package/src/__tests__/conversation-title-service.test.ts +62 -0
- package/src/__tests__/credential-broker.test.ts +449 -1
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
- package/src/__tests__/credential-execution-tools.test.ts +0 -1
- package/src/__tests__/credential-prompt-route.test.ts +4 -4
- package/src/__tests__/credential-routes.test.ts +360 -0
- package/src/__tests__/credential-security-invariants.test.ts +4 -13
- package/src/__tests__/disk-pressure-policy.test.ts +12 -0
- package/src/__tests__/disk-usage.test.ts +65 -0
- package/src/__tests__/dynamic-page-surface.test.ts +152 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
- package/src/__tests__/gateway-flag-listener.test.ts +110 -1
- package/src/__tests__/gateway-only-guard.test.ts +3 -7
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-grant-minting.test.ts +3 -35
- package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
- package/src/__tests__/guardian-routing-state.test.ts +0 -1
- package/src/__tests__/headless-browser-mode.test.ts +10 -0
- package/src/__tests__/headless-browser-navigate.test.ts +8 -3
- package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
- package/src/__tests__/host-browser-proxy.test.ts +87 -0
- package/src/__tests__/identity-routes.test.ts +0 -189
- package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
- package/src/__tests__/injector-v3-suppression.test.ts +27 -20
- package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
- package/src/__tests__/invite-redemption-service.test.ts +4 -7
- package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +30 -23
- package/src/__tests__/llm-resolver.test.ts +70 -24
- package/src/__tests__/llm-schema.test.ts +1 -0
- package/src/__tests__/managed-profile-guard.test.ts +163 -4
- package/src/__tests__/mcp-health-check.test.ts +6 -7
- package/src/__tests__/media-stream-server-integration.test.ts +317 -13
- package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
- package/src/__tests__/onboarding-persona-write.test.ts +1 -1
- package/src/__tests__/path-policy.test.ts +34 -0
- package/src/__tests__/persona-resolver.test.ts +49 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
- package/src/__tests__/plugin-api-provider.test.ts +24 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
- package/src/__tests__/reaction-persistence.test.ts +150 -29
- package/src/__tests__/registry.test.ts +2 -7
- package/src/__tests__/relay-server.test.ts +285 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
- package/src/__tests__/schedule-routes.test.ts +0 -30
- package/src/__tests__/schedule-tools.test.ts +2 -18
- package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
- package/src/__tests__/skill-execute-input.test.ts +51 -1
- package/src/__tests__/skill-runtime-path.test.ts +2 -3
- package/src/__tests__/skills.test.ts +51 -0
- package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
- package/src/__tests__/subagent-tools.test.ts +266 -0
- package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
- package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +100 -3
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
- package/src/__tests__/token-manager.test.ts +519 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
- package/src/__tests__/tool-audit-listener.test.ts +7 -7
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
- package/src/__tests__/tool-executor.test.ts +0 -79
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
- package/src/__tests__/trusted-contact-verification.test.ts +8 -10
- package/src/__tests__/twilio-routes.test.ts +81 -1
- package/src/__tests__/voice-invite-redemption.test.ts +2 -3
- package/src/__tests__/weak-open-model.test.ts +30 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
- package/src/__tests__/workspace-greetings.test.ts +152 -0
- package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
- package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
- package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
- package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
- package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
- package/src/agent/loop.ts +49 -29
- package/src/api/README.md +6 -6
- package/src/api/events/tool-result.ts +6 -0
- package/src/api/events/workflow-completed.ts +53 -0
- package/src/api/events/workflow-leaf-finished.ts +38 -0
- package/src/api/events/workflow-leaf-started.ts +35 -0
- package/src/api/events/workflow-progress.ts +32 -0
- package/src/api/events/workflow-started.ts +31 -0
- package/src/api/index.ts +40 -0
- package/src/api/responses/conversation-message.ts +28 -4
- package/src/api/responses/home.ts +26 -4
- package/src/api/responses/workflow-journal.ts +53 -0
- package/src/approvals/guardian-card-withdrawal.ts +145 -0
- package/src/approvals/guardian-decision-primitive.ts +26 -3
- package/src/approvals/guardian-request-resolvers.ts +183 -80
- package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
- package/src/calls/call-pointer-messages.ts +10 -4
- package/src/calls/channel-admission-reader.ts +104 -0
- package/src/calls/guardian-dispatch.ts +17 -45
- package/src/calls/media-stream-server.ts +84 -2
- package/src/calls/relay-access-wait.ts +1 -1
- package/src/calls/relay-server.ts +66 -0
- package/src/calls/relay-setup-router.ts +82 -1
- package/src/calls/twilio-routes.ts +17 -8
- package/src/calls/voice-session-bridge.ts +2 -2
- package/src/cli/commands/clients.ts +3 -0
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
- package/src/cli/commands/memory/index.ts +30 -0
- package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
- package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
- package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
- package/src/cli/commands/oauth/status.test.ts +36 -0
- package/src/cli/commands/oauth/status.ts +23 -3
- package/src/cli/commands/plugins.ts +197 -4
- package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
- package/src/cli/lib/diff-plugin.ts +346 -0
- package/src/cli/lib/inspect-plugin.ts +12 -1
- package/src/cli/lib/install-from-github.ts +105 -17
- package/src/cli/lib/merge-plugin-tree.ts +328 -0
- package/src/cli/lib/plugin-fingerprint.ts +14 -0
- package/src/cli/lib/plugin-surfaces.ts +104 -0
- package/src/cli/lib/upgrade-plugin.ts +298 -10
- package/src/cli/program.ts +2 -6
- package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
- package/src/config/assistant-feature-flags.ts +22 -7
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
- package/src/config/bundled-skills/messaging/SKILL.md +6 -4
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
- package/src/config/bundled-skills/subagent/SKILL.md +4 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
- package/src/config/bundled-skills/workflows/SKILL.md +14 -8
- package/src/config/bundled-tool-registry.ts +2 -7
- package/src/config/call-site-defaults.ts +15 -2
- package/src/config/feature-flag-registry.json +46 -31
- package/src/config/inference-profile-validation.ts +26 -0
- package/src/config/llm-resolver.ts +3 -0
- package/src/config/loader.ts +4 -0
- package/src/config/memory-v3-gate.ts +11 -0
- package/src/config/profile-order.ts +28 -0
- package/src/config/schema.ts +8 -6
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/channels.ts +11 -0
- package/src/config/schemas/elevenlabs.ts +0 -1
- package/src/config/schemas/llm.ts +31 -0
- package/src/config/schemas/memory-lifecycle.ts +3 -7
- package/src/config/schemas/memory-v3.ts +6 -0
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/schemas/services.ts +18 -0
- package/src/config/seed-inference-profiles.ts +109 -44
- package/src/config/skills.ts +21 -0
- package/src/config/sync-gated-profiles.ts +220 -0
- package/src/contacts/contact-store.ts +89 -106
- package/src/contacts/contacts-write.ts +5 -22
- package/src/contacts/types.ts +0 -1
- package/src/context/compactor.ts +88 -54
- package/src/context/strip-injections.ts +58 -10
- package/src/context/token-estimator.ts +1 -1
- package/src/credential-execution/process-manager.ts +55 -14
- package/src/credential-execution/prompted-credential.ts +2 -3
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
- package/src/daemon/config-watcher.ts +0 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
- package/src/daemon/conversation-agent-loop.ts +114 -22
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +3 -5
- package/src/daemon/conversation-process.ts +13 -5
- package/src/daemon/conversation-runtime-assembly.ts +13 -15
- package/src/daemon/conversation-slash.ts +2 -23
- package/src/daemon/conversation-surfaces.ts +26 -0
- package/src/daemon/conversation-tool-setup.ts +27 -14
- package/src/daemon/conversation.ts +66 -14
- package/src/daemon/disk-pressure-policy.ts +5 -3
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
- package/src/daemon/handlers/config-a2a.ts +0 -2
- package/src/daemon/handlers/config-channels.ts +15 -16
- package/src/daemon/handlers/config-slack-channel.ts +22 -3
- package/src/daemon/handlers/conversations.ts +107 -0
- package/src/daemon/host-browser-proxy.ts +41 -0
- package/src/daemon/lifecycle.ts +55 -27
- package/src/daemon/message-provenance.ts +2 -0
- package/src/daemon/message-types/contacts.ts +0 -1
- package/src/daemon/message-types/conversations.ts +3 -3
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/message-types/web-activity.ts +7 -1
- package/src/daemon/message-types/workflows.ts +83 -1
- package/src/daemon/orphan-reaper.test.ts +0 -19
- package/src/daemon/orphan-reaper.ts +2 -24
- package/src/daemon/server.ts +0 -10
- package/src/daemon/tool-setup-types.ts +4 -0
- package/src/daemon/trust-context.ts +1 -1
- package/src/events/tool-audit-listener.ts +2 -2
- package/src/home/feed-source-enrichment.test.ts +151 -0
- package/src/home/feed-source-enrichment.ts +176 -0
- package/src/home/relationship-state.ts +2 -4
- package/src/instrument.ts +18 -6
- package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
- package/src/ipc/assistant-server.ts +37 -4
- package/src/ipc/gateway-flag-listener.ts +18 -2
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
- package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
- package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
- package/src/memory/auth-fallback-events-store.ts +2 -2
- package/src/memory/auto-analysis-enqueue.ts +3 -5
- package/src/memory/bookmark-crud.ts +1 -2
- package/src/memory/canonical-guardian-store.ts +39 -1
- package/src/memory/conversation-crud.ts +9 -4
- package/src/memory/conversation-key-store.ts +17 -2
- package/src/memory/conversation-title-service.ts +64 -7
- package/src/memory/db-init.ts +17 -17
- package/src/memory/embedding-backend.ts +38 -1
- package/src/memory/embedding-billing-breaker.ts +96 -0
- package/src/memory/jobs-store.ts +25 -13
- package/src/memory/jobs-worker.ts +54 -1
- package/src/memory/lifecycle-events-store.ts +2 -2
- package/src/memory/memory-retrospective-constants.ts +4 -4
- package/src/memory/memory-retrospective-enqueue.ts +31 -6
- package/src/memory/memory-retrospective-job.ts +28 -227
- package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
- package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
- package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
- package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
- package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
- package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/run-migrations.ts +41 -0
- package/src/memory/migrations/validate-migration-state.ts +1 -1
- package/src/memory/onboarding-events-store.ts +3 -3
- package/src/memory/schema/contacts.ts +0 -5
- package/src/memory/skill-loaded-events-store.test.ts +7 -15
- package/src/memory/skill-loaded-events-store.ts +2 -2
- package/src/memory/tool-executed-events-store.test.ts +7 -7
- package/src/memory/turn-trace-store.test.ts +736 -0
- package/src/memory/turn-trace-store.ts +364 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
- package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
- package/src/memory/v2/consolidation-job.ts +2 -2
- package/src/memory/v2/skill-content.ts +25 -7
- package/src/memory/v2/skill-store.ts +7 -1
- package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
- package/src/memory/v3-eval/eval-packets.ts +546 -0
- package/src/messaging/providers/slack/adapter.ts +1 -1
- package/src/messaging/providers/slack/api.ts +31 -0
- package/src/messaging/providers/slack/send.test.ts +114 -2
- package/src/messaging/providers/slack/send.ts +30 -7
- package/src/messaging/providers/slack/withdraw.test.ts +200 -0
- package/src/messaging/providers/slack/withdraw.ts +161 -0
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/access-request-copy.ts +72 -59
- package/src/notifications/adapters/shared.ts +29 -0
- package/src/notifications/adapters/slack.ts +58 -103
- package/src/notifications/adapters/telegram.ts +2 -20
- package/src/notifications/approval-card-data.ts +333 -0
- package/src/notifications/broadcaster.ts +16 -3
- package/src/notifications/canonical-delivery-recorder.ts +139 -0
- package/src/notifications/copy-composer.ts +3 -3
- package/src/notifications/decision-engine.ts +4 -2
- package/src/notifications/destination-resolver.ts +4 -6
- package/src/notifications/guardian-question-mode.ts +10 -0
- package/src/notifications/home-feed-side-effect.ts +7 -16
- package/src/notifications/notification-utils.ts +19 -20
- package/src/notifications/signal.ts +79 -43
- package/src/notifications/types.ts +98 -121
- package/src/oauth/AGENTS.md +5 -24
- package/src/permissions/checker.test.ts +51 -0
- package/src/permissions/checker.ts +185 -26
- package/src/permissions/ipc-risk-types.ts +24 -0
- package/src/permissions/question-prompter.test.ts +27 -0
- package/src/permissions/question-prompter.ts +4 -0
- package/src/platform/client.test.ts +119 -0
- package/src/platform/client.ts +66 -0
- package/src/platform/consent-cache.test.ts +267 -0
- package/src/platform/consent-cache.ts +174 -0
- package/src/plugin-api/constants.ts +1 -1
- package/src/plugin-api/index.ts +33 -1
- package/src/plugin-api/model-profiles.ts +33 -0
- package/src/plugin-api/types.ts +50 -2
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
- package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
- package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
- package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
- package/src/plugins/defaults/advisor/config.ts +21 -0
- package/src/plugins/defaults/advisor/consult.ts +93 -0
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/advisor/package.json +14 -0
- package/src/plugins/defaults/advisor/steering.ts +67 -0
- package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
- package/src/plugins/defaults/advisor/transcript.ts +76 -0
- package/src/plugins/defaults/index.ts +60 -0
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
- package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
- package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
- package/src/prompts/persona-resolver.ts +14 -4
- package/src/prompts/templates/system-sections.ts +7 -2
- package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
- package/src/providers/__tests__/retry-callsite.test.ts +176 -0
- package/src/providers/atlascloud/client.ts +85 -0
- package/src/providers/fetch-provider-catalog.ts +85 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/model-catalog.ts +58 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
- package/src/providers/openai/chat-completions-provider.ts +7 -0
- package/src/providers/openai/responses-provider.ts +10 -0
- package/src/providers/provider-send-message.ts +11 -3
- package/src/providers/retry.ts +53 -12
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/weak-open-model.ts +22 -0
- package/src/runtime/AGENTS.md +0 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/__tests__/client-health.test.ts +44 -0
- package/src/runtime/access-request-helper.ts +21 -53
- package/src/runtime/actor-trust-resolver.ts +59 -63
- package/src/runtime/agent-wake.ts +52 -0
- package/src/runtime/assistant-event-hub.ts +18 -4
- package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
- package/src/runtime/auth/require-bound-guardian.ts +1 -4
- package/src/runtime/btw-sidechain.ts +3 -6
- package/src/runtime/capabilities.test.ts +120 -0
- package/src/runtime/capabilities.ts +197 -0
- package/src/runtime/channel-approval-types.ts +22 -45
- package/src/runtime/channel-invite-transports/telegram.ts +4 -4
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/channel-verification-service.ts +3 -3
- package/src/runtime/client-health.ts +26 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
- package/src/runtime/effective-capabilities.test.ts +128 -0
- package/src/runtime/effective-capabilities.ts +84 -0
- package/src/runtime/guardian-reply-router.ts +106 -21
- package/src/runtime/invite-redemption-service.ts +9 -25
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
- package/src/runtime/migrations/vbundle-builder.ts +49 -20
- package/src/runtime/pending-interactions.ts +15 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
- package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
- package/src/runtime/routes/browser-tabs-routes.ts +9 -0
- package/src/runtime/routes/btw-routes.ts +1 -27
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
- package/src/runtime/routes/client-routes.ts +10 -0
- package/src/runtime/routes/contact-routes.ts +31 -8
- package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +80 -1
- package/src/runtime/routes/conversation-query-routes.ts +68 -22
- package/src/runtime/routes/conversation-routes.ts +39 -14
- package/src/runtime/routes/credential-routes.ts +40 -16
- package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/guardian-approval-interception.ts +14 -73
- package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
- package/src/runtime/routes/home-feed-routes.ts +8 -3
- package/src/runtime/routes/identity-routes.ts +1 -296
- package/src/runtime/routes/inbound-message-handler.ts +214 -228
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
- package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
- package/src/runtime/routes/integrations/slack/channel.ts +36 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
- package/src/runtime/routes/mcp-auth-routes.ts +233 -41
- package/src/runtime/routes/memory-eval-routes.ts +87 -0
- package/src/runtime/routes/notification-routes.ts +122 -133
- package/src/runtime/routes/platform-routes.ts +2 -2
- package/src/runtime/routes/plugins-routes.ts +202 -3
- package/src/runtime/routes/schedule-routes.ts +0 -22
- package/src/runtime/routes/secret-routes.ts +10 -0
- package/src/runtime/routes/surface-action-routes.ts +2 -1
- package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
- package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
- package/src/runtime/routes/workflow-routes.test.ts +229 -44
- package/src/runtime/routes/workflow-routes.ts +131 -29
- package/src/runtime/routes/workspace-greetings.ts +55 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -11
- package/src/runtime/tool-grant-request-helper.ts +18 -16
- package/src/runtime/trust-context-resolver.ts +8 -5
- package/src/schedule/inference-profile.ts +2 -14
- package/src/schedule/schedule-store.ts +1 -1
- package/src/schedule/scheduler-types.ts +5 -1
- package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
- package/src/security/secret-patterns.ts +3 -0
- package/src/subagent/manager.ts +17 -4
- package/src/subagent/types.ts +6 -0
- package/src/telemetry/trace-collection-policy.test.ts +28 -0
- package/src/telemetry/trace-collection-policy.ts +30 -0
- package/src/telemetry/types.ts +89 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
- package/src/telemetry/usage-telemetry-reporter.ts +148 -41
- package/src/tools/AGENTS.md +3 -3
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
- package/src/tools/browser/browser-execution.ts +30 -19
- package/src/tools/document/document-tool.ts +2 -3
- package/src/tools/executor.ts +5 -3
- package/src/tools/host-terminal/host-shell.ts +5 -4
- package/src/tools/memory/register.ts +2 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
- package/src/tools/network/__tests__/web-search.test.ts +143 -0
- package/src/tools/network/web-fetch.ts +372 -1
- package/src/tools/network/web-search-error.ts +1 -1
- package/src/tools/network/web-search.ts +213 -10
- package/src/tools/permission-checker.ts +4 -3
- package/src/tools/registry.ts +20 -0
- package/src/tools/schedule/create.ts +7 -12
- package/src/tools/schedule/update.ts +4 -11
- package/src/tools/shared/filesystem/path-policy.ts +39 -13
- package/src/tools/side-effects.ts +2 -17
- package/src/tools/skills/execute.ts +33 -0
- package/src/tools/subagent/spawn.ts +61 -12
- package/src/tools/terminal/shell.ts +10 -4
- package/src/tools/tool-approval-handler.ts +18 -13
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/types.ts +9 -0
- package/src/tools/ui-surface/definitions.ts +64 -3
- package/src/tools/verification-control-plane-policy.ts +3 -1
- package/src/tools/workflows/run-workflow.test.ts +8 -18
- package/src/tools/workflows/run-workflow.ts +1 -0
- package/src/util/disk-usage.ts +78 -23
- package/src/util/platform.ts +10 -3
- package/src/watcher/telemetry.ts +2 -2
- package/src/workflows/capabilities.ts +2 -3
- package/src/workflows/engine.test.ts +175 -1
- package/src/workflows/engine.ts +82 -0
- package/src/workflows/journal-store.test.ts +70 -0
- package/src/workflows/journal-store.ts +18 -3
- package/src/workflows/run-manager.test.ts +171 -28
- package/src/workflows/run-manager.ts +66 -24
- package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
- package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
- package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
- package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
- package/src/__tests__/credential-security-e2e.test.ts +0 -362
- package/src/__tests__/credential-vault-unit.test.ts +0 -1528
- package/src/__tests__/credential-vault.test.ts +0 -1706
- package/src/__tests__/identity-intro-cache.test.ts +0 -315
- package/src/__tests__/secret-onetime-send.test.ts +0 -182
- package/src/cli/commands/__tests__/task.test.ts +0 -914
- package/src/cli/commands/task.ts +0 -771
- package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
- package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
- package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
- package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
- package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
- package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
- package/src/memory/preloaded-apps.ts +0 -116
- package/src/notifications/tool-approval-copy.ts +0 -142
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
- package/src/runtime/routes/identity-intro-cache.ts +0 -172
- package/src/tools/credentials/vault.ts +0 -712
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default `post-model-call` hook: when a user-facing turn is about to end with a
|
|
3
|
+
* progress surface the model showed but never closed, nudge the model — once
|
|
4
|
+
* per run — to complete or dismiss it, then re-query so it can act.
|
|
5
|
+
*
|
|
6
|
+
* Motivation: the model can call `ui_show` with a `task_progress` card (or a
|
|
7
|
+
* `work_result` in an `in_progress` state) to show live progress, but weaker
|
|
8
|
+
* models often never advance it to a terminal status or `ui_dismiss` it. The
|
|
9
|
+
* surface then renders a spinner forever, long after the work finished. Static
|
|
10
|
+
* prompt guidance is easy to ignore; a reminder injected at the moment the turn
|
|
11
|
+
* would otherwise end — with a concrete "you left this open" signal — is far
|
|
12
|
+
* more salient.
|
|
13
|
+
*
|
|
14
|
+
* The nudge is strictly best-effort and self-targeting:
|
|
15
|
+
* - It fires at most once per run (no looping if the model declines).
|
|
16
|
+
* - It is advisory — the model may leave the surface open if the work it
|
|
17
|
+
* represents is genuinely still running.
|
|
18
|
+
* - A model that already completed or dismissed its surfaces is never nudged,
|
|
19
|
+
* so capable models that close their surfaces pay nothing.
|
|
20
|
+
*
|
|
21
|
+
* Only a finalized, no-tool, main-agent reply is actionable:
|
|
22
|
+
* - A provider rejection carries no turn content to assess (a recovery hook
|
|
23
|
+
* like history-repair owns that).
|
|
24
|
+
* - A tool-bearing turn continues naturally — the loop runs the tools and the
|
|
25
|
+
* model gets another chance to close the surface — so we leave it alone.
|
|
26
|
+
* - Background call sites (wake, title-gen, memory) have no live user watching
|
|
27
|
+
* a spinner, so the nudge would only burn a model round.
|
|
28
|
+
*
|
|
29
|
+
* No subagent guard is needed: the `ui-surface` tools are gated on a connected
|
|
30
|
+
* client (see `conversation-tool-setup.ts`), and subagents have none — so a
|
|
31
|
+
* subagent can never create a surface and so can never trigger this hook.
|
|
32
|
+
*
|
|
33
|
+
* The dangling-surface signal is derived from the current response cycle (the
|
|
34
|
+
* messages after the last genuine user prompt) by correlating each progress
|
|
35
|
+
* `ui_show` with its `surface_id` result and folding in later `ui_update` /
|
|
36
|
+
* `ui_dismiss` calls. Deriving the cycle boundary from history content rather
|
|
37
|
+
* than an index means mid-run compaction (which rewrites the array in place)
|
|
38
|
+
* can't invalidate it.
|
|
39
|
+
*
|
|
40
|
+
* The one-shot bound is split across two hooks: this hook marks the
|
|
41
|
+
* conversation when it nudges, and the sibling `stop` hook clears the mark when
|
|
42
|
+
* the turn terminates, so the next run nudges afresh.
|
|
43
|
+
*
|
|
44
|
+
* Defaults register before any user plugin, so this hook runs at the front of
|
|
45
|
+
* the `post-model-call` chain — later hooks see (and may override) its decision.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import type { PluginHookFn, PostModelCallContext } from "@vellumai/plugin-api";
|
|
49
|
+
|
|
50
|
+
import type { ContentBlock, Message } from "../../../../providers/types.js";
|
|
51
|
+
import {
|
|
52
|
+
isSurfaceCompletionNudged,
|
|
53
|
+
markSurfaceCompletionNudged,
|
|
54
|
+
} from "../nudge-state-store.js";
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Canonical nudge text. Shown to the model as provider-only context, never to
|
|
58
|
+
* the user. Kept verbatim so a plugin that wraps the default sees a stable
|
|
59
|
+
* string. Deliberately soft: the model may leave the surface open if the work
|
|
60
|
+
* is genuinely still running.
|
|
61
|
+
*/
|
|
62
|
+
export const SURFACE_COMPLETION_NUDGE_TEXT =
|
|
63
|
+
'<system_notice>You showed the user a progress surface this turn (a task_progress card or a work_result) and are about to end the turn with it still marked in_progress. If that work is finished, advance it to a terminal state now — call ui_update to set its status to "completed" (or "failed"), or ui_dismiss it — so the user is not left watching a card spin forever. Do this only if the work it represents is actually done; if it is genuinely still running, leave it. Then give your final reply.</system_notice>';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Surface statuses that mean the progress surface has reached a terminal state
|
|
67
|
+
* and needs no completion nudge. Covers both `task_progress`
|
|
68
|
+
* (`completed`/`failed`) and `work_result` (`completed`/`partial`/`failed`),
|
|
69
|
+
* plus `cancelled` for tolerance.
|
|
70
|
+
*/
|
|
71
|
+
const TERMINAL_STATUSES = new Set([
|
|
72
|
+
"completed",
|
|
73
|
+
"failed",
|
|
74
|
+
"partial",
|
|
75
|
+
"cancelled",
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
function hasToolUse(content: ReadonlyArray<ContentBlock>): boolean {
|
|
79
|
+
return content.some((block) => block.type === "tool_use");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A user-role message carrying only tool results, not a fresh prompt. */
|
|
83
|
+
function isToolResultMessage(message: Message): boolean {
|
|
84
|
+
return (
|
|
85
|
+
message.role === "user" &&
|
|
86
|
+
message.content.length > 0 &&
|
|
87
|
+
message.content.every((block) => block.type === "tool_result")
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Messages belonging to the current response cycle: everything after the last
|
|
93
|
+
* genuine user prompt. Falls back to the whole history when none is found.
|
|
94
|
+
*/
|
|
95
|
+
function currentCycleMessages(
|
|
96
|
+
messages: ReadonlyArray<Message>,
|
|
97
|
+
): ReadonlyArray<Message> {
|
|
98
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
99
|
+
const message = messages[i];
|
|
100
|
+
if (message.role === "user" && !isToolResultMessage(message)) {
|
|
101
|
+
return messages.slice(i + 1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return messages;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
108
|
+
return value !== null && typeof value === "object"
|
|
109
|
+
? (value as Record<string, unknown>)
|
|
110
|
+
: undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Pull a surface status out of a `ui_show` / `ui_update` input, tolerating the
|
|
115
|
+
* shapes the server-side normalization accepts: nested under
|
|
116
|
+
* `data.templateData` (task_progress), under `data` (work_result), or at the
|
|
117
|
+
* top level. Returns a lowercased status, or `undefined` when none is present.
|
|
118
|
+
*/
|
|
119
|
+
function extractStatus(input: Record<string, unknown>): string | undefined {
|
|
120
|
+
const data = asRecord(input.data);
|
|
121
|
+
const templateData =
|
|
122
|
+
asRecord(data?.templateData) ?? asRecord(input.templateData);
|
|
123
|
+
const raw = templateData?.status ?? data?.status ?? input.status;
|
|
124
|
+
return typeof raw === "string" ? raw.trim().toLowerCase() : undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Whether a `ui_show` input describes a surface with a progress lifecycle: a
|
|
129
|
+
* `task_progress` card or a `work_result`. Returns the initial status when so.
|
|
130
|
+
*/
|
|
131
|
+
function progressShowInfo(input: Record<string, unknown>): {
|
|
132
|
+
isProgress: boolean;
|
|
133
|
+
status: string | undefined;
|
|
134
|
+
} {
|
|
135
|
+
const surfaceType = input.surface_type;
|
|
136
|
+
if (surfaceType === "work_result") {
|
|
137
|
+
return { isProgress: true, status: extractStatus(input) };
|
|
138
|
+
}
|
|
139
|
+
if (surfaceType === "card") {
|
|
140
|
+
const data = asRecord(input.data);
|
|
141
|
+
const template = data?.template ?? input.template;
|
|
142
|
+
if (template === "task_progress") {
|
|
143
|
+
return { isProgress: true, status: extractStatus(input) };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { isProgress: false, status: undefined };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function surfaceIdOf(input: Record<string, unknown>): string | undefined {
|
|
150
|
+
return typeof input.surface_id === "string" ? input.surface_id : undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Parse the `{ surfaceId }` JSON a successful `ui_show` returns. */
|
|
154
|
+
function parseSurfaceId(content: string): string | undefined {
|
|
155
|
+
try {
|
|
156
|
+
const parsed = JSON.parse(content) as unknown;
|
|
157
|
+
const record = asRecord(parsed);
|
|
158
|
+
return typeof record?.surfaceId === "string" ? record.surfaceId : undefined;
|
|
159
|
+
} catch {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
interface SurfaceState {
|
|
165
|
+
/** Latest known status, lowercased; `undefined` when never set explicitly. */
|
|
166
|
+
status: string | undefined;
|
|
167
|
+
dismissed: boolean;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function isNonTerminal(state: SurfaceState): boolean {
|
|
171
|
+
if (state.dismissed) return false;
|
|
172
|
+
return state.status === undefined || !TERMINAL_STATUSES.has(state.status);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* True when the current response cycle left at least one progress surface
|
|
177
|
+
* (a `task_progress` card or `work_result`) open: shown but neither advanced to
|
|
178
|
+
* a terminal status nor dismissed.
|
|
179
|
+
*
|
|
180
|
+
* Each progress `ui_show` is correlated to its `surface_id` via the matching
|
|
181
|
+
* tool result (the result lands in the next message; updates and dismisses
|
|
182
|
+
* arrive in later messages, after the model has the id in hand). Later
|
|
183
|
+
* `ui_update` / `ui_dismiss` calls fold their status / dismissal onto the
|
|
184
|
+
* tracked surface.
|
|
185
|
+
*/
|
|
186
|
+
function hasDanglingProgressSurface(messages: ReadonlyArray<Message>): boolean {
|
|
187
|
+
const surfaces = new Map<string, SurfaceState>();
|
|
188
|
+
// tool_use_id -> initial status of a progress ui_show awaiting its result id.
|
|
189
|
+
const pendingShows = new Map<string, string | undefined>();
|
|
190
|
+
|
|
191
|
+
for (const message of currentCycleMessages(messages)) {
|
|
192
|
+
if (message.role === "assistant") {
|
|
193
|
+
for (const block of message.content) {
|
|
194
|
+
if (block.type !== "tool_use") continue;
|
|
195
|
+
if (block.name === "ui_show") {
|
|
196
|
+
const info = progressShowInfo(block.input);
|
|
197
|
+
if (info.isProgress) pendingShows.set(block.id, info.status);
|
|
198
|
+
} else if (block.name === "ui_update") {
|
|
199
|
+
const id = surfaceIdOf(block.input);
|
|
200
|
+
const status = extractStatus(block.input);
|
|
201
|
+
if (id && status !== undefined) {
|
|
202
|
+
const existing = surfaces.get(id);
|
|
203
|
+
if (existing) existing.status = status;
|
|
204
|
+
else surfaces.set(id, { status, dismissed: false });
|
|
205
|
+
}
|
|
206
|
+
} else if (block.name === "ui_dismiss") {
|
|
207
|
+
const id = surfaceIdOf(block.input);
|
|
208
|
+
if (id) {
|
|
209
|
+
const existing = surfaces.get(id);
|
|
210
|
+
if (existing) existing.dismissed = true;
|
|
211
|
+
else surfaces.set(id, { status: undefined, dismissed: true });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (message.role !== "user") continue;
|
|
218
|
+
for (const block of message.content) {
|
|
219
|
+
// guard:allow-tool-result-only — only the local tool executor's
|
|
220
|
+
// `tool_result` carries a `ui_show` `surfaceId` to correlate. A
|
|
221
|
+
// `web_search_tool_result` comes from a `server_tool_use`, never a
|
|
222
|
+
// `ui_show`, so it can never match a pending show and is correctly skipped.
|
|
223
|
+
if (block.type !== "tool_result") continue;
|
|
224
|
+
if (!pendingShows.has(block.tool_use_id)) continue;
|
|
225
|
+
const initialStatus = pendingShows.get(block.tool_use_id);
|
|
226
|
+
pendingShows.delete(block.tool_use_id);
|
|
227
|
+
const id = parseSurfaceId(block.content);
|
|
228
|
+
if (!id) continue;
|
|
229
|
+
const existing = surfaces.get(id);
|
|
230
|
+
// A later update/dismiss can register the id before its show result is
|
|
231
|
+
// scanned only if history was reordered; guard so we never clobber a
|
|
232
|
+
// known terminal/dismissed state with the initial status.
|
|
233
|
+
if (existing) {
|
|
234
|
+
if (existing.status === undefined && !existing.dismissed) {
|
|
235
|
+
existing.status = initialStatus;
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
surfaces.set(id, { status: initialStatus, dismissed: false });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
for (const state of surfaces.values()) {
|
|
244
|
+
if (isNonTerminal(state)) return true;
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const postModelCall: PluginHookFn<PostModelCallContext> = async (ctx) => {
|
|
250
|
+
// A provider rejection carries no turn content to assess (a recovery hook
|
|
251
|
+
// owns the rejection).
|
|
252
|
+
if (ctx.error) return;
|
|
253
|
+
// A tool-bearing turn continues mid-run — the loop runs the tools and the
|
|
254
|
+
// model gets another chance to close the surface — so leave it alone.
|
|
255
|
+
if (hasToolUse(ctx.content)) return;
|
|
256
|
+
// Only nudge the user-facing reply: background call sites have no live user
|
|
257
|
+
// watching a spinner.
|
|
258
|
+
if (ctx.callSite !== "mainAgent") return;
|
|
259
|
+
// One nudge per run; the sibling `stop` hook clears the mark on terminal stop.
|
|
260
|
+
if (isSurfaceCompletionNudged(ctx.conversationId)) return;
|
|
261
|
+
|
|
262
|
+
if (!hasDanglingProgressSurface(ctx.messages)) return;
|
|
263
|
+
|
|
264
|
+
markSurfaceCompletionNudged(ctx.conversationId);
|
|
265
|
+
ctx.messages.push({
|
|
266
|
+
role: "user",
|
|
267
|
+
content: [{ type: "text", text: SURFACE_COMPLETION_NUDGE_TEXT }],
|
|
268
|
+
});
|
|
269
|
+
ctx.decision = "continue";
|
|
270
|
+
ctx.logger.info(
|
|
271
|
+
{ plugin: "surface-completion-nudge", conversationId: ctx.conversationId },
|
|
272
|
+
"Turn ending with an open progress surface — nudging the model to complete or dismiss it",
|
|
273
|
+
);
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
export default postModelCall;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default `stop` hook: clears the per-conversation surface-completion nudge
|
|
3
|
+
* bound when a turn terminates.
|
|
4
|
+
*
|
|
5
|
+
* The `post-model-call` hook (see `./post-model-call.ts`) marks the bound when
|
|
6
|
+
* it nudges the model to close a dangling progress surface. `stop` is the
|
|
7
|
+
* definitive terminal hook — it fires exactly once when the turn is truly
|
|
8
|
+
* ending, after every retry decision has been made — so clearing the bound here
|
|
9
|
+
* unconditionally guarantees the next run nudges afresh, no matter how the turn
|
|
10
|
+
* ended (a finalized reply, an abort, or a retry the loop's per-run backstop
|
|
11
|
+
* refused).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
|
|
15
|
+
|
|
16
|
+
import { clearSurfaceCompletionNudged } from "../nudge-state-store.js";
|
|
17
|
+
|
|
18
|
+
const stop: PluginHookFn<StopContext> = async (ctx) => {
|
|
19
|
+
clearSurfaceCompletionNudged(ctx.conversationId);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default stop;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-conversation surface-completion nudge state.
|
|
3
|
+
*
|
|
4
|
+
* The `post-model-call` hook nudges the model — once per run — to complete or
|
|
5
|
+
* dismiss a progress surface it left `in_progress` when the turn was about to
|
|
6
|
+
* end, asking the loop to re-query so the model can act on it. That nudge is
|
|
7
|
+
* bounded to one pass per run: if the model declines or fails to close the
|
|
8
|
+
* surface, the hook lets the turn end rather than looping on it.
|
|
9
|
+
*
|
|
10
|
+
* The two hooks split this state's lifecycle: `post-model-call` marks a
|
|
11
|
+
* conversation when it issues the nudge, and the sibling `stop` hook clears the
|
|
12
|
+
* mark when the turn terminates. A conversation therefore only holds an entry
|
|
13
|
+
* while a nudge is in flight, and the next run nudges afresh.
|
|
14
|
+
*
|
|
15
|
+
* This module is side-effect free: importing it only initializes an empty store
|
|
16
|
+
* and registers no plugin.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Conversations with a surface-completion nudge in flight for the current run. */
|
|
20
|
+
const nudgeInFlight = new Set<string>();
|
|
21
|
+
|
|
22
|
+
/** Whether the conversation already issued its one nudge this run. */
|
|
23
|
+
export function isSurfaceCompletionNudged(conversationId: string): boolean {
|
|
24
|
+
return nudgeInFlight.has(conversationId);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Record that the conversation issued its one nudge this run. */
|
|
28
|
+
export function markSurfaceCompletionNudged(conversationId: string): void {
|
|
29
|
+
nudgeInFlight.add(conversationId);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Clear the conversation's nudge mark so the next run nudges afresh. The
|
|
34
|
+
* sibling `stop` hook calls this when the turn terminates.
|
|
35
|
+
*/
|
|
36
|
+
export function clearSurfaceCompletionNudged(conversationId: string): void {
|
|
37
|
+
nudgeInFlight.delete(conversationId);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Test-only: drop every conversation's nudge state so a suite that drives the
|
|
42
|
+
* hook directly starts each case from an empty store.
|
|
43
|
+
*/
|
|
44
|
+
export function resetSurfaceCompletionNudgeStoreForTests(): void {
|
|
45
|
+
nudgeInFlight.clear();
|
|
46
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "default-surface-completion-nudge",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "First-party default plugin that nudges the model to complete or dismiss a progress surface it left in_progress when the turn ends.",
|
|
5
|
+
"private": true,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=20.12.0"
|
|
10
|
+
},
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"@vellumai/plugin-api": "^0.8.0"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
import type { PluginHookFn, PostToolUseContext } from "@vellumai/plugin-api";
|
|
49
49
|
|
|
50
50
|
import type { ContentBlock, Message } from "../../../../providers/types.js";
|
|
51
|
+
import { isWeakOpenModel } from "../../../../providers/weak-open-model.js";
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
54
|
* Canonical nudge notice. Module-level constant so tests and wrapping plugins
|
|
@@ -56,7 +57,7 @@ import type { ContentBlock, Message } from "../../../../providers/types.js";
|
|
|
56
57
|
* are fine and the model may skip it when wrapping up.
|
|
57
58
|
*/
|
|
58
59
|
export const TASK_PROGRESS_NUDGE_TEXT =
|
|
59
|
-
'<system_notice>You are several tool calls into this turn
|
|
60
|
+
'<system_notice>You are several tool calls into this turn with no progress card shown. A card is optional, not required: if the turn is wrapping up, is not really multi-step, or you cannot form clean steps, skip it and keep working — a one-line note of what you are doing is a fine substitute, and proceeding with no card is also fine. Only if a live step tracker would genuinely help the user, show it with a SINGLE self-contained ui_show call that already contains the steps: ui_show({ surface_type: "card", data: { template: "task_progress", templateData: { title: "<what you are doing>", status: "in_progress", steps: [{ label: "<step 1>", status: "in_progress" }, { label: "<step 2>", status: "pending" }] } } }). Coarse steps are fine. Do not call ui_show with an empty `data: {}` and fill it in afterward — an empty card renders as a blank box; either include the steps now or skip the card. Advance it later with ui_update under `data.templateData`. Never let the card interrupt the actual work; if one ever looks wrong, just dismiss it and move on. You will not be nudged about this again this turn.</system_notice>';
|
|
60
61
|
|
|
61
62
|
/**
|
|
62
63
|
* Number of tool-use rounds in a turn, with no task_progress card shown, that
|
|
@@ -66,17 +67,6 @@ export const TASK_PROGRESS_NUDGE_TEXT =
|
|
|
66
67
|
*/
|
|
67
68
|
export const TASK_PROGRESS_NUDGE_ROUND_THRESHOLD = 3;
|
|
68
69
|
|
|
69
|
-
/**
|
|
70
|
-
* Weaker open models that disregard the static progress-card instruction and
|
|
71
|
-
* so get the mid-turn nudge: Kimi, DeepSeek, and MiniMax. Family-level matching
|
|
72
|
-
* spans provider naming conventions (OpenRouter `moonshotai/kimi-k2.6`,
|
|
73
|
-
* `deepseek/deepseek-chat`, `minimax/minimax-m3`; Fireworks
|
|
74
|
-
* `accounts/fireworks/models/minimax-m3`, `kimi-k2p6`). Extend as other models
|
|
75
|
-
* show the same gap. Capable models (Claude, GPT) follow the prompt and are
|
|
76
|
-
* intentionally excluded.
|
|
77
|
-
*/
|
|
78
|
-
const WEAK_MODEL_PATTERN = /kimi|deepseek|minimax/i;
|
|
79
|
-
|
|
80
70
|
/**
|
|
81
71
|
* Round count at the last nudge, per conversation. A non-zero entry means the
|
|
82
72
|
* turn has already been nudged; it resets when the round count drops below the
|
|
@@ -145,7 +135,7 @@ function scanTurn(messages: ReadonlyArray<Message>): {
|
|
|
145
135
|
}
|
|
146
136
|
|
|
147
137
|
const postToolUse: PluginHookFn<PostToolUseContext> = async (ctx) => {
|
|
148
|
-
if (!
|
|
138
|
+
if (!isWeakOpenModel(ctx.model)) return;
|
|
149
139
|
|
|
150
140
|
const { rounds, taskProgressShown } = scanTurn(ctx.messages);
|
|
151
141
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Default `stop` hook: triggers
|
|
3
|
-
*
|
|
2
|
+
* Default `stop` hook: triggers conversation-title retry/regeneration once a
|
|
3
|
+
* conversation has accumulated useful context.
|
|
4
4
|
*
|
|
5
5
|
* The first title is generated from the opening prompt alone (see
|
|
6
6
|
* `./user-prompt-submit.ts`). After a few exchanges the conversation's real
|
|
7
7
|
* topic is usually clearer, so a single second pass re-titles using the most
|
|
8
|
-
* recent messages. This hook is the trigger — it
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* re-checks that the title is still
|
|
12
|
-
* provider, persists, and broadcasts the
|
|
13
|
-
* `sync_changed` events.
|
|
8
|
+
* recent messages. This hook is the trigger — it retries placeholder titles
|
|
9
|
+
* after successful turns and fires the second-pass regeneration when the
|
|
10
|
+
* conversation reaches its third user turn. The service
|
|
11
|
+
* (`memory/conversation-title-service.ts`) re-checks that the title is still
|
|
12
|
+
* auto-generated, resolves the title provider, persists, and broadcasts the
|
|
13
|
+
* `conversation_title_updated` / `sync_changed` events.
|
|
14
14
|
*
|
|
15
15
|
* Turn count is read from history rather than an external counter: the number
|
|
16
16
|
* of genuine user prompts — user-role messages that aren't purely tool results
|
|
@@ -22,7 +22,11 @@ import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
|
|
|
22
22
|
|
|
23
23
|
import { getConfig } from "../../../../config/loader.js";
|
|
24
24
|
import { getConversation } from "../../../../memory/conversation-crud.js";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
AUTO_TITLE_DETERMINISTIC,
|
|
27
|
+
isReplaceableTitle,
|
|
28
|
+
queueRegenerateConversationTitle,
|
|
29
|
+
} from "../../../../memory/conversation-title-service.js";
|
|
26
30
|
import type { Message } from "../../../../providers/types.js";
|
|
27
31
|
|
|
28
32
|
/**
|
|
@@ -50,37 +54,68 @@ function countUserTurns(messages: ReadonlyArray<Message>): number {
|
|
|
50
54
|
return turns;
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
function shouldRetryFallbackTitle(conversation: {
|
|
58
|
+
title: string | null;
|
|
59
|
+
isAutoTitle: number;
|
|
60
|
+
}): boolean {
|
|
61
|
+
if (!conversation.isAutoTitle) return false;
|
|
62
|
+
return (
|
|
63
|
+
isReplaceableTitle(conversation.title) ||
|
|
64
|
+
conversation.isAutoTitle === AUTO_TITLE_DETERMINISTIC
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
53
68
|
const stop: PluginHookFn<StopContext> = async (ctx) => {
|
|
54
69
|
// Re-title only at a genuine successful turn end (the model returned a reply
|
|
55
70
|
// with no tool calls). Any other terminal — a provider rejection, abort, or
|
|
56
71
|
// an output-limit cutoff — produced no new topic to re-title from.
|
|
57
72
|
if (ctx.exitReason !== "no_tool_calls") return;
|
|
58
73
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (countUserTurns(ctx.messages) !== SECOND_PASS_USER_TURN) return;
|
|
74
|
+
const userTurns = countUserTurns(ctx.messages);
|
|
75
|
+
if (userTurns === 0) return;
|
|
62
76
|
|
|
63
77
|
// System conversations (background/scheduled) keep their deterministic
|
|
64
78
|
// bootstrap title — multi-prompt background jobs can reach three user-role
|
|
65
79
|
// turns with no human present, and a refined LLM title isn't worth the
|
|
66
80
|
// tokens there. The lookup fails open: on a read error the hook behaves as
|
|
67
81
|
// before (queues regeneration; the service re-checks isAutoTitle).
|
|
82
|
+
let retryFallbackTitle = false;
|
|
68
83
|
try {
|
|
69
84
|
const conversation = getConversation(ctx.conversationId);
|
|
70
85
|
if (conversation && conversation.conversationType !== "standard") return;
|
|
86
|
+
retryFallbackTitle = conversation
|
|
87
|
+
? shouldRetryFallbackTitle(conversation)
|
|
88
|
+
: false;
|
|
71
89
|
} catch {
|
|
72
90
|
// Fall through to queueing.
|
|
73
91
|
}
|
|
74
92
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
93
|
+
if (
|
|
94
|
+
userTurns === SECOND_PASS_USER_TURN &&
|
|
95
|
+
!getConfig().conversations.skipAutoRetitling
|
|
96
|
+
) {
|
|
97
|
+
const { conversationId } = ctx;
|
|
98
|
+
// Deferred to a later macrotask so the just-completed turn's persistence
|
|
99
|
+
// settles first. The service regenerates from the most recent stored
|
|
100
|
+
// messages, so it must run after the reply is persisted to reflect it. The
|
|
101
|
+
// service is itself fire-and-forget and re-checks replaceability, owning
|
|
102
|
+
// provider resolution, persistence, and the resulting broadcast.
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
queueRegenerateConversationTitle({ conversationId });
|
|
105
|
+
}, 0);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (retryFallbackTitle) {
|
|
110
|
+
const { conversationId } = ctx;
|
|
111
|
+
setTimeout(() => {
|
|
112
|
+
queueRegenerateConversationTitle({
|
|
113
|
+
conversationId,
|
|
114
|
+
onlyIfReplaceable: true,
|
|
115
|
+
});
|
|
116
|
+
}, 0);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
84
119
|
};
|
|
85
120
|
|
|
86
121
|
export default stop;
|
|
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { basename, dirname, join } from "node:path";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
findContactByAddress,
|
|
6
6
|
findGuardianForChannel,
|
|
7
7
|
listGuardianChannels,
|
|
8
8
|
} from "../contacts/contact-store.js";
|
|
@@ -88,7 +88,7 @@ function resolveUserFilename(
|
|
|
88
88
|
}
|
|
89
89
|
} else if (trustContext.requesterExternalUserId) {
|
|
90
90
|
// Channel-routed request — look up contact by channel identity
|
|
91
|
-
const contactWithChannels =
|
|
91
|
+
const contactWithChannels = findContactByAddress(
|
|
92
92
|
trustContext.sourceChannel,
|
|
93
93
|
trustContext.requesterExternalUserId,
|
|
94
94
|
);
|
|
@@ -96,13 +96,23 @@ function resolveUserFilename(
|
|
|
96
96
|
filename = contactWithChannels.userFile ?? null;
|
|
97
97
|
} else if (trustContext.trustClass === "guardian") {
|
|
98
98
|
// Managed desktop: the JWT principal ID used as requesterExternalUserId
|
|
99
|
-
// may differ from the contact channel's
|
|
100
|
-
//
|
|
99
|
+
// may differ from the contact channel's address (they are separate
|
|
100
|
+
// identity concepts). Fall back to the channel-type guardian.
|
|
101
101
|
const guardian = findGuardianForChannel(trustContext.sourceChannel);
|
|
102
102
|
if (guardian) {
|
|
103
103
|
filename = guardian.contact.userFile ?? "guardian.md";
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
+
} else if (trustContext.trustClass === "guardian") {
|
|
107
|
+
// Guardian-trust turn carrying no requester identity — background and
|
|
108
|
+
// scheduled turns (heartbeat, scheduled pulses) run under the guardian
|
|
109
|
+
// trust class but have no per-actor address to look up. Resolve the
|
|
110
|
+
// channel's guardian user file so they load the same persona as a
|
|
111
|
+
// foreground guardian turn instead of falling back to users/default.md.
|
|
112
|
+
const guardian = findGuardianForChannel(trustContext.sourceChannel);
|
|
113
|
+
if (guardian) {
|
|
114
|
+
filename = guardian.contact.userFile ?? "guardian.md";
|
|
115
|
+
}
|
|
106
116
|
}
|
|
107
117
|
} catch (err) {
|
|
108
118
|
// Contacts table may be absent — happens during early bootstrap
|
|
@@ -259,6 +259,13 @@ Meet your user where they are. If they are nontechnical, prefer "Gmail needs rec
|
|
|
259
259
|
Err toward brevity; expand only when the user follows up or their style calls for more.
|
|
260
260
|
|
|
261
261
|
These are default guidelines. Always prioritize communication preferences that you've established through your relationship with your human.
|
|
262
|
+
`,
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: "01-delegate-subagents",
|
|
266
|
+
body: `## Delegate independent work
|
|
267
|
+
|
|
268
|
+
When part of a task can run on its own — a research sweep, a multi-file investigation, a build-and-test loop — hand it off instead of grinding through it inline: load the \`subagent\` skill, then \`subagent_spawn\` early and in parallel. Make delegating that kind of work your default, not a last resort; an unnecessary subagent is cheaper than serialized work, and a long inline dig floods your own context.
|
|
262
269
|
`,
|
|
263
270
|
},
|
|
264
271
|
{
|
|
@@ -267,8 +274,6 @@ These are default guidelines. Always prioritize communication preferences that y
|
|
|
267
274
|
Batch independent tool calls into the same response. An extra LLM round trip costs orders of magnitude more than a few wasted tool calls — err on the side of parallelizing when calls are independent. Reading multiple files, \`glob\`/\`grep\`, \`ls\`, \`git status\`/\`diff\`/\`log\`, type-checks, and tests should be batched.
|
|
268
275
|
|
|
269
276
|
Before emitting a single tool call, ask whether your next turn would be another tool call that doesn't consume this one's output — if so, they belong together. Serialized tool calls without a real data dependency are a bug.
|
|
270
|
-
|
|
271
|
-
For non-trivial independent workstreams — research, coding, multi-step investigations — delegate to subagents (load the \`subagent\` skill) and spawn them early and in parallel; an unnecessary subagent is cheaper than serialized work.
|
|
272
277
|
</use_parallel_tool_calls>
|
|
273
278
|
`,
|
|
274
279
|
},
|
|
@@ -35,6 +35,7 @@ describe("getLlmProviderEnvVar", () => {
|
|
|
35
35
|
expect(getLlmProviderEnvVar("brave")).toBeUndefined();
|
|
36
36
|
expect(getLlmProviderEnvVar("perplexity")).toBeUndefined();
|
|
37
37
|
expect(getLlmProviderEnvVar("tavily")).toBeUndefined();
|
|
38
|
+
expect(getLlmProviderEnvVar("firecrawl")).toBeUndefined();
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
test("returns undefined for unknown provider", () => {
|
|
@@ -55,6 +56,10 @@ describe("getSearchProviderEnvVar", () => {
|
|
|
55
56
|
expect(getSearchProviderEnvVar("tavily")).toBe("TAVILY_API_KEY");
|
|
56
57
|
});
|
|
57
58
|
|
|
59
|
+
test("returns FIRECRAWL_API_KEY for firecrawl", () => {
|
|
60
|
+
expect(getSearchProviderEnvVar("firecrawl")).toBe("FIRECRAWL_API_KEY");
|
|
61
|
+
});
|
|
62
|
+
|
|
58
63
|
test("returns undefined for LLM providers (out of scope)", () => {
|
|
59
64
|
expect(getSearchProviderEnvVar("anthropic")).toBeUndefined();
|
|
60
65
|
expect(getSearchProviderEnvVar("openai")).toBeUndefined();
|
|
@@ -75,6 +80,7 @@ describe("getAnyProviderEnvVar", () => {
|
|
|
75
80
|
expect(getAnyProviderEnvVar("brave")).toBe("BRAVE_API_KEY");
|
|
76
81
|
expect(getAnyProviderEnvVar("perplexity")).toBe("PERPLEXITY_API_KEY");
|
|
77
82
|
expect(getAnyProviderEnvVar("tavily")).toBe("TAVILY_API_KEY");
|
|
83
|
+
expect(getAnyProviderEnvVar("firecrawl")).toBe("FIRECRAWL_API_KEY");
|
|
78
84
|
});
|
|
79
85
|
|
|
80
86
|
test("returns undefined for ollama (keyless LLM provider)", () => {
|