@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,150 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Verifies the agent loop's exclusive-tool dispatch: when a tool the loop is
|
|
3
|
-
* told is exclusive (e.g. the advisor) appears in a multi-call turn, only that
|
|
4
|
-
* tool runs and the siblings are deferred un-run with a benign result — so the
|
|
5
|
-
* model incorporates the exclusive tool's output before acting on anything
|
|
6
|
-
* else. Drives the REAL loop, mocking only the provider boundary.
|
|
7
|
-
*/
|
|
8
|
-
import { describe, expect, test } from "bun:test";
|
|
9
|
-
|
|
10
|
-
import { createMockProvider } from "../__tests__/helpers/mock-provider.js";
|
|
11
|
-
import type { ContentBlock, ProviderResponse } from "../providers/types.js";
|
|
12
|
-
import { AgentLoop } from "./loop.js";
|
|
13
|
-
|
|
14
|
-
const endTurn = (text: string): ProviderResponse => ({
|
|
15
|
-
content: [{ type: "text", text }],
|
|
16
|
-
model: "mock-model",
|
|
17
|
-
usage: { inputTokens: 1, outputTokens: 1 },
|
|
18
|
-
stopReason: "end_turn",
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const toolUseTurn = (
|
|
22
|
-
blocks: Array<{ id: string; name: string }>,
|
|
23
|
-
): ProviderResponse => ({
|
|
24
|
-
content: [
|
|
25
|
-
{ type: "text", text: "working" },
|
|
26
|
-
...blocks.map((b) => ({
|
|
27
|
-
type: "tool_use" as const,
|
|
28
|
-
id: b.id,
|
|
29
|
-
name: b.name,
|
|
30
|
-
input: {},
|
|
31
|
-
})),
|
|
32
|
-
],
|
|
33
|
-
model: "mock-model",
|
|
34
|
-
usage: { inputTokens: 1, outputTokens: 1 },
|
|
35
|
-
stopReason: "tool_use",
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
function toolResults(history: { content: ContentBlock[] }[]) {
|
|
39
|
-
return history
|
|
40
|
-
.flatMap((m) => m.content)
|
|
41
|
-
.filter(
|
|
42
|
-
(b): b is Extract<ContentBlock, { type: "tool_result" }> =>
|
|
43
|
-
b.type === "tool_result",
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const baseRun = {
|
|
48
|
-
requestId: "req-excl",
|
|
49
|
-
onEvent: () => {},
|
|
50
|
-
callSite: "mainAgent" as const,
|
|
51
|
-
trust: { sourceChannel: "vellum" as const, trustClass: "unknown" as const },
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
describe("AgentLoop — exclusive tool deferral", () => {
|
|
55
|
-
test("runs the exclusive tool alone and defers sibling calls un-run", async () => {
|
|
56
|
-
const { provider } = createMockProvider([
|
|
57
|
-
toolUseTurn([
|
|
58
|
-
{ id: "call-advisor", name: "advisor" },
|
|
59
|
-
{ id: "call-edit", name: "write_file" },
|
|
60
|
-
]),
|
|
61
|
-
endTurn("done"),
|
|
62
|
-
]);
|
|
63
|
-
|
|
64
|
-
const executed: string[] = [];
|
|
65
|
-
const loop = new AgentLoop({
|
|
66
|
-
provider,
|
|
67
|
-
systemPrompt: "sys",
|
|
68
|
-
conversationId: "excl-1",
|
|
69
|
-
tools: [
|
|
70
|
-
{ name: "advisor", description: "", input_schema: { type: "object" } },
|
|
71
|
-
{
|
|
72
|
-
name: "write_file",
|
|
73
|
-
description: "",
|
|
74
|
-
input_schema: { type: "object" },
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
toolExecutor: async (name) => {
|
|
78
|
-
executed.push(name);
|
|
79
|
-
return { content: `ran ${name}`, isError: false };
|
|
80
|
-
},
|
|
81
|
-
isExclusiveTool: (name) => name === "advisor",
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
const { history } = await loop.run({
|
|
85
|
-
...baseRun,
|
|
86
|
-
messages: [{ role: "user", content: [{ type: "text", text: "do it" }] }],
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// Only the exclusive tool actually executed.
|
|
90
|
-
expect(executed).toEqual(["advisor"]);
|
|
91
|
-
|
|
92
|
-
const results = toolResults(history);
|
|
93
|
-
const advisorResult = results.find(
|
|
94
|
-
(b) => b.tool_use_id === "call-advisor",
|
|
95
|
-
)!;
|
|
96
|
-
const editResult = results.find((b) => b.tool_use_id === "call-edit")!;
|
|
97
|
-
|
|
98
|
-
// The advisor ran; the sibling came back un-run (not an error) so the model
|
|
99
|
-
// can re-issue it after reading the guidance.
|
|
100
|
-
expect(advisorResult.content).toBe("ran advisor");
|
|
101
|
-
expect(editResult.content).toContain("not run");
|
|
102
|
-
expect(editResult.content).toContain("advisor");
|
|
103
|
-
expect(editResult.is_error).toBe(false);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("runs sibling tools normally when no exclusive tool is present", async () => {
|
|
107
|
-
const { provider } = createMockProvider([
|
|
108
|
-
toolUseTurn([
|
|
109
|
-
{ id: "call-read", name: "read_file" },
|
|
110
|
-
{ id: "call-write", name: "write_file" },
|
|
111
|
-
]),
|
|
112
|
-
endTurn("done"),
|
|
113
|
-
]);
|
|
114
|
-
|
|
115
|
-
const executed: string[] = [];
|
|
116
|
-
const loop = new AgentLoop({
|
|
117
|
-
provider,
|
|
118
|
-
systemPrompt: "sys",
|
|
119
|
-
conversationId: "excl-2",
|
|
120
|
-
tools: [
|
|
121
|
-
{
|
|
122
|
-
name: "read_file",
|
|
123
|
-
description: "",
|
|
124
|
-
input_schema: { type: "object" },
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
name: "write_file",
|
|
128
|
-
description: "",
|
|
129
|
-
input_schema: { type: "object" },
|
|
130
|
-
},
|
|
131
|
-
],
|
|
132
|
-
toolExecutor: async (name) => {
|
|
133
|
-
executed.push(name);
|
|
134
|
-
return { content: `ran ${name}`, isError: false };
|
|
135
|
-
},
|
|
136
|
-
isExclusiveTool: (name) => name === "advisor",
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
const { history } = await loop.run({
|
|
140
|
-
...baseRun,
|
|
141
|
-
messages: [{ role: "user", content: [{ type: "text", text: "do it" }] }],
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
// Both non-exclusive tools ran; nothing was deferred.
|
|
145
|
-
expect(executed.sort()).toEqual(["read_file", "write_file"]);
|
|
146
|
-
for (const result of toolResults(history)) {
|
|
147
|
-
expect(result.content).not.toContain("not run");
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
});
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Maximum number of events the daemon's per-process SSE replay ring
|
|
3
|
-
* retains for `Last-Event-ID` resume. This is the ring's count bound; the
|
|
4
|
-
* ring is also bounded by total bytes and entry age (whichever limit is
|
|
5
|
-
* hit first wins), so the live ring can hold *fewer* than this many
|
|
6
|
-
* events, never more. The daemon-side definition and eviction live in
|
|
7
|
-
* `assistant/src/runtime/assistant-stream-state.ts`.
|
|
8
|
-
*
|
|
9
|
-
* Exposed on the API surface so the web client's SSE consumer can size
|
|
10
|
-
* its seq-gap tolerance against the same number the daemon buffers
|
|
11
|
-
* against, instead of hard-coding a duplicate.
|
|
12
|
-
*
|
|
13
|
-
* A live seq gap smaller than this is benign: the global per-assistant
|
|
14
|
-
* `seq` counter is stamped before fanout, but the hub deliberately
|
|
15
|
-
* withholds some events from a given subscriber — self-echo-suppressed
|
|
16
|
-
* `sync_changed` (a client's own mutation echo) and capability-targeted
|
|
17
|
-
* host-proxy events — so a subscriber legitimately sees its cursor skip a
|
|
18
|
-
* few seqs it was never going to receive. Such a hole is not data loss
|
|
19
|
-
* and must not trigger a destructive authoritative snapshot heal. Only a
|
|
20
|
-
* gap that meets or exceeds this count proves the live suffix fell
|
|
21
|
-
* outside the ring entirely and is genuinely non-contiguous.
|
|
22
|
-
*/
|
|
23
|
-
export const SSE_REPLAY_RING_COUNT_LIMIT = 200;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Maximum age (in milliseconds) an event may reach in the daemon's SSE
|
|
27
|
-
* replay ring before it is evicted, regardless of count or byte usage.
|
|
28
|
-
* The daemon-side definition and eviction live in
|
|
29
|
-
* `assistant/src/runtime/assistant-stream-state.ts`.
|
|
30
|
-
*
|
|
31
|
-
* Exposed alongside the count bound so the web client can reason about
|
|
32
|
-
* the age dimension of the ring too. A small live seq gap is only safe to
|
|
33
|
-
* treat as benign when the client has been continuously receiving events
|
|
34
|
-
* — live delivery is concurrent with stamping, so a connected subscriber
|
|
35
|
-
* never relies on the ring. Once the connection has been quiet for longer
|
|
36
|
-
* than this window (a disconnect/resume), events the client missed may
|
|
37
|
-
* have aged out of the ring and become unrecoverable by replay, so even a
|
|
38
|
-
* small seq gap must trigger an authoritative reconcile rather than be
|
|
39
|
-
* waved through.
|
|
40
|
-
*/
|
|
41
|
-
export const SSE_REPLAY_RING_AGE_LIMIT_MS = 30_000;
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `conversation_notice` SSE event.
|
|
3
|
-
*
|
|
4
|
-
* Non-terminal, conversation-scoped notice for actionable runtime conditions
|
|
5
|
-
* that should not mark the turn as failed. The client may render CTA UI from
|
|
6
|
-
* this event while preserving the current assistant response.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { z } from "zod";
|
|
10
|
-
|
|
11
|
-
import { ConversationErrorCodeSchema } from "./conversation-error.js";
|
|
12
|
-
|
|
13
|
-
export const ConversationNoticeSourceSchema = z.enum(["memory_v3"]);
|
|
14
|
-
|
|
15
|
-
export const ConversationNoticeEventSchema = z.object({
|
|
16
|
-
type: z.literal("conversation_notice"),
|
|
17
|
-
conversationId: z.string(),
|
|
18
|
-
source: ConversationNoticeSourceSchema,
|
|
19
|
-
code: ConversationErrorCodeSchema,
|
|
20
|
-
userMessage: z.string(),
|
|
21
|
-
errorCategory: z.string().optional(),
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
export type ConversationNoticeEvent = z.infer<
|
|
25
|
-
typeof ConversationNoticeEventSchema
|
|
26
|
-
>;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared addressing helpers for guardian requester-facing channel notices.
|
|
3
|
-
*
|
|
4
|
-
* Requester notices (approval, denial, expiry) are delivered straight to the
|
|
5
|
-
* requester's chat via `deliverChannelReply` — independent of the
|
|
6
|
-
* guardian-facing notification pipeline. Centralizing the addressing rules here
|
|
7
|
-
* keeps the decision resolvers and the timer-driven expiry sweep from drifting
|
|
8
|
-
* apart on how a requester is reached.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Resolve the callback-less delivery route for a channel (e.g. `/deliver/slack`).
|
|
13
|
-
*
|
|
14
|
-
* Used when there is no inbound reply callback URL to post back to — the
|
|
15
|
-
* guardian decided off-channel (desktop), or the expiry sweep fired on a timer
|
|
16
|
-
* with no originating request in hand. Returns null for channels that have no
|
|
17
|
-
* deliverable route (e.g. email, the in-app vellum surface).
|
|
18
|
-
*/
|
|
19
|
-
export function resolveDeliverCallbackUrlForChannel(
|
|
20
|
-
channel: string,
|
|
21
|
-
): string | null {
|
|
22
|
-
switch (channel) {
|
|
23
|
-
case "telegram":
|
|
24
|
-
case "whatsapp":
|
|
25
|
-
case "slack":
|
|
26
|
-
return `/deliver/${channel}`;
|
|
27
|
-
default:
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Expiry side effects for canonical guardian requests.
|
|
3
|
-
*
|
|
4
|
-
* When the canonical expiry sweep transitions a pending request to `expired`,
|
|
5
|
-
* the request's cards are withdrawn — but nobody is told and any in-memory
|
|
6
|
-
* interaction is left dangling. This module fills that gap:
|
|
7
|
-
*
|
|
8
|
-
* - the requester is told their request expired (for the persistent,
|
|
9
|
-
* requester-facing kinds: `access_request`, `tool_grant_request`), and
|
|
10
|
-
* - the in-memory pending interaction is released (for the interaction-bound
|
|
11
|
-
* `tool_approval` kind).
|
|
12
|
-
*
|
|
13
|
-
* Delivery goes straight to the requester via `deliverChannelReply` on the
|
|
14
|
-
* callback-less `/deliver/<channel>` route — NOT the notification pipeline,
|
|
15
|
-
* which is guardian-facing (`emitNotificationSignal` resolves the *guardian's*
|
|
16
|
-
* delivery channels). The guardian is intentionally left passive here: the
|
|
17
|
-
* withdrawn card already reflects the expired state, so a fresh ping would be
|
|
18
|
-
* noise.
|
|
19
|
-
*
|
|
20
|
-
* Best-effort by contract: the request is already resolved (CAS committed)
|
|
21
|
-
* before this runs, so a failed notice or interaction release must never
|
|
22
|
-
* surface as a sweep failure. Nothing here throws.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import type { CanonicalGuardianRequest } from "../memory/canonical-guardian-store.js";
|
|
26
|
-
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
27
|
-
import { deliverChannelReply } from "../runtime/gateway-client.js";
|
|
28
|
-
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
29
|
-
import { getLogger } from "../util/logger.js";
|
|
30
|
-
import { resolveDeliverCallbackUrlForChannel } from "./guardian-channel-delivery.js";
|
|
31
|
-
|
|
32
|
-
const log = getLogger("guardian-expiry-notifier");
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Run the expiry side effects for a single canonical guardian request that the
|
|
36
|
-
* sweep just transitioned to `expired`. Dispatches by kind; never throws.
|
|
37
|
-
*/
|
|
38
|
-
export async function notifyExpiredGuardianRequest(
|
|
39
|
-
request: CanonicalGuardianRequest,
|
|
40
|
-
): Promise<void> {
|
|
41
|
-
try {
|
|
42
|
-
switch (request.kind) {
|
|
43
|
-
case "tool_approval":
|
|
44
|
-
releaseExpiredInteraction(request);
|
|
45
|
-
return;
|
|
46
|
-
case "access_request":
|
|
47
|
-
await notifyRequesterOfExpiry(
|
|
48
|
-
request,
|
|
49
|
-
"Your access request expired before it was reviewed. " +
|
|
50
|
-
"Send a new message if you still need access.",
|
|
51
|
-
);
|
|
52
|
-
return;
|
|
53
|
-
case "tool_grant_request":
|
|
54
|
-
await notifyRequesterOfExpiry(
|
|
55
|
-
request,
|
|
56
|
-
`Your request to use "${request.toolName ?? "a tool"}" expired ` +
|
|
57
|
-
"before it was reviewed. Ask again if you still need it.",
|
|
58
|
-
);
|
|
59
|
-
return;
|
|
60
|
-
case "pending_question":
|
|
61
|
-
// Voice call sessions own their own lifecycle and timeout. By the time
|
|
62
|
-
// the canonical TTL lapses the call is long over and there is no
|
|
63
|
-
// durable requester channel to notify, so there is nothing to do.
|
|
64
|
-
return;
|
|
65
|
-
default:
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
} catch (err) {
|
|
69
|
-
log.warn(
|
|
70
|
-
{ err, requestId: request.id, kind: request.kind },
|
|
71
|
-
"Expiry side effects failed for canonical guardian request (non-fatal)",
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Release the in-memory pending interaction for an expired `tool_approval`.
|
|
78
|
-
*
|
|
79
|
-
* `tool_approval` is the one interaction-bound kind the periodic sweep can
|
|
80
|
-
* reach (it carries a 30-minute `expiresAt`). In practice the canonical request
|
|
81
|
-
* id does not key a *blocking* prompter interaction: ingress escalations — the
|
|
82
|
-
* sole producer of this kind — register no interaction at all, and
|
|
83
|
-
* PermissionPrompter confirmations are keyed by their own request id with their
|
|
84
|
-
* own (far shorter) timeout. So this is a safe cleanup: it no-ops when nothing
|
|
85
|
-
* is registered under the request id, and otherwise drops a waiter-less async
|
|
86
|
-
* entry and emits `interaction_resolved` so clients clear the attention
|
|
87
|
-
* indicator. `cancelled` is the documented runtime-termination/timeout outcome.
|
|
88
|
-
*/
|
|
89
|
-
function releaseExpiredInteraction(request: CanonicalGuardianRequest): void {
|
|
90
|
-
const released = pendingInteractions.resolve(request.id, "cancelled");
|
|
91
|
-
if (released) {
|
|
92
|
-
log.info(
|
|
93
|
-
{ requestId: request.id, kind: request.kind },
|
|
94
|
-
"Released pending interaction for expired guardian request",
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Deliver an expiry notice straight to the requester's chat.
|
|
101
|
-
*
|
|
102
|
-
* The sweep is timer-driven and holds no inbound reply callback URL, so this
|
|
103
|
-
* mirrors the resolvers' off-channel (desktop) delivery path: post to the
|
|
104
|
-
* callback-less `/deliver/<channel>` route. On Slack the notice is routed to
|
|
105
|
-
* the requester's DM via their user id rather than the channel id, so it is
|
|
106
|
-
* never posted into a shared channel. No-ops on channels without a deliverable
|
|
107
|
-
* route (e.g. email, the in-app vellum surface) or when the requester chat is
|
|
108
|
-
* unknown. Best-effort: a delivery failure is logged, never thrown.
|
|
109
|
-
*/
|
|
110
|
-
async function notifyRequesterOfExpiry(
|
|
111
|
-
request: CanonicalGuardianRequest,
|
|
112
|
-
text: string,
|
|
113
|
-
): Promise<void> {
|
|
114
|
-
const channel = request.sourceChannel ?? "";
|
|
115
|
-
const deliverUrl = resolveDeliverCallbackUrlForChannel(channel);
|
|
116
|
-
const requesterChatId =
|
|
117
|
-
request.requesterChatId ?? request.requesterExternalUserId ?? "";
|
|
118
|
-
const requesterExternalUserId = request.requesterExternalUserId ?? "";
|
|
119
|
-
|
|
120
|
-
if (!deliverUrl || !requesterChatId) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// On Slack, target the requester's DM (their `U…` user id) instead of the
|
|
125
|
-
// channel id so the expiry notice stays private. Other channels deliver to
|
|
126
|
-
// the requester chat directly.
|
|
127
|
-
const targetChatId =
|
|
128
|
-
channel === "slack" && requesterExternalUserId
|
|
129
|
-
? requesterExternalUserId
|
|
130
|
-
: requesterChatId;
|
|
131
|
-
|
|
132
|
-
try {
|
|
133
|
-
await deliverChannelReply(deliverUrl, {
|
|
134
|
-
chatId: targetChatId,
|
|
135
|
-
text,
|
|
136
|
-
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
137
|
-
});
|
|
138
|
-
log.info(
|
|
139
|
-
{ requestId: request.id, kind: request.kind, channel },
|
|
140
|
-
"Notified requester that guardian request expired",
|
|
141
|
-
);
|
|
142
|
-
} catch (err) {
|
|
143
|
-
log.warn(
|
|
144
|
-
{ err, requestId: request.id, channel },
|
|
145
|
-
"Failed to notify requester of guardian request expiry (non-fatal)",
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the `assistant memory worker` CLI subgroup.
|
|
3
|
-
*
|
|
4
|
-
* Validates:
|
|
5
|
-
* - Subcommand registration (start, stop, status) under `memory worker`.
|
|
6
|
-
* - `status` reports running/not_running via PID-file liveness.
|
|
7
|
-
* - `stop` sends SIGTERM to a live worker and errors when none is running.
|
|
8
|
-
* - `start` refuses to spawn when a worker is already running, and reports
|
|
9
|
-
* the PID once the spawned process writes its PID file.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
13
|
-
import { tmpdir } from "node:os";
|
|
14
|
-
import { join } from "node:path";
|
|
15
|
-
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
16
|
-
|
|
17
|
-
import { Command } from "commander";
|
|
18
|
-
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
// Mock state
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
|
|
23
|
-
let tmpDir: string;
|
|
24
|
-
let pidPath: string;
|
|
25
|
-
let logOutput: string[] = [];
|
|
26
|
-
|
|
27
|
-
/** Records (pid, signal) pairs passed to the mocked process.kill. */
|
|
28
|
-
let killCalls: Array<{ pid: number; signal: string | number }> = [];
|
|
29
|
-
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
// Mocks
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
|
|
34
|
-
mock.module("../../../../util/platform.js", () => ({
|
|
35
|
-
getMemoryWorkerPidPath: () => pidPath,
|
|
36
|
-
}));
|
|
37
|
-
|
|
38
|
-
const capture = (...args: unknown[]) => {
|
|
39
|
-
logOutput.push(args.map(String).join(" "));
|
|
40
|
-
};
|
|
41
|
-
const fakeLogger = {
|
|
42
|
-
info: capture,
|
|
43
|
-
warn: capture,
|
|
44
|
-
error: capture,
|
|
45
|
-
debug: () => {},
|
|
46
|
-
};
|
|
47
|
-
mock.module("../../../../util/logger.js", () => ({
|
|
48
|
-
getLogger: () => fakeLogger,
|
|
49
|
-
getCliLogger: () => fakeLogger,
|
|
50
|
-
}));
|
|
51
|
-
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
// Import module under test (after mocks)
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
|
|
56
|
-
const { registerMemoryWorkerCommand } = await import("../worker.js");
|
|
57
|
-
|
|
58
|
-
// ---------------------------------------------------------------------------
|
|
59
|
-
// Test helpers
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
|
|
62
|
-
function buildProgram(): Command {
|
|
63
|
-
const program = new Command();
|
|
64
|
-
program.exitOverride();
|
|
65
|
-
program.configureOutput({
|
|
66
|
-
writeErr: () => {},
|
|
67
|
-
writeOut: () => {},
|
|
68
|
-
});
|
|
69
|
-
const memory = program.command("memory");
|
|
70
|
-
registerMemoryWorkerCommand(memory);
|
|
71
|
-
return program;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function runCommand(
|
|
75
|
-
args: string[],
|
|
76
|
-
): Promise<{ stdout: string; exitCode: number }> {
|
|
77
|
-
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
78
|
-
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
79
|
-
const stdoutChunks: string[] = [];
|
|
80
|
-
|
|
81
|
-
process.stdout.write = ((chunk: unknown) => {
|
|
82
|
-
stdoutChunks.push(typeof chunk === "string" ? chunk : String(chunk));
|
|
83
|
-
return true;
|
|
84
|
-
}) as typeof process.stdout.write;
|
|
85
|
-
|
|
86
|
-
process.stderr.write = (() => true) as typeof process.stderr.write;
|
|
87
|
-
|
|
88
|
-
process.exitCode = 0;
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
const program = buildProgram();
|
|
92
|
-
await program.parseAsync(["node", "assistant", ...args]);
|
|
93
|
-
} catch {
|
|
94
|
-
if (process.exitCode === 0) process.exitCode = 1;
|
|
95
|
-
} finally {
|
|
96
|
-
process.stdout.write = originalStdoutWrite;
|
|
97
|
-
process.stderr.write = originalStderrWrite;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const exitCode = process.exitCode ?? 0;
|
|
101
|
-
process.exitCode = 0;
|
|
102
|
-
|
|
103
|
-
return { exitCode, stdout: stdoutChunks.join("") };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Replace process.kill with a recording stub. `signal 0` is the liveness
|
|
108
|
-
* probe: it resolves for `livePids` and throws ESRCH otherwise. Other signals
|
|
109
|
-
* are recorded and no-oped so the test runner is never actually signalled.
|
|
110
|
-
*/
|
|
111
|
-
function stubProcessKill(livePids: Set<number>): () => void {
|
|
112
|
-
const original = process.kill.bind(process);
|
|
113
|
-
killCalls = [];
|
|
114
|
-
process.kill = ((pid: number, signal?: string | number) => {
|
|
115
|
-
const sig = signal ?? 0;
|
|
116
|
-
if (sig === 0) {
|
|
117
|
-
if (livePids.has(pid)) return true;
|
|
118
|
-
const err = new Error("kill ESRCH") as NodeJS.ErrnoException;
|
|
119
|
-
err.code = "ESRCH";
|
|
120
|
-
throw err;
|
|
121
|
-
}
|
|
122
|
-
killCalls.push({ pid, signal: sig });
|
|
123
|
-
return true;
|
|
124
|
-
}) as typeof process.kill;
|
|
125
|
-
return () => {
|
|
126
|
-
process.kill = original;
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
|
-
// Setup
|
|
132
|
-
// ---------------------------------------------------------------------------
|
|
133
|
-
|
|
134
|
-
beforeEach(() => {
|
|
135
|
-
tmpDir = mkdtempSync(join(tmpdir(), "memory-worker-test-"));
|
|
136
|
-
pidPath = join(tmpDir, "memory-worker.pid");
|
|
137
|
-
logOutput = [];
|
|
138
|
-
killCalls = [];
|
|
139
|
-
process.exitCode = 0;
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
afterEach(() => {
|
|
143
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// ---------------------------------------------------------------------------
|
|
147
|
-
// Subcommand registration
|
|
148
|
-
// ---------------------------------------------------------------------------
|
|
149
|
-
|
|
150
|
-
describe("subcommand registration", () => {
|
|
151
|
-
test("registers worker under memory with start/stop/status", () => {
|
|
152
|
-
const program = buildProgram();
|
|
153
|
-
const memory = program.commands.find((c) => c.name() === "memory");
|
|
154
|
-
expect(memory).toBeDefined();
|
|
155
|
-
const worker = memory!.commands.find((c) => c.name() === "worker");
|
|
156
|
-
expect(worker).toBeDefined();
|
|
157
|
-
const subcommandNames = worker!.commands.map((c) => c.name()).sort();
|
|
158
|
-
expect(subcommandNames).toEqual(["start", "status", "stop"]);
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// ---------------------------------------------------------------------------
|
|
163
|
-
// status
|
|
164
|
-
// ---------------------------------------------------------------------------
|
|
165
|
-
|
|
166
|
-
describe("memory worker status", () => {
|
|
167
|
-
test("reports not_running when no PID file exists", async () => {
|
|
168
|
-
const { exitCode, stdout } = await runCommand([
|
|
169
|
-
"memory",
|
|
170
|
-
"worker",
|
|
171
|
-
"status",
|
|
172
|
-
"--json",
|
|
173
|
-
]);
|
|
174
|
-
expect(exitCode).toBe(0);
|
|
175
|
-
expect(JSON.parse(stdout)).toEqual({ status: "not_running" });
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test("reports running when PID file points at a live process", async () => {
|
|
179
|
-
writeFileSync(pidPath, String(process.pid));
|
|
180
|
-
const restore = stubProcessKill(new Set([process.pid]));
|
|
181
|
-
try {
|
|
182
|
-
const { exitCode, stdout } = await runCommand([
|
|
183
|
-
"memory",
|
|
184
|
-
"worker",
|
|
185
|
-
"status",
|
|
186
|
-
"--json",
|
|
187
|
-
]);
|
|
188
|
-
expect(exitCode).toBe(0);
|
|
189
|
-
expect(JSON.parse(stdout)).toEqual({
|
|
190
|
-
status: "running",
|
|
191
|
-
pid: process.pid,
|
|
192
|
-
});
|
|
193
|
-
} finally {
|
|
194
|
-
restore();
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test("treats a stale PID file as not_running and cleans it up", async () => {
|
|
199
|
-
writeFileSync(pidPath, "999999");
|
|
200
|
-
const restore = stubProcessKill(new Set());
|
|
201
|
-
try {
|
|
202
|
-
const { exitCode, stdout } = await runCommand([
|
|
203
|
-
"memory",
|
|
204
|
-
"worker",
|
|
205
|
-
"status",
|
|
206
|
-
"--json",
|
|
207
|
-
]);
|
|
208
|
-
expect(exitCode).toBe(0);
|
|
209
|
-
expect(JSON.parse(stdout)).toEqual({ status: "not_running" });
|
|
210
|
-
expect(existsSync(pidPath)).toBe(false);
|
|
211
|
-
} finally {
|
|
212
|
-
restore();
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// ---------------------------------------------------------------------------
|
|
218
|
-
// stop
|
|
219
|
-
// ---------------------------------------------------------------------------
|
|
220
|
-
|
|
221
|
-
describe("memory worker stop", () => {
|
|
222
|
-
test("errors with exit 1 when no worker is running", async () => {
|
|
223
|
-
const { exitCode, stdout } = await runCommand([
|
|
224
|
-
"memory",
|
|
225
|
-
"worker",
|
|
226
|
-
"stop",
|
|
227
|
-
"--json",
|
|
228
|
-
]);
|
|
229
|
-
expect(exitCode).toBe(1);
|
|
230
|
-
expect(JSON.parse(stdout)).toMatchObject({ ok: false });
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
test("sends SIGTERM to a running worker", async () => {
|
|
234
|
-
writeFileSync(pidPath, String(process.pid));
|
|
235
|
-
const restore = stubProcessKill(new Set([process.pid]));
|
|
236
|
-
try {
|
|
237
|
-
const { exitCode, stdout } = await runCommand([
|
|
238
|
-
"memory",
|
|
239
|
-
"worker",
|
|
240
|
-
"stop",
|
|
241
|
-
"--json",
|
|
242
|
-
]);
|
|
243
|
-
expect(exitCode).toBe(0);
|
|
244
|
-
expect(JSON.parse(stdout)).toEqual({ ok: true, pid: process.pid });
|
|
245
|
-
expect(killCalls).toContainEqual({
|
|
246
|
-
pid: process.pid,
|
|
247
|
-
signal: "SIGTERM",
|
|
248
|
-
});
|
|
249
|
-
} finally {
|
|
250
|
-
restore();
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// ---------------------------------------------------------------------------
|
|
256
|
-
// start
|
|
257
|
-
// ---------------------------------------------------------------------------
|
|
258
|
-
|
|
259
|
-
describe("memory worker start", () => {
|
|
260
|
-
test("refuses to start when a worker is already running", async () => {
|
|
261
|
-
writeFileSync(pidPath, String(process.pid));
|
|
262
|
-
const restore = stubProcessKill(new Set([process.pid]));
|
|
263
|
-
try {
|
|
264
|
-
const { exitCode, stdout } = await runCommand([
|
|
265
|
-
"memory",
|
|
266
|
-
"worker",
|
|
267
|
-
"start",
|
|
268
|
-
"--json",
|
|
269
|
-
]);
|
|
270
|
-
expect(exitCode).toBe(1);
|
|
271
|
-
expect(JSON.parse(stdout)).toMatchObject({
|
|
272
|
-
ok: false,
|
|
273
|
-
pid: process.pid,
|
|
274
|
-
});
|
|
275
|
-
} finally {
|
|
276
|
-
restore();
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
test("spawns the worker and reports the PID it writes", async () => {
|
|
281
|
-
const restore = stubProcessKill(new Set());
|
|
282
|
-
const originalSpawn = Bun.spawn;
|
|
283
|
-
// Simulate the spawned worker writing its PID file on startup.
|
|
284
|
-
(Bun as { spawn: typeof Bun.spawn }).spawn = (() => {
|
|
285
|
-
writeFileSync(pidPath, "424242");
|
|
286
|
-
return { unref: () => {}, pid: 424242 };
|
|
287
|
-
}) as unknown as typeof Bun.spawn;
|
|
288
|
-
try {
|
|
289
|
-
const { exitCode, stdout } = await runCommand([
|
|
290
|
-
"memory",
|
|
291
|
-
"worker",
|
|
292
|
-
"start",
|
|
293
|
-
"--json",
|
|
294
|
-
]);
|
|
295
|
-
expect(exitCode).toBe(0);
|
|
296
|
-
expect(JSON.parse(stdout)).toMatchObject({ ok: true, pid: 424242 });
|
|
297
|
-
} finally {
|
|
298
|
-
(Bun as { spawn: typeof Bun.spawn }).spawn = originalSpawn;
|
|
299
|
-
restore();
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
});
|