@vellumai/assistant 0.8.3 → 0.8.4
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/docker-entrypoint.sh +0 -1
- package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
- package/openapi.yaml +610 -16
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +88 -3
- package/src/__tests__/anthropic-provider.test.ts +272 -0
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
- package/src/__tests__/channel-delivery-store.test.ts +193 -0
- package/src/__tests__/channel-reply-delivery.test.ts +284 -5
- package/src/__tests__/channel-retry-sweep.test.ts +274 -1
- package/src/__tests__/compaction-events.test.ts +1 -1
- package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/context-token-estimator.test.ts +91 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
- package/src/__tests__/conversation-agent-loop.test.ts +25 -7
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-clean-command.test.ts +137 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +161 -0
- package/src/__tests__/conversation-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +2 -2
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
- package/src/__tests__/conversation-seed-composer.test.ts +66 -4
- package/src/__tests__/conversation-slash-commands.test.ts +36 -8
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +6 -0
- package/src/__tests__/cu-unified-flow.test.ts +10 -1
- package/src/__tests__/dm-backfill.test.ts +64 -0
- package/src/__tests__/dm-persistence.test.ts +33 -0
- package/src/__tests__/document-find-replace.test.ts +501 -0
- package/src/__tests__/first-greeting.test.ts +23 -2
- package/src/__tests__/headless-browser-navigate.test.ts +172 -0
- package/src/__tests__/host-bash-proxy.test.ts +6 -0
- package/src/__tests__/host-browser-proxy.test.ts +10 -0
- package/src/__tests__/host-cu-proxy.test.ts +8 -1
- package/src/__tests__/host-file-proxy.test.ts +8 -1
- package/src/__tests__/host-transfer-proxy.test.ts +8 -1
- package/src/__tests__/identity-routes.test.ts +57 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
- package/src/__tests__/injector-chain.test.ts +2 -0
- package/src/__tests__/injector-document-comments.test.ts +378 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
- package/src/__tests__/list-messages-attachments.test.ts +21 -17
- package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
- package/src/__tests__/list-messages-page-latest.test.ts +130 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
- package/src/__tests__/llm-context-normalization.test.ts +0 -2
- package/src/__tests__/llm-resolver.test.ts +85 -1
- package/src/__tests__/log-export-routes.test.ts +99 -2
- package/src/__tests__/message-queue-steer.test.ts +114 -0
- package/src/__tests__/openai-provider.test.ts +105 -0
- package/src/__tests__/openai-responses-provider.test.ts +4 -4
- package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
- package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
- package/src/__tests__/platform.test.ts +0 -3
- package/src/__tests__/plugin-source-watcher.test.ts +302 -0
- package/src/__tests__/process-message-background-slack.test.ts +1 -51
- package/src/__tests__/process-message-display-content.test.ts +21 -16
- package/src/__tests__/server-history-render.test.ts +83 -4
- package/src/__tests__/steer-tool-repair.test.ts +249 -0
- package/src/__tests__/system-prompt.test.ts +51 -28
- package/src/__tests__/terminal-tools.test.ts +11 -1
- package/src/__tests__/thinking-block-replay.test.ts +113 -0
- package/src/__tests__/thread-backfill.test.ts +370 -22
- package/src/__tests__/tool-executor.test.ts +90 -1
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/web-fetch.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +88 -5
- package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
- package/src/agent/attachments.ts +1 -0
- package/src/agent/loop.ts +57 -20
- package/src/background-wake/next-wake.test.ts +289 -0
- package/src/background-wake/next-wake.ts +172 -0
- package/src/browser/operations.ts +15 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
- package/src/cli/commands/conversations.ts +128 -1
- package/src/cli/commands/inference-providers.ts +147 -1
- package/src/cli/commands/memory-v2.ts +308 -0
- package/src/cli/commands/notifications.ts +24 -2
- package/src/cli/utils/conversation-id.ts +17 -5
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
- package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
- package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
- package/src/config/bundled-skills/schedule/SKILL.md +8 -0
- package/src/config/bundled-tool-registry.ts +22 -12
- package/src/config/call-site-defaults.ts +19 -0
- package/src/config/feature-flag-registry.json +99 -3
- package/src/config/llm-resolver.ts +16 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +4 -0
- package/src/config/schemas/call-site-catalog.ts +21 -0
- package/src/config/schemas/llm.ts +3 -0
- package/src/config/schemas/memory-v2.ts +48 -1
- package/src/context/compactor.ts +8 -1
- package/src/context/token-estimator.ts +47 -4
- package/src/context/window-manager.ts +25 -0
- package/src/credential-health/credential-health-service.ts +34 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +153 -23
- package/src/daemon/conversation-agent-loop.ts +223 -54
- package/src/daemon/conversation-lifecycle.ts +142 -116
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +273 -0
- package/src/daemon/conversation-queue-manager.ts +14 -0
- package/src/daemon/conversation-runtime-assembly.ts +135 -75
- package/src/daemon/conversation-slash.ts +37 -5
- package/src/daemon/conversation-surfaces.ts +45 -2
- package/src/daemon/conversation-tool-setup.ts +7 -0
- package/src/daemon/conversation.ts +42 -5
- package/src/daemon/first-greeting.ts +10 -0
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
- package/src/daemon/handlers/config-a2a.ts +160 -0
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +79 -0
- package/src/daemon/handlers/shared.ts +92 -29
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/host-file-proxy.ts +1 -1
- package/src/daemon/host-transfer-proxy.ts +1 -1
- package/src/daemon/lifecycle.ts +18 -4
- package/src/daemon/message-protocol.ts +4 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/document-comments.ts +50 -0
- package/src/daemon/message-types/messages.ts +68 -1
- package/src/daemon/message-types/surfaces.ts +3 -1
- package/src/daemon/message-types/web-activity.ts +57 -0
- package/src/daemon/plugin-source-watcher.ts +135 -3
- package/src/daemon/process-message.ts +69 -12
- package/src/daemon/query-complexity-router.ts +75 -0
- package/src/daemon/trust-context.ts +6 -0
- package/src/documents/document-comments-store.test.ts +338 -0
- package/src/documents/document-comments-store.ts +237 -0
- package/src/documents/document-store.ts +202 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/heartbeat/heartbeat-service.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +33 -2
- package/src/home/feed-types.ts +6 -1
- package/src/home/home-content-refresh.ts +52 -0
- package/src/home/home-greeting-cache.ts +69 -0
- package/src/home/home-greeting.ts +94 -0
- package/src/home/suggested-prompts.ts +177 -9
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +320 -6
- package/src/memory/conversation-crud.ts +133 -43
- package/src/memory/db-init.ts +16 -0
- package/src/memory/delivery-crud.ts +41 -0
- package/src/memory/delivery-status.ts +141 -15
- package/src/memory/external-conversation-store.ts +32 -1
- package/src/memory/jobs-worker.ts +21 -1
- package/src/memory/memory-retrospective-constants.ts +28 -0
- package/src/memory/memory-retrospective-enqueue.ts +3 -2
- package/src/memory/memory-retrospective-job.ts +408 -18
- package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
- package/src/memory/memory-v2-activation-log-store.ts +26 -8
- package/src/memory/migrations/100-core-tables.ts +1 -0
- package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
- package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
- package/src/memory/migrations/253-document-comments.ts +47 -0
- package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
- package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
- package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
- package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
- package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
- package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
- package/src/memory/migrations/index.ts +17 -0
- package/src/memory/migrations/registry.ts +25 -0
- package/src/memory/onboarding-events-store.ts +7 -0
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
- package/src/memory/v2/__tests__/injection.test.ts +31 -14
- package/src/memory/v2/__tests__/page-index.test.ts +365 -1
- package/src/memory/v2/__tests__/router.test.ts +489 -1
- package/src/memory/v2/consolidation-job.ts +14 -0
- package/src/memory/v2/injection-events.ts +101 -0
- package/src/memory/v2/injection.ts +21 -10
- package/src/memory/v2/page-index.ts +209 -7
- package/src/memory/v2/page-store.ts +18 -0
- package/src/memory/v2/router.ts +209 -55
- package/src/messaging/providers/index.ts +7 -1
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
- package/src/messaging/providers/slack/adapter.ts +178 -25
- package/src/messaging/providers/slack/api.test.ts +54 -0
- package/src/messaging/providers/slack/api.ts +119 -3
- package/src/messaging/providers/slack/client.ts +12 -0
- package/src/messaging/providers/slack/deep-link.ts +20 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
- package/src/messaging/providers/slack/message-metadata.ts +156 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
- package/src/messaging/providers/slack/render-transcript.ts +176 -49
- package/src/messaging/providers/slack/send.test.ts +77 -0
- package/src/messaging/providers/slack/send.ts +8 -2
- package/src/messaging/providers/slack/types.ts +14 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
- package/src/notifications/conversation-seed-composer.ts +14 -2
- package/src/notifications/deferred-emit.ts +135 -0
- package/src/notifications/emit-signal.ts +9 -1
- package/src/notifications/home-feed-side-effect.ts +60 -30
- package/src/oauth/connect-orchestrator.ts +3 -0
- package/src/oauth/credential-token-resolver.ts +2 -0
- package/src/oauth/manual-token-connection.ts +19 -0
- package/src/oauth/oauth-store.ts +12 -0
- package/src/oauth/seed-providers.ts +22 -0
- package/src/permissions/prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +4 -1
- package/src/plugins/defaults/injectors.ts +82 -9
- package/src/prompts/__tests__/system-prompt.test.ts +46 -2
- package/src/prompts/normalize-onboarding.ts +40 -0
- package/src/prompts/sections.ts +32 -14
- package/src/prompts/system-prompt.ts +105 -68
- package/src/prompts/template-detection.ts +37 -0
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
- package/src/prompts/templates/BOOTSTRAP.md +8 -0
- package/src/prompts/templates/VOICE.md +3 -0
- package/src/prompts/templates/system-sections.ts +53 -3
- package/src/providers/anthropic/client.ts +132 -5
- package/src/providers/fireworks/client.ts +20 -2
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
- package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
- package/src/providers/inference/adapter-factory.ts +15 -1
- package/src/providers/inference/auth.ts +3 -3
- package/src/providers/inference/codex-token-refresh.ts +128 -0
- package/src/providers/inference/resolve-auth.ts +49 -6
- package/src/providers/model-catalog.ts +48 -1
- package/src/providers/openai/chat-completions-provider.ts +57 -20
- package/src/providers/openai/responses-provider.ts +9 -3
- package/src/providers/openrouter/client.ts +5 -1
- package/src/providers/types.ts +25 -0
- package/src/runtime/__tests__/agent-wake.test.ts +214 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
- package/src/runtime/agent-wake.ts +151 -56
- package/src/runtime/auth/route-policy.ts +7 -3
- package/src/runtime/background-job-runner.ts +26 -0
- package/src/runtime/channel-reply-delivery.ts +182 -47
- package/src/runtime/channel-retry-sweep.ts +141 -16
- package/src/runtime/http-types.ts +7 -4
- package/src/runtime/pending-interactions.ts +51 -8
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +55 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
- package/src/runtime/routes/approval-routes.ts +4 -1
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
- package/src/runtime/routes/content-source-routes.ts +78 -0
- package/src/runtime/routes/conversation-cli-routes.ts +146 -1
- package/src/runtime/routes/conversation-query-routes.ts +60 -1
- package/src/runtime/routes/conversation-routes.ts +281 -76
- package/src/runtime/routes/document-comments-routes.ts +287 -0
- package/src/runtime/routes/documents-routes.ts +33 -0
- package/src/runtime/routes/home-feed-routes.ts +6 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-browser-routes.ts +8 -1
- package/src/runtime/routes/identity-routes.ts +21 -0
- package/src/runtime/routes/inbound-message-handler.ts +288 -58
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
- package/src/runtime/routes/index.ts +12 -4
- package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
- package/src/runtime/routes/integrations/a2a.ts +60 -1
- package/src/runtime/routes/log-export-routes.ts +39 -0
- package/src/runtime/routes/memory-v2-routes.ts +217 -0
- package/src/runtime/routes/notification-routes.ts +19 -2
- package/src/runtime/routes/question-routes.ts +4 -1
- package/src/runtime/routes/sanity-routes.ts +159 -0
- package/src/runtime/routes/slack-channel-routes.ts +187 -0
- package/src/runtime/services/conversation-serializer.ts +30 -4
- package/src/schedule/integration-status.ts +3 -1
- package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
- package/src/security/oauth2-device-code.ts +307 -0
- package/src/security/oauth2.ts +26 -9
- package/src/security/secure-keys.ts +5 -0
- package/src/skills/catalog-install.ts +6 -2
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
- package/src/tools/browser/browser-execution.ts +93 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
- package/src/tools/browser/cdp-client/factory.ts +87 -3
- package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
- package/src/tools/browser/cdp-client/types.ts +36 -0
- package/src/tools/browser/pinned-tabs.ts +90 -0
- package/src/tools/document/document-comment-tool.test.ts +379 -0
- package/src/tools/document/document-comment-tool.ts +156 -0
- package/src/tools/document/document-tool.ts +128 -2
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
- package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
- package/src/tools/network/domain-normalize.ts +17 -0
- package/src/tools/network/web-fetch.ts +213 -64
- package/src/tools/network/web-search.ts +191 -66
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/tool-approval-handler.ts +19 -12
- package/src/tools/types.ts +4 -0
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/types/onboarding-context.ts +4 -0
- package/src/util/__tests__/favicon.test.ts +84 -0
- package/src/util/favicon.ts +40 -0
- package/src/util/platform.ts +0 -5
- package/src/workspace/git-service.ts +75 -4
- package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/config/bundled-skills/document/SKILL.md +0 -54
- package/src/config/bundled-skills/document/TOOLS.json +0 -106
- package/src/daemon/seed-files.ts +0 -18
- package/src/runtime/routes/interface-routes.ts +0 -43
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests that `ToolExecutionResult.activityMetadata` plumbs through to the
|
|
3
|
+
* emitted `tool_result` server event via `handleToolResult`. The forward path
|
|
4
|
+
* is: ToolExecutionResult (set by the tool executor) → AgentEvent tool_result
|
|
5
|
+
* (emitted by the agent loop) → tool_result server message (emitted by
|
|
6
|
+
* handleToolResult to the SSE sink).
|
|
7
|
+
*
|
|
8
|
+
* Mirrors the mocked-dependency pattern used in tool-preview-lifecycle.test.ts
|
|
9
|
+
* and annotate-risk-options.test.ts.
|
|
10
|
+
*/
|
|
11
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
// ── Mock platform (must precede imports that read it) ─────────────────────────
|
|
14
|
+
mock.module("../util/logger.js", () => ({
|
|
15
|
+
getLogger: () =>
|
|
16
|
+
new Proxy({} as Record<string, unknown>, {
|
|
17
|
+
get: () => () => {},
|
|
18
|
+
}),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
mock.module("../config/loader.js", () => ({
|
|
22
|
+
getConfig: () => ({
|
|
23
|
+
skills: {
|
|
24
|
+
entries: {},
|
|
25
|
+
load: { extraDirs: [], watch: false, watchDebounceMs: 0 },
|
|
26
|
+
install: { nodeManager: "npm" },
|
|
27
|
+
allowBundled: null,
|
|
28
|
+
remoteProviders: {
|
|
29
|
+
skillssh: { enabled: true },
|
|
30
|
+
clawhub: { enabled: true },
|
|
31
|
+
},
|
|
32
|
+
remotePolicy: {
|
|
33
|
+
blockSuspicious: true,
|
|
34
|
+
blockMalware: true,
|
|
35
|
+
maxSkillsShRisk: "medium",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
loadConfig: () => ({}),
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
mock.module("../memory/conversation-crud.js", () => ({
|
|
43
|
+
addMessage: () => ({ id: "mock-msg-id" }),
|
|
44
|
+
getMessageById: () => null,
|
|
45
|
+
updateMessageContent: () => {},
|
|
46
|
+
provenanceFromTrustContext: () => ({}),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
mock.module("../memory/llm-request-log-store.js", () => ({
|
|
50
|
+
recordRequestLog: () => {},
|
|
51
|
+
backfillMessageIdOnLogs: () => {},
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
// ── Imports (after mocks) ─────────────────────────────────────────────────────
|
|
55
|
+
import type {
|
|
56
|
+
EventHandlerDeps,
|
|
57
|
+
EventHandlerState,
|
|
58
|
+
} from "../daemon/conversation-agent-loop-handlers.js";
|
|
59
|
+
import {
|
|
60
|
+
createEventHandlerState,
|
|
61
|
+
handleToolResult,
|
|
62
|
+
} from "../daemon/conversation-agent-loop-handlers.js";
|
|
63
|
+
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
64
|
+
import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
|
|
65
|
+
|
|
66
|
+
type ToolResultEvent = Extract<ServerMessage, { type: "tool_result" }>;
|
|
67
|
+
|
|
68
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
function createCollectorDeps(): {
|
|
71
|
+
deps: EventHandlerDeps;
|
|
72
|
+
events: ServerMessage[];
|
|
73
|
+
} {
|
|
74
|
+
const events: ServerMessage[] = [];
|
|
75
|
+
const deps = {
|
|
76
|
+
ctx: {
|
|
77
|
+
conversationId: "conv-meta",
|
|
78
|
+
provider: { name: "anthropic" },
|
|
79
|
+
traceEmitter: { emit: () => {} },
|
|
80
|
+
streamThinking: false,
|
|
81
|
+
emitActivityState: () => {},
|
|
82
|
+
markWorkspaceTopLevelDirty: () => {},
|
|
83
|
+
currentTurnSurfaces: [],
|
|
84
|
+
} as unknown as EventHandlerDeps["ctx"],
|
|
85
|
+
onEvent: (msg: ServerMessage) => events.push(msg),
|
|
86
|
+
reqId: "req-meta",
|
|
87
|
+
isFirstMessage: false,
|
|
88
|
+
shouldGenerateTitle: false,
|
|
89
|
+
rlog: new Proxy({} as Record<string, unknown>, {
|
|
90
|
+
get: () => () => {},
|
|
91
|
+
}) as unknown as EventHandlerDeps["rlog"],
|
|
92
|
+
turnChannelContext: {
|
|
93
|
+
userMessageChannel: "vellum",
|
|
94
|
+
assistantMessageChannel: "vellum",
|
|
95
|
+
} as EventHandlerDeps["turnChannelContext"],
|
|
96
|
+
turnInterfaceContext: {
|
|
97
|
+
userMessageInterface: "macos",
|
|
98
|
+
assistantMessageInterface: "macos",
|
|
99
|
+
} as EventHandlerDeps["turnInterfaceContext"],
|
|
100
|
+
} as EventHandlerDeps;
|
|
101
|
+
return { deps, events };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function primeState(state: EventHandlerState, toolUseId: string): void {
|
|
105
|
+
state.toolUseIdToName.set(toolUseId, "web_search");
|
|
106
|
+
state.toolCallTimestamps.set(toolUseId, { startedAt: Date.now() });
|
|
107
|
+
state.currentTurnToolUseIds.push(toolUseId);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
describe("tool_result activityMetadata plumbing", () => {
|
|
113
|
+
let state: EventHandlerState;
|
|
114
|
+
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
state = createEventHandlerState();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("forwards activityMetadata to the emitted tool_result event", () => {
|
|
120
|
+
const { deps, events } = createCollectorDeps();
|
|
121
|
+
const toolUseId = "toolu_meta_present";
|
|
122
|
+
primeState(state, toolUseId);
|
|
123
|
+
|
|
124
|
+
const activityMetadata: ToolActivityMetadata = {
|
|
125
|
+
webSearch: {
|
|
126
|
+
query: "x",
|
|
127
|
+
provider: "tavily",
|
|
128
|
+
resultCount: 0,
|
|
129
|
+
durationMs: 1,
|
|
130
|
+
results: [],
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
handleToolResult(state, deps, {
|
|
135
|
+
type: "tool_result",
|
|
136
|
+
toolUseId,
|
|
137
|
+
content: "",
|
|
138
|
+
isError: false,
|
|
139
|
+
activityMetadata,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const toolResultEvent = events.find(
|
|
143
|
+
(e): e is ToolResultEvent => e.type === "tool_result",
|
|
144
|
+
);
|
|
145
|
+
expect(toolResultEvent).toBeDefined();
|
|
146
|
+
expect(toolResultEvent?.activityMetadata).toEqual(activityMetadata);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("omits activityMetadata when the executor did not populate it", () => {
|
|
150
|
+
const { deps, events } = createCollectorDeps();
|
|
151
|
+
const toolUseId = "toolu_meta_absent";
|
|
152
|
+
primeState(state, toolUseId);
|
|
153
|
+
|
|
154
|
+
handleToolResult(state, deps, {
|
|
155
|
+
type: "tool_result",
|
|
156
|
+
toolUseId,
|
|
157
|
+
content: "ok",
|
|
158
|
+
isError: false,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const toolResultEvent = events.find(
|
|
162
|
+
(e): e is ToolResultEvent => e.type === "tool_result",
|
|
163
|
+
);
|
|
164
|
+
expect(toolResultEvent).toBeDefined();
|
|
165
|
+
expect(toolResultEvent?.activityMetadata).toBeUndefined();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -1622,8 +1622,8 @@ describe("web_fetch tool", () => {
|
|
|
1622
1622
|
);
|
|
1623
1623
|
|
|
1624
1624
|
expect(result.isError).toBe(false);
|
|
1625
|
-
expect(result.content).toContain("Extracted
|
|
1626
|
-
expect(result.content).toContain("JavaScript
|
|
1625
|
+
expect(result.content).toContain("Extracted only");
|
|
1626
|
+
expect(result.content).toContain("JavaScript-rendered");
|
|
1627
1627
|
});
|
|
1628
1628
|
|
|
1629
1629
|
test("does not suggest browser skill when HTML page has substantial content", async () => {
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
mkdirSync,
|
|
6
6
|
readFileSync,
|
|
7
7
|
rmSync,
|
|
8
|
+
statSync,
|
|
8
9
|
writeFileSync,
|
|
9
10
|
} from "node:fs";
|
|
10
11
|
import { tmpdir } from "node:os";
|
|
@@ -87,6 +88,58 @@ describe("WorkspaceGitService", () => {
|
|
|
87
88
|
expect(userEmail).toBe("assistant@vellum.ai");
|
|
88
89
|
});
|
|
89
90
|
|
|
91
|
+
test("installs branch guard hook that blocks non-main branches", async () => {
|
|
92
|
+
const service = new WorkspaceGitService(testDir);
|
|
93
|
+
await service.ensureInitialized();
|
|
94
|
+
|
|
95
|
+
const hooksPath = execFileSync("git", ["config", "core.hooksPath"], {
|
|
96
|
+
cwd: testDir,
|
|
97
|
+
encoding: "utf-8",
|
|
98
|
+
}).trim();
|
|
99
|
+
expect(hooksPath).toBe(".githooks");
|
|
100
|
+
|
|
101
|
+
const hookPath = join(testDir, ".githooks", "reference-transaction");
|
|
102
|
+
expect(existsSync(hookPath)).toBe(true);
|
|
103
|
+
expect(statSync(hookPath).mode & 0o111).not.toBe(0);
|
|
104
|
+
|
|
105
|
+
const hookContent = readFileSync(hookPath, "utf-8");
|
|
106
|
+
expect(hookContent).toContain(
|
|
107
|
+
"assistant workspace git branches are disabled",
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
expect(() =>
|
|
111
|
+
execFileSync("git", ["branch", "apollo/test-branch"], {
|
|
112
|
+
cwd: testDir,
|
|
113
|
+
encoding: "utf-8",
|
|
114
|
+
}),
|
|
115
|
+
).toThrow(/assistant workspace git branches are disabled/);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("branch guard allows deleting old non-main branches", async () => {
|
|
119
|
+
const service = new WorkspaceGitService(testDir);
|
|
120
|
+
await service.ensureInitialized();
|
|
121
|
+
|
|
122
|
+
execFileSync(
|
|
123
|
+
"git",
|
|
124
|
+
["-c", "core.hooksPath=/dev/null", "branch", "old-branch"],
|
|
125
|
+
{
|
|
126
|
+
cwd: testDir,
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
execFileSync("git", ["branch", "-D", "old-branch"], { cwd: testDir });
|
|
131
|
+
|
|
132
|
+
const branches = execFileSync(
|
|
133
|
+
"git",
|
|
134
|
+
["branch", "--format=%(refname:short)"],
|
|
135
|
+
{
|
|
136
|
+
cwd: testDir,
|
|
137
|
+
encoding: "utf-8",
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
expect(branches).not.toContain("old-branch");
|
|
141
|
+
});
|
|
142
|
+
|
|
90
143
|
test("multiple ensureInitialized calls are idempotent", async () => {
|
|
91
144
|
const service = new WorkspaceGitService(testDir);
|
|
92
145
|
|
|
@@ -487,7 +540,7 @@ describe("WorkspaceGitService", () => {
|
|
|
487
540
|
// Set up a pre-existing git repo on a feature branch
|
|
488
541
|
execFileSync("git", ["init", "-b", "main"], { cwd: testDir });
|
|
489
542
|
execFileSync("git", ["config", "user.name", "Test"], { cwd: testDir });
|
|
490
|
-
execFileSync("git", ["config", "user.email", "
|
|
543
|
+
execFileSync("git", ["config", "user.email", "user@example.com"], {
|
|
491
544
|
cwd: testDir,
|
|
492
545
|
});
|
|
493
546
|
writeFileSync(join(testDir, "file.txt"), "content");
|
|
@@ -527,7 +580,7 @@ describe("WorkspaceGitService", () => {
|
|
|
527
580
|
// Set up a pre-existing git repo then detach HEAD
|
|
528
581
|
execFileSync("git", ["init", "-b", "main"], { cwd: testDir });
|
|
529
582
|
execFileSync("git", ["config", "user.name", "Test"], { cwd: testDir });
|
|
530
|
-
execFileSync("git", ["config", "user.email", "
|
|
583
|
+
execFileSync("git", ["config", "user.email", "user@example.com"], {
|
|
531
584
|
cwd: testDir,
|
|
532
585
|
});
|
|
533
586
|
writeFileSync(join(testDir, "file.txt"), "content");
|
|
@@ -571,7 +624,7 @@ describe("WorkspaceGitService", () => {
|
|
|
571
624
|
// This exercises the --discard-changes fallback in ensureOnMainLocked().
|
|
572
625
|
execFileSync("git", ["init", "-b", "main"], { cwd: testDir });
|
|
573
626
|
execFileSync("git", ["config", "user.name", "Test"], { cwd: testDir });
|
|
574
|
-
execFileSync("git", ["config", "user.email", "
|
|
627
|
+
execFileSync("git", ["config", "user.email", "user@example.com"], {
|
|
575
628
|
cwd: testDir,
|
|
576
629
|
});
|
|
577
630
|
writeFileSync(join(testDir, "file.txt"), "original content");
|
|
@@ -619,7 +672,7 @@ describe("WorkspaceGitService", () => {
|
|
|
619
672
|
// Set up a pre-existing git repo without our gitignore rules
|
|
620
673
|
execFileSync("git", ["init", "-b", "main"], { cwd: testDir });
|
|
621
674
|
execFileSync("git", ["config", "user.name", "Test"], { cwd: testDir });
|
|
622
|
-
execFileSync("git", ["config", "user.email", "
|
|
675
|
+
execFileSync("git", ["config", "user.email", "user@example.com"], {
|
|
623
676
|
cwd: testDir,
|
|
624
677
|
});
|
|
625
678
|
writeFileSync(join(testDir, ".gitignore"), "node_modules/\n");
|
|
@@ -648,7 +701,7 @@ describe("WorkspaceGitService", () => {
|
|
|
648
701
|
// Set up a pre-existing git repo with the OLD broad data/ rule
|
|
649
702
|
execFileSync("git", ["init", "-b", "main"], { cwd: testDir });
|
|
650
703
|
execFileSync("git", ["config", "user.name", "Test"], { cwd: testDir });
|
|
651
|
-
execFileSync("git", ["config", "user.email", "
|
|
704
|
+
execFileSync("git", ["config", "user.email", "user@example.com"], {
|
|
652
705
|
cwd: testDir,
|
|
653
706
|
});
|
|
654
707
|
const oldGitignore =
|
|
@@ -711,6 +764,36 @@ describe("WorkspaceGitService", () => {
|
|
|
711
764
|
expect(userEmail).toBe("assistant@vellum.ai");
|
|
712
765
|
});
|
|
713
766
|
|
|
767
|
+
test("existing repo gets branch guard installed on init", async () => {
|
|
768
|
+
execFileSync("git", ["init", "-b", "main"], { cwd: testDir });
|
|
769
|
+
execFileSync("git", ["config", "user.name", "Test"], { cwd: testDir });
|
|
770
|
+
execFileSync("git", ["config", "user.email", "user@example.com"], {
|
|
771
|
+
cwd: testDir,
|
|
772
|
+
});
|
|
773
|
+
writeFileSync(join(testDir, "file.txt"), "content");
|
|
774
|
+
execFileSync("git", ["add", "-A"], { cwd: testDir });
|
|
775
|
+
execFileSync("git", ["commit", "-m", "init"], { cwd: testDir });
|
|
776
|
+
|
|
777
|
+
const service = new WorkspaceGitService(testDir);
|
|
778
|
+
await service.ensureInitialized();
|
|
779
|
+
|
|
780
|
+
const hooksPath = execFileSync("git", ["config", "core.hooksPath"], {
|
|
781
|
+
cwd: testDir,
|
|
782
|
+
encoding: "utf-8",
|
|
783
|
+
}).trim();
|
|
784
|
+
expect(hooksPath).toBe(".githooks");
|
|
785
|
+
expect(
|
|
786
|
+
existsSync(join(testDir, ".githooks", "reference-transaction")),
|
|
787
|
+
).toBe(true);
|
|
788
|
+
|
|
789
|
+
expect(() =>
|
|
790
|
+
execFileSync("git", ["branch", "feature-after-init"], {
|
|
791
|
+
cwd: testDir,
|
|
792
|
+
encoding: "utf-8",
|
|
793
|
+
}),
|
|
794
|
+
).toThrow(/assistant workspace git branches are disabled/);
|
|
795
|
+
});
|
|
796
|
+
|
|
714
797
|
test("existing repo with correct config is idempotent", async () => {
|
|
715
798
|
// Set up a repo that already has everything configured correctly
|
|
716
799
|
execFileSync("git", ["init", "-b", "main"], { cwd: testDir });
|
package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
mkdtempSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
import { deprecateBackgroundConversationOverrideMigration } from "../workspace/migrations/088-deprecate-background-conversation-override.js";
|
|
14
|
+
|
|
15
|
+
let workspaceDir: string;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
workspaceDir = mkdtempSync(join(tmpdir(), "vellum-migration-088-test-"));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
if (existsSync(workspaceDir)) {
|
|
23
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
function promptSystemDir(): string {
|
|
28
|
+
return join(workspaceDir, "prompts", "system");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function writeOverride(body: string): string {
|
|
32
|
+
const dir = promptSystemDir();
|
|
33
|
+
mkdirSync(dir, { recursive: true });
|
|
34
|
+
const filePath = join(dir, "08-background-conversation.md");
|
|
35
|
+
writeFileSync(filePath, body, "utf-8");
|
|
36
|
+
return filePath;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("088-deprecate-background-conversation-override migration", () => {
|
|
40
|
+
test("has expected id and description", () => {
|
|
41
|
+
expect(deprecateBackgroundConversationOverrideMigration.id).toBe(
|
|
42
|
+
"088-deprecate-background-conversation-override",
|
|
43
|
+
);
|
|
44
|
+
expect(
|
|
45
|
+
deprecateBackgroundConversationOverrideMigration.description,
|
|
46
|
+
).toContain("08-background-conversation");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("renames the override to .deprecated, preserving body", () => {
|
|
50
|
+
const body = "## Custom\n\nUser-customized background note.\n";
|
|
51
|
+
writeOverride(body);
|
|
52
|
+
|
|
53
|
+
deprecateBackgroundConversationOverrideMigration.run(workspaceDir);
|
|
54
|
+
|
|
55
|
+
const originalPath = join(
|
|
56
|
+
promptSystemDir(),
|
|
57
|
+
"08-background-conversation.md",
|
|
58
|
+
);
|
|
59
|
+
const deprecatedPath = join(
|
|
60
|
+
promptSystemDir(),
|
|
61
|
+
"08-background-conversation.md.deprecated",
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
expect(existsSync(originalPath)).toBe(false);
|
|
65
|
+
expect(existsSync(deprecatedPath)).toBe(true);
|
|
66
|
+
expect(readFileSync(deprecatedPath, "utf-8")).toBe(body);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("no-ops when the override is absent", () => {
|
|
70
|
+
expect(() =>
|
|
71
|
+
deprecateBackgroundConversationOverrideMigration.run(workspaceDir),
|
|
72
|
+
).not.toThrow();
|
|
73
|
+
|
|
74
|
+
const deprecatedPath = join(
|
|
75
|
+
promptSystemDir(),
|
|
76
|
+
"08-background-conversation.md.deprecated",
|
|
77
|
+
);
|
|
78
|
+
expect(existsSync(deprecatedPath)).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("does not touch unrelated section overrides", () => {
|
|
82
|
+
const dir = promptSystemDir();
|
|
83
|
+
mkdirSync(dir, { recursive: true });
|
|
84
|
+
const otherPath = join(dir, "07-external-content.md");
|
|
85
|
+
writeFileSync(otherPath, "## External Content\n\nUntouched.\n", "utf-8");
|
|
86
|
+
writeOverride("body");
|
|
87
|
+
|
|
88
|
+
deprecateBackgroundConversationOverrideMigration.run(workspaceDir);
|
|
89
|
+
|
|
90
|
+
expect(readFileSync(otherPath, "utf-8")).toContain("Untouched");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("is safe to re-run after the rename has happened", () => {
|
|
94
|
+
writeOverride("body");
|
|
95
|
+
|
|
96
|
+
deprecateBackgroundConversationOverrideMigration.run(workspaceDir);
|
|
97
|
+
expect(() =>
|
|
98
|
+
deprecateBackgroundConversationOverrideMigration.run(workspaceDir),
|
|
99
|
+
).not.toThrow();
|
|
100
|
+
|
|
101
|
+
const deprecatedPath = join(
|
|
102
|
+
promptSystemDir(),
|
|
103
|
+
"08-background-conversation.md.deprecated",
|
|
104
|
+
);
|
|
105
|
+
expect(readFileSync(deprecatedPath, "utf-8")).toBe("body");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("drops the .md and keeps the .deprecated copy when both exist", () => {
|
|
109
|
+
// A user re-created the override after a prior partial run. The bundled
|
|
110
|
+
// section is gone, so the .md would render unconditionally — drop it.
|
|
111
|
+
const dir = promptSystemDir();
|
|
112
|
+
mkdirSync(dir, { recursive: true });
|
|
113
|
+
const deprecatedPath = join(
|
|
114
|
+
dir,
|
|
115
|
+
"08-background-conversation.md.deprecated",
|
|
116
|
+
);
|
|
117
|
+
writeFileSync(deprecatedPath, "preserved.\n", "utf-8");
|
|
118
|
+
const recreatedPath = writeOverride("re-created body\n");
|
|
119
|
+
|
|
120
|
+
deprecateBackgroundConversationOverrideMigration.run(workspaceDir);
|
|
121
|
+
|
|
122
|
+
expect(existsSync(recreatedPath)).toBe(false);
|
|
123
|
+
expect(readFileSync(deprecatedPath, "utf-8")).toBe("preserved.\n");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("down() restores the override when only .deprecated exists", () => {
|
|
127
|
+
writeOverride("body");
|
|
128
|
+
deprecateBackgroundConversationOverrideMigration.run(workspaceDir);
|
|
129
|
+
|
|
130
|
+
deprecateBackgroundConversationOverrideMigration.down(workspaceDir);
|
|
131
|
+
|
|
132
|
+
const overridePath = join(
|
|
133
|
+
promptSystemDir(),
|
|
134
|
+
"08-background-conversation.md",
|
|
135
|
+
);
|
|
136
|
+
const deprecatedPath = join(
|
|
137
|
+
promptSystemDir(),
|
|
138
|
+
"08-background-conversation.md.deprecated",
|
|
139
|
+
);
|
|
140
|
+
expect(existsSync(overridePath)).toBe(true);
|
|
141
|
+
expect(existsSync(deprecatedPath)).toBe(false);
|
|
142
|
+
expect(readFileSync(overridePath, "utf-8")).toBe("body");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("down() is a no-op when only the .md exists", () => {
|
|
146
|
+
writeOverride("body");
|
|
147
|
+
|
|
148
|
+
expect(() =>
|
|
149
|
+
deprecateBackgroundConversationOverrideMigration.down(workspaceDir),
|
|
150
|
+
).not.toThrow();
|
|
151
|
+
|
|
152
|
+
const overridePath = join(
|
|
153
|
+
promptSystemDir(),
|
|
154
|
+
"08-background-conversation.md",
|
|
155
|
+
);
|
|
156
|
+
expect(readFileSync(overridePath, "utf-8")).toBe("body");
|
|
157
|
+
});
|
|
158
|
+
});
|
package/src/agent/attachments.ts
CHANGED
package/src/agent/loop.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getCalibrationProviderKey,
|
|
8
8
|
} from "../context/token-estimator.js";
|
|
9
9
|
import { calculateMaxToolResultChars } from "../context/tool-result-truncation.js";
|
|
10
|
+
import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
|
|
10
11
|
import { defaultEmptyResponseTerminal } from "../plugins/defaults/empty-response.js";
|
|
11
12
|
import { defaultToolErrorTerminal } from "../plugins/defaults/tool-error.js";
|
|
12
13
|
import { defaultToolResultTruncateTerminal } from "../plugins/defaults/tool-result-truncate.js";
|
|
@@ -68,21 +69,22 @@ export interface CheckpointInfo {
|
|
|
68
69
|
export type CheckpointDecision = "continue" | "yield";
|
|
69
70
|
|
|
70
71
|
/**
|
|
71
|
-
* Why an
|
|
72
|
+
* Why an agent turn reached a terminal state.
|
|
72
73
|
*
|
|
73
|
-
* Emitted
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
74
|
+
* Emitted as part of an {@link AgentEvent} of type `agent_loop_exit`, then
|
|
75
|
+
* persisted onto the **final** `llm_request_logs` row of the turn. Rows from
|
|
76
|
+
* intermediate turns keep a NULL `agent_loop_exit_reason`, which is how
|
|
77
|
+
* downstream tooling (and the LLM Context Inspector) distinguishes "loop kept
|
|
78
|
+
* going" from "loop is done".
|
|
78
79
|
*
|
|
79
80
|
* Values are stable wire/DB strings — they are written to SQLite and
|
|
80
81
|
* surfaced over the inspector wire format, so renaming any of them is a
|
|
81
82
|
* breaking change.
|
|
82
83
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
84
|
+
* Keep in sync with `emitExit` call sites in {@link AgentLoop.run} and the
|
|
85
|
+
* outer conversation orchestrator paths that terminate after a checkpoint
|
|
86
|
+
* yield. A checkpoint yield used for budget compaction is intentionally not
|
|
87
|
+
* a terminal reason — it is a control transfer before re-entering the loop.
|
|
86
88
|
*/
|
|
87
89
|
export type AgentLoopExitReason =
|
|
88
90
|
/** `if (signal?.aborted) break;` at the top of the loop. */
|
|
@@ -97,8 +99,12 @@ export type AgentLoopExitReason =
|
|
|
97
99
|
| "aborted_during_tools"
|
|
98
100
|
/** A tool result requested handing back to the user. */
|
|
99
101
|
| "yield_to_user"
|
|
100
|
-
/** The orchestrator
|
|
101
|
-
| "
|
|
102
|
+
/** The orchestrator yielded at checkpoint to process a queued message. */
|
|
103
|
+
| "checkpoint_handoff"
|
|
104
|
+
/** Context-window recovery exhausted and the turn ended with an error. */
|
|
105
|
+
| "context_too_large"
|
|
106
|
+
/** User cancellation landed after a non-terminal checkpoint yield. */
|
|
107
|
+
| "aborted_after_checkpoint"
|
|
102
108
|
/** Signal aborted while the catch handler was synthesizing an error turn. */
|
|
103
109
|
| "aborted_via_error"
|
|
104
110
|
/** Catch-block fallback: an unhandled error broke the loop. */
|
|
@@ -142,6 +148,7 @@ export type AgentEvent =
|
|
|
142
148
|
approvalMode?: string;
|
|
143
149
|
approvalReason?: string;
|
|
144
150
|
riskThreshold?: string;
|
|
151
|
+
activityMetadata?: ToolActivityMetadata;
|
|
145
152
|
}
|
|
146
153
|
| { type: "tool_use_preview_start"; toolUseId: string; toolName: string }
|
|
147
154
|
| {
|
|
@@ -161,6 +168,17 @@ export type AgentEvent =
|
|
|
161
168
|
toolUseId: string;
|
|
162
169
|
isError: boolean;
|
|
163
170
|
content?: unknown[];
|
|
171
|
+
/**
|
|
172
|
+
* Finalized input for the server tool (e.g. the actual web-search
|
|
173
|
+
* query). Carried through so the daemon can populate accurate activity
|
|
174
|
+
* metadata; Anthropic streams server-tool input via deltas that aren't
|
|
175
|
+
* resolved at `server_tool_start` time.
|
|
176
|
+
*/
|
|
177
|
+
resolvedInput?: Record<string, unknown>;
|
|
178
|
+
/** Provider-specific error code (e.g. `max_uses_exceeded`). */
|
|
179
|
+
errorCode?: string;
|
|
180
|
+
/** Optional human-readable error message from the provider. */
|
|
181
|
+
errorMessage?: string;
|
|
164
182
|
}
|
|
165
183
|
| { type: "error"; error: Error }
|
|
166
184
|
| {
|
|
@@ -210,10 +228,11 @@ export type AgentEvent =
|
|
|
210
228
|
}
|
|
211
229
|
| {
|
|
212
230
|
/**
|
|
213
|
-
* Emitted
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
231
|
+
* Emitted when an agent turn reaches a terminal state. Checkpoint
|
|
232
|
+
* yields used for orchestration (handoff or budget compaction) are not
|
|
233
|
+
* emitted by {@link AgentLoop.run}; the outer orchestrator emits a
|
|
234
|
+
* terminal reason only if that control transfer truly ends the turn.
|
|
235
|
+
* Consumers persist `reason` onto the final `llm_request_logs` row;
|
|
217
236
|
* intermediate rows keep `agent_loop_exit_reason = NULL`, which is the
|
|
218
237
|
* canonical "loop kept going" signal.
|
|
219
238
|
*/
|
|
@@ -371,6 +390,7 @@ export type LoopToolExecutor = (
|
|
|
371
390
|
approvalMode?: string;
|
|
372
391
|
approvalReason?: string;
|
|
373
392
|
riskThreshold?: string;
|
|
393
|
+
activityMetadata?: ToolActivityMetadata;
|
|
374
394
|
}>;
|
|
375
395
|
|
|
376
396
|
export class AgentLoop {
|
|
@@ -458,6 +478,8 @@ export class AgentLoop {
|
|
|
458
478
|
*/
|
|
459
479
|
overrideProfile?: string,
|
|
460
480
|
effectiveMaxInputTokens?: number,
|
|
481
|
+
resolveOverrideProfile?: () => string | undefined,
|
|
482
|
+
resolveEffectiveMaxInputTokens?: () => number | undefined,
|
|
461
483
|
): Promise<Message[]> {
|
|
462
484
|
const history = [...messages];
|
|
463
485
|
const initialHistoryLength = messages.length;
|
|
@@ -580,9 +602,14 @@ export class AgentLoop {
|
|
|
580
602
|
// `activeProfile` and any call-site named profile. Threading it on
|
|
581
603
|
// every send (rather than once at construction) keeps subagents that
|
|
582
604
|
// share an `AgentLoop` instance but ought to inherit a different
|
|
583
|
-
// profile correct — and matches how `callSite` is plumbed.
|
|
584
|
-
|
|
585
|
-
|
|
605
|
+
// profile correct — and matches how `callSite` is plumbed. The
|
|
606
|
+
// optional resolver lets a turn observe an explicitly confirmed
|
|
607
|
+
// profile-session switch before the next model call.
|
|
608
|
+
const effectiveOverrideProfile = resolveOverrideProfile
|
|
609
|
+
? resolveOverrideProfile()
|
|
610
|
+
: overrideProfile;
|
|
611
|
+
if (effectiveOverrideProfile) {
|
|
612
|
+
providerConfig.overrideProfile = effectiveOverrideProfile;
|
|
586
613
|
}
|
|
587
614
|
|
|
588
615
|
// Rate-limit consecutive LLM calls to prevent spin when tools return instantly
|
|
@@ -690,6 +717,13 @@ export class AgentLoop {
|
|
|
690
717
|
toolUseId: event.toolUseId,
|
|
691
718
|
isError: event.isError,
|
|
692
719
|
...(event.content ? { content: event.content } : {}),
|
|
720
|
+
...(event.resolvedInput
|
|
721
|
+
? { resolvedInput: event.resolvedInput }
|
|
722
|
+
: {}),
|
|
723
|
+
...(event.errorCode ? { errorCode: event.errorCode } : {}),
|
|
724
|
+
...(event.errorMessage
|
|
725
|
+
? { errorMessage: event.errorMessage }
|
|
726
|
+
: {}),
|
|
693
727
|
});
|
|
694
728
|
}
|
|
695
729
|
},
|
|
@@ -1079,7 +1113,10 @@ export class AgentLoop {
|
|
|
1079
1113
|
// truncation strategy (e.g. a summariser) while the default
|
|
1080
1114
|
// middleware preserves the historical tail-drop behaviour.
|
|
1081
1115
|
const contextWindowTokens =
|
|
1082
|
-
|
|
1116
|
+
resolveEffectiveMaxInputTokens?.() ??
|
|
1117
|
+
effectiveMaxInputTokens ??
|
|
1118
|
+
this.config.maxInputTokens ??
|
|
1119
|
+
180_000;
|
|
1083
1120
|
const maxChars = calculateMaxToolResultChars(contextWindowTokens);
|
|
1084
1121
|
const truncateMiddlewares = getMiddlewaresFor("toolResultTruncate");
|
|
1085
1122
|
|
|
@@ -1155,6 +1192,7 @@ export class AgentLoop {
|
|
|
1155
1192
|
approvalMode: result.approvalMode,
|
|
1156
1193
|
approvalReason: result.approvalReason,
|
|
1157
1194
|
riskThreshold: result.riskThreshold,
|
|
1195
|
+
activityMetadata: result.activityMetadata,
|
|
1158
1196
|
});
|
|
1159
1197
|
}
|
|
1160
1198
|
|
|
@@ -1236,7 +1274,6 @@ export class AgentLoop {
|
|
|
1236
1274
|
history,
|
|
1237
1275
|
});
|
|
1238
1276
|
if (decision === "yield") {
|
|
1239
|
-
await emitExit("checkpoint_yield");
|
|
1240
1277
|
break;
|
|
1241
1278
|
}
|
|
1242
1279
|
}
|