@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
package/src/tools/registry.ts
CHANGED
|
@@ -100,9 +100,9 @@ const pluginRefCount = new Map<string, number>();
|
|
|
100
100
|
/**
|
|
101
101
|
* Format an owner for log messages and error strings. Returns a stable
|
|
102
102
|
* human-readable description (e.g. `skill "deploy"`, `plugin "weather"`,
|
|
103
|
-
* `MCP server "github"`). When an owner is
|
|
104
|
-
* unrecognized kind, returns a fallback
|
|
105
|
-
* produce `undefined` interpolations.
|
|
103
|
+
* `MCP server "github"`, `workspace tool override`). When an owner is
|
|
104
|
+
* missing (core tool) or has an unrecognized kind, returns a fallback
|
|
105
|
+
* string so log/error sites never produce `undefined` interpolations.
|
|
106
106
|
*/
|
|
107
107
|
function describeOwner(owner: OwnerInfo | undefined): string {
|
|
108
108
|
if (!owner) return "core tool";
|
|
@@ -113,11 +113,46 @@ function describeOwner(owner: OwnerInfo | undefined): string {
|
|
|
113
113
|
return `plugin "${owner.id}"`;
|
|
114
114
|
case "mcp":
|
|
115
115
|
return `MCP server "${owner.id}"`;
|
|
116
|
+
case "workspace":
|
|
117
|
+
return `workspace tool override`;
|
|
116
118
|
default:
|
|
117
119
|
return `${(owner as OwnerInfo).kind}-origin tool`;
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
|
|
123
|
+
// ── Workspace tool overrides ─────────────────────────────────────────
|
|
124
|
+
// Two distinct workspace-tool operations both move a core tool entry
|
|
125
|
+
// into this stash:
|
|
126
|
+
//
|
|
127
|
+
// 1. {@link registerWorkspaceTools} — workspace tool with same name as a
|
|
128
|
+
// core tool registers; the original is stashed and the workspace tool
|
|
129
|
+
// takes its place in `tools`. {@link unregisterWorkspaceTool} restores
|
|
130
|
+
// on teardown.
|
|
131
|
+
// 2. {@link removeCoreToolViaWorkspace} — a `<name>.removed` sentinel in
|
|
132
|
+
// `<workspaceDir>/tools/` strips a core tool from the registry. The
|
|
133
|
+
// original is stashed; `tools[name]` is cleared without a replacement.
|
|
134
|
+
// {@link restoreStrippedCoreTool} restores when the sentinel is gone.
|
|
135
|
+
//
|
|
136
|
+
// Both operations are reversible because the stash holds the original
|
|
137
|
+
// core entry verbatim. The two states are distinguished by what (if
|
|
138
|
+
// anything) sits in `tools[name]` and `ownersByName[name]`:
|
|
139
|
+
//
|
|
140
|
+
// - `ownersByName[name].kind === "workspace"` + stash present → override
|
|
141
|
+
// - `tools[name]` absent + stash present → stripped via `.removed`
|
|
142
|
+
// - `tools[name]` present + no owner → normal core tool
|
|
143
|
+
// - `ownersByName[name].kind === "workspace"` + no stash → net-new workspace tool
|
|
144
|
+
//
|
|
145
|
+
// Keyed by tool name. At most one stashed entry per name — the registry
|
|
146
|
+
// rejects a second workspace registration for the same name without an
|
|
147
|
+
// explicit unregister/restore.
|
|
148
|
+
//
|
|
149
|
+
// Plugin/skill/MCP code paths consult this map only indirectly: they see
|
|
150
|
+
// a workspace-kind entry in `ownersByName` (or a stash entry but no live
|
|
151
|
+
// entry) and refuse to register over it, preserving the
|
|
152
|
+
// single-canonical-source invariant the override / strip paths exist to
|
|
153
|
+
// enforce.
|
|
154
|
+
const coreToolOverrides = new Map<string, Tool>();
|
|
155
|
+
|
|
121
156
|
function withProviderSafeToolName(tool: Tool): Tool {
|
|
122
157
|
const safeName = toProviderSafeToolName(tool.name);
|
|
123
158
|
if (safeName === tool.name) {
|
|
@@ -185,6 +220,8 @@ export function getToolOwner(name: string): OwnerInfo | undefined {
|
|
|
185
220
|
*
|
|
186
221
|
* Skips any tool whose name collides with a core tool (logs a warning instead
|
|
187
222
|
* of throwing so the remaining tools in the batch still get registered).
|
|
223
|
+
* Also skips when the name is owned by a workspace tool — workspace
|
|
224
|
+
* overrides are authoritative and silently win over skill registration.
|
|
188
225
|
* Throws if a tool name collides with a skill tool owned by a different skill.
|
|
189
226
|
* Allows replacement when the incoming tool has the same skill owner id as
|
|
190
227
|
* the existing one, which supports hot-reloading a skill without tearing
|
|
@@ -198,9 +235,20 @@ export function registerSkillTools(skillId: string, newTools: Tool[]): Tool[] {
|
|
|
198
235
|
// Filter out tools that collide with core tools, and validate the rest.
|
|
199
236
|
const accepted: Tool[] = [];
|
|
200
237
|
for (const tool of newTools) {
|
|
238
|
+
// A workspace `.removed` sentinel stripped a core tool of this name —
|
|
239
|
+
// the slot is reserved by the stash even though `tools` has no live
|
|
240
|
+
// entry. Refuse to fill the slot from a non-workspace surface.
|
|
241
|
+
if (!tools.has(tool.name) && coreToolOverrides.has(tool.name)) {
|
|
242
|
+
log.warn(
|
|
243
|
+
{ toolName: tool.name, skillId },
|
|
244
|
+
`Skill "${skillId}" tried to register tool "${tool.name}" which is reserved by a workspace .removed sentinel. Skipping.`,
|
|
245
|
+
);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
201
248
|
const existing = tools.get(tool.name);
|
|
202
249
|
if (existing) {
|
|
203
|
-
const
|
|
250
|
+
const existingOwner = ownersByName.get(tool.name);
|
|
251
|
+
const existingIsCore = !existingOwner;
|
|
204
252
|
if (existingIsCore) {
|
|
205
253
|
log.warn(
|
|
206
254
|
{ toolName: tool.name, ownerSkillId: skillId },
|
|
@@ -208,9 +256,15 @@ export function registerSkillTools(skillId: string, newTools: Tool[]): Tool[] {
|
|
|
208
256
|
);
|
|
209
257
|
continue;
|
|
210
258
|
}
|
|
259
|
+
if (existingOwner?.kind === "workspace") {
|
|
260
|
+
log.warn(
|
|
261
|
+
{ toolName: tool.name, skillId },
|
|
262
|
+
`Skill "${skillId}" tried to register tool "${tool.name}" which is owned by a workspace tool override. Skipping.`,
|
|
263
|
+
);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
211
266
|
// Existing is from a different owner (plugin/mcp) or a different
|
|
212
267
|
// skill — skill tools can only replace themselves (hot-reload).
|
|
213
|
-
const existingOwner = ownersByName.get(tool.name);
|
|
214
268
|
const existingSkillId =
|
|
215
269
|
existingOwner?.kind === "skill" ? existingOwner.id : undefined;
|
|
216
270
|
if (existingOwner?.kind !== "skill" || existingSkillId !== skillId) {
|
|
@@ -267,6 +321,16 @@ export function registerPluginTools(
|
|
|
267
321
|
|
|
268
322
|
const accepted: Tool[] = [];
|
|
269
323
|
for (const tool of stamped) {
|
|
324
|
+
// A workspace `.removed` sentinel stripped a core tool of this name —
|
|
325
|
+
// the slot is reserved by the stash even though `tools` has no live
|
|
326
|
+
// entry. Refuse to fill the slot from a non-workspace surface.
|
|
327
|
+
if (!tools.has(tool.name) && coreToolOverrides.has(tool.name)) {
|
|
328
|
+
log.warn(
|
|
329
|
+
{ toolName: tool.name, pluginName },
|
|
330
|
+
`Plugin "${pluginName}" tried to register tool "${tool.name}" which is reserved by a workspace .removed sentinel. Skipping.`,
|
|
331
|
+
);
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
270
334
|
const existing = tools.get(tool.name);
|
|
271
335
|
if (existing) {
|
|
272
336
|
const existingIsCore = !ownersByName.has(tool.name);
|
|
@@ -278,6 +342,13 @@ export function registerPluginTools(
|
|
|
278
342
|
continue;
|
|
279
343
|
}
|
|
280
344
|
const existingOwner = ownersByName.get(tool.name);
|
|
345
|
+
if (existingOwner?.kind === "workspace") {
|
|
346
|
+
log.warn(
|
|
347
|
+
{ toolName: tool.name, pluginName },
|
|
348
|
+
`Plugin "${pluginName}" tried to register tool "${tool.name}" which is owned by a workspace tool override. Skipping.`,
|
|
349
|
+
);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
281
352
|
if (existingOwner?.kind === "plugin") {
|
|
282
353
|
if (existingOwner.id !== pluginName) {
|
|
283
354
|
throw new Error(
|
|
@@ -382,6 +453,16 @@ export function unregisterSkillTools(skillId: string): void {
|
|
|
382
453
|
export function registerMcpTools(serverId: string, newTools: Tool[]): Tool[] {
|
|
383
454
|
const accepted: Tool[] = [];
|
|
384
455
|
for (const tool of newTools) {
|
|
456
|
+
// A workspace `.removed` sentinel stripped a core tool of this name —
|
|
457
|
+
// the slot is reserved by the stash even though `tools` has no live
|
|
458
|
+
// entry. Refuse to fill the slot from a non-workspace surface.
|
|
459
|
+
if (!tools.has(tool.name) && coreToolOverrides.has(tool.name)) {
|
|
460
|
+
log.warn(
|
|
461
|
+
{ toolName: tool.name, serverId },
|
|
462
|
+
`MCP server "${serverId}" tried to register tool "${tool.name}" which is reserved by a workspace .removed sentinel. Skipping.`,
|
|
463
|
+
);
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
385
466
|
const existing = tools.get(tool.name);
|
|
386
467
|
if (existing) {
|
|
387
468
|
const existingIsCore = !ownersByName.has(tool.name);
|
|
@@ -393,6 +474,13 @@ export function registerMcpTools(serverId: string, newTools: Tool[]): Tool[] {
|
|
|
393
474
|
continue;
|
|
394
475
|
}
|
|
395
476
|
const existingOwner = ownersByName.get(tool.name);
|
|
477
|
+
if (existingOwner?.kind === "workspace") {
|
|
478
|
+
log.warn(
|
|
479
|
+
{ toolName: tool.name, serverId },
|
|
480
|
+
`MCP server "${serverId}" tried to register tool "${tool.name}" which is owned by a workspace tool override. Skipping.`,
|
|
481
|
+
);
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
396
484
|
if (existingOwner?.kind === "skill" || existingOwner?.kind === "plugin") {
|
|
397
485
|
log.warn(
|
|
398
486
|
{
|
|
@@ -458,6 +546,280 @@ export function getSkillToolNames(): string[] {
|
|
|
458
546
|
.map((t) => t.name);
|
|
459
547
|
}
|
|
460
548
|
|
|
549
|
+
/**
|
|
550
|
+
* Register a batch of workspace-origin tools — entries discovered under
|
|
551
|
+
* `<workspaceDir>/tools/<name>.{ts,js,json}`. Each call records ownership
|
|
552
|
+
* (`kind: "workspace"`, `id: <workspacePath>`) in `ownersByName` keyed by
|
|
553
|
+
* tool name — the `Tool` object itself carries no owner metadata.
|
|
554
|
+
*
|
|
555
|
+
* Conflict handling:
|
|
556
|
+
*
|
|
557
|
+
* - **Core tool same name**: the original core entry is moved into
|
|
558
|
+
* {@link coreToolOverrides}, and the workspace tool takes its place in
|
|
559
|
+
* `tools`. {@link unregisterWorkspaceTool} restores the original later.
|
|
560
|
+
* - **Workspace tool same name**: rejected with a hard throw. There is
|
|
561
|
+
* exactly one canonical source per workspace tool name on disk; a
|
|
562
|
+
* second registration without an intervening
|
|
563
|
+
* {@link unregisterWorkspaceTool} is a caller bug.
|
|
564
|
+
* - **Plugin / skill / MCP same name**: rejected with a hard throw.
|
|
565
|
+
* Workspace tools must register before any other extension category in
|
|
566
|
+
* the daemon lifecycle (between {@link initializeTools} and
|
|
567
|
+
* {@link loadUserPlugins}); seeing one of these origins here would mean
|
|
568
|
+
* a lifecycle-order regression.
|
|
569
|
+
* - **Net-new name (no existing entry)**: registers as a new tool. The
|
|
570
|
+
* stash stays empty for this name so {@link unregisterWorkspaceTool}
|
|
571
|
+
* simply removes the tool with no restoration.
|
|
572
|
+
*
|
|
573
|
+
* The batch is validated end-to-end before any mutation lands on `tools`
|
|
574
|
+
* or `coreToolOverrides` — a single rejected entry aborts the whole call
|
|
575
|
+
* so callers never observe a partially-applied registration.
|
|
576
|
+
*/
|
|
577
|
+
export function registerWorkspaceTools(
|
|
578
|
+
newTools: Array<{
|
|
579
|
+
tool: Tool;
|
|
580
|
+
workspacePath: string;
|
|
581
|
+
}>,
|
|
582
|
+
): Tool[] {
|
|
583
|
+
// Build provider-safe Tool objects up front. We do not mutate the
|
|
584
|
+
// registry until the entire batch has cleared the conflict checks
|
|
585
|
+
// below. Ownership (kind + workspace path) is tracked separately
|
|
586
|
+
// alongside each stamped entry so the mutation phase can set
|
|
587
|
+
// `ownersByName` in lockstep with `tools`.
|
|
588
|
+
const stamped: Array<{ tool: Tool; workspacePath: string }> = newTools.map(
|
|
589
|
+
({ tool: workspaceTool, workspacePath }) => ({
|
|
590
|
+
tool: withProviderSafeToolName(workspaceTool as Tool),
|
|
591
|
+
workspacePath,
|
|
592
|
+
}),
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
// Validate the whole batch first so we never leave the registry in a
|
|
596
|
+
// half-applied state. The validation phase only reads; the mutation
|
|
597
|
+
// phase below only fires once every entry has passed.
|
|
598
|
+
const seenInBatch = new Set<string>();
|
|
599
|
+
for (const { tool } of stamped) {
|
|
600
|
+
if (seenInBatch.has(tool.name)) {
|
|
601
|
+
throw new Error(
|
|
602
|
+
`Workspace tool batch contains duplicate name "${tool.name}"`,
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
seenInBatch.add(tool.name);
|
|
606
|
+
|
|
607
|
+
const existing = tools.get(tool.name);
|
|
608
|
+
if (!existing) continue;
|
|
609
|
+
|
|
610
|
+
const existingOwner = ownersByName.get(tool.name);
|
|
611
|
+
if (existingOwner?.kind === "workspace") {
|
|
612
|
+
throw new Error(
|
|
613
|
+
`Workspace tool "${tool.name}" is already registered (path: ${existingOwner.id}). Call unregisterWorkspaceTool("${tool.name}") before re-registering.`,
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (!existingOwner) continue; // Core tool — override allowed, handled in mutation phase below.
|
|
618
|
+
|
|
619
|
+
throw new Error(
|
|
620
|
+
`Workspace tool "${tool.name}" conflicts with an existing ${describeOwner(existingOwner)}. Workspace tools must register before other extension categories.`,
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
for (const { tool, workspacePath } of stamped) {
|
|
625
|
+
const existing = tools.get(tool.name);
|
|
626
|
+
const existingIsCore = existing && !ownersByName.has(tool.name);
|
|
627
|
+
if (existingIsCore) {
|
|
628
|
+
coreToolOverrides.set(tool.name, existing);
|
|
629
|
+
log.info(
|
|
630
|
+
{ name: tool.name, workspacePath },
|
|
631
|
+
"Stashing core tool ahead of workspace override",
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
tools.set(tool.name, tool);
|
|
635
|
+
ownersByName.set(tool.name, { kind: "workspace", id: workspacePath });
|
|
636
|
+
log.info(
|
|
637
|
+
{
|
|
638
|
+
name: tool.name,
|
|
639
|
+
workspacePath,
|
|
640
|
+
overridesCore: coreToolOverrides.has(tool.name),
|
|
641
|
+
},
|
|
642
|
+
"Workspace tool registered",
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return stamped.map(({ tool }) => tool);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Remove a workspace tool registration. If the name had a stashed core
|
|
651
|
+
* tool ({@link coreToolOverrides}), the original is restored; otherwise
|
|
652
|
+
* the entry is simply deleted (net-new workspace tool case).
|
|
653
|
+
*
|
|
654
|
+
* No-op when the named tool is not currently registered as a workspace
|
|
655
|
+
* tool — the function is safe to call on every shutdown path without
|
|
656
|
+
* needing to track which tools the loader actually registered.
|
|
657
|
+
*/
|
|
658
|
+
export function unregisterWorkspaceTool(name: string): void {
|
|
659
|
+
const existingOwner = ownersByName.get(name);
|
|
660
|
+
if (existingOwner?.kind !== "workspace") {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const workspacePath = existingOwner.id;
|
|
664
|
+
|
|
665
|
+
const stashed = coreToolOverrides.get(name);
|
|
666
|
+
if (stashed) {
|
|
667
|
+
tools.set(name, stashed);
|
|
668
|
+
ownersByName.delete(name);
|
|
669
|
+
coreToolOverrides.delete(name);
|
|
670
|
+
log.info(
|
|
671
|
+
{ name, workspacePath },
|
|
672
|
+
"Workspace tool unregistered — core tool restored",
|
|
673
|
+
);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
tools.delete(name);
|
|
678
|
+
ownersByName.delete(name);
|
|
679
|
+
log.info(
|
|
680
|
+
{ name, workspacePath },
|
|
681
|
+
"Workspace tool unregistered (no core tool to restore)",
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Strip a core tool from the registry on behalf of a workspace
|
|
687
|
+
* `<name>.removed` sentinel. The original core entry is stashed (same
|
|
688
|
+
* map as override-style stashing) so {@link restoreStrippedCoreTool} can
|
|
689
|
+
* undo the strip if the sentinel file is later removed.
|
|
690
|
+
*
|
|
691
|
+
* No-op cases (logged at debug, never throws):
|
|
692
|
+
* - `name` doesn't exist in the registry — nothing to strip
|
|
693
|
+
* - `name` is already stripped (stash present, live entry absent) — idempotent
|
|
694
|
+
* - `name` is owned by a non-core origin (plugin / skill / mcp) — workspace
|
|
695
|
+
* strip cannot evict another extension's tool; that's a namespacing
|
|
696
|
+
* collision the operator must resolve at the source
|
|
697
|
+
*
|
|
698
|
+
* Throws when `name` is owned by an existing workspace tool. The loader
|
|
699
|
+
* is supposed to filter out files where both `<name>.<ext>` and
|
|
700
|
+
* `<name>.removed` coexist before getting here; if we land in this state
|
|
701
|
+
* something earlier failed and the caller needs to know.
|
|
702
|
+
*/
|
|
703
|
+
export function removeCoreToolViaWorkspace(name: string): void {
|
|
704
|
+
const existing = tools.get(name);
|
|
705
|
+
|
|
706
|
+
if (!existing) {
|
|
707
|
+
if (coreToolOverrides.has(name)) {
|
|
708
|
+
log.debug(
|
|
709
|
+
{ name },
|
|
710
|
+
"removeCoreToolViaWorkspace: core tool already stripped — no-op",
|
|
711
|
+
);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
log.debug(
|
|
715
|
+
{ name },
|
|
716
|
+
"removeCoreToolViaWorkspace: no tool registered under this name — no-op",
|
|
717
|
+
);
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const existingOwner = ownersByName.get(name);
|
|
722
|
+
if (existingOwner?.kind === "workspace") {
|
|
723
|
+
throw new Error(
|
|
724
|
+
`Cannot strip "${name}" via .removed sentinel — name is owned by a workspace tool override (path: ${existingOwner.id}). Remove the workspace tool file first.`,
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (existingOwner) {
|
|
729
|
+
log.warn(
|
|
730
|
+
{ name, owner: existingOwner },
|
|
731
|
+
`removeCoreToolViaWorkspace: "${name}" is owned by ${describeOwner(existingOwner)}, not a core tool — cannot strip from workspace. Resolve at the source (uninstall the ${existingOwner.kind}).`,
|
|
732
|
+
);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
coreToolOverrides.set(name, existing);
|
|
737
|
+
tools.delete(name);
|
|
738
|
+
log.info(
|
|
739
|
+
{ name },
|
|
740
|
+
"Stripped core tool via workspace .removed sentinel — stashed for potential restore",
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Restore a core tool that was previously stripped via
|
|
746
|
+
* {@link removeCoreToolViaWorkspace}. Called when the `<name>.removed`
|
|
747
|
+
* sentinel file is deleted (typically by the workspace-tool file watcher).
|
|
748
|
+
*
|
|
749
|
+
* No-op cases (logged at debug, never throws):
|
|
750
|
+
* - No stash exists for `name` — nothing to restore
|
|
751
|
+
* - A workspace tool currently owns the name — the restore is implicit
|
|
752
|
+
* when the workspace tool is later unregistered; doing it here would
|
|
753
|
+
* evict the live workspace tool
|
|
754
|
+
* - A core tool already sits at the name — already restored, idempotent
|
|
755
|
+
*/
|
|
756
|
+
export function restoreStrippedCoreTool(name: string): void {
|
|
757
|
+
const stashed = coreToolOverrides.get(name);
|
|
758
|
+
if (!stashed) {
|
|
759
|
+
log.debug(
|
|
760
|
+
{ name },
|
|
761
|
+
"restoreStrippedCoreTool: no stashed core tool — no-op",
|
|
762
|
+
);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
const existing = tools.get(name);
|
|
766
|
+
if (existing) {
|
|
767
|
+
const existingOwner = ownersByName.get(name);
|
|
768
|
+
if (existingOwner?.kind === "workspace") {
|
|
769
|
+
log.debug(
|
|
770
|
+
{ name },
|
|
771
|
+
"restoreStrippedCoreTool: workspace tool currently owns this name — leaving stash in place for the workspace tool's eventual unregister",
|
|
772
|
+
);
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
log.debug(
|
|
776
|
+
{ name, currentOwner: existingOwner ?? "core" },
|
|
777
|
+
"restoreStrippedCoreTool: a non-workspace entry already sits at this name — leaving stash in place",
|
|
778
|
+
);
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
tools.set(name, stashed);
|
|
782
|
+
coreToolOverrides.delete(name);
|
|
783
|
+
log.info(
|
|
784
|
+
{ name },
|
|
785
|
+
"Restored core tool after workspace .removed sentinel was deleted",
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Return the names of all currently registered workspace-origin tools.
|
|
791
|
+
*/
|
|
792
|
+
export function getWorkspaceToolNames(): string[] {
|
|
793
|
+
return Array.from(tools.values())
|
|
794
|
+
.filter((t) => ownersByName.get(t.name)?.kind === "workspace")
|
|
795
|
+
.map((t) => t.name);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Return the names of core tools currently stripped via workspace
|
|
800
|
+
* `.removed` sentinels — i.e. names where the stash holds an entry but
|
|
801
|
+
* no live tool sits in the registry.
|
|
802
|
+
*/
|
|
803
|
+
export function getStrippedCoreToolNames(): string[] {
|
|
804
|
+
const stripped: string[] = [];
|
|
805
|
+
for (const name of coreToolOverrides.keys()) {
|
|
806
|
+
if (!tools.has(name)) stripped.push(name);
|
|
807
|
+
}
|
|
808
|
+
return stripped;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Inspect the override stash for a tool name. Returns the original core
|
|
813
|
+
* tool that was displaced by a workspace registration (or stripped via
|
|
814
|
+
* `.removed`), or `undefined` when no such override exists.
|
|
815
|
+
*
|
|
816
|
+
* Useful for tooling that needs to show "this core tool is overridden by
|
|
817
|
+
* a workspace entry" without exposing the full stash map.
|
|
818
|
+
*/
|
|
819
|
+
export function getCoreToolOverride(name: string): Tool | undefined {
|
|
820
|
+
return coreToolOverrides.get(name);
|
|
821
|
+
}
|
|
822
|
+
|
|
461
823
|
/**
|
|
462
824
|
* Return the current reference count for a skill's tools. Exposed for testing.
|
|
463
825
|
*/
|
|
@@ -566,6 +928,20 @@ export async function initializeTools(): Promise<void> {
|
|
|
566
928
|
}
|
|
567
929
|
|
|
568
930
|
log.info({ count: tools.size }, "Tools initialized");
|
|
931
|
+
|
|
932
|
+
// Load workspace tool overrides from `<workspaceDir>/tools/<name>.{ts,js,json}`
|
|
933
|
+
// immediately after core tools have settled, before MCP / plugin
|
|
934
|
+
// registrations get a chance to claim names. This ordering makes
|
|
935
|
+
// workspace tools the canonical owner per name:
|
|
936
|
+
// core registrations → workspace tools → MCP → plugins.
|
|
937
|
+
// Workspace tools land after the core snapshot above so they're never
|
|
938
|
+
// baked into the test-reset baseline.
|
|
939
|
+
//
|
|
940
|
+
// Imported dynamically because the loader imports back from this module
|
|
941
|
+
// (registerWorkspaceTools / removeCoreToolViaWorkspace); a static import
|
|
942
|
+
// here would create a registry ↔ loader cycle.
|
|
943
|
+
const { loadWorkspaceTools } = await import("./workspace-tools/loader.js");
|
|
944
|
+
await loadWorkspaceTools();
|
|
569
945
|
}
|
|
570
946
|
|
|
571
947
|
/**
|
|
@@ -583,6 +959,11 @@ export function __resetRegistryForTesting(): void {
|
|
|
583
959
|
ownersByName.clear();
|
|
584
960
|
skillRefCount.clear();
|
|
585
961
|
pluginRefCount.clear();
|
|
962
|
+
// Drop the override stash too — the snapshot already represents the
|
|
963
|
+
// pre-override baseline, so leaving stashed entries here would let a
|
|
964
|
+
// later registerWorkspaceTools() falsely report "overridesCore: true"
|
|
965
|
+
// against a fresh registry.
|
|
966
|
+
coreToolOverrides.clear();
|
|
586
967
|
|
|
587
968
|
if (coreToolsSnapshot) {
|
|
588
969
|
for (const [name, tool] of coreToolsSnapshot) {
|
|
@@ -601,6 +982,7 @@ export function __clearRegistryForTesting(): void {
|
|
|
601
982
|
ownersByName.clear();
|
|
602
983
|
skillRefCount.clear();
|
|
603
984
|
pluginRefCount.clear();
|
|
985
|
+
coreToolOverrides.clear();
|
|
604
986
|
}
|
|
605
987
|
|
|
606
988
|
/**
|
|
@@ -33,6 +33,7 @@ export async function executeScheduleCreate(
|
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
const name = input.name as string;
|
|
36
|
+
const description = input.description as string | undefined;
|
|
36
37
|
const timezone = (input.timezone as string) ?? null;
|
|
37
38
|
const message = (input.message as string) ?? "";
|
|
38
39
|
const script = (input.script as string) ?? null;
|
|
@@ -63,6 +64,17 @@ export async function executeScheduleCreate(
|
|
|
63
64
|
};
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
if (
|
|
68
|
+
!description ||
|
|
69
|
+
typeof description !== "string" ||
|
|
70
|
+
description.trim().length === 0
|
|
71
|
+
) {
|
|
72
|
+
return {
|
|
73
|
+
content: "Error: description is required and must be a non-empty string",
|
|
74
|
+
isError: true,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
66
78
|
// Validate mode
|
|
67
79
|
if (!VALID_MODES.includes(mode)) {
|
|
68
80
|
return {
|
|
@@ -128,6 +140,7 @@ export async function executeScheduleCreate(
|
|
|
128
140
|
try {
|
|
129
141
|
const job = createSchedule({
|
|
130
142
|
name,
|
|
143
|
+
description,
|
|
131
144
|
cronExpression: null,
|
|
132
145
|
timezone,
|
|
133
146
|
message,
|
|
@@ -154,6 +167,7 @@ export async function executeScheduleCreate(
|
|
|
154
167
|
`One-shot schedule created successfully.`,
|
|
155
168
|
` ID: ${job.id}`,
|
|
156
169
|
` Name: ${job.name}`,
|
|
170
|
+
` Description: ${job.description}`,
|
|
157
171
|
` Type: one-shot`,
|
|
158
172
|
` Mode: ${job.mode}`,
|
|
159
173
|
` Fire at: ${fireDate}`,
|
|
@@ -211,6 +225,7 @@ export async function executeScheduleCreate(
|
|
|
211
225
|
try {
|
|
212
226
|
const job = createSchedule({
|
|
213
227
|
name,
|
|
228
|
+
description,
|
|
214
229
|
cronExpression: resolved.expression,
|
|
215
230
|
timezone,
|
|
216
231
|
message,
|
|
@@ -243,6 +258,7 @@ export async function executeScheduleCreate(
|
|
|
243
258
|
`Recurring schedule created successfully.`,
|
|
244
259
|
` ID: ${job.id}`,
|
|
245
260
|
` Name: ${job.name}`,
|
|
261
|
+
` Description: ${job.description}`,
|
|
246
262
|
` Syntax: ${job.syntax}`,
|
|
247
263
|
` Mode: ${job.mode}`,
|
|
248
264
|
` Schedule: ${scheduleDescription}${
|
|
@@ -25,6 +25,10 @@ function isOneShot(job: { expression: string | null }): boolean {
|
|
|
25
25
|
return job.expression == null;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
function describeAuthoredPurpose(job: { description: string }): string {
|
|
29
|
+
return job.description || "(none)";
|
|
30
|
+
}
|
|
31
|
+
|
|
28
32
|
export async function executeScheduleList(
|
|
29
33
|
input: Record<string, unknown>,
|
|
30
34
|
_context: ToolContext,
|
|
@@ -48,6 +52,7 @@ export async function executeScheduleList(
|
|
|
48
52
|
` Type: ${oneShot ? "one-shot" : "recurring"}`,
|
|
49
53
|
` Mode: ${job.mode}`,
|
|
50
54
|
` Status: ${job.status}`,
|
|
55
|
+
` Description: ${describeAuthoredPurpose(job)}`,
|
|
51
56
|
];
|
|
52
57
|
|
|
53
58
|
if (oneShot) {
|
|
@@ -118,14 +123,17 @@ export async function executeScheduleList(
|
|
|
118
123
|
if (oneShot) {
|
|
119
124
|
const fireTime = formatLocalDate(job.nextRunAt);
|
|
120
125
|
lines.push(
|
|
121
|
-
` - [${status}] ${job.name} (id: ${job.id}) (one-shot, ${job.mode})
|
|
126
|
+
` - [${status}] ${job.name} (id: ${job.id}) (one-shot, ${job.mode}) [${job.status}]`,
|
|
127
|
+
` Description: ${describeAuthoredPurpose(job)}`,
|
|
128
|
+
` fire at: ${fireTime}`,
|
|
122
129
|
);
|
|
123
130
|
} else {
|
|
124
131
|
const next = job.enabled ? formatLocalDate(job.nextRunAt) : "n/a";
|
|
125
132
|
lines.push(
|
|
126
|
-
` - [${status}] ${job.name} (id: ${job.id}) (
|
|
127
|
-
|
|
128
|
-
|
|
133
|
+
` - [${status}] ${job.name} (id: ${job.id}) (${job.mode})`,
|
|
134
|
+
` Description: ${describeAuthoredPurpose(job)}`,
|
|
135
|
+
` Schedule: [${job.syntax}] ${describeSchedule(job)}`,
|
|
136
|
+
` Next: ${next}`,
|
|
129
137
|
);
|
|
130
138
|
}
|
|
131
139
|
}
|
|
@@ -65,6 +65,16 @@ export async function executeScheduleUpdate(
|
|
|
65
65
|
|
|
66
66
|
const updates: Record<string, unknown> = {};
|
|
67
67
|
if (input.name !== undefined) updates.name = input.name;
|
|
68
|
+
if (input.description !== undefined) {
|
|
69
|
+
const description = input.description as string;
|
|
70
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
71
|
+
return {
|
|
72
|
+
content: "Error: description must be a non-empty string when provided",
|
|
73
|
+
isError: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
updates.description = description;
|
|
77
|
+
}
|
|
68
78
|
if (input.timezone !== undefined) updates.timezone = input.timezone;
|
|
69
79
|
if (input.message !== undefined) updates.message = input.message;
|
|
70
80
|
if (input.script !== undefined) updates.script = input.script;
|
|
@@ -184,6 +194,7 @@ export async function executeScheduleUpdate(
|
|
|
184
194
|
jobId,
|
|
185
195
|
updates as {
|
|
186
196
|
name?: string;
|
|
197
|
+
description?: string;
|
|
187
198
|
cronExpression?: string;
|
|
188
199
|
timezone?: string | null;
|
|
189
200
|
message?: string;
|
|
@@ -217,6 +228,7 @@ export async function executeScheduleUpdate(
|
|
|
217
228
|
content: [
|
|
218
229
|
`Schedule updated successfully.`,
|
|
219
230
|
` Name: ${job.name}`,
|
|
231
|
+
` Description: ${job.description}`,
|
|
220
232
|
` Syntax: ${job.syntax}`,
|
|
221
233
|
` Mode: ${job.mode}`,
|
|
222
234
|
` Schedule: ${scheduleDescription}${job.timezone ? ` (${job.timezone})` : ""}`,
|
package/src/tools/skills/load.ts
CHANGED
|
@@ -290,11 +290,13 @@ export const skillLoadTool = {
|
|
|
290
290
|
skill.inlineCommandExpansions && skill.inlineCommandExpansions.length > 0;
|
|
291
291
|
|
|
292
292
|
if (hasInlineCommands) {
|
|
293
|
-
if (skill.source === "extra") {
|
|
294
|
-
// Third-party
|
|
295
|
-
//
|
|
293
|
+
if (skill.source === "extra" || skill.source === "plugin") {
|
|
294
|
+
// Third-party skill roots — `extra` dirs and skills shipped inside
|
|
295
|
+
// installed plugins — are out of scope for inline command expansion.
|
|
296
|
+
// Their bodies are untrusted, so reject explicitly rather than
|
|
297
|
+
// executing shell from them.
|
|
296
298
|
return {
|
|
297
|
-
content: `Error: skill "${skill.id}" contains inline command expansions but inline commands are not supported for third-party (
|
|
299
|
+
content: `Error: skill "${skill.id}" contains inline command expansions but inline commands are not supported for third-party (${skill.source}) skill sources.`,
|
|
298
300
|
isError: true,
|
|
299
301
|
};
|
|
300
302
|
}
|
|
@@ -367,9 +369,12 @@ export const skillLoadTool = {
|
|
|
367
369
|
childLoaded.skill.inlineCommandExpansions.length > 0;
|
|
368
370
|
|
|
369
371
|
if (childHasInlineCommands) {
|
|
370
|
-
if (
|
|
372
|
+
if (
|
|
373
|
+
childLoaded.skill.source === "extra" ||
|
|
374
|
+
childLoaded.skill.source === "plugin"
|
|
375
|
+
) {
|
|
371
376
|
return {
|
|
372
|
-
content: `Error: included skill "${childId}" contains inline command expansions but inline commands are not supported for third-party (
|
|
377
|
+
content: `Error: included skill "${childId}" contains inline command expansions but inline commands are not supported for third-party (${childLoaded.skill.source}) skill sources.`,
|
|
373
378
|
isError: true,
|
|
374
379
|
};
|
|
375
380
|
}
|