@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
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* 2. Walks {@link getRegisteredPlugins} in registration order.
|
|
15
15
|
* 3. For each plugin, consults `manifest.requiresFlag` against
|
|
16
16
|
* {@link isAssistantFeatureFlagEnabled}. If any listed flag is disabled,
|
|
17
|
-
* the plugin is skipped wholesale — no `init()`, no tool/route
|
|
17
|
+
* the plugin is skipped wholesale — no `init()`, no tool/route
|
|
18
18
|
* contributions, no entry in the shutdown hook, and the plugin is also
|
|
19
19
|
* dropped from the registry via {@link unregisterPlugin} so none of its
|
|
20
20
|
* hooks participate in the turn lifecycle. This is the primary mechanism for
|
|
@@ -29,22 +29,21 @@
|
|
|
29
29
|
* through untouched).
|
|
30
30
|
* 6. Creates `<workspaceDir>/plugins-data/<plugin>/` on demand for per-plugin
|
|
31
31
|
* writable state and exposes it via {@link PluginInitContext.pluginStorageDir}.
|
|
32
|
-
* 7. For each surviving plugin, registers its contributed tools
|
|
33
|
-
*
|
|
34
|
-
* {@link
|
|
35
|
-
* {@link registerPluginSkills}. Contributions land BEFORE `init()` so
|
|
32
|
+
* 7. For each surviving plugin, registers its contributed tools and routes
|
|
33
|
+
* into their global registries via {@link registerPluginTools} and
|
|
34
|
+
* {@link registerSkillRoute}. Contributions land BEFORE `init()` so
|
|
36
35
|
* the plugin's hook can observe a registry where its own model-visible
|
|
37
36
|
* surface is already wired — useful for plugins that want to attach
|
|
38
37
|
* metadata, warm caches, or otherwise interact with their own
|
|
39
38
|
* contributions during initialization.
|
|
40
|
-
* 8. Awaits `plugin.init(ctx)` sequentially. An init failure
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
39
|
+
* 8. Awaits `plugin.init(ctx)` sequentially. An init failure is contained to
|
|
40
|
+
* the offending plugin: its already-registered tools and routes are rolled
|
|
41
|
+
* back, it is dropped from the registry, the failure is logged, and
|
|
42
|
+
* bootstrap continues with the remaining plugins. A single plugin's failure
|
|
43
|
+
* never deregisters plugins that already initialized — in particular the
|
|
44
|
+
* first-party defaults, which carry core turn behavior (memory retrieval,
|
|
45
|
+
* history repair, title generation) — so the daemon comes up with the
|
|
46
|
+
* failing plugin absent rather than blacking out the whole plugin layer.
|
|
48
47
|
*
|
|
49
48
|
* A single shutdown hook is registered via
|
|
50
49
|
* {@link registerShutdownHook} that walks the plugin list in **reverse
|
|
@@ -65,12 +64,7 @@ import { getConfig } from "../config/loader.js";
|
|
|
65
64
|
import type { AssistantConfig } from "../config/schema.js";
|
|
66
65
|
import { HOOKS } from "../plugin-api/constants.js";
|
|
67
66
|
import { registerDefaultPlugins } from "../plugins/defaults/index.js";
|
|
68
|
-
import { installPluginRuntime } from "../plugins/external-api.js";
|
|
69
67
|
import { buildExternalPlugin } from "../plugins/external-plugin-loader.js";
|
|
70
|
-
import {
|
|
71
|
-
registerPluginSkills,
|
|
72
|
-
unregisterPluginSkills,
|
|
73
|
-
} from "../plugins/plugin-skill-contributions.js";
|
|
74
68
|
import {
|
|
75
69
|
getRegisteredPlugin,
|
|
76
70
|
getRegisteredPlugins,
|
|
@@ -81,7 +75,6 @@ import {
|
|
|
81
75
|
type Plugin,
|
|
82
76
|
PluginExecutionError,
|
|
83
77
|
type PluginShutdownContext,
|
|
84
|
-
type PluginSkillRegistration,
|
|
85
78
|
} from "../plugins/types.js";
|
|
86
79
|
import { loadUserPlugins } from "../plugins/user-loader.js";
|
|
87
80
|
import {
|
|
@@ -187,21 +180,17 @@ function getDisabledPluginFlag(
|
|
|
187
180
|
* Bring the plugin layer up during daemon startup. Runs the full sequence in
|
|
188
181
|
* the one order the rest of the system depends on:
|
|
189
182
|
*
|
|
190
|
-
* 1.
|
|
191
|
-
* it from their module body (see `plugins/external-api.ts` — compiled-binary
|
|
192
|
-
* module identity).
|
|
193
|
-
* 2. Register the first-party defaults so their middleware composes innermost,
|
|
183
|
+
* 1. Register the first-party defaults so their middleware composes innermost,
|
|
194
184
|
* ahead of any user plugins.
|
|
195
|
-
*
|
|
185
|
+
* 2. Load user plugins from `<workspaceDir>/plugins/*`. A failing user plugin is
|
|
196
186
|
* logged and skipped; `loadUserPlugins()` closes the registration window
|
|
197
187
|
* when it returns, so the defaults must already be registered by then.
|
|
198
|
-
*
|
|
188
|
+
* 3. Run every registered plugin's `init()` via {@link bootstrapPlugins}.
|
|
199
189
|
*
|
|
200
190
|
* Plugin bootstrap is wrapped so a failing plugin cannot block daemon startup —
|
|
201
191
|
* the daemon comes up with degraded plugin functionality instead.
|
|
202
192
|
*/
|
|
203
193
|
export async function initializePlugins(): Promise<void> {
|
|
204
|
-
installPluginRuntime();
|
|
205
194
|
registerDefaultPlugins();
|
|
206
195
|
await loadUserPlugins();
|
|
207
196
|
try {
|
|
@@ -218,9 +207,10 @@ export async function initializePlugins(): Promise<void> {
|
|
|
218
207
|
* Run every registered plugin's `init()` hook sequentially and install a
|
|
219
208
|
* reverse-order shutdown hook. See the module docstring for full semantics.
|
|
220
209
|
*
|
|
221
|
-
* Returns once every plugin has
|
|
222
|
-
*
|
|
223
|
-
* the
|
|
210
|
+
* Returns once every plugin has been processed. A plugin whose `init()` throws
|
|
211
|
+
* is rolled back, dropped from the registry, and logged; bootstrap continues
|
|
212
|
+
* with the rest, so one failing plugin never blocks the others — or the
|
|
213
|
+
* first-party defaults — from coming up.
|
|
224
214
|
*
|
|
225
215
|
* Must be called after any custom/third-party plugin registrations have run
|
|
226
216
|
* and before the first conversation is served. First-party defaults are
|
|
@@ -259,35 +249,14 @@ export async function bootstrapPlugins(): Promise<void> {
|
|
|
259
249
|
// onShutdown" invariant.
|
|
260
250
|
const activePlugins: ActivePlugin[] = [];
|
|
261
251
|
|
|
262
|
-
// Tear down a plugin's contributions AND remove it from the registry. The
|
|
263
|
-
// two steps always move together on the bootstrap failure path: the former
|
|
264
|
-
// clears tools/routes/skills (so they stop appearing to the model/HTTP
|
|
265
|
-
// server), the latter drops the plugin's entry from the Map (so the registry
|
|
266
|
-
// doesn't expose an uninitialized plugin's hooks).
|
|
267
252
|
// Shutdown context is identical for every plugin in this boot — construct
|
|
268
|
-
// once and reuse across the
|
|
269
|
-
//
|
|
270
|
-
//
|
|
253
|
+
// once and reuse across the per-plugin teardown and the normal shutdown
|
|
254
|
+
// hook below. Only `assistantVersion` is exposed today; future additions
|
|
255
|
+
// live on {@link PluginShutdownContext}.
|
|
271
256
|
const shutdownContext: PluginShutdownContext = {
|
|
272
257
|
assistantVersion: APP_VERSION,
|
|
273
258
|
};
|
|
274
259
|
|
|
275
|
-
async function rollbackPlugin(active: ActivePlugin): Promise<void> {
|
|
276
|
-
await teardownPlugin(active, "bootstrap-failed", shutdownContext);
|
|
277
|
-
unregisterPlugin(active.plugin.manifest.name);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// If one plugin's init or contribution phase throws, tear down any plugins
|
|
281
|
-
// that already fully initialized (in reverse registration order) before
|
|
282
|
-
// re-throwing. Without this, a mid-loop failure would leave earlier plugins
|
|
283
|
-
// with live tools/routes/skills and no `onShutdown()` call — the shutdown
|
|
284
|
-
// hook is only registered once the loop completes successfully.
|
|
285
|
-
async function teardownPartialInit(): Promise<void> {
|
|
286
|
-
for (let i = activePlugins.length - 1; i >= 0; i--) {
|
|
287
|
-
await rollbackPlugin(activePlugins[i]!);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
260
|
for (const plugin of plugins) {
|
|
292
261
|
const name = plugin.manifest.name;
|
|
293
262
|
const disabledFlag = getDisabledPluginFlag(plugin, assistantConfig);
|
|
@@ -303,9 +272,18 @@ export async function bootstrapPlugins(): Promise<void> {
|
|
|
303
272
|
try {
|
|
304
273
|
activePlugins.push(await initializePlugin(plugin, assistantConfig));
|
|
305
274
|
} catch (err) {
|
|
275
|
+
// Contain the failure to this plugin. `initializePlugin` already rolled
|
|
276
|
+
// back its own partial tool/route contributions, so we just drop
|
|
277
|
+
// it from the registry and move on. A single plugin's init failure must
|
|
278
|
+
// never deregister the plugins that already came up — above all the
|
|
279
|
+
// first-party defaults, which carry core turn behavior. The daemon stays
|
|
280
|
+
// reachable with the failing plugin absent rather than losing the whole
|
|
281
|
+
// plugin layer.
|
|
306
282
|
unregisterPlugin(name);
|
|
307
|
-
|
|
308
|
-
|
|
283
|
+
log.warn(
|
|
284
|
+
{ err, plugin: name },
|
|
285
|
+
`plugin ${name} failed to initialize — skipping (continuing with remaining plugins)`,
|
|
286
|
+
);
|
|
309
287
|
}
|
|
310
288
|
}
|
|
311
289
|
|
|
@@ -323,12 +301,6 @@ export async function bootstrapPlugins(): Promise<void> {
|
|
|
323
301
|
// 3. Call `onShutdown()` (if defined) so the plugin can release resources
|
|
324
302
|
// (background tasks, timers, connections) with its tools and routes
|
|
325
303
|
// already removed.
|
|
326
|
-
// 4. Unregister contributed skills via the ref-counted helper. Skills tear
|
|
327
|
-
// down last so `onShutdown()` can still invoke skill-resolving code
|
|
328
|
-
// (e.g. to flush pending skill work) before the catalog is emptied.
|
|
329
|
-
// This mirrors the symmetry of registerPluginSkills() — every
|
|
330
|
-
// successful registration must get a matching unregister call,
|
|
331
|
-
// regardless of whether onShutdown throws.
|
|
332
304
|
const shutdownSnapshot: ActivePlugin[] = [...activePlugins];
|
|
333
305
|
registerShutdownHook("plugins", async (reason) => {
|
|
334
306
|
for (let i = shutdownSnapshot.length - 1; i >= 0; i--) {
|
|
@@ -404,23 +376,6 @@ async function initializePlugin(
|
|
|
404
376
|
);
|
|
405
377
|
}
|
|
406
378
|
|
|
407
|
-
if (plugin.skills && plugin.skills.length > 0) {
|
|
408
|
-
try {
|
|
409
|
-
registerPluginSkills(
|
|
410
|
-
name,
|
|
411
|
-
plugin.skills as readonly PluginSkillRegistration[],
|
|
412
|
-
);
|
|
413
|
-
} catch (err) {
|
|
414
|
-
throw new PluginExecutionError(
|
|
415
|
-
`plugin ${name} skill registration failed: ${
|
|
416
|
-
err instanceof Error ? err.message : String(err)
|
|
417
|
-
}`,
|
|
418
|
-
name,
|
|
419
|
-
{ cause: err },
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
379
|
if (plugin.hooks?.[HOOKS.INIT]) {
|
|
425
380
|
try {
|
|
426
381
|
await plugin.hooks[HOOKS.INIT](initContext);
|
|
@@ -448,9 +403,6 @@ async function initializePlugin(
|
|
|
448
403
|
unregisterSkillRoute(handle);
|
|
449
404
|
}
|
|
450
405
|
unregisterPluginTools(name);
|
|
451
|
-
if (plugin.skills && plugin.skills.length > 0) {
|
|
452
|
-
unregisterPluginSkills(name);
|
|
453
|
-
}
|
|
454
406
|
}
|
|
455
407
|
throw err;
|
|
456
408
|
}
|
|
@@ -458,9 +410,9 @@ async function initializePlugin(
|
|
|
458
410
|
|
|
459
411
|
/**
|
|
460
412
|
* Tear down a single fully-initialized plugin: unregister routes, unregister
|
|
461
|
-
* tools, invoke `onShutdown()` if present
|
|
462
|
-
*
|
|
463
|
-
*
|
|
413
|
+
* tools, then invoke `onShutdown()` if present. Every step swallows errors and
|
|
414
|
+
* logs with plugin attribution so one bad plugin can't block teardown of the
|
|
415
|
+
* rest.
|
|
464
416
|
*
|
|
465
417
|
* Shared between the normal shutdown hook and the bootstrap error path; both
|
|
466
418
|
* consume plugins that already cleared every contribution step.
|
|
@@ -514,17 +466,6 @@ async function teardownPlugin(
|
|
|
514
466
|
);
|
|
515
467
|
}
|
|
516
468
|
}
|
|
517
|
-
|
|
518
|
-
if (plugin.skills && plugin.skills.length > 0) {
|
|
519
|
-
try {
|
|
520
|
-
unregisterPluginSkills(name);
|
|
521
|
-
} catch (err) {
|
|
522
|
-
log.warn(
|
|
523
|
-
{ err, plugin: name, reason },
|
|
524
|
-
"plugin skill unregistration failed (continuing with remaining plugins)",
|
|
525
|
-
);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
469
|
}
|
|
529
470
|
|
|
530
471
|
/** Rebuild a changed external plugin and swap it into the live registry. */
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from "../../runtime/channel-verification-service.js";
|
|
32
32
|
import {
|
|
33
33
|
cancelOutbound,
|
|
34
|
+
deliverVerificationEmail,
|
|
34
35
|
deliverVerificationSlack,
|
|
35
36
|
deliverVerificationTelegram,
|
|
36
37
|
DESTINATION_RATE_WINDOW_MS,
|
|
@@ -555,7 +556,11 @@ export async function handleChannelVerificationSession(
|
|
|
555
556
|
const { userId, text, assistantId: aid } = result._pendingSlackDm;
|
|
556
557
|
deliverVerificationSlack(userId, text, aid);
|
|
557
558
|
}
|
|
558
|
-
|
|
559
|
+
if (result._pendingEmail) {
|
|
560
|
+
const { to, text, subject, assistantId: aid } = result._pendingEmail;
|
|
561
|
+
deliverVerificationEmail(to, text, subject, aid);
|
|
562
|
+
}
|
|
563
|
+
const { _pendingSlackDm: _, _pendingEmail: __, ...publicResult } = result;
|
|
559
564
|
broadcastMessage({
|
|
560
565
|
type: "channel_verification_session_response",
|
|
561
566
|
...publicResult,
|
|
@@ -600,7 +605,11 @@ export async function handleChannelVerificationSession(
|
|
|
600
605
|
const { userId, text, assistantId: aid } = result._pendingSlackDm;
|
|
601
606
|
deliverVerificationSlack(userId, text, aid);
|
|
602
607
|
}
|
|
603
|
-
|
|
608
|
+
if (result._pendingEmail) {
|
|
609
|
+
const { to, text, subject, assistantId: aid } = result._pendingEmail;
|
|
610
|
+
deliverVerificationEmail(to, text, subject, aid);
|
|
611
|
+
}
|
|
612
|
+
const { _pendingSlackDm: _, _pendingEmail: __, ...publicResult } = result;
|
|
604
613
|
broadcastMessage({
|
|
605
614
|
type: "channel_verification_session_response",
|
|
606
615
|
...publicResult,
|
|
@@ -428,7 +428,15 @@ export function renderHistoryContent(
|
|
|
428
428
|
finalizeSegment();
|
|
429
429
|
thinkingSegments.push(block.thinking);
|
|
430
430
|
contentOrder.push(`thinking:${thinkingSegments.length - 1}`);
|
|
431
|
-
|
|
431
|
+
const thinkingBlock: Extract<
|
|
432
|
+
ConversationContentBlock,
|
|
433
|
+
{ type: "thinking" }
|
|
434
|
+
> = { type: "thinking", thinking: block.thinking };
|
|
435
|
+
if (typeof block._startedAt === "number")
|
|
436
|
+
thinkingBlock.startedAt = block._startedAt;
|
|
437
|
+
if (typeof block._completedAt === "number")
|
|
438
|
+
thinkingBlock.completedAt = block._completedAt;
|
|
439
|
+
contentBlocks.push(thinkingBlock);
|
|
432
440
|
continue;
|
|
433
441
|
}
|
|
434
442
|
|
|
@@ -314,9 +314,10 @@ function deriveKind(
|
|
|
314
314
|
): SlimSkillResponse["kind"] {
|
|
315
315
|
if (source === "bundled") return "bundled";
|
|
316
316
|
if (source === "catalog") return "catalog";
|
|
317
|
-
// Plugin-
|
|
317
|
+
// Plugin-resident skills are framework-provided like bundled skills —
|
|
318
318
|
// expose them under the same "bundled" kind so the UI doesn't invent a
|
|
319
|
-
// new category that existing clients don't know how to render.
|
|
319
|
+
// new category that existing clients don't know how to render. Attribution
|
|
320
|
+
// to the owning plugin rides on the separate `owner` descriptor.
|
|
320
321
|
if (source === "plugin") return "bundled";
|
|
321
322
|
return "installed"; // managed, workspace, extra
|
|
322
323
|
}
|
|
@@ -349,7 +350,8 @@ function toSlimSkillResponse(
|
|
|
349
350
|
const origin = deriveOrigin(kind, summary.directoryPath, installMeta);
|
|
350
351
|
const status: SlimSkillResponse["status"] = state;
|
|
351
352
|
|
|
352
|
-
const category =
|
|
353
|
+
const category =
|
|
354
|
+
getCatalogCategoryMap().get(summary.id) ?? summary.category ?? "system";
|
|
353
355
|
const base = {
|
|
354
356
|
id: summary.id,
|
|
355
357
|
name: summary.displayName,
|
|
@@ -359,6 +361,7 @@ function toSlimSkillResponse(
|
|
|
359
361
|
kind,
|
|
360
362
|
status,
|
|
361
363
|
category,
|
|
364
|
+
owner: summary.owner,
|
|
362
365
|
} as const;
|
|
363
366
|
|
|
364
367
|
switch (origin) {
|
|
@@ -701,7 +704,9 @@ export async function getSkill(
|
|
|
701
704
|
return { skill: detail };
|
|
702
705
|
}
|
|
703
706
|
|
|
704
|
-
// vellum or custom origin — base fields
|
|
707
|
+
// vellum or custom origin — base fields plus owner attribution. Plugin-
|
|
708
|
+
// resident skills are mapped to the vellum origin, so `owner` is how their
|
|
709
|
+
// `{ kind: "plugin", id }` provenance is surfaced on the detail response.
|
|
705
710
|
const detail: SkillDetailResponse = {
|
|
706
711
|
id: slim.id,
|
|
707
712
|
name: slim.name,
|
|
@@ -712,6 +717,7 @@ export async function getSkill(
|
|
|
712
717
|
origin: slim.origin,
|
|
713
718
|
status: slim.status,
|
|
714
719
|
category: slim.category,
|
|
720
|
+
owner: slim.owner,
|
|
715
721
|
};
|
|
716
722
|
return { skill: detail };
|
|
717
723
|
}
|
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
* translation on top.
|
|
13
13
|
*
|
|
14
14
|
* **Session lock.** Only one conversation may hold an active app-control
|
|
15
|
-
* session at a time, and that session is bound to a specific target app
|
|
15
|
+
* session at a time, and that session is bound to a specific target app
|
|
16
|
+
* on a specific client.
|
|
16
17
|
* The lock is module-level (`activeAppControlSession`) because the session
|
|
17
18
|
* targets the user's actual desktop application, which is a host-wide
|
|
18
19
|
* resource. It is acquired optimistically when `app_control_start` is
|
|
19
|
-
* dispatched (storing `(conversationId, app)`) so that the synchronous
|
|
20
|
+
* dispatched (storing `(conversationId, app, targetClientId)`) so that the synchronous
|
|
20
21
|
* guard and the asynchronous host round-trip cannot race. A separate
|
|
21
22
|
* `confirmedAppControlSession` tracks the last session the host reported
|
|
22
23
|
* `running`; the rollback path on a failed `start` restores from the
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
* user's medium-risk approval at start time is the consent boundary. All
|
|
33
34
|
* other tools (observe / press / combo / sequence / type / click / drag)
|
|
34
35
|
* require the calling conversation to own an active session targeting the
|
|
35
|
-
* same `app
|
|
36
|
+
* same `app` and client; otherwise the call is rejected before any host dispatch.
|
|
36
37
|
* This prevents prompt-injected tool calls from sending raw input to
|
|
37
38
|
* arbitrary apps without the user having approved control of that
|
|
38
39
|
* specific app.
|
|
@@ -78,9 +79,9 @@ const STUCK_REPEAT_THRESHOLD = 4;
|
|
|
78
79
|
// ---------------------------------------------------------------------------
|
|
79
80
|
|
|
80
81
|
/**
|
|
81
|
-
* Active app-control session: the conversation that owns the lock
|
|
82
|
-
* `app` it was approved against. Set on a successful
|
|
83
|
-
* cleared by the owning proxy's `dispose()`.
|
|
82
|
+
* Active app-control session: the conversation that owns the lock, the
|
|
83
|
+
* `app` and client it was approved against. Set on a successful
|
|
84
|
+
* `app_control_start`; cleared by the owning proxy's `dispose()`.
|
|
84
85
|
*/
|
|
85
86
|
export interface ActiveAppControlSession {
|
|
86
87
|
conversationId: string;
|
|
@@ -90,6 +91,11 @@ export interface ActiveAppControlSession {
|
|
|
90
91
|
* the `app` of subsequent non-start tool calls.
|
|
91
92
|
*/
|
|
92
93
|
app: string;
|
|
94
|
+
/**
|
|
95
|
+
* The client selected for `app_control_start`. Undefined when the start
|
|
96
|
+
* was untargeted; subsequent calls must match the same value.
|
|
97
|
+
*/
|
|
98
|
+
targetClientId?: string;
|
|
93
99
|
/**
|
|
94
100
|
* Strictly monotonic counter assigned when the session is created (in
|
|
95
101
|
* `request()` for a `start`). Used by {@link promoteStartIfCurrent} to
|
|
@@ -153,11 +159,13 @@ export function _resetActiveAppControlSession(): void {
|
|
|
153
159
|
export function _setActiveAppControlSession(session: {
|
|
154
160
|
conversationId: string;
|
|
155
161
|
app: string;
|
|
162
|
+
targetClientId?: string;
|
|
156
163
|
dispatchedAt?: number;
|
|
157
164
|
}): void {
|
|
158
165
|
const full: ActiveAppControlSession = {
|
|
159
166
|
conversationId: session.conversationId,
|
|
160
167
|
app: session.app,
|
|
168
|
+
targetClientId: session.targetClientId,
|
|
161
169
|
dispatchedAt: session.dispatchedAt ?? nextDispatchedAt++,
|
|
162
170
|
};
|
|
163
171
|
activeAppControlSession = full;
|
|
@@ -177,6 +185,7 @@ export function _setActiveAppControlSession(session: {
|
|
|
177
185
|
function checkNonStartAuthorization(
|
|
178
186
|
input: HostAppControlInput,
|
|
179
187
|
conversationId: string,
|
|
188
|
+
targetClientId: string | undefined,
|
|
180
189
|
): ToolExecutionResult | null {
|
|
181
190
|
if (activeAppControlSession == null) {
|
|
182
191
|
return {
|
|
@@ -219,6 +228,16 @@ function checkNonStartAuthorization(
|
|
|
219
228
|
isError: true,
|
|
220
229
|
};
|
|
221
230
|
}
|
|
231
|
+
if (activeAppControlSession.targetClientId !== targetClientId) {
|
|
232
|
+
return {
|
|
233
|
+
content:
|
|
234
|
+
`Active app-control session targets client ` +
|
|
235
|
+
`${activeAppControlSession.targetClientId ?? "<default>"}; cannot ` +
|
|
236
|
+
`send actions to client ${targetClientId ?? "<default>"}. Call ` +
|
|
237
|
+
`app_control_stop and app_control_start to switch clients.`,
|
|
238
|
+
isError: true,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
222
241
|
return null;
|
|
223
242
|
}
|
|
224
243
|
|
|
@@ -284,11 +303,53 @@ export class HostAppControlProxy extends HostProxyBase<
|
|
|
284
303
|
return { content: "Aborted", isError: true };
|
|
285
304
|
}
|
|
286
305
|
|
|
306
|
+
// Resolve target client and enforce same-actor BEFORE acquiring the
|
|
307
|
+
// session lock. This way early-exit errors don't need rollback, and
|
|
308
|
+
// the resolved client id can be stored in the session.
|
|
309
|
+
let resolvedTargetClientId = targetClientId;
|
|
310
|
+
if (resolvedTargetClientId == null) {
|
|
311
|
+
const resolved = pickSameUserAutoResolve({
|
|
312
|
+
hub: assistantEventHub,
|
|
313
|
+
capability: "host_app_control",
|
|
314
|
+
sourceActorPrincipalId,
|
|
315
|
+
});
|
|
316
|
+
if (resolved.kind === "ambiguous") {
|
|
317
|
+
return {
|
|
318
|
+
content:
|
|
319
|
+
"Multiple host_app_control clients are connected for this user. Specify target_client_id to disambiguate.",
|
|
320
|
+
isError: true,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
if (resolved.kind === "match") {
|
|
324
|
+
resolvedTargetClientId = resolved.clientId;
|
|
325
|
+
} else if (
|
|
326
|
+
assistantEventHub.listClientsByCapability("host_app_control").length > 0
|
|
327
|
+
) {
|
|
328
|
+
return {
|
|
329
|
+
content:
|
|
330
|
+
"App control is not available for the current actor. Connect a host_app_control-capable client as the same user.",
|
|
331
|
+
isError: true,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (resolvedTargetClientId != null) {
|
|
337
|
+
const rejection = enforceSameActorOrErrorResult({
|
|
338
|
+
hub: assistantEventHub,
|
|
339
|
+
sourceActorPrincipalId,
|
|
340
|
+
targetClientId: resolvedTargetClientId,
|
|
341
|
+
op: "host_app_control",
|
|
342
|
+
});
|
|
343
|
+
if (rejection) {
|
|
344
|
+
return rejection;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
287
348
|
// Authorization gate. `start` acquires the session lock (the user's
|
|
288
349
|
// medium-risk approval is the consent boundary); all other tools must
|
|
289
|
-
// belong to the active session and target the same `app
|
|
290
|
-
// gate, prompt-injected calls would bypass the start-time
|
|
291
|
-
// send raw input to arbitrary apps.
|
|
350
|
+
// belong to the active session and target the same `app` and client.
|
|
351
|
+
// Without this gate, prompt-injected calls would bypass the start-time
|
|
352
|
+
// approval and send raw input to arbitrary apps or clients.
|
|
292
353
|
let attemptedSession: ActiveAppControlSession | undefined;
|
|
293
354
|
if (input.tool === "start") {
|
|
294
355
|
if (
|
|
@@ -314,6 +375,7 @@ export class HostAppControlProxy extends HostProxyBase<
|
|
|
314
375
|
attemptedSession = {
|
|
315
376
|
conversationId: this.conversationId,
|
|
316
377
|
app: input.app,
|
|
378
|
+
targetClientId: resolvedTargetClientId,
|
|
317
379
|
dispatchedAt: nextDispatchedAt++,
|
|
318
380
|
};
|
|
319
381
|
activeAppControlSession = attemptedSession;
|
|
@@ -321,60 +383,13 @@ export class HostAppControlProxy extends HostProxyBase<
|
|
|
321
383
|
const sessionError = checkNonStartAuthorization(
|
|
322
384
|
input,
|
|
323
385
|
this.conversationId,
|
|
386
|
+
resolvedTargetClientId,
|
|
324
387
|
);
|
|
325
388
|
if (sessionError != null) {
|
|
326
389
|
return sessionError;
|
|
327
390
|
}
|
|
328
391
|
}
|
|
329
392
|
|
|
330
|
-
let resolvedTargetClientId = targetClientId;
|
|
331
|
-
if (resolvedTargetClientId == null) {
|
|
332
|
-
const resolved = pickSameUserAutoResolve({
|
|
333
|
-
hub: assistantEventHub,
|
|
334
|
-
capability: "host_app_control",
|
|
335
|
-
sourceActorPrincipalId,
|
|
336
|
-
});
|
|
337
|
-
if (resolved.kind === "ambiguous") {
|
|
338
|
-
if (input.tool === "start") {
|
|
339
|
-
this.rollbackStartIfCurrent(attemptedSession);
|
|
340
|
-
}
|
|
341
|
-
return {
|
|
342
|
-
content:
|
|
343
|
-
"Multiple host_app_control clients are connected for this user. Specify target_client_id to disambiguate.",
|
|
344
|
-
isError: true,
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
if (resolved.kind === "match") {
|
|
348
|
-
resolvedTargetClientId = resolved.clientId;
|
|
349
|
-
} else if (
|
|
350
|
-
assistantEventHub.listClientsByCapability("host_app_control").length > 0
|
|
351
|
-
) {
|
|
352
|
-
if (input.tool === "start") {
|
|
353
|
-
this.rollbackStartIfCurrent(attemptedSession);
|
|
354
|
-
}
|
|
355
|
-
return {
|
|
356
|
-
content:
|
|
357
|
-
"App control is not available for the current actor. Connect a host_app_control-capable client as the same user.",
|
|
358
|
-
isError: true,
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
if (resolvedTargetClientId != null) {
|
|
364
|
-
const rejection = enforceSameActorOrErrorResult({
|
|
365
|
-
hub: assistantEventHub,
|
|
366
|
-
sourceActorPrincipalId,
|
|
367
|
-
targetClientId: resolvedTargetClientId,
|
|
368
|
-
op: "host_app_control",
|
|
369
|
-
});
|
|
370
|
-
if (rejection) {
|
|
371
|
-
if (input.tool === "start") {
|
|
372
|
-
this.rollbackStartIfCurrent(attemptedSession);
|
|
373
|
-
}
|
|
374
|
-
return rejection;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
393
|
try {
|
|
379
394
|
const payload = await this.dispatchRequest(
|
|
380
395
|
toolName,
|