@vellumai/assistant 0.6.5 → 0.6.6
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/AGENTS.md +9 -1
- package/ARCHITECTURE.md +15 -17
- package/Dockerfile +6 -4
- package/__tests__/permissions/gateway-threshold-reader.test.ts +283 -0
- package/docs/architecture/integrations.md +32 -39
- package/docs/architecture/memory.md +25 -30
- package/docs/architecture/security.md +7 -6
- package/docs/browser-use-architecture-phase2.md +63 -20
- package/docs/plugins.md +761 -0
- package/examples/plugins/echo/README.md +132 -0
- package/examples/plugins/echo/package.json +17 -0
- package/examples/plugins/echo/register.ts +187 -0
- package/node_modules/@vellumai/egress-proxy/src/types.ts +19 -0
- package/openapi.yaml +212 -68
- package/package.json +1 -1
- package/src/__tests__/app-compiler.test.ts +57 -0
- package/src/__tests__/approval-cascade.test.ts +7 -2
- package/src/__tests__/auto-analysis-end-to-end.test.ts +1 -0
- package/src/__tests__/avatar-generator.test.ts +4 -2
- package/src/__tests__/bundled-asset.test.ts +6 -6
- package/src/__tests__/catalog-cache.test.ts +69 -0
- package/src/__tests__/checker.test.ts +459 -171
- package/src/__tests__/circuit-breaker-pipeline.test.ts +406 -0
- package/src/__tests__/compaction-events.test.ts +501 -0
- package/src/__tests__/compaction-pipeline.test.ts +210 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +181 -0
- package/src/__tests__/compaction-timeout-recovery.test.ts +262 -0
- package/src/__tests__/config-model-image-provider.test.ts +110 -0
- package/src/__tests__/config-schema.test.ts +22 -9
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -4
- package/src/__tests__/contacts-tools.test.ts +26 -0
- package/src/__tests__/context-overflow-policy.test.ts +7 -7
- package/src/__tests__/context-window-manager.test.ts +355 -4
- package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +26 -30
- package/src/__tests__/conversation-agent-loop.test.ts +30 -141
- package/src/__tests__/conversation-confirmation-signals.test.ts +6 -1
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +2 -16
- package/src/__tests__/conversation-pairing.test.ts +174 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +4 -1
- package/src/__tests__/conversation-process-callsite.test.ts +3 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +16 -7
- package/src/__tests__/conversation-queue.test.ts +29 -14
- package/src/__tests__/conversation-routes-disk-view.test.ts +7 -6
- package/src/__tests__/conversation-runtime-assembly.test.ts +155 -110
- package/src/__tests__/conversation-runtime-workspace.test.ts +23 -38
- package/src/__tests__/conversation-seed-composer.test.ts +2 -2
- package/src/__tests__/conversation-slash-queue.test.ts +7 -2
- package/src/__tests__/conversation-slash-unknown.test.ts +25 -2
- package/src/__tests__/conversation-speed-override.test.ts +6 -1
- package/src/__tests__/conversation-title-service.test.ts +116 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +41 -2
- package/src/__tests__/conversation-usage.test.ts +1 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +4 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +3 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -1
- package/src/__tests__/credential-health-service.test.ts +78 -9
- package/src/__tests__/credential-security-invariants.test.ts +2 -2
- package/src/__tests__/db-schedule-syntax-migration.test.ts +1 -0
- package/src/__tests__/empty-response-pipeline.test.ts +305 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +3 -3
- package/src/__tests__/first-greeting.test.ts +247 -5
- package/src/__tests__/headless-browser-mode.test.ts +57 -0
- package/src/__tests__/history-repair-pipeline.test.ts +399 -0
- package/src/__tests__/host-browser-e2e-cloud.test.ts +307 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +3 -3
- package/src/__tests__/host-proxy-interface.test.ts +36 -2
- package/src/__tests__/image-credentials.test.ts +137 -0
- package/src/__tests__/image-service-dispatcher.test.ts +186 -0
- package/src/__tests__/injector-chain.test.ts +526 -0
- package/src/__tests__/intent-routing.test.ts +0 -26
- package/src/__tests__/llm-call-pipeline.test.ts +285 -0
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/media-generate-image.test.ts +119 -13
- package/src/__tests__/memory-retrieval-pipeline.test.ts +401 -0
- package/src/__tests__/memory-upsert-concurrency.test.ts +1 -0
- package/src/__tests__/migration-import-from-url.test.ts +5 -68
- package/src/__tests__/model-intents.test.ts +4 -2
- package/src/__tests__/notification-broadcaster.test.ts +3 -3
- package/src/__tests__/notification-decision-strategy.test.ts +0 -11
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +108 -0
- package/src/__tests__/oauth-apps-routes.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +14 -12
- package/src/__tests__/oauth-connect-orchestrator.test.ts +4 -13
- package/src/__tests__/oauth-provider-serializer.test.ts +6 -4
- package/src/__tests__/oauth-provider-visibility.test.ts +3 -5
- package/src/__tests__/oauth-providers-routes.test.ts +3 -2
- package/src/__tests__/oauth-store.test.ts +41 -76
- package/src/__tests__/onboarding-template-contract.test.ts +16 -64
- package/src/__tests__/openai-image-service.test.ts +368 -0
- package/src/__tests__/overflow-reduce-pipeline.test.ts +676 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +0 -24
- package/src/__tests__/persist-onboarding-artifacts.test.ts +266 -0
- package/src/__tests__/persistence-pipeline.test.ts +377 -0
- package/src/__tests__/pipeline-runner.test.ts +565 -0
- package/src/__tests__/platform.test.ts +5 -2
- package/src/__tests__/plugin-bootstrap.test.ts +483 -0
- package/src/__tests__/plugin-registry.test.ts +273 -0
- package/src/__tests__/plugin-route-contribution.test.ts +288 -0
- package/src/__tests__/plugin-skill-contribution.test.ts +367 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +286 -0
- package/src/__tests__/plugin-types.test.ts +320 -0
- package/src/__tests__/pricing.test.ts +44 -12
- package/src/__tests__/proxy-approval-callback.test.ts +69 -8
- package/src/__tests__/reaction-persistence.test.ts +1 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
- package/src/__tests__/registry.test.ts +0 -2
- package/src/__tests__/schedule-routes.test.ts +131 -1
- package/src/__tests__/scheduler-recurrence.test.ts +14 -70
- package/src/__tests__/scheduler-reuse-conversation.test.ts +10 -50
- package/src/__tests__/secret-detection-handler.test.ts +0 -10
- package/src/__tests__/shell-identity.test.ts +0 -134
- package/src/__tests__/suggestion-routes.test.ts +103 -4
- package/src/__tests__/task-memory-cleanup.test.ts +1 -0
- package/src/__tests__/task-scheduler.test.ts +3 -15
- package/src/__tests__/test-preload.ts +11 -0
- package/src/__tests__/title-generate-pipeline.test.ts +224 -0
- package/src/__tests__/token-estimate-pipeline.test.ts +431 -0
- package/src/__tests__/tool-error-pipeline.test.ts +244 -0
- package/src/__tests__/tool-execute-pipeline.test.ts +431 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -6
- package/src/__tests__/tool-executor-shell-integration.test.ts +7 -10
- package/src/__tests__/tool-executor.test.ts +141 -0
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +356 -0
- package/src/__tests__/tool-result-truncation.test.ts +0 -110
- package/src/__tests__/user-plugin-loader.test.ts +191 -0
- package/src/__tests__/workspace-migration-046-seed-conversation-starters-callsite.test.ts +185 -0
- package/src/__tests__/workspace-migration-049-release-notes-default-sonnet.test.ts +100 -0
- package/src/__tests__/workspace-migration-050-seed-main-agent-opus-callsite.test.ts +171 -0
- package/src/__tests__/workspace-migration-051-seed-conversation-summarization-callsite.test.ts +252 -0
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +99 -0
- package/src/__tests__/workspace-policy.test.ts +21 -3
- package/src/agent/loop.ts +340 -102
- package/src/approvals/__tests__/guardian-feed-event.test.ts +304 -0
- package/src/approvals/guardian-request-resolvers.ts +80 -0
- package/src/backup/__tests__/backup-worker.test.ts +2 -13
- package/src/backup/backup-worker.ts +3 -15
- package/src/bundler/app-compiler.ts +84 -1
- package/src/calls/call-state.ts +2 -2
- package/src/channels/__tests__/types.test.ts +3 -3
- package/src/channels/types.ts +6 -4
- package/src/cli/__tests__/notifications.test.ts +87 -211
- package/src/cli/commands/__tests__/backup.test.ts +1 -1
- package/src/cli/commands/__tests__/image-generation.test.ts +255 -35
- package/src/cli/commands/__tests__/inference-send.test.ts +12 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +12 -0
- package/src/cli/commands/backup.ts +2 -2
- package/src/cli/commands/clients.ts +138 -0
- package/src/cli/commands/completions.ts +2 -9
- package/src/cli/commands/conversations.ts +55 -7
- package/src/cli/commands/image-generation.ts +33 -34
- package/src/cli/commands/notifications.ts +68 -103
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -1
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
- package/src/cli/commands/oauth/connect.ts +2 -2
- package/src/cli/commands/oauth/providers.ts +176 -8
- package/src/cli/commands/oauth/status.ts +46 -36
- package/src/cli/commands/skills.ts +3 -4
- package/src/cli/program.ts +25 -29
- package/src/config/__tests__/backup-schema.test.ts +7 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +10 -10
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +66 -87
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +28 -51
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +22 -40
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -1
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +23 -39
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +207 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +12 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +58 -0
- package/src/config/bundled-skills/schedule/SKILL.md +8 -3
- package/src/config/bundled-skills/schedule/TOOLS.json +15 -7
- package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +59 -0
- package/src/config/bundled-tool-registry.ts +0 -15
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/schema.ts +19 -0
- package/src/config/schemas/backup.ts +1 -1
- package/src/config/schemas/conversations.ts +16 -0
- package/src/config/schemas/llm.ts +2 -3
- package/src/config/schemas/security.ts +6 -6
- package/src/config/schemas/tts.ts +11 -0
- package/src/config/skill-state.ts +6 -2
- package/src/config/skills.ts +94 -5
- package/src/context/__tests__/compact-prompt.test.ts +27 -9
- package/src/context/prompts/compact.md +26 -12
- package/src/context/tool-result-truncation.ts +3 -63
- package/src/context/window-manager.ts +190 -16
- package/src/credential-health/credential-health-service.ts +19 -6
- package/src/daemon/__tests__/conversation-feed-event.test.ts +317 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +4 -12
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +14 -15
- package/src/daemon/config-watcher.ts +0 -2
- package/src/daemon/context-overflow-policy.ts +4 -13
- package/src/daemon/conversation-agent-loop-handlers.ts +83 -22
- package/src/daemon/conversation-agent-loop.ts +984 -683
- package/src/daemon/conversation-history.ts +10 -19
- package/src/daemon/conversation-lifecycle.ts +37 -19
- package/src/daemon/conversation-notifiers.ts +2 -110
- package/src/daemon/conversation-process.ts +14 -7
- package/src/daemon/conversation-runtime-assembly.ts +532 -411
- package/src/daemon/conversation-tool-setup.ts +41 -4
- package/src/daemon/conversation.ts +80 -35
- package/src/daemon/external-plugins-bootstrap.ts +478 -0
- package/src/daemon/first-greeting.ts +191 -14
- package/src/daemon/handlers/config-model.ts +11 -0
- package/src/daemon/handlers/skills.ts +5 -1
- package/src/daemon/lifecycle.ts +33 -68
- package/src/daemon/message-types/computer-use.ts +2 -34
- package/src/daemon/message-types/conversations.ts +49 -0
- package/src/daemon/message-types/messages.ts +12 -0
- package/src/daemon/server.ts +5 -3
- package/src/daemon/shutdown-handlers.ts +2 -12
- package/src/daemon/tool-side-effects.ts +14 -56
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +160 -0
- package/src/heartbeat/heartbeat-service.ts +24 -1
- package/src/home/__tests__/feed-population-integration.test.ts +312 -0
- package/src/home/emit-feed-event.ts +7 -0
- package/src/home/feed-types.ts +41 -2
- package/src/home/rewrite-command-preview.ts +66 -0
- package/src/ipc/__tests__/socket-path.test.ts +11 -50
- package/src/ipc/cli-client.ts +1 -1
- package/src/ipc/cli-server.ts +3 -3
- package/src/ipc/gateway-client.ts +4 -1
- package/src/ipc/routes/browser-context.ts +2 -0
- package/src/ipc/routes/browser.ts +1 -0
- package/src/ipc/routes/get-contact.ts +16 -0
- package/src/ipc/routes/index.ts +14 -0
- package/src/ipc/routes/list-clients.ts +31 -0
- package/src/ipc/routes/merge-contacts.ts +17 -0
- package/src/ipc/routes/notification.ts +133 -0
- package/src/ipc/routes/rename-conversation.ts +59 -0
- package/src/ipc/routes/search-contacts.ts +19 -0
- package/src/ipc/routes/upsert-contact.ts +25 -0
- package/src/ipc/socket-path.ts +14 -38
- package/src/media/app-icon-generator.ts +23 -46
- package/src/media/avatar-router.ts +26 -41
- package/src/media/gemini-image-service.ts +8 -41
- package/src/media/image-credentials.ts +73 -0
- package/src/media/image-service.ts +85 -0
- package/src/media/openai-image-service.ts +131 -0
- package/src/media/types.ts +46 -0
- package/src/memory/conversation-crud.ts +48 -18
- package/src/memory/conversation-queries.ts +57 -4
- package/src/memory/conversation-title-service.ts +25 -0
- package/src/memory/db-init.ts +8 -0
- package/src/memory/embedding-gemini.test.ts +41 -2
- package/src/memory/embedding-gemini.ts +6 -1
- package/src/memory/graph/bootstrap.test.ts +282 -0
- package/src/memory/graph/bootstrap.ts +8 -5
- package/src/memory/graph/extraction.ts +10 -2
- package/src/memory/graph/graph-search.test.ts +1 -0
- package/src/memory/graph/inspect.ts +2 -2
- package/src/memory/graph/retriever.ts +10 -3
- package/src/memory/migrations/041-approval-prompt-ts-tracker.ts +26 -0
- package/src/memory/migrations/149-oauth-tables.ts +1 -0
- package/src/memory/migrations/223-schedule-script-column.ts +11 -0
- package/src/memory/migrations/224-oauth-providers-managed-service-is-paid.ts +24 -0
- package/src/memory/migrations/225-oauth-providers-available-scopes.ts +13 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/pkb/pkb-index.test.ts +1 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +1 -0
- package/src/memory/pkb/pkb-search.test.ts +65 -4
- package/src/memory/pkb/pkb-search.ts +40 -18
- package/src/memory/qdrant-client.test.ts +60 -0
- package/src/memory/qdrant-client.ts +25 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/schema/oauth.ts +4 -1
- package/src/messaging/providers/slack/render-transcript.test.ts +77 -29
- package/src/messaging/providers/slack/render-transcript.ts +58 -0
- package/src/notifications/conversation-pairing.ts +78 -19
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +1 -2
- package/src/oauth/AGENTS.md +1 -1
- package/src/oauth/__tests__/identity-verifier.test.ts +2 -1
- package/src/oauth/connect-orchestrator.ts +8 -34
- package/src/oauth/connect-types.ts +6 -10
- package/src/oauth/manual-token-connection.ts +23 -0
- package/src/oauth/oauth-store.ts +30 -14
- package/src/oauth/provider-serializer.ts +6 -1
- package/src/oauth/seed-providers.ts +56 -108
- package/src/outbound-proxy/http-forwarder.ts +9 -0
- package/src/permissions/approval-policy.test.ts +293 -18
- package/src/permissions/approval-policy.ts +110 -58
- package/src/permissions/arg-parser.test.ts +161 -0
- package/src/permissions/arg-parser.ts +141 -0
- package/src/permissions/bash-risk-classifier.test.ts +414 -2
- package/src/permissions/bash-risk-classifier.ts +303 -60
- package/src/permissions/checker.ts +157 -29
- package/src/permissions/command-registry.test.ts +239 -0
- package/src/permissions/command-registry.ts +234 -54
- package/src/permissions/defaults.ts +5 -4
- package/src/permissions/gateway-threshold-reader.ts +196 -0
- package/src/permissions/prompter.ts +4 -0
- package/src/permissions/risk-types.ts +61 -4
- package/src/permissions/schedule-risk-classifier.test.ts +129 -0
- package/src/permissions/schedule-risk-classifier.ts +85 -0
- package/src/permissions/shell-identity.ts +2 -42
- package/src/permissions/types.ts +2 -0
- package/src/permissions/workspace-policy.ts +8 -3
- package/src/plugins/defaults/circuit-breaker.ts +146 -0
- package/src/plugins/defaults/compaction.ts +145 -0
- package/src/plugins/defaults/empty-response.ts +126 -0
- package/src/plugins/defaults/history-repair.ts +85 -0
- package/src/plugins/defaults/index.ts +116 -0
- package/src/plugins/defaults/injectors.ts +491 -0
- package/src/plugins/defaults/llm-call.ts +82 -0
- package/src/plugins/defaults/memory-retrieval.ts +226 -0
- package/src/plugins/defaults/overflow-reduce.ts +181 -0
- package/src/plugins/defaults/persistence.ts +129 -0
- package/src/plugins/defaults/title-generate.ts +95 -0
- package/src/plugins/defaults/token-estimate.ts +104 -0
- package/src/plugins/defaults/tool-error.ts +126 -0
- package/src/plugins/defaults/tool-execute.ts +89 -0
- package/src/plugins/defaults/tool-result-truncate.ts +88 -0
- package/src/plugins/pipeline.ts +316 -0
- package/src/plugins/plugin-skill-contributions.ts +292 -0
- package/src/plugins/registry.ts +241 -0
- package/src/plugins/types.ts +1134 -0
- package/src/plugins/user-loader.ts +177 -0
- package/src/prompts/templates/BOOTSTRAP.md +27 -77
- package/src/providers/model-catalog.ts +52 -29
- package/src/providers/model-intents.ts +1 -1
- package/src/providers/openrouter/client.ts +5 -1
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +61 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +57 -0
- package/src/providers/speech-to-text/xai-realtime.test.ts +72 -4
- package/src/providers/speech-to-text/xai-realtime.ts +39 -14
- package/src/runtime/AGENTS.md +25 -16
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +3 -3
- package/src/runtime/__tests__/client-registry.test.ts +293 -0
- package/src/runtime/client-registry.ts +261 -0
- package/src/runtime/http-server.ts +77 -8
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/migrations/vbundle-builder.ts +1 -22
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +51 -31
- package/src/runtime/routes/approval-routes.ts +17 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +27 -8
- package/src/runtime/routes/conversation-routes.ts +223 -116
- package/src/runtime/routes/inbound-message-handler.ts +88 -13
- package/src/runtime/routes/memory-item-routes.test.ts +1 -0
- package/src/runtime/routes/migration-routes.ts +0 -3
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +284 -0
- package/src/runtime/routes/playground/__tests__/guard.test.ts +80 -0
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +294 -0
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +271 -0
- package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +202 -0
- package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +309 -0
- package/src/runtime/routes/playground/__tests__/state.test.ts +224 -0
- package/src/runtime/routes/playground/conversation-not-found.ts +29 -0
- package/src/runtime/routes/playground/deps.ts +56 -0
- package/src/runtime/routes/playground/force-compact.ts +73 -0
- package/src/runtime/routes/playground/guard.ts +37 -0
- package/src/runtime/routes/playground/index.ts +28 -0
- package/src/runtime/routes/playground/inject-failures.ts +159 -0
- package/src/runtime/routes/playground/reset-circuit.ts +115 -0
- package/src/runtime/routes/playground/seed-conversation.ts +139 -0
- package/src/runtime/routes/playground/seeded-conversations.ts +78 -0
- package/src/runtime/routes/playground/state.ts +78 -0
- package/src/runtime/routes/schedule-routes.ts +89 -8
- package/src/runtime/skill-route-registry.ts +75 -15
- package/src/schedule/run-script.ts +68 -0
- package/src/schedule/schedule-store.ts +7 -1
- package/src/schedule/scheduler.ts +48 -8
- package/src/skills/catalog-cache.ts +12 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +189 -0
- package/src/tools/browser/browser-execution.ts +88 -19
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +230 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +146 -3
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +54 -3
- package/src/tools/browser/cdp-client/factory.ts +15 -4
- package/src/tools/executor.ts +126 -74
- package/src/tools/network/script-proxy/session-manager.ts +37 -1
- package/src/tools/permission-checker.ts +98 -49
- package/src/tools/policy-context.ts +4 -0
- package/src/tools/registry.ts +140 -3
- package/src/tools/schedule/create.ts +23 -8
- package/src/tools/schedule/update.ts +3 -1
- package/src/tools/secret-detection-handler.ts +0 -51
- package/src/tools/system/avatar-generator.ts +6 -2
- package/src/tools/types.ts +28 -2
- package/src/util/platform.ts +7 -2
- package/src/util/pricing.ts +26 -3
- package/src/workspace/migrations/006-services-config.ts +2 -4
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +2 -3
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +3 -4
- package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +108 -0
- package/src/workspace/migrations/047-remove-watch-callsites.ts +54 -0
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +81 -0
- package/src/workspace/migrations/049-release-notes-default-sonnet.ts +80 -0
- package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +86 -0
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +128 -0
- package/src/workspace/migrations/registry.ts +12 -0
- package/tsconfig.json +1 -1
- package/hook-templates/debug-prompt-logger/hook.json +0 -7
- package/hook-templates/debug-prompt-logger/run.sh +0 -66
- package/src/__tests__/compaction-circuit-breaker.test.ts +0 -336
- package/src/__tests__/context-overflow-approval.test.ts +0 -156
- package/src/__tests__/hooks-blocking.test.ts +0 -178
- package/src/__tests__/hooks-cli.test.ts +0 -182
- package/src/__tests__/hooks-config.test.ts +0 -108
- package/src/__tests__/hooks-discovery.test.ts +0 -211
- package/src/__tests__/hooks-integration.test.ts +0 -196
- package/src/__tests__/hooks-manager.test.ts +0 -226
- package/src/__tests__/hooks-runner.test.ts +0 -175
- package/src/__tests__/hooks-settings.test.ts +0 -160
- package/src/__tests__/hooks-templates.test.ts +0 -169
- package/src/__tests__/hooks-ts-runner.test.ts +0 -170
- package/src/__tests__/hooks-watch.test.ts +0 -112
- package/src/__tests__/notification-schedule-dedup.test.ts +0 -213
- package/src/__tests__/oauth-scope-policy.test.ts +0 -180
- package/src/__tests__/send-notification-tool.test.ts +0 -83
- package/src/cli/commands/shotgun.ts +0 -266
- package/src/config/bundled-skills/conversations/SKILL.md +0 -20
- package/src/config/bundled-skills/conversations/TOOLS.json +0 -23
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +0 -88
- package/src/config/bundled-skills/heartbeat/SKILL.md +0 -43
- package/src/config/bundled-skills/notifications/SKILL.md +0 -40
- package/src/config/bundled-skills/notifications/TOOLS.json +0 -80
- package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -152
- package/src/config/bundled-skills/notifications/tools/shared.ts +0 -13
- package/src/config/bundled-skills/screen-watch/SKILL.md +0 -27
- package/src/config/bundled-skills/screen-watch/TOOLS.json +0 -35
- package/src/config/bundled-skills/screen-watch/tools/start-screen-watch.ts +0 -12
- package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -84
- package/src/daemon/context-overflow-approval.ts +0 -52
- package/src/daemon/watch-handler.ts +0 -399
- package/src/hooks/cli.ts +0 -253
- package/src/hooks/config.ts +0 -100
- package/src/hooks/discovery.ts +0 -135
- package/src/hooks/manager.ts +0 -179
- package/src/hooks/runner.ts +0 -117
- package/src/hooks/templates.ts +0 -77
- package/src/hooks/types.ts +0 -75
- package/src/oauth/scope-policy.ts +0 -89
- package/src/runtime/gateway-internal-client.ts +0 -94
- package/src/runtime/routes/watch-routes.ts +0 -156
- package/src/signals/shotgun.ts +0 -203
- package/src/tools/watch/screen-watch.ts +0 -144
- package/src/tools/watch/watch-state.ts +0 -142
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
2
|
+
import { getIsContainerized } from "../config/env-registry.js";
|
|
2
3
|
import { getConfig } from "../config/loader.js";
|
|
3
|
-
import { getHookManager } from "../hooks/manager.js";
|
|
4
4
|
import { resolveThreshold } from "../permissions/approval-policy.js";
|
|
5
5
|
import {
|
|
6
6
|
check,
|
|
7
7
|
classifyRisk,
|
|
8
8
|
generateAllowlistOptions,
|
|
9
9
|
generateScopeOptions,
|
|
10
|
+
getCachedAssessment,
|
|
10
11
|
} from "../permissions/checker.js";
|
|
12
|
+
import { getAutoApproveThreshold } from "../permissions/gateway-threshold-reader.js";
|
|
11
13
|
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
12
14
|
import { addRule } from "../permissions/trust-store.js";
|
|
13
15
|
import { RiskLevel } from "../permissions/types.js";
|
|
@@ -35,8 +37,27 @@ export type PermissionDecision =
|
|
|
35
37
|
decision: string;
|
|
36
38
|
riskLevel: string;
|
|
37
39
|
wasPrompted?: boolean;
|
|
40
|
+
/** Risk metadata from the classifier assessment cache (when available). */
|
|
41
|
+
riskMeta?: {
|
|
42
|
+
riskLevel: string;
|
|
43
|
+
riskReason: string;
|
|
44
|
+
riskScopeOptions: Array<{ pattern: string; label: string }>;
|
|
45
|
+
isContainerized?: boolean;
|
|
46
|
+
};
|
|
38
47
|
}
|
|
39
|
-
| {
|
|
48
|
+
| {
|
|
49
|
+
allowed: false;
|
|
50
|
+
decision: string;
|
|
51
|
+
riskLevel: string;
|
|
52
|
+
content: string;
|
|
53
|
+
/** Risk metadata from the classifier assessment cache (when available). */
|
|
54
|
+
riskMeta?: {
|
|
55
|
+
riskLevel: string;
|
|
56
|
+
riskReason: string;
|
|
57
|
+
riskScopeOptions: Array<{ pattern: string; label: string }>;
|
|
58
|
+
isContainerized?: boolean;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
40
61
|
|
|
41
62
|
export class PermissionChecker {
|
|
42
63
|
private prompter: PermissionPrompter;
|
|
@@ -58,10 +79,6 @@ export class PermissionChecker {
|
|
|
58
79
|
context: ToolContext,
|
|
59
80
|
executionTarget: ExecutionTarget,
|
|
60
81
|
emitLifecycleEvent: (event: ToolLifecycleEvent) => void,
|
|
61
|
-
sanitizeToolInput: (
|
|
62
|
-
toolName: string,
|
|
63
|
-
input: Record<string, unknown>,
|
|
64
|
-
) => Record<string, unknown>,
|
|
65
82
|
startTime: number,
|
|
66
83
|
computePreviewDiff: (
|
|
67
84
|
toolName: string,
|
|
@@ -111,6 +128,19 @@ export class PermissionChecker {
|
|
|
111
128
|
);
|
|
112
129
|
const riskLevel: string = risk;
|
|
113
130
|
|
|
131
|
+
// Look up the cached assessment to extract risk metadata for the tool result.
|
|
132
|
+
// This is populated by classifyRisk() for classifier-backed tools (bash, file, web, skill).
|
|
133
|
+
// For tools without classifiers (e.g. MCP tools), the cache returns undefined.
|
|
134
|
+
const cachedAssessment = getCachedAssessment(name, input);
|
|
135
|
+
const riskMeta = cachedAssessment
|
|
136
|
+
? {
|
|
137
|
+
riskLevel: cachedAssessment.riskLevel,
|
|
138
|
+
riskReason: cachedAssessment.reason,
|
|
139
|
+
riskScopeOptions: cachedAssessment.scopeOptions,
|
|
140
|
+
isContainerized: getIsContainerized(),
|
|
141
|
+
}
|
|
142
|
+
: undefined;
|
|
143
|
+
|
|
114
144
|
// Wrap the rest of permission evaluation so that any exception
|
|
115
145
|
// carries the classified risk level back to the caller. Without
|
|
116
146
|
// this, the executor's catch block would fall back to the default
|
|
@@ -179,6 +209,7 @@ export class PermissionChecker {
|
|
|
179
209
|
decision: "denied",
|
|
180
210
|
riskLevel,
|
|
181
211
|
content: result.reason,
|
|
212
|
+
riskMeta,
|
|
182
213
|
};
|
|
183
214
|
}
|
|
184
215
|
|
|
@@ -199,7 +230,12 @@ export class PermissionChecker {
|
|
|
199
230
|
{ toolName: name, riskLevel },
|
|
200
231
|
"Auto-approving bash tool for platform-hosted guardian session",
|
|
201
232
|
);
|
|
202
|
-
return {
|
|
233
|
+
return {
|
|
234
|
+
allowed: true,
|
|
235
|
+
decision: "platform_auto_approve",
|
|
236
|
+
riskLevel,
|
|
237
|
+
riskMeta,
|
|
238
|
+
};
|
|
203
239
|
}
|
|
204
240
|
|
|
205
241
|
if (result.decision === "prompt") {
|
|
@@ -218,39 +254,50 @@ export class PermissionChecker {
|
|
|
218
254
|
const isDynamicSkillLoad =
|
|
219
255
|
result.matchedRule?.pattern.startsWith("skill_load_dynamic:") ===
|
|
220
256
|
true;
|
|
221
|
-
const bgThreshold = resolveThreshold(
|
|
222
|
-
cfg.permissions.autoApproveUpTo,
|
|
223
|
-
"background",
|
|
224
|
-
);
|
|
225
|
-
const thresholdOrdinal: Record<string, number> = {
|
|
226
|
-
none: -1,
|
|
227
|
-
low: 0,
|
|
228
|
-
medium: 1,
|
|
229
|
-
};
|
|
230
|
-
const riskOrdinal: Record<string, number> = {
|
|
231
|
-
[RiskLevel.Low]: 0,
|
|
232
|
-
[RiskLevel.Medium]: 1,
|
|
233
|
-
[RiskLevel.High]: 2,
|
|
234
|
-
};
|
|
235
|
-
const withinThreshold =
|
|
236
|
-
(riskOrdinal[riskLevel] ?? 2) <= (thresholdOrdinal[bgThreshold] ?? 0);
|
|
237
257
|
if (
|
|
238
258
|
context.isInteractive === false &&
|
|
239
259
|
context.trustClass === "guardian" &&
|
|
240
260
|
!context.requireFreshApproval &&
|
|
241
261
|
!isDynamicSkillLoad &&
|
|
242
|
-
!v2ForcePrompt
|
|
243
|
-
withinThreshold
|
|
262
|
+
!v2ForcePrompt
|
|
244
263
|
) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
264
|
+
// Use gateway threshold when v3 is enabled, falling back to config.
|
|
265
|
+
// getAutoApproveThreshold returns from cache (populated by check() above).
|
|
266
|
+
// Deferred inside the non-interactive branch so interactive prompts
|
|
267
|
+
// don't pay the gateway I/O cost.
|
|
268
|
+
const gatewayBgThreshold = await getAutoApproveThreshold(
|
|
269
|
+
context.conversationId,
|
|
270
|
+
"background",
|
|
248
271
|
);
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
272
|
+
const bgThreshold =
|
|
273
|
+
gatewayBgThreshold ??
|
|
274
|
+
resolveThreshold(cfg.permissions.autoApproveUpTo, "background");
|
|
275
|
+
const thresholdOrdinal: Record<string, number> = {
|
|
276
|
+
none: -1,
|
|
277
|
+
low: 0,
|
|
278
|
+
medium: 1,
|
|
279
|
+
high: 2,
|
|
280
|
+
};
|
|
281
|
+
const riskOrdinal: Record<string, number> = {
|
|
282
|
+
[RiskLevel.Low]: 0,
|
|
283
|
+
[RiskLevel.Medium]: 1,
|
|
284
|
+
[RiskLevel.High]: 2,
|
|
253
285
|
};
|
|
286
|
+
const withinThreshold =
|
|
287
|
+
(riskOrdinal[riskLevel] ?? 2) <=
|
|
288
|
+
(thresholdOrdinal[bgThreshold] ?? 0);
|
|
289
|
+
if (withinThreshold) {
|
|
290
|
+
log.info(
|
|
291
|
+
{ toolName: name, riskLevel },
|
|
292
|
+
"Auto-approving for non-interactive guardian session",
|
|
293
|
+
);
|
|
294
|
+
return {
|
|
295
|
+
allowed: true,
|
|
296
|
+
decision: "guardian_auto_approve",
|
|
297
|
+
riskLevel,
|
|
298
|
+
riskMeta,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
254
301
|
}
|
|
255
302
|
|
|
256
303
|
// Non-interactive sessions have no client to respond to prompts -
|
|
@@ -280,6 +327,7 @@ export class PermissionChecker {
|
|
|
280
327
|
decision: "denied",
|
|
281
328
|
riskLevel,
|
|
282
329
|
content: `Permission denied: tool "${name}" requires user approval but no interactive client is connected. The tool was not executed. To allow this tool in non-interactive sessions, add a trust rule via permission settings.`,
|
|
330
|
+
riskMeta,
|
|
283
331
|
};
|
|
284
332
|
}
|
|
285
333
|
|
|
@@ -304,7 +352,12 @@ export class PermissionChecker {
|
|
|
304
352
|
},
|
|
305
353
|
"Temporary approval override active - auto-approving without prompt",
|
|
306
354
|
);
|
|
307
|
-
return {
|
|
355
|
+
return {
|
|
356
|
+
allowed: true,
|
|
357
|
+
decision: "temporary_override",
|
|
358
|
+
riskLevel,
|
|
359
|
+
riskMeta,
|
|
360
|
+
};
|
|
308
361
|
}
|
|
309
362
|
|
|
310
363
|
const previewDiff = computePreviewDiff(name, input, context.workingDir);
|
|
@@ -353,13 +406,6 @@ export class PermissionChecker {
|
|
|
353
406
|
persistentDecisionsAllowed: promptOptions.persistentDecisionsAllowed,
|
|
354
407
|
});
|
|
355
408
|
|
|
356
|
-
await getHookManager().trigger("permission-request", {
|
|
357
|
-
toolName: name,
|
|
358
|
-
input: sanitizeToolInput(name, input),
|
|
359
|
-
riskLevel,
|
|
360
|
-
conversationId: context.conversationId,
|
|
361
|
-
});
|
|
362
|
-
|
|
363
409
|
const response = await this.prompter.prompt(
|
|
364
410
|
name,
|
|
365
411
|
input,
|
|
@@ -374,6 +420,8 @@ export class PermissionChecker {
|
|
|
374
420
|
promptOptions.temporaryOptionsAvailable,
|
|
375
421
|
context.toolUseId,
|
|
376
422
|
v2ForcePrompt,
|
|
423
|
+
riskReason,
|
|
424
|
+
getIsContainerized(),
|
|
377
425
|
);
|
|
378
426
|
|
|
379
427
|
const decision =
|
|
@@ -381,13 +429,6 @@ export class PermissionChecker {
|
|
|
381
429
|
? "deny"
|
|
382
430
|
: response.decision;
|
|
383
431
|
|
|
384
|
-
await getHookManager().trigger("permission-resolve", {
|
|
385
|
-
toolName: name,
|
|
386
|
-
decision,
|
|
387
|
-
riskLevel,
|
|
388
|
-
conversationId: context.conversationId,
|
|
389
|
-
});
|
|
390
|
-
|
|
391
432
|
if (decision === "deny") {
|
|
392
433
|
const contextualDenial =
|
|
393
434
|
typeof response.decisionContext === "string"
|
|
@@ -421,6 +462,7 @@ export class PermissionChecker {
|
|
|
421
462
|
decision,
|
|
422
463
|
riskLevel,
|
|
423
464
|
content: denialMessage,
|
|
465
|
+
riskMeta,
|
|
424
466
|
};
|
|
425
467
|
}
|
|
426
468
|
|
|
@@ -470,6 +512,7 @@ export class PermissionChecker {
|
|
|
470
512
|
decision,
|
|
471
513
|
riskLevel,
|
|
472
514
|
content: denialMessage,
|
|
515
|
+
riskMeta,
|
|
473
516
|
};
|
|
474
517
|
}
|
|
475
518
|
|
|
@@ -523,11 +566,17 @@ export class PermissionChecker {
|
|
|
523
566
|
);
|
|
524
567
|
}
|
|
525
568
|
|
|
526
|
-
return {
|
|
569
|
+
return {
|
|
570
|
+
allowed: true,
|
|
571
|
+
decision,
|
|
572
|
+
riskLevel,
|
|
573
|
+
wasPrompted: true,
|
|
574
|
+
riskMeta,
|
|
575
|
+
};
|
|
527
576
|
}
|
|
528
577
|
|
|
529
578
|
// result.decision === 'allow'
|
|
530
|
-
return { allowed: true, decision: "allow", riskLevel };
|
|
579
|
+
return { allowed: true, decision: "allow", riskLevel, riskMeta };
|
|
531
580
|
} catch (err) {
|
|
532
581
|
if (err instanceof Error) {
|
|
533
582
|
(err as Error & { riskLevel?: string }).riskLevel = riskLevel;
|
|
@@ -34,16 +34,20 @@ export function buildPolicyContext(
|
|
|
34
34
|
|
|
35
35
|
const executionContext = deriveExecutionContext(context);
|
|
36
36
|
|
|
37
|
+
const conversationId = context?.conversationId;
|
|
38
|
+
|
|
37
39
|
if (tool.origin === "skill") {
|
|
38
40
|
return {
|
|
39
41
|
executionTarget: tool.executionTarget,
|
|
40
42
|
ephemeralRules: ephemeralRules?.length ? ephemeralRules : undefined,
|
|
41
43
|
executionContext,
|
|
44
|
+
conversationId,
|
|
42
45
|
};
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
return {
|
|
46
49
|
ephemeralRules: ephemeralRules?.length ? ephemeralRules : undefined,
|
|
47
50
|
executionContext,
|
|
51
|
+
conversationId,
|
|
48
52
|
};
|
|
49
53
|
}
|
package/src/tools/registry.ts
CHANGED
|
@@ -68,6 +68,12 @@ let coreToolsSnapshot: Map<string, Tool> | null = null;
|
|
|
68
68
|
// Tools are only removed from the global registry when this drops to 0.
|
|
69
69
|
const skillRefCount = new Map<string, number>();
|
|
70
70
|
|
|
71
|
+
// Plugin-tool refcount lives in its own namespace so plugin and skill IDs
|
|
72
|
+
// cannot collide in the ref map even if a plugin's `manifest.name` happens to
|
|
73
|
+
// match a skill id. Conflict detection on `tools` (keyed by tool name) is
|
|
74
|
+
// separate and covers the case of two extensions choosing the same tool name.
|
|
75
|
+
const pluginRefCount = new Map<string, number>();
|
|
76
|
+
|
|
71
77
|
export function registerTool(tool: Tool): void {
|
|
72
78
|
const existing = tools.get(tool.name);
|
|
73
79
|
if (existing) {
|
|
@@ -108,10 +114,22 @@ export function registerSkillTools(newTools: Tool[]): Tool[] {
|
|
|
108
114
|
);
|
|
109
115
|
continue;
|
|
110
116
|
}
|
|
111
|
-
// Existing is
|
|
112
|
-
|
|
117
|
+
// Existing is from a different origin (plugin/mcp) or a different
|
|
118
|
+
// skill — skill tools can only replace themselves (hot-reload).
|
|
119
|
+
if (
|
|
120
|
+
existing.origin !== "skill" ||
|
|
121
|
+
existing.ownerSkillId !== tool.ownerSkillId
|
|
122
|
+
) {
|
|
123
|
+
const owner =
|
|
124
|
+
existing.origin === "skill"
|
|
125
|
+
? `skill "${existing.ownerSkillId}"`
|
|
126
|
+
: existing.origin === "plugin"
|
|
127
|
+
? `plugin "${existing.ownerPluginId}"`
|
|
128
|
+
: existing.origin === "mcp"
|
|
129
|
+
? `MCP server "${existing.ownerMcpServerId}"`
|
|
130
|
+
: `${existing.origin ?? "unknown"}-origin tool`;
|
|
113
131
|
throw new Error(
|
|
114
|
-
`Skill tool "${tool.name}" is already registered by
|
|
132
|
+
`Skill tool "${tool.name}" is already registered by ${owner}`,
|
|
115
133
|
);
|
|
116
134
|
}
|
|
117
135
|
}
|
|
@@ -136,6 +154,111 @@ export function registerSkillTools(newTools: Tool[]): Tool[] {
|
|
|
136
154
|
return accepted;
|
|
137
155
|
}
|
|
138
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Register tools contributed by a plugin. Stamps `origin: "plugin"` and
|
|
159
|
+
* `ownerPluginId: pluginName` on every incoming tool so plugin ownership is
|
|
160
|
+
* tracked in a namespace disjoint from skill tools — if a plugin's
|
|
161
|
+
* `manifest.name` happens to match a skill id, the two do not share refcount
|
|
162
|
+
* state or conflict-detection paths.
|
|
163
|
+
*
|
|
164
|
+
* Conflict handling mirrors {@link registerSkillTools}: collisions with core
|
|
165
|
+
* tools log a warning and skip; collisions with tools owned by a different
|
|
166
|
+
* plugin, skill, or MCP server throw; re-registering the same plugin's own
|
|
167
|
+
* tool (hot reload) is allowed.
|
|
168
|
+
*
|
|
169
|
+
* The stamp is authoritative: any pre-existing `origin` / `ownerPluginId` /
|
|
170
|
+
* `ownerSkillId` / `ownerMcpServerId` fields on the incoming tools are
|
|
171
|
+
* overwritten so the bootstrap cannot be spoofed into claiming tools on
|
|
172
|
+
* behalf of an unrelated extension.
|
|
173
|
+
*/
|
|
174
|
+
export function registerPluginTools(
|
|
175
|
+
pluginName: string,
|
|
176
|
+
newTools: Tool[],
|
|
177
|
+
): Tool[] {
|
|
178
|
+
const stamped: Tool[] = newTools.map((tool) => ({
|
|
179
|
+
...tool,
|
|
180
|
+
origin: "plugin" as const,
|
|
181
|
+
ownerPluginId: pluginName,
|
|
182
|
+
ownerSkillId: undefined,
|
|
183
|
+
ownerMcpServerId: undefined,
|
|
184
|
+
}));
|
|
185
|
+
|
|
186
|
+
const accepted: Tool[] = [];
|
|
187
|
+
for (const tool of stamped) {
|
|
188
|
+
const existing = tools.get(tool.name);
|
|
189
|
+
if (existing) {
|
|
190
|
+
const existingIsCore = existing.origin === "core" || !existing.origin;
|
|
191
|
+
if (existingIsCore) {
|
|
192
|
+
log.warn(
|
|
193
|
+
{ toolName: tool.name, pluginName },
|
|
194
|
+
`Plugin "${pluginName}" tried to register tool "${tool.name}" which conflicts with a core tool. Skipping.`,
|
|
195
|
+
);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (existing.origin === "plugin") {
|
|
199
|
+
if (existing.ownerPluginId !== pluginName) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Plugin tool "${tool.name}" is already registered by plugin "${existing.ownerPluginId}"`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
// Same plugin re-registering its own tool (hot reload) — allow.
|
|
205
|
+
} else {
|
|
206
|
+
// Conflict with a skill or MCP-owned tool.
|
|
207
|
+
throw new Error(
|
|
208
|
+
`Plugin "${pluginName}" tried to register tool "${tool.name}" which conflicts with a ${existing.origin}-origin tool`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
accepted.push(tool);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
for (const tool of accepted) {
|
|
216
|
+
tools.set(tool.name, tool);
|
|
217
|
+
log.info(
|
|
218
|
+
{ name: tool.name, ownerPluginId: tool.ownerPluginId },
|
|
219
|
+
"Plugin tool registered",
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (accepted.length > 0) {
|
|
224
|
+
pluginRefCount.set(pluginName, (pluginRefCount.get(pluginName) ?? 0) + 1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return accepted;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Decrement the reference count for a plugin and remove its tools only when
|
|
232
|
+
* no more references remain. Safe to call when the plugin never contributed
|
|
233
|
+
* tools (no-op).
|
|
234
|
+
*/
|
|
235
|
+
export function unregisterPluginTools(pluginName: string): void {
|
|
236
|
+
const current = pluginRefCount.get(pluginName) ?? 0;
|
|
237
|
+
if (current > 1) {
|
|
238
|
+
pluginRefCount.set(pluginName, current - 1);
|
|
239
|
+
log.info(
|
|
240
|
+
{ pluginName, remaining: current - 1 },
|
|
241
|
+
"Decremented plugin ref count, tools kept",
|
|
242
|
+
);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
pluginRefCount.delete(pluginName);
|
|
247
|
+
for (const [name, tool] of tools) {
|
|
248
|
+
if (tool.origin === "plugin" && tool.ownerPluginId === pluginName) {
|
|
249
|
+
tools.delete(name);
|
|
250
|
+
log.info({ name, pluginName }, "Plugin tool unregistered");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Return the current reference count for a plugin's tools. Exposed for testing.
|
|
257
|
+
*/
|
|
258
|
+
export function getPluginRefCount(pluginName: string): number {
|
|
259
|
+
return pluginRefCount.get(pluginName) ?? 0;
|
|
260
|
+
}
|
|
261
|
+
|
|
139
262
|
/**
|
|
140
263
|
* Decrement the reference count for a skill and remove its tools only when
|
|
141
264
|
* no more sessions reference them.
|
|
@@ -190,6 +313,17 @@ export function registerMcpTools(newTools: Tool[]): Tool[] {
|
|
|
190
313
|
);
|
|
191
314
|
continue;
|
|
192
315
|
}
|
|
316
|
+
if (existing.origin === "plugin") {
|
|
317
|
+
log.warn(
|
|
318
|
+
{
|
|
319
|
+
toolName: tool.name,
|
|
320
|
+
serverId: tool.ownerMcpServerId,
|
|
321
|
+
pluginName: existing.ownerPluginId,
|
|
322
|
+
},
|
|
323
|
+
`MCP server "${tool.ownerMcpServerId}" tried to register tool "${tool.name}" which conflicts with plugin tool from "${existing.ownerPluginId}". Skipping.`,
|
|
324
|
+
);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
193
327
|
if (
|
|
194
328
|
existing.origin === "mcp" &&
|
|
195
329
|
existing.ownerMcpServerId !== tool.ownerMcpServerId
|
|
@@ -341,6 +475,7 @@ export async function initializeTools(): Promise<void> {
|
|
|
341
475
|
coreToolsSnapshot = new Map<string, Tool>();
|
|
342
476
|
for (const [name, tool] of tools) {
|
|
343
477
|
if (tool.origin === "skill") continue;
|
|
478
|
+
if (tool.origin === "plugin") continue;
|
|
344
479
|
// Exclude pre-existing tools not declared in the manifest
|
|
345
480
|
if (preExisting.has(name) && !manifestToolNames.has(name)) continue;
|
|
346
481
|
coreToolsSnapshot.set(name, tool);
|
|
@@ -363,6 +498,7 @@ export async function initializeTools(): Promise<void> {
|
|
|
363
498
|
export function __resetRegistryForTesting(): void {
|
|
364
499
|
tools.clear();
|
|
365
500
|
skillRefCount.clear();
|
|
501
|
+
pluginRefCount.clear();
|
|
366
502
|
|
|
367
503
|
if (coreToolsSnapshot) {
|
|
368
504
|
for (const [name, tool] of coreToolsSnapshot) {
|
|
@@ -379,4 +515,5 @@ export function __resetRegistryForTesting(): void {
|
|
|
379
515
|
export function __clearRegistryForTesting(): void {
|
|
380
516
|
tools.clear();
|
|
381
517
|
skillRefCount.clear();
|
|
518
|
+
pluginRefCount.clear();
|
|
382
519
|
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "../../schedule/schedule-store.js";
|
|
14
14
|
import type { ToolContext, ToolExecutionResult } from "../types.js";
|
|
15
15
|
|
|
16
|
-
const VALID_MODES: ScheduleMode[] = ["notify", "execute"];
|
|
16
|
+
const VALID_MODES: ScheduleMode[] = ["notify", "execute", "script"];
|
|
17
17
|
const VALID_ROUTING_INTENTS: RoutingIntent[] = [
|
|
18
18
|
"single_channel",
|
|
19
19
|
"multi_channel",
|
|
@@ -33,7 +33,8 @@ export async function executeScheduleCreate(
|
|
|
33
33
|
}
|
|
34
34
|
const name = input.name as string;
|
|
35
35
|
const timezone = (input.timezone as string) ?? null;
|
|
36
|
-
const message = input.message as string;
|
|
36
|
+
const message = (input.message as string) ?? "";
|
|
37
|
+
const script = (input.script as string) ?? null;
|
|
37
38
|
const enabled = (input.enabled as boolean) ?? true;
|
|
38
39
|
const fireAt = input.fire_at as string | undefined;
|
|
39
40
|
const mode = (input.mode as ScheduleMode | undefined) ?? "execute";
|
|
@@ -50,12 +51,6 @@ export async function executeScheduleCreate(
|
|
|
50
51
|
isError: true,
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
|
-
if (!message || typeof message !== "string") {
|
|
54
|
-
return {
|
|
55
|
-
content: "Error: message is required and must be a string",
|
|
56
|
-
isError: true,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
54
|
|
|
60
55
|
// Validate mode
|
|
61
56
|
if (!VALID_MODES.includes(mode)) {
|
|
@@ -65,6 +60,24 @@ export async function executeScheduleCreate(
|
|
|
65
60
|
};
|
|
66
61
|
}
|
|
67
62
|
|
|
63
|
+
// Mode-specific field validation
|
|
64
|
+
if (mode === "script") {
|
|
65
|
+
if (!script || typeof script !== "string") {
|
|
66
|
+
return {
|
|
67
|
+
content:
|
|
68
|
+
"Error: script is required for script mode and must be a non-empty string",
|
|
69
|
+
isError: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
if (!message || typeof message !== "string") {
|
|
74
|
+
return {
|
|
75
|
+
content: "Error: message is required and must be a string",
|
|
76
|
+
isError: true,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
68
81
|
// Validate routing_intent
|
|
69
82
|
if (
|
|
70
83
|
routingIntent !== undefined &&
|
|
@@ -107,6 +120,7 @@ export async function executeScheduleCreate(
|
|
|
107
120
|
cronExpression: null,
|
|
108
121
|
timezone,
|
|
109
122
|
message,
|
|
123
|
+
script,
|
|
110
124
|
enabled,
|
|
111
125
|
syntax: "cron",
|
|
112
126
|
expression: null,
|
|
@@ -185,6 +199,7 @@ export async function executeScheduleCreate(
|
|
|
185
199
|
cronExpression: resolved.expression,
|
|
186
200
|
timezone,
|
|
187
201
|
message,
|
|
202
|
+
script,
|
|
188
203
|
enabled,
|
|
189
204
|
syntax: resolved.syntax,
|
|
190
205
|
expression: resolved.expression,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from "../../schedule/schedule-store.js";
|
|
17
17
|
import type { ToolContext, ToolExecutionResult } from "../types.js";
|
|
18
18
|
|
|
19
|
-
const VALID_MODES: ScheduleMode[] = ["notify", "execute"];
|
|
19
|
+
const VALID_MODES: ScheduleMode[] = ["notify", "execute", "script"];
|
|
20
20
|
const VALID_ROUTING_INTENTS: RoutingIntent[] = [
|
|
21
21
|
"single_channel",
|
|
22
22
|
"multi_channel",
|
|
@@ -66,6 +66,7 @@ export async function executeScheduleUpdate(
|
|
|
66
66
|
if (input.name !== undefined) updates.name = input.name;
|
|
67
67
|
if (input.timezone !== undefined) updates.timezone = input.timezone;
|
|
68
68
|
if (input.message !== undefined) updates.message = input.message;
|
|
69
|
+
if (input.script !== undefined) updates.script = input.script;
|
|
69
70
|
if (input.enabled !== undefined) updates.enabled = input.enabled;
|
|
70
71
|
|
|
71
72
|
// Mode validation and pass-through
|
|
@@ -163,6 +164,7 @@ export async function executeScheduleUpdate(
|
|
|
163
164
|
cronExpression?: string;
|
|
164
165
|
timezone?: string | null;
|
|
165
166
|
message?: string;
|
|
167
|
+
script?: string | null;
|
|
166
168
|
enabled?: boolean;
|
|
167
169
|
syntax?: ScheduleSyntax;
|
|
168
170
|
expression?: string;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { getConfig } from "../config/loader.js";
|
|
2
|
-
import { getHookManager } from "../hooks/manager.js";
|
|
3
2
|
import { PermissionPrompter } from "../permissions/prompter.js";
|
|
4
3
|
import { RiskLevel } from "../permissions/types.js";
|
|
5
4
|
import { isPermissionControlsV2Enabled } from "../permissions/v2-consent-policy.js";
|
|
@@ -46,10 +45,6 @@ export class SecretDetectionHandler {
|
|
|
46
45
|
context: ToolContext,
|
|
47
46
|
event: ToolLifecycleEvent,
|
|
48
47
|
) => void,
|
|
49
|
-
sanitizeToolInput: (
|
|
50
|
-
toolName: string,
|
|
51
|
-
input: Record<string, unknown>,
|
|
52
|
-
) => Record<string, unknown>,
|
|
53
48
|
): Promise<{ result: ToolExecutionResult; earlyReturn: boolean }> {
|
|
54
49
|
const sdConfig = getConfig().secretDetection;
|
|
55
50
|
if (!sdConfig.enabled || execResult.isError) {
|
|
@@ -108,7 +103,6 @@ export class SecretDetectionHandler {
|
|
|
108
103
|
decision,
|
|
109
104
|
startTime,
|
|
110
105
|
emitLifecycleEvent,
|
|
111
|
-
sanitizeToolInput,
|
|
112
106
|
);
|
|
113
107
|
}
|
|
114
108
|
|
|
@@ -124,7 +118,6 @@ export class SecretDetectionHandler {
|
|
|
124
118
|
decision,
|
|
125
119
|
startTime,
|
|
126
120
|
emitLifecycleEvent,
|
|
127
|
-
sanitizeToolInput,
|
|
128
121
|
);
|
|
129
122
|
}
|
|
130
123
|
|
|
@@ -210,10 +203,6 @@ export class SecretDetectionHandler {
|
|
|
210
203
|
context: ToolContext,
|
|
211
204
|
event: ToolLifecycleEvent,
|
|
212
205
|
) => void,
|
|
213
|
-
sanitizeToolInput: (
|
|
214
|
-
toolName: string,
|
|
215
|
-
input: Record<string, unknown>,
|
|
216
|
-
) => Record<string, unknown>,
|
|
217
206
|
): { result: ToolExecutionResult; earlyReturn: boolean } {
|
|
218
207
|
const types = [...new Set(allMatches.map((m) => m.type))].join(", ");
|
|
219
208
|
const blockedContent = `Tool output blocked: detected ${allMatches.length} potential secret(s) (${types}). Configure secretDetection.action to "redact" or "prompt" to allow output.`;
|
|
@@ -237,15 +226,6 @@ export class SecretDetectionHandler {
|
|
|
237
226
|
result: blockedResult,
|
|
238
227
|
});
|
|
239
228
|
|
|
240
|
-
void getHookManager().trigger("post-tool-execute", {
|
|
241
|
-
toolName: name,
|
|
242
|
-
input: sanitizeToolInput(name, input),
|
|
243
|
-
riskLevel,
|
|
244
|
-
isError: true,
|
|
245
|
-
durationMs,
|
|
246
|
-
conversationId: context.conversationId,
|
|
247
|
-
});
|
|
248
|
-
|
|
249
229
|
return { result: blockedResult, earlyReturn: true };
|
|
250
230
|
}
|
|
251
231
|
|
|
@@ -263,10 +243,6 @@ export class SecretDetectionHandler {
|
|
|
263
243
|
context: ToolContext,
|
|
264
244
|
event: ToolLifecycleEvent,
|
|
265
245
|
) => void,
|
|
266
|
-
sanitizeToolInput: (
|
|
267
|
-
toolName: string,
|
|
268
|
-
input: Record<string, unknown>,
|
|
269
|
-
) => Record<string, unknown>,
|
|
270
246
|
): Promise<{ result: ToolExecutionResult; earlyReturn: boolean }> {
|
|
271
247
|
const types = [...new Set(allMatches.map((m) => m.type))].join(", ");
|
|
272
248
|
|
|
@@ -288,15 +264,6 @@ export class SecretDetectionHandler {
|
|
|
288
264
|
durationMs,
|
|
289
265
|
});
|
|
290
266
|
|
|
291
|
-
void getHookManager().trigger("post-tool-execute", {
|
|
292
|
-
toolName: name,
|
|
293
|
-
input: sanitizeToolInput(name, input),
|
|
294
|
-
riskLevel,
|
|
295
|
-
isError: true,
|
|
296
|
-
durationMs,
|
|
297
|
-
conversationId: context.conversationId,
|
|
298
|
-
});
|
|
299
|
-
|
|
300
267
|
return {
|
|
301
268
|
result: { content: blockedContent, isError: true },
|
|
302
269
|
earlyReturn: true,
|
|
@@ -322,15 +289,6 @@ export class SecretDetectionHandler {
|
|
|
322
289
|
durationMs,
|
|
323
290
|
});
|
|
324
291
|
|
|
325
|
-
void getHookManager().trigger("post-tool-execute", {
|
|
326
|
-
toolName: name,
|
|
327
|
-
input: sanitizeToolInput(name, input),
|
|
328
|
-
riskLevel,
|
|
329
|
-
isError: true,
|
|
330
|
-
durationMs,
|
|
331
|
-
conversationId: context.conversationId,
|
|
332
|
-
});
|
|
333
|
-
|
|
334
292
|
return {
|
|
335
293
|
result: { content: blockedContent, isError: true },
|
|
336
294
|
earlyReturn: true,
|
|
@@ -389,15 +347,6 @@ export class SecretDetectionHandler {
|
|
|
389
347
|
durationMs,
|
|
390
348
|
});
|
|
391
349
|
|
|
392
|
-
void getHookManager().trigger("post-tool-execute", {
|
|
393
|
-
toolName: name,
|
|
394
|
-
input: sanitizeToolInput(name, input),
|
|
395
|
-
riskLevel,
|
|
396
|
-
isError: true,
|
|
397
|
-
durationMs,
|
|
398
|
-
conversationId: context.conversationId,
|
|
399
|
-
});
|
|
400
|
-
|
|
401
350
|
return {
|
|
402
351
|
result: { content: blockedContent, isError: true },
|
|
403
352
|
earlyReturn: true,
|