@vellumai/assistant 0.8.10 → 0.8.11-staging.1
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 +62 -1
- package/docs/workspace-tools.md +196 -0
- package/examples/plugins/echo/README.md +3 -3
- package/knip.json +1 -0
- package/openapi.yaml +460 -128
- package/package.json +2 -1
- package/scripts/build-plugin-api.ts +299 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +7 -0
- package/src/__tests__/agent-loop-compaction-events.test.ts +197 -0
- package/src/__tests__/agent-loop-exit-reason.test.ts +93 -96
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +2 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +274 -1
- package/src/__tests__/agent-loop-override-profile.test.ts +3 -0
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +4 -0
- package/src/__tests__/agent-loop-thinking.test.ts +4 -0
- package/src/__tests__/agent-loop.test.ts +578 -5
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -1
- package/src/__tests__/approval-cascade.test.ts +1 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
- package/src/__tests__/btw-routes.test.ts +0 -1
- package/src/__tests__/build-persisted-content.test.ts +75 -1
- package/src/__tests__/catalog-install-normalize.test.ts +141 -0
- package/src/__tests__/ces-startup-timeout.test.ts +60 -0
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
- package/src/__tests__/context-overflow-reducer.test.ts +264 -124
- package/src/__tests__/context-window-manager-overflow-rung.test.ts +351 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +13 -5
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +284 -455
- package/src/__tests__/conversation-agent-loop.test.ts +131 -551
- package/src/__tests__/conversation-app-control-instantiation.test.ts +13 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
- package/src/__tests__/conversation-fork-crud.test.ts +259 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-lifecycle.test.ts +257 -1
- package/src/__tests__/conversation-process-callsite.test.ts +1 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +38 -377
- package/src/__tests__/conversation-queue.test.ts +1 -39
- package/src/__tests__/conversation-runtime-assembly.test.ts +119 -8
- package/src/__tests__/conversation-skill-tools.test.ts +491 -5
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
- package/src/__tests__/conversation-speed-override.test.ts +1 -0
- package/src/__tests__/conversation-store.test.ts +74 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +4 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +323 -0
- package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +34 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -0
- package/src/__tests__/corrected-target.test.ts +93 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -5
- package/src/__tests__/credential-execution-tools.test.ts +23 -11
- package/src/__tests__/credential-security-invariants.test.ts +6 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +80 -0
- package/src/__tests__/device-id.test.ts +70 -1
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
- package/src/__tests__/empty-response-hook.test.ts +242 -66
- package/src/__tests__/external-plugin-loader.test.ts +0 -31
- package/src/__tests__/get-skill-detail-audit.test.ts +43 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +91 -0
- package/src/__tests__/history-repair-hook.test.ts +228 -3
- package/src/__tests__/host-app-control-proxy.test.ts +45 -0
- package/src/__tests__/host-browser-proxy.test.ts +254 -9
- package/src/__tests__/identity-routes.test.ts +1 -0
- package/src/__tests__/image-recovery-hook.test.ts +387 -0
- package/src/__tests__/injector-chain.test.ts +5 -4
- package/src/__tests__/injector-v3-suppression.test.ts +373 -47
- package/src/__tests__/intent-routing.test.ts +7 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +117 -15
- package/src/__tests__/notification-decision-strategy.test.ts +3 -3
- package/src/__tests__/oauth-store.test.ts +0 -85
- package/src/__tests__/{context-overflow-policy.test.ts → overflow-policy.test.ts} +1 -1
- package/src/__tests__/parallel-tool.benchmark.test.ts +4 -0
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +29 -9
- package/src/__tests__/persist-unsendable-image.test.ts +4 -4
- package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
- package/src/__tests__/plugin-bootstrap.test.ts +82 -73
- package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
- package/src/__tests__/plugin-types.test.ts +0 -8
- package/src/__tests__/provider-catalog-visibility.test.ts +1 -9
- package/src/__tests__/prune-old-conversations-job.test.ts +99 -0
- package/src/__tests__/registry.test.ts +240 -1
- package/src/__tests__/require-fresh-approval.test.ts +3 -0
- package/src/__tests__/schedule-routes.test.ts +116 -1
- package/src/__tests__/schedule-store.test.ts +28 -0
- package/src/__tests__/schedule-tools.test.ts +94 -1
- package/src/__tests__/server-history-render.test.ts +39 -0
- package/src/__tests__/skill-projection-feature-flag.test.ts +13 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +25 -7
- package/src/__tests__/skills.test.ts +202 -0
- package/src/__tests__/slim-skill-category.test.ts +195 -0
- package/src/__tests__/strip-memory-injections.test.ts +33 -39
- package/src/__tests__/test-support/tool-invocation-seed.ts +79 -0
- package/src/__tests__/title-generate-hook.test.ts +9 -7
- package/src/__tests__/tool-audit-listener.test.ts +264 -1
- package/src/__tests__/tool-error-hook.test.ts +4 -3
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +273 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
- package/src/__tests__/tool-start-timestamp.test.ts +218 -0
- package/src/__tests__/tools-get-route.test.ts +202 -0
- package/src/__tests__/workspace-tool-loader.test.ts +319 -0
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
- package/src/agent/loop.ts +569 -319
- package/src/api/events/tool-result.ts +9 -0
- package/src/api/events/tool-use-start.ts +7 -0
- package/src/api/index.ts +10 -0
- package/src/api/responses/conversation-message.ts +135 -27
- package/src/api/responses/memory-v3-selection-log.ts +4 -4
- package/src/approvals/guardian-request-resolvers.ts +26 -0
- package/src/browser-session/backends/host-bridge.ts +29 -0
- package/src/browser-session/index.ts +1 -0
- package/src/browser-session/types.ts +5 -1
- package/src/cli/commands/__tests__/schedules.test.ts +62 -4
- package/src/cli/commands/__tests__/skills.test.ts +53 -0
- package/src/cli/commands/channel-verification-sessions.ts +6 -6
- package/src/cli/commands/inference-providers.ts +0 -8
- package/src/cli/commands/plugins.ts +2 -2
- package/src/cli/commands/schedules.ts +27 -4
- package/src/cli/commands/skills.ts +187 -146
- package/src/cli/commands/tools.ts +106 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +256 -328
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +6 -2
- package/src/cli/lib/__tests__/plugin-details.test.ts +10 -16
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +2 -2
- package/src/cli/lib/__tests__/search-plugins.test.ts +145 -240
- package/src/cli/lib/install-from-github.ts +187 -117
- package/src/cli/lib/plugin-catalog-cache.ts +9 -9
- package/src/cli/lib/plugin-details.ts +38 -68
- package/src/cli/lib/plugin-marketplace.ts +42 -14
- package/src/cli/lib/search-plugins.ts +29 -129
- package/src/cli/program.ts +2 -0
- package/src/config/bundled-skills/acp/SKILL.md +1 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -0
- package/src/config/bundled-skills/app-control/SKILL.md +1 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +1 -0
- package/src/config/bundled-skills/contacts/SKILL.md +1 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +1 -0
- package/src/config/bundled-skills/followups/SKILL.md +1 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +1 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +1 -0
- package/src/config/bundled-skills/messaging/SKILL.md +1 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -0
- package/src/config/bundled-skills/playbooks/SKILL.md +1 -0
- package/src/config/bundled-skills/schedule/SKILL.md +1 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +11 -3
- package/src/config/bundled-skills/sequences/SKILL.md +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +1 -0
- package/src/config/bundled-skills/skill-management/SKILL.md +101 -1
- package/src/config/bundled-skills/subagent/SKILL.md +1 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -0
- package/src/config/env-registry.ts +23 -0
- package/src/config/feature-flag-registry.json +17 -81
- package/src/config/loader.ts +5 -22
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/compaction-logs.test.ts +56 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +0 -1
- package/src/config/schemas/__tests__/memory-v3.test.ts +61 -1
- package/src/config/schemas/compaction-logs.ts +79 -0
- package/src/config/schemas/memory-v2.ts +0 -8
- package/src/config/schemas/memory-v3.ts +104 -33
- package/src/config/seed-inference-profiles.ts +1 -1
- package/src/config/skills.ts +117 -47
- package/src/context/compactor.ts +11 -0
- package/src/context/strip-injections.ts +38 -4
- package/src/credential-execution/feature-gates.ts +0 -21
- package/src/credential-execution/startup-timeout.ts +32 -4
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +18 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +140 -91
- package/src/daemon/conversation-agent-loop.ts +123 -658
- package/src/daemon/conversation-error.ts +6 -33
- package/src/daemon/conversation-lifecycle.ts +1 -1
- package/src/daemon/conversation-runtime-assembly.ts +183 -22
- package/src/daemon/conversation-skill-tools.ts +137 -8
- package/src/daemon/conversation-slash.ts +0 -14
- package/src/daemon/conversation-store.ts +2 -19
- package/src/daemon/conversation-tool-setup.ts +87 -1
- package/src/daemon/conversation.ts +119 -50
- package/src/daemon/external-plugins-bootstrap.ts +36 -95
- package/src/daemon/handlers/config-channels.ts +11 -2
- package/src/daemon/handlers/shared.ts +9 -1
- package/src/daemon/handlers/skills.ts +10 -4
- package/src/daemon/host-app-control-proxy.ts +72 -57
- package/src/daemon/host-browser-proxy.ts +117 -22
- package/src/daemon/lifecycle.ts +34 -5
- package/src/daemon/message-protocol.ts +0 -7
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/skills.ts +17 -0
- package/src/daemon/providers-setup.ts +3 -0
- package/src/daemon/server.ts +3 -3
- package/src/daemon/tool-setup-types.ts +9 -3
- package/src/daemon/trust-context.ts +23 -0
- package/src/daemon/workspace-tools-watcher.ts +324 -0
- package/src/events/tool-audit-listener.ts +78 -16
- package/src/events/tool-metrics-listener.ts +2 -5
- package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +227 -0
- package/src/memory/__tests__/conversation-queries.test.ts +176 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +20 -32
- package/src/memory/compaction-log-writer-clickhouse.ts +418 -0
- package/src/memory/conversation-crud.ts +134 -3
- package/src/memory/conversation-queries.ts +64 -7
- package/src/memory/db-init.ts +14 -0
- package/src/memory/embedding-backend.test.ts +130 -1
- package/src/memory/embedding-backend.ts +79 -106
- package/src/memory/embedding-gemini.ts +5 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +12 -0
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +19 -0
- package/src/memory/graph/conversation-graph-memory.ts +36 -25
- package/src/memory/graph/tool-handlers.ts +3 -0
- package/src/memory/job-handlers/cleanup.ts +3 -1
- package/src/memory/jobs-store.ts +0 -28
- package/src/memory/jobs-worker.ts +10 -22
- package/src/memory/memory-marker.ts +29 -0
- package/src/memory/memory-retrospective-startup-cleanup.ts +1 -1
- package/src/memory/migrations/268-add-memory-v3-selections.ts +6 -0
- package/src/memory/migrations/270-schedule-description.ts +36 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.test.ts +81 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.ts +20 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.test.ts +68 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.ts +20 -0
- package/src/memory/migrations/277-add-memory-v3-ever-injected.ts +29 -0
- package/src/memory/migrations/278-tool-invocations-telemetry-columns.test.ts +96 -0
- package/src/memory/migrations/278-tool-invocations-telemetry-columns.ts +39 -0
- package/src/memory/migrations/279-create-skill-loaded-events.test.ts +84 -0
- package/src/memory/migrations/279-create-skill-loaded-events.ts +26 -0
- package/src/memory/migrations/280-conversations-surfaced-at.test.ts +88 -0
- package/src/memory/migrations/280-conversations-surfaced-at.ts +24 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +16 -0
- package/src/memory/schema/infrastructure.ts +26 -0
- package/src/memory/skill-loaded-events-store.test.ts +160 -0
- package/src/memory/skill-loaded-events-store.ts +95 -0
- package/src/memory/tool-executed-events-store.test.ts +219 -0
- package/src/memory/tool-executed-events-store.ts +102 -0
- package/src/memory/tool-usage-store.ts +15 -3
- package/src/memory/v2/__tests__/consolidation-job.test.ts +117 -12
- package/src/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +189 -0
- package/src/memory/v2/__tests__/injected-block-slugs.test.ts +90 -0
- package/src/memory/v2/__tests__/page-store.test.ts +33 -0
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +88 -15
- package/src/memory/v2/activation-store.ts +50 -1
- package/src/memory/v2/consolidation-job.ts +113 -29
- package/src/memory/v2/injected-block-slugs.ts +79 -0
- package/src/memory/v2/injection.ts +6 -1
- package/src/memory/v2/prompts/consolidation.ts +414 -13
- package/src/memory/v2/router.ts +2 -28
- package/src/memory/v2/static-context.ts +1 -1
- package/src/memory/v2/types.ts +16 -0
- package/src/notifications/__tests__/copy-composer.test.ts +244 -0
- package/src/notifications/access-request-copy.ts +298 -0
- package/src/notifications/adapters/slack.ts +3 -3
- package/src/notifications/adapters/telegram.ts +2 -1
- package/src/notifications/copy-composer.ts +49 -267
- package/src/notifications/decision-engine.ts +16 -35
- package/src/notifications/home-feed-side-effect.ts +1 -6
- package/src/oauth/oauth-store.ts +0 -9
- package/src/permissions/checker.test.ts +83 -1
- package/src/permissions/checker.ts +25 -2
- package/src/permissions/gateway-threshold-reader.test.ts +182 -0
- package/src/permissions/gateway-threshold-reader.ts +80 -0
- package/src/platform/client.ts +1 -3
- package/src/platform/feature-gate.ts +3 -12
- package/src/plugin-api/constants.ts +4 -2
- package/src/plugin-api/index.ts +63 -11
- package/src/plugin-api/types.ts +236 -71
- package/src/plugins/defaults/compaction/compact.ts +66 -2
- package/src/plugins/defaults/compaction/context-overflow-reducer.ts +240 -32
- package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
- package/src/{daemon/context-overflow-policy.ts → plugins/defaults/compaction/overflow-policy.ts} +1 -1
- package/src/plugins/defaults/compaction/window-manager.ts +303 -1
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +173 -0
- package/src/plugins/defaults/empty-response/hooks/stop.ts +11 -115
- package/src/plugins/defaults/empty-response/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +50 -0
- package/src/plugins/defaults/history-repair/hooks/stop.ts +22 -0
- package/src/plugins/defaults/history-repair/repair-state-store.ts +51 -0
- package/src/plugins/defaults/history-repair/terminal.ts +39 -2
- package/src/plugins/defaults/image-recovery/detect.ts +25 -0
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +73 -0
- package/src/plugins/defaults/image-recovery/hooks/stop.ts +22 -0
- package/src/plugins/defaults/image-recovery/image-recovery-state-store.ts +48 -0
- package/src/plugins/defaults/image-recovery/package.json +14 -0
- package/src/{daemon/persist-unsendable-image.ts → plugins/defaults/image-recovery/recover.ts} +67 -14
- package/src/plugins/defaults/index.ts +71 -5
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +76 -112
- package/src/plugins/defaults/memory-retrieval/hooks/{user-prompt-submit-temp.ts → user-prompt-submit.ts} +83 -74
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +14 -8
- package/src/plugins/defaults/memory-retrieval/injectors.ts +2 -18
- package/src/plugins/defaults/memory-retrieval/package.json +14 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1157 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +683 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +161 -140
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +160 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +335 -316
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +145 -53
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +38 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +18 -8
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +112 -71
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +192 -51
- package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +4 -16
- package/src/plugins/defaults/memory-v3-shadow/card.test.ts +173 -0
- package/src/plugins/defaults/memory-v3-shadow/card.ts +116 -0
- package/src/plugins/defaults/memory-v3-shadow/core-set.test.ts +104 -0
- package/src/plugins/defaults/memory-v3-shadow/core-set.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.test.ts +305 -0
- package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.ts +278 -0
- package/src/plugins/defaults/memory-v3-shadow/hot-set.test.ts +138 -0
- package/src/plugins/defaults/memory-v3-shadow/hot-set.ts +85 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +331 -24
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +119 -13
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +169 -114
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +47 -16
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +144 -66
- package/src/plugins/defaults/memory-v3-shadow/prune.test.ts +758 -0
- package/src/plugins/defaults/memory-v3-shadow/prune.ts +471 -0
- package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +68 -16
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +19 -12
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +96 -43
- package/src/plugins/defaults/memory-v3-shadow/types.ts +34 -17
- package/src/plugins/defaults/title-generate/hooks/stop.ts +9 -11
- package/src/plugins/pipeline.ts +8 -5
- package/src/plugins/types.ts +5 -51
- package/src/providers/cache-control.ts +26 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -2
- package/src/providers/model-catalog.ts +13 -1
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +147 -0
- package/src/providers/openai/chat-completions-provider.ts +46 -0
- package/src/providers/openai/responses-provider.ts +45 -0
- package/src/providers/registry.ts +0 -8
- package/src/runtime/__tests__/agent-wake.test.ts +0 -1
- package/src/runtime/agent-wake.ts +13 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +151 -0
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +12 -50
- package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +322 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +0 -62
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +31 -11
- package/src/runtime/routes/acp-routes.test.ts +106 -0
- package/src/runtime/routes/acp-routes.ts +248 -2
- package/src/runtime/routes/browser-tabs-routes.ts +1 -1
- package/src/runtime/routes/channel-verification-routes.ts +14 -5
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +0 -14
- package/src/runtime/routes/consolidation-routes.ts +6 -82
- package/src/runtime/routes/conversation-list-routes.ts +6 -0
- package/src/runtime/routes/conversation-management-routes.ts +71 -0
- package/src/runtime/routes/identity-routes.ts +8 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
- package/src/runtime/routes/inference-provider-connection-routes.ts +0 -45
- package/src/runtime/routes/plugins-routes.ts +34 -45
- package/src/runtime/routes/schedule-routes.ts +43 -5
- package/src/runtime/routes/settings-routes.ts +140 -15
- package/src/runtime/routes/skills-routes.ts +18 -6
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +140 -0
- package/src/runtime/services/conversation-serializer.ts +38 -1
- package/src/runtime/verification-outbound-actions.ts +147 -2
- package/src/runtime/verification-templates.ts +29 -3
- package/src/schedule/schedule-store.ts +19 -0
- package/src/skills/catalog-install.ts +77 -13
- package/src/tasks/task-scheduler.ts +1 -0
- package/src/telemetry/types.ts +66 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +542 -13
- package/src/telemetry/usage-telemetry-reporter.ts +213 -20
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +49 -2
- package/src/tools/browser/__tests__/browser-status.test.ts +29 -5
- package/src/tools/browser/browser-execution.ts +27 -9
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +380 -4
- package/src/tools/browser/cdp-client/__tests__/host-bridge-cdp-client.test.ts +107 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +6 -1
- package/src/tools/browser/cdp-client/factory.ts +217 -17
- package/src/tools/browser/cdp-client/host-bridge-cdp-client.ts +67 -0
- package/src/tools/browser/cdp-client/types.ts +22 -2
- package/src/tools/credential-execution/make-authenticated-request.ts +2 -1
- package/src/tools/credential-execution/manage-secure-command-tool.ts +169 -164
- package/src/tools/credential-execution/run-authenticated-command.ts +2 -1
- package/src/tools/executor.ts +39 -7
- package/src/tools/registry.ts +387 -5
- package/src/tools/schedule/create.ts +16 -0
- package/src/tools/schedule/list.ts +12 -4
- package/src/tools/schedule/update.ts +12 -0
- package/src/tools/skills/load.ts +11 -6
- package/src/tools/terminal/safe-env.ts +2 -0
- package/src/tools/types.ts +65 -9
- package/src/tools/workspace-tools/loader.ts +673 -0
- package/src/usage/attribution.ts +28 -0
- package/src/util/device-id.ts +17 -3
- package/src/util/platform.ts +16 -0
- package/tsconfig.plugin-api.json +13 -0
- package/src/__tests__/plugin-external-api.test.ts +0 -68
- package/src/__tests__/plugin-skill-contribution.test.ts +0 -355
- package/src/daemon/message-types/browser.ts +0 -10
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +0 -36
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +0 -37
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +0 -106
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +0 -44
- package/src/plugins/defaults/memory-v3-shadow/working-set.ts +0 -91
- package/src/plugins/external-api.ts +0 -114
- package/src/plugins/plugin-skill-contributions.ts +0 -292
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for the default `empty-response` plugin's
|
|
2
|
+
* Tests for the default `empty-response` plugin's hooks.
|
|
3
3
|
*
|
|
4
4
|
* Covers:
|
|
5
|
-
* - The
|
|
5
|
+
* - The `post-model-call` hook's decision for the canonical cases: empty turn
|
|
6
6
|
* after a prior tool-use turn → continue (with the canonical nudge text);
|
|
7
7
|
* visible text → stop; prior turn already delivered visible text → stop;
|
|
8
|
-
* first model call with no prior turn → stop; provider refusal →
|
|
9
|
-
*
|
|
8
|
+
* first model call with no prior turn → stop; provider refusal → stop with
|
|
9
|
+
* the turn rewritten to the user-facing fallback; refusal-but-recovered →
|
|
10
|
+
* stop; refusal after a visible reply this run → stop with no rewrite.
|
|
10
11
|
* - The hook scopes its prior-turn signals to the current response cycle (the
|
|
11
12
|
* messages after the last genuine user prompt), so visible text from the
|
|
12
13
|
* inbound conversation does not suppress the nudge.
|
|
13
14
|
* - When the hook continues, it appends the nudge as a `user` message to
|
|
14
15
|
* `messages`.
|
|
16
|
+
* - The retry bound is split across the two hooks: `post-model-call` marks the
|
|
17
|
+
* bound (nudging at most once per run) and the `stop` hook clears it on the
|
|
18
|
+
* definitive terminal so the next run nudges afresh.
|
|
19
|
+
* - The hook ignores outcomes it does not own: a provider rejection (it carries
|
|
20
|
+
* an `error`) and a tool-bearing turn (the loop continues on its own).
|
|
15
21
|
* - End-to-end through `runHook` + the registry: registering the default
|
|
16
22
|
* plugin makes the hook fire, and a later-registered user hook chains after
|
|
17
23
|
* it and can read/override the decision.
|
|
18
24
|
*
|
|
19
|
-
* The loop's actual side-effects (
|
|
20
|
-
*
|
|
25
|
+
* The loop's actual side-effects (per-run backstop, history splice, streamed
|
|
26
|
+
* output) live in `agent/loop.ts` and are covered by integration tests in
|
|
21
27
|
* `conversation-agent-loop.test.ts` / `agent-loop.test.ts`. This file isolates
|
|
22
28
|
* the hook.
|
|
23
29
|
*/
|
|
@@ -25,12 +31,22 @@
|
|
|
25
31
|
import { beforeEach, describe, expect, test } from "bun:test";
|
|
26
32
|
|
|
27
33
|
import { HOOKS } from "../plugin-api/constants.js";
|
|
28
|
-
import type {
|
|
34
|
+
import type {
|
|
35
|
+
PluginLogger,
|
|
36
|
+
PostModelCallContext,
|
|
37
|
+
StopContext,
|
|
38
|
+
} from "../plugin-api/types.js";
|
|
29
39
|
import {
|
|
30
40
|
NUDGE_TEXT,
|
|
31
|
-
|
|
32
|
-
} from "../plugins/defaults/empty-response/hooks/
|
|
41
|
+
REFUSAL_FALLBACK_TEXT,
|
|
42
|
+
} from "../plugins/defaults/empty-response/hooks/post-model-call.js";
|
|
43
|
+
import postModelCall from "../plugins/defaults/empty-response/hooks/post-model-call.js";
|
|
33
44
|
import stop from "../plugins/defaults/empty-response/hooks/stop.js";
|
|
45
|
+
import {
|
|
46
|
+
isEmptyResponseNudged,
|
|
47
|
+
markEmptyResponseNudged,
|
|
48
|
+
resetEmptyResponseNudgeStoreForTests,
|
|
49
|
+
} from "../plugins/defaults/empty-response/nudge-state-store.js";
|
|
34
50
|
import { defaultEmptyResponsePlugin } from "../plugins/defaults/index.js";
|
|
35
51
|
import { runHook } from "../plugins/pipeline.js";
|
|
36
52
|
import {
|
|
@@ -67,11 +83,14 @@ function userPrompt(text: string): Message {
|
|
|
67
83
|
return { role: "user", content: [{ type: "text", text }] };
|
|
68
84
|
}
|
|
69
85
|
|
|
70
|
-
function makeCtx(
|
|
86
|
+
function makeCtx(
|
|
87
|
+
overrides: Partial<PostModelCallContext> = {},
|
|
88
|
+
): PostModelCallContext {
|
|
71
89
|
return {
|
|
72
|
-
conversationId: "conv-
|
|
90
|
+
conversationId: "conv-pmc",
|
|
91
|
+
callSite: "mainAgent",
|
|
92
|
+
content: [],
|
|
73
93
|
messages: [],
|
|
74
|
-
responseContent: [],
|
|
75
94
|
stopReason: null,
|
|
76
95
|
decision: "stop",
|
|
77
96
|
logger: noopLogger,
|
|
@@ -79,19 +98,23 @@ function makeCtx(overrides: Partial<StopContext> = {}): StopContext {
|
|
|
79
98
|
};
|
|
80
99
|
}
|
|
81
100
|
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
resetEmptyResponseNudgeStoreForTests();
|
|
103
|
+
});
|
|
104
|
+
|
|
82
105
|
// ─── Default decisions ───────────────────────────────────────────────────────
|
|
83
106
|
|
|
84
|
-
describe("empty-response
|
|
107
|
+
describe("empty-response post-model-call hook — default decisions", () => {
|
|
85
108
|
test("empty turn after a prior tool-use turn → continue with canonical nudge", async () => {
|
|
86
109
|
// GIVEN a run that already issued a tool call, then returned an empty
|
|
87
110
|
// (whitespace-only) assistant turn with no visible text.
|
|
88
111
|
const ctx = makeCtx({
|
|
89
112
|
messages: [priorToolUseTurn],
|
|
90
|
-
|
|
113
|
+
content: [emptyTextBlock],
|
|
91
114
|
});
|
|
92
115
|
|
|
93
|
-
// WHEN the default
|
|
94
|
-
await
|
|
116
|
+
// WHEN the default post-model-call hook runs.
|
|
117
|
+
await postModelCall(ctx);
|
|
95
118
|
|
|
96
119
|
// THEN it asks the loop to continue and appends the canonical nudge.
|
|
97
120
|
expect(ctx.decision).toBe("continue");
|
|
@@ -105,28 +128,44 @@ describe("empty-response stop hook — default decisions", () => {
|
|
|
105
128
|
// GIVEN a turn that delivered visible text.
|
|
106
129
|
const ctx = makeCtx({
|
|
107
130
|
messages: [priorToolUseTurn],
|
|
108
|
-
|
|
131
|
+
content: [{ type: "text", text: "here is a summary" }],
|
|
109
132
|
});
|
|
110
133
|
|
|
111
134
|
// WHEN the hook runs.
|
|
112
|
-
await
|
|
135
|
+
await postModelCall(ctx);
|
|
113
136
|
|
|
114
137
|
// THEN the decision stays at stop and nothing is appended.
|
|
115
138
|
expect(ctx.decision).toBe("stop");
|
|
116
139
|
expect(ctx.messages).toEqual([priorToolUseTurn]);
|
|
117
140
|
});
|
|
118
141
|
|
|
142
|
+
test("tool-bearing turn is ignored — no nudge, no rewrite", async () => {
|
|
143
|
+
// GIVEN a turn that carries a tool call (the loop continues on its own to
|
|
144
|
+
// run the tool, so the retry decision is not the hook's to make).
|
|
145
|
+
const ctx = makeCtx({
|
|
146
|
+
messages: [priorToolUseTurn],
|
|
147
|
+
content: [{ type: "tool_use", id: "tu_2", name: "read_file", input: {} }],
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// WHEN the hook runs.
|
|
151
|
+
await postModelCall(ctx);
|
|
152
|
+
|
|
153
|
+
// THEN it leaves the decision and history untouched.
|
|
154
|
+
expect(ctx.decision).toBe("stop");
|
|
155
|
+
expect(ctx.messages).toEqual([priorToolUseTurn]);
|
|
156
|
+
});
|
|
157
|
+
|
|
119
158
|
test("prior turn already delivered visible text → stop", async () => {
|
|
120
159
|
// GIVEN the model said its piece in an earlier turn this run, then ended
|
|
121
160
|
// with a side-effect tool and returned empty. Nudging would force a
|
|
122
161
|
// verbatim re-send of text the user already saw.
|
|
123
162
|
const ctx = makeCtx({
|
|
124
163
|
messages: [userPrompt("do X"), priorVisibleTextTurn, priorToolUseTurn],
|
|
125
|
-
|
|
164
|
+
content: [],
|
|
126
165
|
});
|
|
127
166
|
|
|
128
167
|
// WHEN the hook runs.
|
|
129
|
-
await
|
|
168
|
+
await postModelCall(ctx);
|
|
130
169
|
|
|
131
170
|
// THEN the decision stays at stop.
|
|
132
171
|
expect(ctx.decision).toBe("stop");
|
|
@@ -143,11 +182,11 @@ describe("empty-response stop hook — default decisions", () => {
|
|
|
143
182
|
userPrompt("do the next thing"),
|
|
144
183
|
priorToolUseTurn,
|
|
145
184
|
],
|
|
146
|
-
|
|
185
|
+
content: [],
|
|
147
186
|
});
|
|
148
187
|
|
|
149
188
|
// WHEN the hook runs.
|
|
150
|
-
await
|
|
189
|
+
await postModelCall(ctx);
|
|
151
190
|
|
|
152
191
|
// THEN it continues — the cycle scope sees only the tool-use turn, not the
|
|
153
192
|
// inbound conversation's visible text.
|
|
@@ -161,10 +200,10 @@ describe("empty-response stop hook — default decisions", () => {
|
|
|
161
200
|
test("first model call with no prior turn → stop", async () => {
|
|
162
201
|
// GIVEN an empty first assistant response with no prior turn this run and
|
|
163
202
|
// no refusal — not the pattern the organic-empty-turn nudge guards against.
|
|
164
|
-
const ctx = makeCtx({ messages: [],
|
|
203
|
+
const ctx = makeCtx({ messages: [], content: [] });
|
|
165
204
|
|
|
166
205
|
// WHEN the hook runs.
|
|
167
|
-
await
|
|
206
|
+
await postModelCall(ctx);
|
|
168
207
|
|
|
169
208
|
// THEN the decision stays at stop.
|
|
170
209
|
expect(ctx.decision).toBe("stop");
|
|
@@ -172,88 +211,225 @@ describe("empty-response stop hook — default decisions", () => {
|
|
|
172
211
|
|
|
173
212
|
// ─── Refusal stop ──────────────────────────────────────────────────────────
|
|
174
213
|
|
|
175
|
-
test("refusal on the first call with no content →
|
|
214
|
+
test("refusal on the first call with no content → stop with rewritten fallback", async () => {
|
|
176
215
|
// GIVEN the canonical failure mode: the provider's safety classifier zeros
|
|
177
216
|
// the response on the very first model call, returning `stopReason:
|
|
178
217
|
// "refusal"` and no visible text.
|
|
179
218
|
const ctx = makeCtx({
|
|
180
219
|
messages: [],
|
|
181
|
-
|
|
220
|
+
content: [],
|
|
182
221
|
stopReason: "refusal",
|
|
183
222
|
});
|
|
184
223
|
|
|
185
224
|
// WHEN the hook runs.
|
|
186
|
-
await
|
|
225
|
+
await postModelCall(ctx);
|
|
187
226
|
|
|
188
|
-
// THEN it
|
|
189
|
-
|
|
190
|
-
expect(ctx.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
227
|
+
// THEN it lets the turn end and rewrites the turn content to the
|
|
228
|
+
// user-facing fallback, without nudging the model.
|
|
229
|
+
expect(ctx.decision).toBe("stop");
|
|
230
|
+
expect(ctx.content).toEqual([
|
|
231
|
+
{ type: "text", text: REFUSAL_FALLBACK_TEXT },
|
|
232
|
+
]);
|
|
233
|
+
expect(ctx.messages).toEqual([]);
|
|
194
234
|
});
|
|
195
235
|
|
|
196
|
-
test("refusal with a thinking-only block still
|
|
236
|
+
test("refusal with a thinking-only block is still rewritten", async () => {
|
|
197
237
|
// GIVEN a refusal whose only content is a thinking block — the user sees
|
|
198
238
|
// nothing.
|
|
199
239
|
const ctx = makeCtx({
|
|
200
240
|
messages: [],
|
|
201
|
-
|
|
202
|
-
{ type: "thinking", thinking: "...", signature: "sig" },
|
|
203
|
-
],
|
|
241
|
+
content: [{ type: "thinking", thinking: "...", signature: "sig" }],
|
|
204
242
|
stopReason: "refusal",
|
|
205
243
|
});
|
|
206
244
|
|
|
207
245
|
// WHEN the hook runs.
|
|
208
|
-
await
|
|
246
|
+
await postModelCall(ctx);
|
|
209
247
|
|
|
210
|
-
// THEN it
|
|
211
|
-
expect(ctx.decision).toBe("
|
|
212
|
-
expect(ctx.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
});
|
|
248
|
+
// THEN it stops and rewrites the turn content to the fallback.
|
|
249
|
+
expect(ctx.decision).toBe("stop");
|
|
250
|
+
expect(ctx.content).toEqual([
|
|
251
|
+
{ type: "text", text: REFUSAL_FALLBACK_TEXT },
|
|
252
|
+
]);
|
|
216
253
|
});
|
|
217
254
|
|
|
218
|
-
test("refusal but visible text present → stop (model recovered)", async () => {
|
|
255
|
+
test("refusal but visible text present → stop, no rewrite (model recovered)", async () => {
|
|
219
256
|
// GIVEN a refusal that still delivered some visible text before refusing —
|
|
220
257
|
// the user has something to see.
|
|
221
258
|
const ctx = makeCtx({
|
|
222
|
-
|
|
259
|
+
content: [{ type: "text", text: "partial answer" }],
|
|
223
260
|
stopReason: "refusal",
|
|
224
261
|
});
|
|
225
262
|
|
|
226
263
|
// WHEN the hook runs.
|
|
227
|
-
await
|
|
264
|
+
await postModelCall(ctx);
|
|
228
265
|
|
|
229
|
-
// THEN the decision stays at stop.
|
|
266
|
+
// THEN the decision stays at stop and the turn content is left untouched.
|
|
230
267
|
expect(ctx.decision).toBe("stop");
|
|
268
|
+
expect(ctx.content).toEqual([{ type: "text", text: "partial answer" }]);
|
|
231
269
|
});
|
|
232
270
|
|
|
233
|
-
test("refusal
|
|
271
|
+
test("refusal after a visible reply this run → stop, no rewrite", async () => {
|
|
272
|
+
// GIVEN an earlier turn this run already delivered a real answer (text
|
|
273
|
+
// alongside a tool call), then the trailing turn refuses after the tool
|
|
274
|
+
// result. Rewriting here would stack an apology beneath the real answer.
|
|
275
|
+
const ctx = makeCtx({
|
|
276
|
+
messages: [userPrompt("do X"), priorVisibleTextTurn, priorToolUseTurn],
|
|
277
|
+
content: [],
|
|
278
|
+
stopReason: "refusal",
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// WHEN the hook runs.
|
|
282
|
+
await postModelCall(ctx);
|
|
283
|
+
|
|
284
|
+
// THEN the refusal is left as-is — no fallback is stacked beneath the
|
|
285
|
+
// earlier reply.
|
|
286
|
+
expect(ctx.decision).toBe("stop");
|
|
287
|
+
expect(ctx.content).toEqual([]);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("refusal beats the post-tool-empty nudge", async () => {
|
|
234
291
|
// GIVEN conditions that would trip both the refusal branch and the
|
|
235
|
-
// post-tool-empty branch.
|
|
292
|
+
// post-tool-empty branch (a prior tool-use turn with no visible text).
|
|
236
293
|
const ctx = makeCtx({
|
|
237
294
|
messages: [priorToolUseTurn],
|
|
238
|
-
|
|
295
|
+
content: [],
|
|
239
296
|
stopReason: "refusal",
|
|
240
297
|
});
|
|
241
298
|
|
|
242
299
|
// WHEN the hook runs.
|
|
243
|
-
await
|
|
300
|
+
await postModelCall(ctx);
|
|
244
301
|
|
|
245
|
-
// THEN refusal wins
|
|
246
|
-
|
|
247
|
-
expect(ctx.
|
|
248
|
-
|
|
249
|
-
|
|
302
|
+
// THEN refusal wins: the turn content is rewritten to the fallback and the
|
|
303
|
+
// model is not nudged.
|
|
304
|
+
expect(ctx.decision).toBe("stop");
|
|
305
|
+
expect(ctx.content).toEqual([
|
|
306
|
+
{ type: "text", text: REFUSAL_FALLBACK_TEXT },
|
|
307
|
+
]);
|
|
308
|
+
expect(ctx.messages).toEqual([priorToolUseTurn]);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("provider rejection is ignored — no nudge, no rewrite", async () => {
|
|
312
|
+
// GIVEN a rejection outcome (the call threw before any reply existed). The
|
|
313
|
+
// shape otherwise matches the post-tool-empty nudge case, but no response
|
|
314
|
+
// was produced, so this plugin defers to the recovery hooks.
|
|
315
|
+
const ctx = makeCtx({
|
|
316
|
+
messages: [priorToolUseTurn],
|
|
317
|
+
content: [],
|
|
318
|
+
error: new Error("provider rejected the request"),
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// WHEN the hook runs.
|
|
322
|
+
await postModelCall(ctx);
|
|
323
|
+
|
|
324
|
+
// THEN it leaves the decision and history untouched — empty-response only
|
|
325
|
+
// acts on a finalized (successful) reply.
|
|
326
|
+
expect(ctx.decision).toBe("stop");
|
|
327
|
+
expect(ctx.messages).toEqual([priorToolUseTurn]);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// ─── One-shot retry bound ────────────────────────────────────────────────────
|
|
332
|
+
|
|
333
|
+
describe("empty-response post-model-call hook — one-shot bound", () => {
|
|
334
|
+
test("a second empty turn this run is not nudged again", async () => {
|
|
335
|
+
// GIVEN the hook already nudged once this run for the conversation.
|
|
336
|
+
const first = makeCtx({
|
|
337
|
+
conversationId: "conv-bound",
|
|
338
|
+
messages: [priorToolUseTurn],
|
|
339
|
+
content: [],
|
|
340
|
+
});
|
|
341
|
+
await postModelCall(first);
|
|
342
|
+
expect(first.decision).toBe("continue");
|
|
343
|
+
|
|
344
|
+
// WHEN a second empty-after-tools turn arrives for the same conversation.
|
|
345
|
+
const second = makeCtx({
|
|
346
|
+
conversationId: "conv-bound",
|
|
347
|
+
messages: [priorToolUseTurn],
|
|
348
|
+
content: [],
|
|
349
|
+
});
|
|
350
|
+
await postModelCall(second);
|
|
351
|
+
|
|
352
|
+
// THEN the hook lets the turn end rather than nudging again.
|
|
353
|
+
expect(second.decision).toBe("stop");
|
|
354
|
+
expect(second.messages).toEqual([priorToolUseTurn]);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("the hook never clears its own bound — clearing is the stop hook's job", async () => {
|
|
358
|
+
// GIVEN the hook nudged once this run for the conversation.
|
|
359
|
+
const nudged = makeCtx({
|
|
360
|
+
conversationId: "conv-no-self-clear",
|
|
361
|
+
messages: [priorToolUseTurn],
|
|
362
|
+
content: [],
|
|
250
363
|
});
|
|
364
|
+
await postModelCall(nudged);
|
|
365
|
+
|
|
366
|
+
// WHEN the re-queried call comes back as a provider rejection, then as a
|
|
367
|
+
// recovered visible-text reply — outcomes that end the turn.
|
|
368
|
+
await postModelCall(
|
|
369
|
+
makeCtx({
|
|
370
|
+
conversationId: "conv-no-self-clear",
|
|
371
|
+
messages: [priorToolUseTurn],
|
|
372
|
+
content: [],
|
|
373
|
+
error: new Error("provider rejected the request"),
|
|
374
|
+
}),
|
|
375
|
+
);
|
|
376
|
+
await postModelCall(
|
|
377
|
+
makeCtx({
|
|
378
|
+
conversationId: "conv-no-self-clear",
|
|
379
|
+
messages: [priorToolUseTurn],
|
|
380
|
+
content: [{ type: "text", text: "here is the summary" }],
|
|
381
|
+
}),
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
// THEN the bound stays marked — `post-model-call` only ever marks; the
|
|
385
|
+
// sibling `stop` hook is what clears it at the turn boundary.
|
|
386
|
+
expect(isEmptyResponseNudged("conv-no-self-clear")).toBe(true);
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// ─── stop hook (terminal cleanup) ────────────────────────────────────────────
|
|
391
|
+
|
|
392
|
+
function makeStopCtx(overrides: Partial<StopContext> = {}): StopContext {
|
|
393
|
+
return {
|
|
394
|
+
conversationId: "conv-stop",
|
|
395
|
+
messages: [],
|
|
396
|
+
exitReason: "no_tool_calls",
|
|
397
|
+
logger: noopLogger,
|
|
398
|
+
...overrides,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
describe("empty-response stop hook — terminal cleanup", () => {
|
|
403
|
+
test("a terminal stop clears the nudge bound", async () => {
|
|
404
|
+
// GIVEN a conversation that nudged this run.
|
|
405
|
+
const conversationId = "conv-stop-clear";
|
|
406
|
+
markEmptyResponseNudged(conversationId);
|
|
407
|
+
|
|
408
|
+
// WHEN the terminal stop hook runs.
|
|
409
|
+
await stop(makeStopCtx({ conversationId }));
|
|
410
|
+
|
|
411
|
+
// THEN the bound is cleared so the next run nudges afresh.
|
|
412
|
+
expect(isEmptyResponseNudged(conversationId)).toBe(false);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test("clears the bound regardless of how the turn ended", async () => {
|
|
416
|
+
// GIVEN a conversation that nudged this run, ending on an abort rather than
|
|
417
|
+
// a finalized reply.
|
|
418
|
+
const conversationId = "conv-stop-abort";
|
|
419
|
+
markEmptyResponseNudged(conversationId);
|
|
420
|
+
|
|
421
|
+
// WHEN the terminal stop hook runs for that exit.
|
|
422
|
+
await stop(makeStopCtx({ conversationId, exitReason: "aborted_pre_call" }));
|
|
423
|
+
|
|
424
|
+
// THEN the bound is still cleared — `stop` is the definitive terminal, so
|
|
425
|
+
// the next run always nudges afresh.
|
|
426
|
+
expect(isEmptyResponseNudged(conversationId)).toBe(false);
|
|
251
427
|
});
|
|
252
428
|
});
|
|
253
429
|
|
|
254
430
|
// ─── Via runHook + registry ──────────────────────────────────────────────────
|
|
255
431
|
|
|
256
|
-
describe("empty-response
|
|
432
|
+
describe("empty-response post-model-call hook — via runHook", () => {
|
|
257
433
|
beforeEach(() => {
|
|
258
434
|
resetPluginRegistryForTests();
|
|
259
435
|
});
|
|
@@ -262,10 +438,10 @@ describe("empty-response stop hook — via runHook", () => {
|
|
|
262
438
|
// GIVEN the default empty-response plugin is registered.
|
|
263
439
|
registerPlugin(defaultEmptyResponsePlugin);
|
|
264
440
|
|
|
265
|
-
// WHEN the
|
|
266
|
-
const result = await runHook<
|
|
267
|
-
HOOKS.
|
|
268
|
-
makeCtx({ messages: [priorToolUseTurn],
|
|
441
|
+
// WHEN the post-model-call chain runs over an empty-after-tools context.
|
|
442
|
+
const result = await runHook<PostModelCallContext>(
|
|
443
|
+
HOOKS.POST_MODEL_CALL,
|
|
444
|
+
makeCtx({ messages: [priorToolUseTurn], content: [] }),
|
|
269
445
|
);
|
|
270
446
|
|
|
271
447
|
// THEN the chain settles on continue with the canonical nudge appended.
|
|
@@ -284,7 +460,7 @@ describe("empty-response stop hook — via runHook", () => {
|
|
|
284
460
|
registerPlugin({
|
|
285
461
|
manifest: { name: "force-stop", version: "0.0.1" },
|
|
286
462
|
hooks: {
|
|
287
|
-
|
|
463
|
+
"post-model-call": async (ctx: PostModelCallContext) => {
|
|
288
464
|
observedDecision = ctx.decision;
|
|
289
465
|
ctx.decision = "stop";
|
|
290
466
|
},
|
|
@@ -292,9 +468,9 @@ describe("empty-response stop hook — via runHook", () => {
|
|
|
292
468
|
});
|
|
293
469
|
|
|
294
470
|
// WHEN the chain runs over an empty-after-tools context.
|
|
295
|
-
const result = await runHook<
|
|
296
|
-
HOOKS.
|
|
297
|
-
makeCtx({ messages: [priorToolUseTurn],
|
|
471
|
+
const result = await runHook<PostModelCallContext>(
|
|
472
|
+
HOOKS.POST_MODEL_CALL,
|
|
473
|
+
makeCtx({ messages: [priorToolUseTurn], content: [] }),
|
|
298
474
|
);
|
|
299
475
|
|
|
300
476
|
// THEN the user hook saw the default's continue, and its override wins.
|
|
@@ -521,34 +521,3 @@ export default async function init(_ctx: unknown): Promise<void> {}
|
|
|
521
521
|
expect(registeredNames()).not.toContain("hanging-init");
|
|
522
522
|
});
|
|
523
523
|
});
|
|
524
|
-
|
|
525
|
-
describe("loadExternalPlugin — end-to-end @vellumai/simple-memory", () => {
|
|
526
|
-
test("loads the in-tree simple-memory plugin", async () => {
|
|
527
|
-
// Resolve the real on-disk plugin from the worktree. This double-acts
|
|
528
|
-
// as the loader's contract test against the canonical Phase 0 plugin
|
|
529
|
-
// and exercises the relative `../src/state.js` imports inside the
|
|
530
|
-
// plugin's surface files.
|
|
531
|
-
const pluginDir = join(
|
|
532
|
-
import.meta.dir,
|
|
533
|
-
"..",
|
|
534
|
-
"..",
|
|
535
|
-
"..",
|
|
536
|
-
"experimental",
|
|
537
|
-
"plugins",
|
|
538
|
-
"simple-memory",
|
|
539
|
-
);
|
|
540
|
-
|
|
541
|
-
await loadExternalPlugin(pluginDir);
|
|
542
|
-
|
|
543
|
-
const registered = getRegisteredPlugins().find(
|
|
544
|
-
(p) => p.manifest.name === "simple-memory",
|
|
545
|
-
);
|
|
546
|
-
expect(registered).toBeDefined();
|
|
547
|
-
expect(typeof registered?.hooks?.init).toBe("function");
|
|
548
|
-
expect(typeof registered?.hooks?.shutdown).toBe("function");
|
|
549
|
-
const toolNames = (registered?.tools ?? [])
|
|
550
|
-
.map((t) => (t as { name: string }).name)
|
|
551
|
-
.sort();
|
|
552
|
-
expect(toolNames).toEqual(["recall", "remember"]);
|
|
553
|
-
});
|
|
554
|
-
});
|
|
@@ -11,8 +11,15 @@ const mockResolveSkillStates = mock(
|
|
|
11
11
|
displayName: string;
|
|
12
12
|
description: string;
|
|
13
13
|
emoji?: string;
|
|
14
|
-
source:
|
|
14
|
+
source:
|
|
15
|
+
| "bundled"
|
|
16
|
+
| "managed"
|
|
17
|
+
| "workspace"
|
|
18
|
+
| "extra"
|
|
19
|
+
| "catalog"
|
|
20
|
+
| "plugin";
|
|
15
21
|
directoryPath: string;
|
|
22
|
+
owner?: { kind: "skill" | "mcp" | "plugin" | "workspace"; id: string };
|
|
16
23
|
};
|
|
17
24
|
state: "enabled" | "disabled";
|
|
18
25
|
}> => [],
|
|
@@ -313,4 +320,39 @@ describe("getSkill — skillssh audit enrichment", () => {
|
|
|
313
320
|
// fetchSkillAudits was still called (just failed)
|
|
314
321
|
expect(mockFetchSkillAudits).toHaveBeenCalledTimes(1);
|
|
315
322
|
});
|
|
323
|
+
|
|
324
|
+
test("attributes a plugin-resident skill to its owning plugin on the detail response", async () => {
|
|
325
|
+
// GIVEN a skill shipped inside an installed plugin, resolved with a
|
|
326
|
+
// `plugin` source and a plugin owner descriptor
|
|
327
|
+
mockResolveSkillStates.mockReturnValue([
|
|
328
|
+
{
|
|
329
|
+
summary: {
|
|
330
|
+
id: "caveman",
|
|
331
|
+
displayName: "Caveman",
|
|
332
|
+
description: "Ultra-compressed communication mode",
|
|
333
|
+
source: "plugin" as const,
|
|
334
|
+
directoryPath: "/tmp/test-skills/plugins/caveman/skills/caveman",
|
|
335
|
+
owner: { kind: "plugin", id: "caveman" },
|
|
336
|
+
},
|
|
337
|
+
state: "enabled" as const,
|
|
338
|
+
},
|
|
339
|
+
]);
|
|
340
|
+
|
|
341
|
+
// WHEN we fetch its detail
|
|
342
|
+
const result = await getSkill("caveman");
|
|
343
|
+
|
|
344
|
+
// THEN it resolves to the vellum origin (plugin skills are mapped to
|
|
345
|
+
// bundled/vellum for older clients) while preserving plugin attribution
|
|
346
|
+
// on the owner descriptor
|
|
347
|
+
expect("skill" in result).toBe(true);
|
|
348
|
+
if (!("skill" in result)) throw new Error("Expected skill response");
|
|
349
|
+
|
|
350
|
+
const detail = result.skill;
|
|
351
|
+
expect(detail.origin).toBe("vellum");
|
|
352
|
+
expect(detail.id).toBe("caveman");
|
|
353
|
+
// AND the owner identifies the plugin so detail consumers can attribute it
|
|
354
|
+
if (detail.origin === "vellum") {
|
|
355
|
+
expect(detail.owner).toEqual({ kind: "plugin", id: "caveman" });
|
|
356
|
+
}
|
|
357
|
+
});
|
|
316
358
|
});
|
|
@@ -1332,6 +1332,97 @@ describe("routing invariant: callback buttons route through canonical primitive"
|
|
|
1332
1332
|
});
|
|
1333
1333
|
});
|
|
1334
1334
|
|
|
1335
|
+
// ===========================================================================
|
|
1336
|
+
// SECTION 8b: route-owned (directResolve) confirmations resolve via the
|
|
1337
|
+
// channel/guardian-action path too.
|
|
1338
|
+
//
|
|
1339
|
+
// The ACP spawn/steer approval gate (acp-routes.ts, ATL-822) registers
|
|
1340
|
+
// pending confirmations with a `directResolve` callback rather than a
|
|
1341
|
+
// Conversation.prompter. The tool_approval resolver must fire that callback
|
|
1342
|
+
// directly; otherwise a guardian approval submitted over a channel never
|
|
1343
|
+
// reaches the waiting route and the spawn/resume hangs until timeout.
|
|
1344
|
+
// ===========================================================================
|
|
1345
|
+
|
|
1346
|
+
describe("routing invariant: directResolve interactions resolve via guardian decisions", () => {
|
|
1347
|
+
beforeEach(() => resetTables());
|
|
1348
|
+
|
|
1349
|
+
function registerDirectResolveInteraction(
|
|
1350
|
+
requestId: string,
|
|
1351
|
+
conversationId: string,
|
|
1352
|
+
): string[] {
|
|
1353
|
+
const decisions: string[] = [];
|
|
1354
|
+
pendingInteractions.register(requestId, {
|
|
1355
|
+
conversationId,
|
|
1356
|
+
kind: "confirmation",
|
|
1357
|
+
confirmationDetails: {
|
|
1358
|
+
toolName: "acp_spawn",
|
|
1359
|
+
input: { agent: "claude", task: "t", cwd: "/work" },
|
|
1360
|
+
riskLevel: "high",
|
|
1361
|
+
executionTarget: "host",
|
|
1362
|
+
allowlistOptions: [],
|
|
1363
|
+
scopeOptions: [],
|
|
1364
|
+
persistentDecisionsAllowed: false,
|
|
1365
|
+
},
|
|
1366
|
+
directResolve: (d) => decisions.push(d),
|
|
1367
|
+
});
|
|
1368
|
+
return decisions;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
test("channel approval fires directResolve('allow') with no live Conversation", async () => {
|
|
1372
|
+
const req = createCanonicalGuardianRequest({
|
|
1373
|
+
kind: "tool_approval",
|
|
1374
|
+
sourceType: "channel",
|
|
1375
|
+
conversationId: "conv-acp",
|
|
1376
|
+
guardianExternalUserId: "guardian-1",
|
|
1377
|
+
guardianPrincipalId: TEST_PRINCIPAL_ID,
|
|
1378
|
+
toolName: "acp_spawn",
|
|
1379
|
+
expiresAt: Date.now() + 60_000,
|
|
1380
|
+
});
|
|
1381
|
+
// Deliberately no _conversationMocks entry: the prompter path would have
|
|
1382
|
+
// failed with conversation_not_found, proving directResolve is what runs.
|
|
1383
|
+
const decisions = registerDirectResolveInteraction(req.id, "conv-acp");
|
|
1384
|
+
|
|
1385
|
+
const result = await routeGuardianReply(
|
|
1386
|
+
replyCtx({
|
|
1387
|
+
messageText: "",
|
|
1388
|
+
callbackData: `apr:${req.id}:approve_once`,
|
|
1389
|
+
conversationId: "conv-acp",
|
|
1390
|
+
}),
|
|
1391
|
+
);
|
|
1392
|
+
|
|
1393
|
+
expect(result.decisionApplied).toBe(true);
|
|
1394
|
+
expect(decisions).toEqual(["allow"]);
|
|
1395
|
+
expect(pendingInteractions.get(req.id)).toBeUndefined();
|
|
1396
|
+
expect(getCanonicalGuardianRequest(req.id)!.status).toBe("approved");
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
test("channel rejection fires directResolve('deny')", async () => {
|
|
1400
|
+
const req = createCanonicalGuardianRequest({
|
|
1401
|
+
kind: "tool_approval",
|
|
1402
|
+
sourceType: "channel",
|
|
1403
|
+
conversationId: "conv-acp",
|
|
1404
|
+
guardianExternalUserId: "guardian-1",
|
|
1405
|
+
guardianPrincipalId: TEST_PRINCIPAL_ID,
|
|
1406
|
+
toolName: "acp_spawn",
|
|
1407
|
+
expiresAt: Date.now() + 60_000,
|
|
1408
|
+
});
|
|
1409
|
+
const decisions = registerDirectResolveInteraction(req.id, "conv-acp");
|
|
1410
|
+
|
|
1411
|
+
const result = await routeGuardianReply(
|
|
1412
|
+
replyCtx({
|
|
1413
|
+
messageText: "",
|
|
1414
|
+
callbackData: `apr:${req.id}:reject`,
|
|
1415
|
+
conversationId: "conv-acp",
|
|
1416
|
+
}),
|
|
1417
|
+
);
|
|
1418
|
+
|
|
1419
|
+
expect(result.decisionApplied).toBe(true);
|
|
1420
|
+
expect(decisions).toEqual(["deny"]);
|
|
1421
|
+
expect(pendingInteractions.get(req.id)).toBeUndefined();
|
|
1422
|
+
expect(getCanonicalGuardianRequest(req.id)!.status).toBe("denied");
|
|
1423
|
+
});
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1335
1426
|
// ===========================================================================
|
|
1336
1427
|
// SECTION 9: Destination hints do not bypass principal binding for tool_approval
|
|
1337
1428
|
// ===========================================================================
|