@vellumai/assistant 0.10.2-dev.202606250318.5e7cfb0 → 0.10.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/bun.lock +0 -20
- package/docs/workspace-tools.md +33 -42
- package/eslint-rules/cli-no-daemon-internals.js +0 -6
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +0 -31
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +0 -44
- package/node_modules/@vellumai/gateway-client/src/index.ts +0 -14
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +0 -17
- package/node_modules/@vellumai/service-contracts/package.json +0 -1
- package/node_modules/@vellumai/service-contracts/src/index.ts +0 -1
- package/openapi.yaml +0 -155
- package/package.json +1 -4
- package/scripts/test.sh +15 -36
- package/src/__tests__/actor-token-service.test.ts +14 -36
- package/src/__tests__/agent-loop-override-profile.test.ts +0 -1
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -2
- package/src/__tests__/agent-wake-override-profile.test.ts +0 -2
- package/src/__tests__/annotate-activity-metadata.test.ts +0 -2
- package/src/__tests__/annotate-risk-options.test.ts +0 -2
- package/src/__tests__/approval-cascade.test.ts +0 -2
- package/src/__tests__/assistant-attachments.test.ts +0 -42
- package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
- package/src/__tests__/btw-routes.test.ts +0 -2
- package/src/__tests__/build-persisted-content.test.ts +0 -2
- package/src/__tests__/call-controller.test.ts +0 -19
- package/src/__tests__/channel-guardian.test.ts +58 -94
- package/src/__tests__/channel-reply-delivery.test.ts +0 -2
- package/src/__tests__/compaction-events.test.ts +0 -2
- package/src/__tests__/compaction.benchmark.test.ts +0 -2
- package/src/__tests__/compactor-call-site-logging.test.ts +0 -2
- package/src/__tests__/compactor-low-watermark-cut.test.ts +0 -2
- package/src/__tests__/compactor-preserved-tail-count.test.ts +0 -2
- package/src/__tests__/compactor-summary-call-truncation.test.ts +0 -2
- package/src/__tests__/compactor-web-search-strip.test.ts +0 -2
- package/src/__tests__/config-loader-backfill.test.ts +10 -123
- package/src/__tests__/config-schema.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +29 -31
- package/src/__tests__/contacts-relay-reads.test.ts +15 -13
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +0 -134
- package/src/__tests__/conversation-analysis-routes.test.ts +0 -2
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +0 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -2
- package/src/__tests__/conversation-history-web-search.test.ts +0 -2
- package/src/__tests__/conversation-load-history-repair.test.ts +0 -2
- package/src/__tests__/conversation-load-history-stripped.test.ts +0 -2
- package/src/__tests__/conversation-pairing.test.ts +0 -2
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +0 -2
- package/src/__tests__/conversation-process-callsite.test.ts +0 -2
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -2
- package/src/__tests__/conversation-queue.test.ts +0 -91
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -14
- package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -14
- package/src/__tests__/conversation-slash-queue.test.ts +0 -2
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -2
- package/src/__tests__/conversation-speed-override.test.ts +0 -2
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -29
- package/src/__tests__/conversation-title-service.test.ts +0 -2
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -47
- package/src/__tests__/conversation-usage.test.ts +0 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -2
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -2
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/db-migration-rollback.test.ts +171 -205
- package/src/__tests__/db-test-helpers.ts +4 -5
- package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -4
- package/src/__tests__/disk-pressure-guard.test.ts +0 -41
- package/src/__tests__/dm-persistence.test.ts +0 -2
- package/src/__tests__/emit-signal-routing-intent.test.ts +5 -10
- package/src/__tests__/events-dev-bypass-actor.test.ts +1 -7
- package/src/__tests__/exploration-drift-hook.test.ts +2 -3
- package/src/__tests__/filing-service.test.ts +0 -2
- package/src/__tests__/guardian-binding-drift-heal.test.ts +10 -75
- package/src/__tests__/guardian-dispatch.test.ts +1 -95
- package/src/__tests__/guardian-outbound-http.test.ts +0 -13
- package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -2
- package/src/__tests__/heartbeat-service.test.ts +0 -2
- package/src/__tests__/helpers/channel-test-adapter.ts +7 -1
- package/src/__tests__/host-app-control-routes.test.ts +30 -24
- package/src/__tests__/host-bash-routes.test.ts +41 -31
- package/src/__tests__/host-browser-routes.test.ts +32 -26
- package/src/__tests__/host-cu-routes-targeted.test.ts +33 -25
- package/src/__tests__/host-file-routes-targeted.test.ts +52 -40
- package/src/__tests__/host-transfer-routes-targeted.test.ts +43 -31
- package/src/__tests__/http-user-message-parity.test.ts +8 -290
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -28
- package/src/__tests__/inbound-slack-persistence.test.ts +0 -2
- package/src/__tests__/invite-redemption-service.test.ts +0 -198
- package/src/__tests__/llm-context-normalization.test.ts +0 -105
- package/src/__tests__/llm-request-log-error-payload.test.ts +9 -71
- package/src/__tests__/llm-usage-store.test.ts +0 -25
- package/src/__tests__/mcp-health-check.test.ts +1 -2
- package/src/__tests__/media-stream-server-integration.test.ts +0 -127
- package/src/__tests__/memory-retrieval-hook.test.ts +0 -2
- package/src/__tests__/messaging-send-tool.test.ts +0 -2
- package/src/__tests__/migration-import-from-url.test.ts +2 -2
- package/src/__tests__/mtime-cache.test.ts +5 -146
- package/src/__tests__/native-web-search.test.ts +0 -2
- package/src/__tests__/non-member-access-request.test.ts +17 -189
- package/src/__tests__/notification-broadcaster.test.ts +0 -4
- package/src/__tests__/notification-decision-recipient-context.test.ts +32 -33
- package/src/__tests__/notification-deep-link.test.ts +0 -6
- package/src/__tests__/notification-guardian-path.test.ts +0 -19
- package/src/__tests__/openai-provider.test.ts +12 -22
- package/src/__tests__/openai-responses-provider.test.ts +2 -12
- package/src/__tests__/outbound-slack-persistence.test.ts +0 -2
- package/src/__tests__/pending-interactions-resolved-event.test.ts +4 -7
- package/src/__tests__/persistence-secret-redaction.test.ts +0 -2
- package/src/__tests__/plugin-bootstrap.test.ts +73 -3
- package/src/__tests__/plugin-route-contribution.test.ts +17 -4
- package/src/__tests__/plugin-tool-contribution.test.ts +18 -3
- package/src/__tests__/plugin-types.test.ts +2 -0
- package/src/__tests__/process-message-background-slack.test.ts +0 -2
- package/src/__tests__/process-message-display-content.test.ts +0 -2
- package/src/__tests__/provider-error-scenarios.test.ts +4 -5
- package/src/__tests__/provider-usage-tracking.test.ts +0 -39
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -2
- package/src/__tests__/registry.test.ts +1 -4
- package/src/__tests__/relay-server.test.ts +25 -694
- package/src/__tests__/runtime-attachment-metadata.test.ts +1 -0
- package/src/__tests__/secret-ingress-http.test.ts +0 -14
- package/src/__tests__/send-endpoint-busy.test.ts +8 -30
- package/src/__tests__/skills.test.ts +0 -44
- package/src/__tests__/slack-inbound-verification.test.ts +2 -47
- package/src/__tests__/stt-hints.test.ts +13 -44
- package/src/__tests__/subagent-detail.test.ts +0 -27
- package/src/__tests__/subagent-disposal.test.ts +0 -65
- package/src/__tests__/subagent-notify-parent.test.ts +0 -2
- package/src/__tests__/subagent-role-registry.test.ts +2 -7
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +0 -2
- package/src/__tests__/subagent-tools.test.ts +0 -2
- package/src/__tests__/suggestion-routes.test.ts +0 -2
- package/src/__tests__/title-generate-hook.test.ts +0 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -2
- package/src/__tests__/tool-executor.test.ts +11 -16
- package/src/__tests__/tool-preview-lifecycle.test.ts +0 -2
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +0 -2
- package/src/__tests__/tool-start-timestamp.test.ts +0 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
- package/src/__tests__/twilio-routes.test.ts +0 -96
- package/src/__tests__/ui-file-upload-surface.test.ts +0 -86
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-invite-redemption.test.ts +0 -33
- package/src/__tests__/web-search-backend-failure.test.ts +0 -2
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +35 -14
- package/src/__tests__/workspace-tool-loader.test.ts +2 -195
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
- package/src/agent/loop.ts +0 -56
- package/src/api/index.ts +1 -19
- package/src/api/responses/llm-request-log-entry.ts +0 -29
- package/src/api/responses/subagent-detail.ts +0 -17
- package/src/api/surfaces.ts +3 -39
- package/src/approvals/guardian-request-resolvers.ts +11 -1
- package/src/calls/__tests__/relay-setup-router.test.ts +4 -262
- package/src/calls/call-domain.ts +3 -3
- package/src/calls/guardian-dispatch.ts +8 -10
- package/src/calls/inbound-trust-reader.ts +1 -17
- package/src/calls/media-stream-server.ts +0 -21
- package/src/calls/relay-server.ts +50 -167
- package/src/calls/relay-setup-router.ts +7 -37
- package/src/calls/relay-verification.ts +4 -4
- package/src/calls/stt-hints.ts +12 -9
- package/src/calls/twilio-routes.ts +4 -14
- package/src/channels/types.ts +20 -10
- package/src/cli/commands/__tests__/cache.test.ts +1 -8
- package/src/cli/commands/cache.ts +181 -194
- package/src/cli/commands/db/__tests__/repair.test.ts +5 -6
- package/src/cli/commands/db/status.ts +1 -37
- package/src/cli/commands/mcp.ts +218 -252
- package/src/cli/commands/memory/index.ts +0 -2
- package/src/cli/commands/plugins.ts +3 -75
- package/src/cli/lib/__tests__/install-from-github.test.ts +0 -102
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +1 -160
- package/src/cli/lib/list-installed-plugins.ts +1 -179
- package/src/config/__tests__/sync-gated-profiles.test.ts +3 -11
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +17 -27
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +3 -13
- package/src/config/bundled-skills/subagent/SKILL.md +1 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
- package/src/config/feature-flag-registry.json +13 -5
- package/src/config/loader.ts +5 -38
- package/src/config/schemas/__tests__/memory-v3.test.ts +0 -1
- package/src/config/schemas/memory-lifecycle.ts +0 -12
- package/src/config/schemas/memory-v3.ts +0 -7
- package/src/config/schemas/memory.ts +0 -4
- package/src/config/schemas/timeouts.ts +0 -8
- package/src/config/seed-inference-profiles.ts +11 -21
- package/src/config/skills.ts +5 -27
- package/src/config/sync-gated-profiles.ts +13 -12
- package/src/contacts/contacts-write.ts +0 -3
- package/src/daemon/assistant-attachments.ts +4 -27
- package/src/daemon/conversation-agent-loop.ts +0 -28
- package/src/daemon/conversation-process.ts +16 -35
- package/src/daemon/conversation-surfaces.ts +38 -111
- package/src/daemon/conversation-tool-setup.ts +16 -50
- package/src/daemon/conversation.ts +1 -13
- package/src/daemon/disk-pressure-guard.ts +2 -12
- package/src/daemon/event-loop-watchdog.ts +1 -28
- package/src/daemon/external-plugins-bootstrap.ts +34 -4
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -25
- package/src/daemon/handlers/config-a2a.ts +14 -6
- package/src/daemon/handlers/config-channels.ts +22 -78
- package/src/daemon/handlers/conversations.ts +0 -77
- package/src/daemon/lifecycle.ts +0 -4
- package/src/daemon/mcp-reload-service.ts +0 -10
- package/src/daemon/memory-v2-startup.test.ts +0 -72
- package/src/daemon/memory-v2-startup.ts +19 -87
- package/src/daemon/message-types/conversations.ts +0 -2
- package/src/daemon/message-types/surfaces.ts +12 -12
- package/src/daemon/server.ts +4 -0
- package/src/daemon/shutdown-handlers.ts +0 -20
- package/src/daemon/tool-setup-types.ts +0 -9
- package/src/daemon/workspace-tools-watcher.ts +328 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
- package/src/ipc/assistant-server.ts +2 -2
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
- package/src/mcp/client.ts +1 -15
- package/src/mcp/mcp-auth-orchestrator.ts +1 -6
- package/src/mcp/mcp-oauth-provider.ts +8 -19
- package/src/memory/__tests__/memory-retrospective-job.test.ts +0 -8
- package/src/memory/conversation-crud.ts +0 -38
- package/src/memory/db-connection.ts +3 -22
- package/src/memory/db-init.ts +502 -36
- package/src/memory/db-singleton.ts +4 -6
- package/src/memory/jobs-worker.ts +0 -58
- package/src/memory/llm-request-log-store.ts +1 -26
- package/src/memory/llm-usage-store.ts +20 -48
- package/src/memory/memory-retrospective-job.ts +8 -9
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +56 -130
- package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
- package/src/memory/migrations/registry.ts +573 -0
- package/src/memory/migrations/run-migrations.ts +6 -90
- package/src/memory/migrations/validate-migration-state.ts +66 -101
- package/src/memory/schema/conversations.ts +0 -9
- package/src/memory/schema/infrastructure.ts +0 -20
- package/src/memory/v2/__tests__/cli-command-store.test.ts +0 -25
- package/src/memory/v2/__tests__/skill-store.test.ts +0 -80
- package/src/memory/v2/cli-command-store.ts +38 -75
- package/src/memory/v2/prompts/consolidation.ts +82 -13
- package/src/memory/v2/prompts/router.ts +93 -21
- package/src/memory/v2/skill-store.ts +31 -68
- package/src/notifications/__tests__/broadcaster.test.ts +8 -16
- package/src/notifications/__tests__/decision-engine.test.ts +9 -78
- package/src/notifications/broadcaster.ts +1 -8
- package/src/notifications/decision-engine.ts +7 -15
- package/src/notifications/destination-resolver.ts +24 -68
- package/src/notifications/emit-signal.ts +14 -39
- package/src/permissions/question-prompter.test.ts +1 -1
- package/src/permissions/question-prompter.ts +4 -7
- package/src/plugin-api/index.ts +6 -6
- package/src/plugin-api/types.ts +5 -3
- package/src/plugin-api/vision-support.test.ts +4 -28
- package/src/plugin-api/vision-support.ts +31 -66
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -161
- package/src/plugins/defaults/advisor/consult.ts +6 -110
- package/src/plugins/defaults/advisor/steering.ts +2 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +5 -32
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +1 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +7 -47
- package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +11 -10
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +20 -12
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +11 -42
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +3 -33
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +4 -48
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +8 -4
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +15 -43
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +2 -11
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -77
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +11 -12
- package/src/plugins/mtime-cache.ts +291 -76
- package/src/plugins/pipeline.ts +13 -111
- package/src/plugins/types.ts +2 -0
- package/src/providers/anthropic/client.ts +0 -5
- package/src/providers/call-site-routing.ts +0 -4
- package/src/providers/model-catalog.ts +0 -16
- package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
- package/src/providers/openai/chat-completions-provider.ts +83 -37
- package/src/providers/openai/responses-provider.ts +46 -50
- package/src/providers/openrouter/client.ts +0 -5
- package/src/providers/provider-send-message.ts +0 -4
- package/src/providers/ratelimit.ts +0 -4
- package/src/providers/retry.ts +0 -4
- package/src/providers/types.ts +0 -9
- package/src/providers/usage-tracking.ts +0 -4
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +3 -335
- package/src/runtime/access-request-helper.ts +39 -19
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/assistant-stream-state.ts +2 -9
- package/src/runtime/auth/require-bound-guardian.ts +11 -21
- package/src/runtime/channel-verification-service.ts +31 -56
- package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
- package/src/runtime/guardian-vellum-migration.ts +7 -66
- package/src/runtime/invite-redemption-service.ts +187 -198
- package/src/runtime/local-actor-identity.ts +11 -76
- package/src/runtime/pending-interactions.ts +1 -11
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +5 -56
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +0 -187
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +5 -13
- package/src/runtime/routes/channel-verification-routes.ts +3 -3
- package/src/runtime/routes/contact-routes.ts +32 -8
- package/src/runtime/routes/conversation-cli-routes.ts +5 -4
- package/src/runtime/routes/conversation-list-routes.ts +7 -4
- package/src/runtime/routes/conversation-query-routes.ts +0 -72
- package/src/runtime/routes/conversation-routes.ts +85 -84
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/global-search-routes.ts +1 -3
- package/src/runtime/routes/guardian-action-routes.ts +5 -4
- package/src/runtime/routes/host-app-control-routes.ts +4 -5
- package/src/runtime/routes/host-bash-routes.ts +4 -5
- package/src/runtime/routes/host-browser-routes.ts +11 -9
- package/src/runtime/routes/host-cu-routes.ts +4 -5
- package/src/runtime/routes/host-file-routes.ts +4 -5
- package/src/runtime/routes/host-transfer-routes.ts +6 -6
- package/src/runtime/routes/http-adapter.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +2 -3
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +5 -97
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +49 -61
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -16
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +8 -21
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +3 -14
- package/src/runtime/routes/index.ts +0 -2
- package/src/runtime/routes/llm-context-normalization.ts +0 -83
- package/src/runtime/routes/mcp-auth-routes.ts +19 -171
- package/src/runtime/routes/migration-rollback-routes.ts +3 -4
- package/src/runtime/routes/migration-routes.ts +1 -4
- package/src/runtime/routes/subagents-routes.ts +0 -5
- package/src/runtime/routes/surface-action-routes.ts +56 -42
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -1
- package/src/runtime/services/conversation-serializer.ts +9 -7
- package/src/runtime/tool-grant-request-helper.ts +3 -3
- package/src/runtime/trust-verdict-consumer.ts +9 -85
- package/src/runtime/verification-outbound-actions.ts +18 -18
- package/src/signals/user-message.ts +0 -16
- package/src/subagent/manager.ts +0 -9
- package/src/subagent/types.ts +3 -3
- package/src/telemetry/types.ts +1 -34
- package/src/telemetry/usage-telemetry-reporter.test.ts +2 -3
- package/src/telemetry/usage-telemetry-reporter.ts +3 -87
- package/src/tools/ask-question/ask-question-tool.test.ts +0 -29
- package/src/tools/ask-question/ask-question-tool.ts +0 -13
- package/src/tools/executor.ts +4 -4
- package/src/tools/registry.ts +0 -18
- package/src/tools/shared/filesystem/path-policy.ts +5 -12
- package/src/tools/tool-approval-handler.ts +1 -1
- package/src/tools/tool-defaults.ts +2 -9
- package/src/tools/tool-manifest.ts +0 -3
- package/src/tools/types.ts +2 -17
- package/src/tools/workspace-tools/loader.ts +244 -348
- package/src/util/errors.ts +1 -26
- package/src/util/platform.ts +0 -5
- package/src/workflows/library.test.ts +0 -140
- package/src/workflows/library.ts +28 -82
- package/src/workspace/migrations/017-seed-persona-dirs.ts +34 -3
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +24 -3
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +66 -14
- package/src/workspace/migrations/registry.ts +0 -2
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +0 -91
- package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +0 -48
- package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +0 -28
- package/node_modules/@vellumai/service-contracts/src/channels.ts +0 -41
- package/src/__tests__/code-search-tool.test.ts +0 -585
- package/src/__tests__/guardian-expiry-notifier.test.ts +0 -282
- package/src/__tests__/mcp-config-secret-boundary.test.ts +0 -390
- package/src/__tests__/plugin-pipeline.test.ts +0 -96
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +0 -102
- package/src/__tests__/steer-on-enqueue-question.test.ts +0 -181
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +0 -208
- package/src/agent/loop-exclusive-tool.test.ts +0 -150
- package/src/api/constants/sse-replay.ts +0 -41
- package/src/api/events/conversation-notice.ts +0 -26
- package/src/approvals/guardian-channel-delivery.ts +0 -30
- package/src/approvals/guardian-expiry-notifier.ts +0 -148
- package/src/cli/commands/memory/__tests__/worker.test.ts +0 -302
- package/src/cli/commands/memory/worker.ts +0 -175
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +0 -143
- package/src/config/prune-seeded-callsite-defaults.ts +0 -110
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -129
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +0 -312
- package/src/contacts/__tests__/member-write-relay.test.ts +0 -202
- package/src/contacts/guardian-delivery-reader.ts +0 -223
- package/src/contacts/member-write-relay.ts +0 -189
- package/src/daemon/conversation-notices.ts +0 -60
- package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -225
- package/src/hooks/hook-loader.ts +0 -341
- package/src/mcp/mcp-header-store.ts +0 -134
- package/src/memory/__tests__/301-create-watchdog-events.test.ts +0 -110
- package/src/memory/__tests__/prompt-override.test.ts +0 -192
- package/src/memory/__tests__/watchdog-events-store.test.ts +0 -161
- package/src/memory/migrations/300-add-processing-started-at.ts +0 -30
- package/src/memory/migrations/301-create-watchdog-events.ts +0 -45
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +0 -224
- package/src/memory/prompt-override.ts +0 -129
- package/src/memory/steps.ts +0 -573
- package/src/memory/watchdog-events-store.ts +0 -87
- package/src/memory/worker-control.ts +0 -118
- package/src/memory/worker-process.ts +0 -72
- package/src/notifications/__tests__/connected-channels.test.ts +0 -114
- package/src/notifications/__tests__/destination-resolver.test.ts +0 -256
- package/src/onboarding/checkin-event.test.ts +0 -222
- package/src/onboarding/checkin-event.ts +0 -321
- package/src/onboarding/schedule-checkin.ts +0 -190
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
- package/src/plugins/defaults/advisor/context-pack.ts +0 -288
- package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +0 -146
- package/src/plugins/surface-import.ts +0 -121
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +0 -321
- package/src/providers/openai/api-error-normalization.ts +0 -270
- package/src/runtime/__tests__/channel-verification-service.test.ts +0 -133
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +0 -181
- package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +0 -66
- package/src/runtime/__tests__/local-principal-trust.test.ts +0 -164
- package/src/runtime/anchored-guardian.test.ts +0 -156
- package/src/runtime/anchored-guardian.ts +0 -135
- package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +0 -99
- package/src/runtime/local-principal-trust.ts +0 -52
- package/src/runtime/routes/__tests__/contact-routes.test.ts +0 -212
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -93
- package/src/runtime/routes/onboarding-checkin-routes.ts +0 -86
- package/src/tools/filesystem/search.ts +0 -543
- package/src/util/telemetry-db-path.ts +0 -24
- package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +0 -134
|
@@ -1,585 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
chmodSync,
|
|
3
|
-
mkdirSync,
|
|
4
|
-
mkdtempSync,
|
|
5
|
-
readdirSync,
|
|
6
|
-
readFileSync,
|
|
7
|
-
realpathSync,
|
|
8
|
-
rmSync,
|
|
9
|
-
statSync,
|
|
10
|
-
writeFileSync,
|
|
11
|
-
} from "node:fs";
|
|
12
|
-
import { tmpdir } from "node:os";
|
|
13
|
-
import { join } from "node:path";
|
|
14
|
-
import { afterEach, describe, expect, test } from "bun:test";
|
|
15
|
-
|
|
16
|
-
import { SUBAGENT_ONLY_TOOL_NAMES } from "../daemon/conversation-tool-setup.js";
|
|
17
|
-
import { codeSearchTool } from "../tools/filesystem/search.js";
|
|
18
|
-
import type { ToolContext } from "../tools/types.js";
|
|
19
|
-
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
// Helpers
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
|
|
24
|
-
const testDirs: string[] = [];
|
|
25
|
-
|
|
26
|
-
function makeTempDir(): string {
|
|
27
|
-
const dir = realpathSync(mkdtempSync(join(tmpdir(), "code-search-test-")));
|
|
28
|
-
testDirs.push(dir);
|
|
29
|
-
return dir;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
afterEach(() => {
|
|
33
|
-
for (const dir of testDirs.splice(0)) {
|
|
34
|
-
rmSync(dir, { recursive: true, force: true });
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
function makeToolContext(workingDir: string): ToolContext {
|
|
39
|
-
return {
|
|
40
|
-
workingDir,
|
|
41
|
-
conversationId: "test-conv",
|
|
42
|
-
trustClass: "guardian",
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
// code_search
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
|
|
50
|
-
describe("codeSearchTool", () => {
|
|
51
|
-
test("finds a known pattern with correct file:line", async () => {
|
|
52
|
-
const dir = makeTempDir();
|
|
53
|
-
writeFileSync(join(dir, "a.ts"), "const foo = 1;\nconst bar = 2;\n");
|
|
54
|
-
|
|
55
|
-
const result = await codeSearchTool.execute(
|
|
56
|
-
{ pattern: "bar", activity: "search" },
|
|
57
|
-
makeToolContext(dir),
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
expect(result.isError).toBe(false);
|
|
61
|
-
expect(result.content).toBe("a.ts:2: const bar = 2;");
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test("respects glob filter", async () => {
|
|
65
|
-
const dir = makeTempDir();
|
|
66
|
-
writeFileSync(join(dir, "match.ts"), "needle here\n");
|
|
67
|
-
writeFileSync(join(dir, "skip.md"), "needle here too\n");
|
|
68
|
-
|
|
69
|
-
const result = await codeSearchTool.execute(
|
|
70
|
-
{ pattern: "needle", glob: "*.ts", activity: "search" },
|
|
71
|
-
makeToolContext(dir),
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
expect(result.isError).toBe(false);
|
|
75
|
-
expect(result.content).toContain("match.ts:1:");
|
|
76
|
-
expect(result.content).not.toContain("skip.md");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("rejects a path escaping the workspace root", async () => {
|
|
80
|
-
const dir = makeTempDir();
|
|
81
|
-
|
|
82
|
-
const result = await codeSearchTool.execute(
|
|
83
|
-
{ pattern: "anything", path: "../../../etc", activity: "search" },
|
|
84
|
-
makeToolContext(dir),
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
expect(result.isError).toBe(true);
|
|
88
|
-
expect(result.content).toContain("outside the working directory");
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test("case_insensitive matching", async () => {
|
|
92
|
-
const dir = makeTempDir();
|
|
93
|
-
writeFileSync(join(dir, "a.txt"), "Hello WORLD\n");
|
|
94
|
-
|
|
95
|
-
const sensitive = await codeSearchTool.execute(
|
|
96
|
-
{ pattern: "world", activity: "search" },
|
|
97
|
-
makeToolContext(dir),
|
|
98
|
-
);
|
|
99
|
-
expect(sensitive.content).toContain("No matches found");
|
|
100
|
-
|
|
101
|
-
const insensitive = await codeSearchTool.execute(
|
|
102
|
-
{ pattern: "world", case_insensitive: true, activity: "search" },
|
|
103
|
-
makeToolContext(dir),
|
|
104
|
-
);
|
|
105
|
-
expect(insensitive.isError).toBe(false);
|
|
106
|
-
expect(insensitive.content).toBe("a.txt:1: Hello WORLD");
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("no match returns a clear empty result", async () => {
|
|
110
|
-
const dir = makeTempDir();
|
|
111
|
-
writeFileSync(join(dir, "a.txt"), "nothing relevant\n");
|
|
112
|
-
|
|
113
|
-
const result = await codeSearchTool.execute(
|
|
114
|
-
{ pattern: "absent-token", activity: "search" },
|
|
115
|
-
makeToolContext(dir),
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
expect(result.isError).toBe(false);
|
|
119
|
-
expect(result.content).toContain("No matches found");
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("non-existent root path returns a path error, not a false 'No matches'", async () => {
|
|
123
|
-
const dir = makeTempDir();
|
|
124
|
-
writeFileSync(join(dir, "a.ts"), "needle\n");
|
|
125
|
-
|
|
126
|
-
// A typo'd subdirectory (e.g. "srcc") must surface an actionable path error
|
|
127
|
-
// rather than being swallowed and reported as a successful empty search.
|
|
128
|
-
const result = await codeSearchTool.execute(
|
|
129
|
-
{ pattern: "needle", path: "srcc", activity: "search" },
|
|
130
|
-
makeToolContext(dir),
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
expect(result.isError).toBe(true);
|
|
134
|
-
expect(result.content).toContain("path not found");
|
|
135
|
-
expect(result.content).not.toContain("No matches found");
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test("searches a single file when the root resolves to a regular file", async () => {
|
|
139
|
-
const dir = makeTempDir();
|
|
140
|
-
writeFileSync(join(dir, "a.ts"), "const foo = 1;\nconst needle = 2;\n");
|
|
141
|
-
|
|
142
|
-
const result = await codeSearchTool.execute(
|
|
143
|
-
{ pattern: "needle", path: "a.ts", activity: "search" },
|
|
144
|
-
makeToolContext(dir),
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
expect(result.isError).toBe(false);
|
|
148
|
-
expect(result.content).toBe("a.ts:2: const needle = 2;");
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test("an oversized explicit-file root returns an error, not a false 'No matches'", async () => {
|
|
152
|
-
const dir = makeTempDir();
|
|
153
|
-
// A single-file root larger than MAX_FILE_BYTES (8 MiB) was never searched,
|
|
154
|
-
// so reporting "No matches found" would be a silent false negative. Surface
|
|
155
|
-
// a hard error instead.
|
|
156
|
-
const oversize = 8 * 1024 * 1024 + 1;
|
|
157
|
-
writeFileSync(join(dir, "huge.txt"), "x".repeat(oversize));
|
|
158
|
-
|
|
159
|
-
const result = await codeSearchTool.execute(
|
|
160
|
-
{ pattern: "x", path: "huge.txt", activity: "search" },
|
|
161
|
-
makeToolContext(dir),
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
expect(result.isError).toBe(true);
|
|
165
|
-
expect(result.content).toContain("file too large to search");
|
|
166
|
-
expect(result.content).not.toContain("No matches found");
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test("an unreadable explicit file root surfaces a read error, not a false 'No matches'", async () => {
|
|
170
|
-
const dir = makeTempDir();
|
|
171
|
-
const f = join(dir, "secret.txt");
|
|
172
|
-
writeFileSync(f, "needle here\n");
|
|
173
|
-
chmodSync(f, 0o000);
|
|
174
|
-
// When the suite runs as root (e.g. CI in Docker), chmod 000 does not block
|
|
175
|
-
// reads, so EACCES can't be simulated — skip the assertion in that case.
|
|
176
|
-
let readable = true;
|
|
177
|
-
try {
|
|
178
|
-
readFileSync(f);
|
|
179
|
-
} catch {
|
|
180
|
-
readable = false;
|
|
181
|
-
}
|
|
182
|
-
if (!readable) {
|
|
183
|
-
const result = await codeSearchTool.execute(
|
|
184
|
-
{ pattern: "needle", path: "secret.txt", activity: "search" },
|
|
185
|
-
makeToolContext(dir),
|
|
186
|
-
);
|
|
187
|
-
expect(result.isError).toBe(true);
|
|
188
|
-
expect(result.content).toContain("failed to read file");
|
|
189
|
-
expect(result.content).not.toContain("No matches found");
|
|
190
|
-
}
|
|
191
|
-
chmodSync(f, 0o644);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test("an unreadable explicit directory root surfaces a read error, not a false 'No matches'", async () => {
|
|
195
|
-
const dir = makeTempDir();
|
|
196
|
-
const subdir = join(dir, "locked");
|
|
197
|
-
mkdirSync(subdir);
|
|
198
|
-
writeFileSync(join(subdir, "a.ts"), "needle here\n");
|
|
199
|
-
chmodSync(subdir, 0o000);
|
|
200
|
-
// When the suite runs as root (e.g. CI in Docker), chmod 000 does not block
|
|
201
|
-
// reads, so EACCES can't be simulated — probe readdir and skip the assertion
|
|
202
|
-
// in that case.
|
|
203
|
-
let readable = true;
|
|
204
|
-
try {
|
|
205
|
-
readdirSync(subdir);
|
|
206
|
-
} catch {
|
|
207
|
-
readable = false;
|
|
208
|
-
}
|
|
209
|
-
if (!readable) {
|
|
210
|
-
const result = await codeSearchTool.execute(
|
|
211
|
-
{ pattern: "needle", path: "locked", activity: "search" },
|
|
212
|
-
makeToolContext(dir),
|
|
213
|
-
);
|
|
214
|
-
expect(result.isError).toBe(true);
|
|
215
|
-
expect(result.content).toContain("failed to read directory");
|
|
216
|
-
expect(result.content).not.toContain("No matches found");
|
|
217
|
-
}
|
|
218
|
-
chmodSync(subdir, 0o755);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
test("an oversized file skipped mid-walk keeps scanning others but flags the result incomplete", async () => {
|
|
222
|
-
const dir = makeTempDir();
|
|
223
|
-
// A directory search must keep scanning sibling files when it hits an
|
|
224
|
-
// oversized one (so a smaller file still matches), yet flag the overall
|
|
225
|
-
// result incomplete since the skipped file could have contained the pattern.
|
|
226
|
-
const oversize = 8 * 1024 * 1024 + 1;
|
|
227
|
-
writeFileSync(join(dir, "huge.txt"), "needle " + "x".repeat(oversize));
|
|
228
|
-
writeFileSync(join(dir, "small.txt"), "needle here\n");
|
|
229
|
-
|
|
230
|
-
const result = await codeSearchTool.execute(
|
|
231
|
-
{ pattern: "needle", activity: "search" },
|
|
232
|
-
makeToolContext(dir),
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
expect(result.isError).toBe(false);
|
|
236
|
-
// The walk wasn't aborted by the skip — the small file still matched...
|
|
237
|
-
expect(result.content).toContain("small.txt");
|
|
238
|
-
// ...and the result is flagged incomplete with a size-limit note.
|
|
239
|
-
expect(result.status).toBe("truncated");
|
|
240
|
-
expect(result.content).toContain("size limit");
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test("a directory whose only candidate is oversized reports incompleteness, not a clean miss", async () => {
|
|
244
|
-
const dir = makeTempDir();
|
|
245
|
-
const oversize = 8 * 1024 * 1024 + 1;
|
|
246
|
-
writeFileSync(join(dir, "huge.txt"), "needle " + "x".repeat(oversize));
|
|
247
|
-
|
|
248
|
-
const result = await codeSearchTool.execute(
|
|
249
|
-
{ pattern: "needle", activity: "search" },
|
|
250
|
-
makeToolContext(dir),
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
expect(result.isError).toBe(false);
|
|
254
|
-
// Zero matches because the only file was skipped — but the result must say
|
|
255
|
-
// it's incomplete (status truncated + size-limit note), not a definitive miss.
|
|
256
|
-
expect(result.status).toBe("truncated");
|
|
257
|
-
expect(result.content).toContain("size limit");
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
test("a single multi-MiB matching line is found but its printed output is display-bounded", async () => {
|
|
261
|
-
const dir = makeTempDir();
|
|
262
|
-
// One matching line many megabytes wide whose file stays under MAX_FILE_BYTES
|
|
263
|
-
// (8 MiB) so it isn't skipped. The match is on the full line (so it's found),
|
|
264
|
-
// but the EMITTED line is truncated to the display cap — so a single wide
|
|
265
|
-
// line cannot, by itself, blow the output-byte budget anymore. The result is
|
|
266
|
-
// a clean single match, not truncated, with the printed line bounded.
|
|
267
|
-
const lineLen = 5 * 1024 * 1024;
|
|
268
|
-
writeFileSync(join(dir, "wide.txt"), "needle" + "z".repeat(lineLen) + "\n");
|
|
269
|
-
|
|
270
|
-
const result = await codeSearchTool.execute(
|
|
271
|
-
{ pattern: "needle", path: "wide.txt", activity: "search" },
|
|
272
|
-
makeToolContext(dir),
|
|
273
|
-
);
|
|
274
|
-
|
|
275
|
-
expect(result.isError).toBe(false);
|
|
276
|
-
// Found on the full line — never a false "No matches" or scan-cap message.
|
|
277
|
-
expect(result.content).not.toContain("No matches found");
|
|
278
|
-
expect(result.content).not.toContain("scan cap");
|
|
279
|
-
expect(result.content).toContain("wide.txt:1:");
|
|
280
|
-
// The printed line is display-truncated, so the output stays tiny and the
|
|
281
|
-
// output budget is never hit — single match => not truncated.
|
|
282
|
-
expect(result.content).toContain("…[line truncated]");
|
|
283
|
-
expect(result.status).toBeUndefined();
|
|
284
|
-
expect(result.content).not.toContain("Output capped");
|
|
285
|
-
// The multi-MiB body never reaches the emitted output.
|
|
286
|
-
expect(Buffer.byteLength(result.content, "utf8")).toBeLessThan(64 * 1024);
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
test("single-file search still honors the denied-basename guard", async () => {
|
|
290
|
-
const dir = makeTempDir();
|
|
291
|
-
writeFileSync(join(dir, "backup.key"), "supersecret token\n");
|
|
292
|
-
|
|
293
|
-
const result = await codeSearchTool.execute(
|
|
294
|
-
{ pattern: "supersecret", path: "backup.key", activity: "search" },
|
|
295
|
-
makeToolContext(dir),
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
// The denied basename is rejected by the sandbox policy before any read,
|
|
299
|
-
// so the secret never surfaces in the result.
|
|
300
|
-
expect(result.isError).toBe(true);
|
|
301
|
-
expect(result.content).not.toContain("supersecret");
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
test("max_results truncates and reports it", async () => {
|
|
305
|
-
const dir = makeTempDir();
|
|
306
|
-
const body = Array.from({ length: 10 }, () => "match").join("\n");
|
|
307
|
-
writeFileSync(join(dir, "a.txt"), body + "\n");
|
|
308
|
-
|
|
309
|
-
const result = await codeSearchTool.execute(
|
|
310
|
-
{ pattern: "match", max_results: 3, activity: "search" },
|
|
311
|
-
makeToolContext(dir),
|
|
312
|
-
);
|
|
313
|
-
|
|
314
|
-
expect(result.isError).toBe(false);
|
|
315
|
-
expect(result.status).toBe("truncated");
|
|
316
|
-
expect(result.content).toContain("truncated at 3 matches");
|
|
317
|
-
const matchLines = result.content
|
|
318
|
-
.split("\n")
|
|
319
|
-
.filter((l) => l.startsWith("a.txt:"));
|
|
320
|
-
expect(matchLines.length).toBe(3);
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
test("includes context lines when requested", async () => {
|
|
324
|
-
const dir = makeTempDir();
|
|
325
|
-
writeFileSync(join(dir, "a.txt"), "before\nTARGET\nafter\n");
|
|
326
|
-
|
|
327
|
-
const result = await codeSearchTool.execute(
|
|
328
|
-
{ pattern: "TARGET", context_lines: 1, activity: "search" },
|
|
329
|
-
makeToolContext(dir),
|
|
330
|
-
);
|
|
331
|
-
|
|
332
|
-
expect(result.isError).toBe(false);
|
|
333
|
-
expect(result.content).toContain("a.txt:1- before");
|
|
334
|
-
expect(result.content).toContain("a.txt:2: TARGET");
|
|
335
|
-
expect(result.content).toContain("a.txt:3- after");
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
test("matches a token past the display cap on a long line, truncating only the printed output", async () => {
|
|
339
|
-
const dir = makeTempDir();
|
|
340
|
-
// A line far longer than MAX_DISPLAY_LINE_LENGTH (2000) whose matchable token
|
|
341
|
-
// sits well beyond column 2000. With RE2's linear-time matching the pattern
|
|
342
|
-
// is run against the FULL line, so the token MUST be found — but the emitted
|
|
343
|
-
// line is truncated for display with a clear marker. This also exercises that
|
|
344
|
-
// a huge line is handled without throwing/hanging.
|
|
345
|
-
const prefix = "x".repeat(5000);
|
|
346
|
-
writeFileSync(
|
|
347
|
-
join(dir, "huge.txt"),
|
|
348
|
-
`${prefix}BEYOND_CAP_TOKEN\ninside INSIDE_CAP_TOKEN here\n`,
|
|
349
|
-
);
|
|
350
|
-
|
|
351
|
-
const beyond = await codeSearchTool.execute(
|
|
352
|
-
{ pattern: "BEYOND_CAP_TOKEN", activity: "search" },
|
|
353
|
-
makeToolContext(dir),
|
|
354
|
-
);
|
|
355
|
-
expect(beyond.isError).toBe(false);
|
|
356
|
-
// The token appears past column 2000, but matching runs against the full
|
|
357
|
-
// line, so it IS found (correct grep behavior — no false "No matches").
|
|
358
|
-
expect(beyond.content).not.toContain("No matches found");
|
|
359
|
-
expect(beyond.content).toContain("huge.txt:1:");
|
|
360
|
-
// The displayed line is truncated with a marker, so the multi-kilobyte line
|
|
361
|
-
// never balloons the output...
|
|
362
|
-
expect(beyond.content).toContain("…[line truncated]");
|
|
363
|
-
// ...and the matched token (which sits beyond the display cap) is NOT echoed
|
|
364
|
-
// back in the bounded output even though the match was found.
|
|
365
|
-
expect(beyond.content).not.toContain("BEYOND_CAP_TOKEN\n");
|
|
366
|
-
expect(beyond.content.length).toBeLessThan(prefix.length);
|
|
367
|
-
|
|
368
|
-
const inside = await codeSearchTool.execute(
|
|
369
|
-
{ pattern: "INSIDE_CAP_TOKEN", activity: "search" },
|
|
370
|
-
makeToolContext(dir),
|
|
371
|
-
);
|
|
372
|
-
expect(inside.isError).toBe(false);
|
|
373
|
-
expect(inside.content).toContain("huge.txt:2:");
|
|
374
|
-
// A short line is emitted verbatim — no truncation marker.
|
|
375
|
-
expect(inside.content).toContain("inside INSIDE_CAP_TOKEN here");
|
|
376
|
-
expect(inside.content).not.toContain("…[line truncated]");
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
test("caps output via the byte budget when many matches have context", async () => {
|
|
380
|
-
const dir = makeTempDir();
|
|
381
|
-
// Many matches with context lines. Even though context_lines is requested
|
|
382
|
-
// far above the clamp (MAX_CONTEXT_LINES = 20), the clamp plus the
|
|
383
|
-
// output-byte budget must produce a truncated result rather than an
|
|
384
|
-
// unbounded one. The source file stays under MAX_FILE_BYTES (8 MiB) so it
|
|
385
|
-
// isn't skipped, but because every line both matches and is re-emitted as
|
|
386
|
-
// context for ~41 nearby matches, the accumulated output crosses the 4 MiB
|
|
387
|
-
// budget.
|
|
388
|
-
const filler = "y".repeat(200);
|
|
389
|
-
const lineCount = 20_000;
|
|
390
|
-
const body = Array.from(
|
|
391
|
-
{ length: lineCount },
|
|
392
|
-
() => `match ${filler}`,
|
|
393
|
-
).join("\n");
|
|
394
|
-
writeFileSync(join(dir, "big.txt"), body + "\n");
|
|
395
|
-
|
|
396
|
-
const result = await codeSearchTool.execute(
|
|
397
|
-
{
|
|
398
|
-
pattern: "match",
|
|
399
|
-
context_lines: 1000,
|
|
400
|
-
max_results: 1_000_000,
|
|
401
|
-
activity: "search",
|
|
402
|
-
},
|
|
403
|
-
makeToolContext(dir),
|
|
404
|
-
);
|
|
405
|
-
|
|
406
|
-
expect(result.isError).toBe(false);
|
|
407
|
-
expect(result.status).toBe("truncated");
|
|
408
|
-
expect(result.content).toContain("Output capped");
|
|
409
|
-
// The accumulated output must stay near the budget, not balloon to the full
|
|
410
|
-
// file size (~20 MiB of body * surrounding context).
|
|
411
|
-
expect(Buffer.byteLength(result.content, "utf8")).toBeLessThan(
|
|
412
|
-
8 * 1024 * 1024,
|
|
413
|
-
);
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
test("an already-aborted signal stops the scan and reports a timed-out result", async () => {
|
|
417
|
-
const dir = makeTempDir();
|
|
418
|
-
writeFileSync(join(dir, "a.txt"), "needle here\nneedle there\n");
|
|
419
|
-
|
|
420
|
-
// The wall-clock deadline (MAX_SEARCH_MS) and the abort signal are checked
|
|
421
|
-
// at the same per-line checkpoint. MAX_SEARCH_MS is a 10s const that isn't
|
|
422
|
-
// injectable, so we exercise the shared stop path deterministically via an
|
|
423
|
-
// already-aborted signal: the scan must stop at the first line check and
|
|
424
|
-
// return a truncated/timed-out result instead of running the regex.
|
|
425
|
-
const controller = new AbortController();
|
|
426
|
-
controller.abort();
|
|
427
|
-
const context = { ...makeToolContext(dir), signal: controller.signal };
|
|
428
|
-
|
|
429
|
-
const result = await codeSearchTool.execute(
|
|
430
|
-
{ pattern: "needle", activity: "search" },
|
|
431
|
-
context,
|
|
432
|
-
);
|
|
433
|
-
|
|
434
|
-
// Aborted before any match was emitted, so this is the zero-match
|
|
435
|
-
// timed-out branch (incomplete), not a definitive "No matches found".
|
|
436
|
-
expect(result.isError).toBe(false);
|
|
437
|
-
expect(result.status).toBe("truncated");
|
|
438
|
-
expect(result.content).toContain("timed out");
|
|
439
|
-
expect(result.content).not.toContain("No matches found");
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
test("an already-aborted signal stops the directory traversal, not just per-line scanning", async () => {
|
|
443
|
-
const dir = makeTempDir();
|
|
444
|
-
// Build a small tree of subdirectories and files. The traversal deadline /
|
|
445
|
-
// abort check now lives at the top of walk() and inside its per-entry loop
|
|
446
|
-
// (shared with the per-line checkpoint), so an already-aborted signal must
|
|
447
|
-
// stop the walk before it descends/scans the whole tree, returning a
|
|
448
|
-
// truncated/timed-out result rather than scanning everything.
|
|
449
|
-
for (let d = 0; d < 5; d++) {
|
|
450
|
-
const sub = join(dir, `sub${d}`);
|
|
451
|
-
mkdirSync(sub);
|
|
452
|
-
for (let f = 0; f < 5; f++) {
|
|
453
|
-
writeFileSync(join(sub, `f${f}.txt`), "needle here\n");
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
const controller = new AbortController();
|
|
458
|
-
controller.abort();
|
|
459
|
-
const context = { ...makeToolContext(dir), signal: controller.signal };
|
|
460
|
-
|
|
461
|
-
const result = await codeSearchTool.execute(
|
|
462
|
-
{ pattern: "needle", activity: "search" },
|
|
463
|
-
context,
|
|
464
|
-
);
|
|
465
|
-
|
|
466
|
-
// The traversal honored the abort: stopped promptly with a timed-out /
|
|
467
|
-
// incomplete result instead of a definitive miss or full scan.
|
|
468
|
-
expect(result.isError).toBe(false);
|
|
469
|
-
expect(result.status).toBe("truncated");
|
|
470
|
-
expect(result.content).toContain("timed out");
|
|
471
|
-
expect(result.content).not.toContain("No matches found");
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
test("a catastrophic-backtracking pattern returns near-instantly (linear-time RE2)", async () => {
|
|
475
|
-
const dir = makeTempDir();
|
|
476
|
-
// The classic ReDoS proof: `(a+)+$` against many non-matching lines of the
|
|
477
|
-
// form `'a'.repeat(50) + '!'`. Under V8's backtracking RegExp a single
|
|
478
|
-
// `regex.test()` on one such line blocks the event loop for seconds, and
|
|
479
|
-
// the synchronous scan never yields so neither the wall-clock deadline nor
|
|
480
|
-
// the promise timeout can interrupt it — the call would hang. With the
|
|
481
|
-
// linear-time RE2 engine the whole file scans in milliseconds and returns
|
|
482
|
-
// a clean, non-truncated "No matches found". The small per-call timeout
|
|
483
|
-
// below would trip if matching ever fell back to backtracking.
|
|
484
|
-
const evilLine = "a".repeat(50) + "!"; // forces backtracking, no match
|
|
485
|
-
const body = Array.from({ length: 200 }, () => evilLine).join("\n");
|
|
486
|
-
writeFileSync(join(dir, "evil.txt"), body + "\n");
|
|
487
|
-
|
|
488
|
-
const result = await codeSearchTool.execute(
|
|
489
|
-
{ pattern: "(a+)+$", activity: "search" },
|
|
490
|
-
makeToolContext(dir),
|
|
491
|
-
);
|
|
492
|
-
|
|
493
|
-
expect(result.isError).toBe(false);
|
|
494
|
-
// Linear-time matching completes well within the deadline, so this is a
|
|
495
|
-
// definitive miss, not a timed-out/truncated result.
|
|
496
|
-
expect(result.status).toBeUndefined();
|
|
497
|
-
expect(result.content).toContain("No matches found");
|
|
498
|
-
}, 5_000);
|
|
499
|
-
|
|
500
|
-
test("an unsupported pattern (backreference) returns a clean error, not a throw", async () => {
|
|
501
|
-
const dir = makeTempDir();
|
|
502
|
-
writeFileSync(join(dir, "a.txt"), "aa\n");
|
|
503
|
-
|
|
504
|
-
// RE2 does not support backreferences (or lookarounds). The pattern must be
|
|
505
|
-
// rejected at compile time and surfaced as an isError result rather than
|
|
506
|
-
// throwing out of execute().
|
|
507
|
-
const result = await codeSearchTool.execute(
|
|
508
|
-
{ pattern: "(a)\\1", activity: "search" },
|
|
509
|
-
makeToolContext(dir),
|
|
510
|
-
);
|
|
511
|
-
|
|
512
|
-
expect(result.isError).toBe(true);
|
|
513
|
-
expect(result.content).toContain("invalid or unsupported pattern");
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
test("does not return contents of denied-basename files", async () => {
|
|
517
|
-
const dir = makeTempDir();
|
|
518
|
-
// A denied file (forbidden to file_read/file_write) that contains the
|
|
519
|
-
// search pattern must never surface in code_search results.
|
|
520
|
-
writeFileSync(join(dir, ".backup.key"), "supersecret token\n");
|
|
521
|
-
writeFileSync(join(dir, "backup.key"), "another supersecret\n");
|
|
522
|
-
writeFileSync(join(dir, "ok.txt"), "supersecret in a normal file\n");
|
|
523
|
-
|
|
524
|
-
const result = await codeSearchTool.execute(
|
|
525
|
-
{ pattern: "supersecret", activity: "search" },
|
|
526
|
-
makeToolContext(dir),
|
|
527
|
-
);
|
|
528
|
-
|
|
529
|
-
expect(result.isError).toBe(false);
|
|
530
|
-
expect(result.content).toContain("ok.txt:1:");
|
|
531
|
-
expect(result.content).not.toContain(".backup.key");
|
|
532
|
-
expect(result.content).not.toContain("backup.key");
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
test("ignores node_modules and .git", async () => {
|
|
536
|
-
const dir = makeTempDir();
|
|
537
|
-
mkdirSync(join(dir, "node_modules"));
|
|
538
|
-
writeFileSync(join(dir, "node_modules", "dep.js"), "secret\n");
|
|
539
|
-
writeFileSync(join(dir, "src.js"), "secret\n");
|
|
540
|
-
|
|
541
|
-
const result = await codeSearchTool.execute(
|
|
542
|
-
{ pattern: "secret", activity: "search" },
|
|
543
|
-
makeToolContext(dir),
|
|
544
|
-
);
|
|
545
|
-
|
|
546
|
-
expect(result.content).toContain("src.js:1:");
|
|
547
|
-
expect(result.content).not.toContain("node_modules");
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
test("requires a non-empty pattern", async () => {
|
|
551
|
-
const dir = makeTempDir();
|
|
552
|
-
const result = await codeSearchTool.execute(
|
|
553
|
-
{ pattern: "", activity: "search" },
|
|
554
|
-
makeToolContext(dir),
|
|
555
|
-
);
|
|
556
|
-
expect(result.isError).toBe(true);
|
|
557
|
-
expect(result.content).toContain("pattern is required");
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
test("is read-only: directory is unchanged after a search", async () => {
|
|
561
|
-
const dir = makeTempDir();
|
|
562
|
-
const filePath = join(dir, "a.txt");
|
|
563
|
-
writeFileSync(filePath, "hello search\n");
|
|
564
|
-
const before = readFileSync(filePath, "utf8");
|
|
565
|
-
const mtimeBefore = statSync(filePath).mtimeMs;
|
|
566
|
-
|
|
567
|
-
await codeSearchTool.execute(
|
|
568
|
-
{ pattern: "search", activity: "search" },
|
|
569
|
-
makeToolContext(dir),
|
|
570
|
-
);
|
|
571
|
-
|
|
572
|
-
expect(readFileSync(filePath, "utf8")).toBe(before);
|
|
573
|
-
expect(statSync(filePath).mtimeMs).toBe(mtimeBefore);
|
|
574
|
-
});
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
// ---------------------------------------------------------------------------
|
|
578
|
-
// Subagent-only visibility
|
|
579
|
-
// ---------------------------------------------------------------------------
|
|
580
|
-
|
|
581
|
-
describe("code_search subagent visibility", () => {
|
|
582
|
-
test("code_search is in SUBAGENT_ONLY_TOOL_NAMES", () => {
|
|
583
|
-
expect(SUBAGENT_ONLY_TOOL_NAMES.has("code_search")).toBe(true);
|
|
584
|
-
});
|
|
585
|
-
});
|