@vellumai/assistant 0.6.5 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -1
- package/ARCHITECTURE.md +15 -17
- package/Dockerfile +6 -4
- package/__tests__/permissions/gateway-threshold-reader.test.ts +283 -0
- package/docs/architecture/integrations.md +32 -39
- package/docs/architecture/memory.md +25 -30
- package/docs/architecture/security.md +7 -6
- package/docs/browser-use-architecture-phase2.md +63 -20
- package/docs/plugins.md +761 -0
- package/examples/plugins/echo/README.md +132 -0
- package/examples/plugins/echo/package.json +17 -0
- package/examples/plugins/echo/register.ts +187 -0
- package/node_modules/@vellumai/egress-proxy/src/types.ts +19 -0
- package/openapi.yaml +212 -68
- package/package.json +1 -1
- package/src/__tests__/app-compiler.test.ts +57 -0
- package/src/__tests__/approval-cascade.test.ts +7 -2
- package/src/__tests__/auto-analysis-end-to-end.test.ts +1 -0
- package/src/__tests__/avatar-generator.test.ts +4 -2
- package/src/__tests__/bundled-asset.test.ts +6 -6
- package/src/__tests__/catalog-cache.test.ts +69 -0
- package/src/__tests__/checker.test.ts +459 -171
- package/src/__tests__/circuit-breaker-pipeline.test.ts +406 -0
- package/src/__tests__/compaction-events.test.ts +501 -0
- package/src/__tests__/compaction-pipeline.test.ts +210 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +181 -0
- package/src/__tests__/compaction-timeout-recovery.test.ts +262 -0
- package/src/__tests__/config-model-image-provider.test.ts +110 -0
- package/src/__tests__/config-schema.test.ts +22 -9
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -4
- package/src/__tests__/contacts-tools.test.ts +26 -0
- package/src/__tests__/context-overflow-policy.test.ts +7 -7
- package/src/__tests__/context-window-manager.test.ts +355 -4
- package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +26 -30
- package/src/__tests__/conversation-agent-loop.test.ts +30 -141
- package/src/__tests__/conversation-confirmation-signals.test.ts +6 -1
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +2 -16
- package/src/__tests__/conversation-pairing.test.ts +174 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +4 -1
- package/src/__tests__/conversation-process-callsite.test.ts +3 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +16 -7
- package/src/__tests__/conversation-queue.test.ts +29 -14
- package/src/__tests__/conversation-routes-disk-view.test.ts +7 -6
- package/src/__tests__/conversation-runtime-assembly.test.ts +155 -110
- package/src/__tests__/conversation-runtime-workspace.test.ts +23 -38
- package/src/__tests__/conversation-seed-composer.test.ts +2 -2
- package/src/__tests__/conversation-slash-queue.test.ts +7 -2
- package/src/__tests__/conversation-slash-unknown.test.ts +25 -2
- package/src/__tests__/conversation-speed-override.test.ts +6 -1
- package/src/__tests__/conversation-title-service.test.ts +116 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +41 -2
- package/src/__tests__/conversation-usage.test.ts +1 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +4 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +3 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -1
- package/src/__tests__/credential-health-service.test.ts +78 -9
- package/src/__tests__/credential-security-invariants.test.ts +2 -2
- package/src/__tests__/db-schedule-syntax-migration.test.ts +1 -0
- package/src/__tests__/empty-response-pipeline.test.ts +305 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +3 -3
- package/src/__tests__/first-greeting.test.ts +247 -5
- package/src/__tests__/headless-browser-mode.test.ts +57 -0
- package/src/__tests__/history-repair-pipeline.test.ts +399 -0
- package/src/__tests__/host-browser-e2e-cloud.test.ts +307 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +3 -3
- package/src/__tests__/host-proxy-interface.test.ts +36 -2
- package/src/__tests__/image-credentials.test.ts +137 -0
- package/src/__tests__/image-service-dispatcher.test.ts +186 -0
- package/src/__tests__/injector-chain.test.ts +526 -0
- package/src/__tests__/intent-routing.test.ts +0 -26
- package/src/__tests__/llm-call-pipeline.test.ts +285 -0
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/media-generate-image.test.ts +119 -13
- package/src/__tests__/memory-retrieval-pipeline.test.ts +401 -0
- package/src/__tests__/memory-upsert-concurrency.test.ts +1 -0
- package/src/__tests__/migration-import-from-url.test.ts +5 -68
- package/src/__tests__/model-intents.test.ts +4 -2
- package/src/__tests__/notification-broadcaster.test.ts +3 -3
- package/src/__tests__/notification-decision-strategy.test.ts +0 -11
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +108 -0
- package/src/__tests__/oauth-apps-routes.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +14 -12
- package/src/__tests__/oauth-connect-orchestrator.test.ts +4 -13
- package/src/__tests__/oauth-provider-serializer.test.ts +6 -4
- package/src/__tests__/oauth-provider-visibility.test.ts +3 -5
- package/src/__tests__/oauth-providers-routes.test.ts +3 -2
- package/src/__tests__/oauth-store.test.ts +41 -76
- package/src/__tests__/onboarding-template-contract.test.ts +16 -64
- package/src/__tests__/openai-image-service.test.ts +368 -0
- package/src/__tests__/overflow-reduce-pipeline.test.ts +676 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +0 -24
- package/src/__tests__/persist-onboarding-artifacts.test.ts +266 -0
- package/src/__tests__/persistence-pipeline.test.ts +377 -0
- package/src/__tests__/pipeline-runner.test.ts +565 -0
- package/src/__tests__/platform.test.ts +5 -2
- package/src/__tests__/plugin-bootstrap.test.ts +483 -0
- package/src/__tests__/plugin-registry.test.ts +273 -0
- package/src/__tests__/plugin-route-contribution.test.ts +288 -0
- package/src/__tests__/plugin-skill-contribution.test.ts +367 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +286 -0
- package/src/__tests__/plugin-types.test.ts +320 -0
- package/src/__tests__/pricing.test.ts +44 -12
- package/src/__tests__/proxy-approval-callback.test.ts +69 -8
- package/src/__tests__/reaction-persistence.test.ts +1 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
- package/src/__tests__/registry.test.ts +0 -2
- package/src/__tests__/schedule-routes.test.ts +131 -1
- package/src/__tests__/scheduler-recurrence.test.ts +14 -70
- package/src/__tests__/scheduler-reuse-conversation.test.ts +10 -50
- package/src/__tests__/secret-detection-handler.test.ts +0 -10
- package/src/__tests__/shell-identity.test.ts +0 -134
- package/src/__tests__/suggestion-routes.test.ts +103 -4
- package/src/__tests__/task-memory-cleanup.test.ts +1 -0
- package/src/__tests__/task-scheduler.test.ts +3 -15
- package/src/__tests__/test-preload.ts +11 -0
- package/src/__tests__/title-generate-pipeline.test.ts +224 -0
- package/src/__tests__/token-estimate-pipeline.test.ts +431 -0
- package/src/__tests__/tool-error-pipeline.test.ts +244 -0
- package/src/__tests__/tool-execute-pipeline.test.ts +431 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -6
- package/src/__tests__/tool-executor-shell-integration.test.ts +7 -10
- package/src/__tests__/tool-executor.test.ts +141 -0
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +356 -0
- package/src/__tests__/tool-result-truncation.test.ts +0 -110
- package/src/__tests__/user-plugin-loader.test.ts +191 -0
- package/src/__tests__/workspace-migration-046-seed-conversation-starters-callsite.test.ts +185 -0
- package/src/__tests__/workspace-migration-049-release-notes-default-sonnet.test.ts +100 -0
- package/src/__tests__/workspace-migration-050-seed-main-agent-opus-callsite.test.ts +171 -0
- package/src/__tests__/workspace-migration-051-seed-conversation-summarization-callsite.test.ts +252 -0
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +99 -0
- package/src/__tests__/workspace-policy.test.ts +21 -3
- package/src/agent/loop.ts +340 -102
- package/src/approvals/__tests__/guardian-feed-event.test.ts +304 -0
- package/src/approvals/guardian-request-resolvers.ts +80 -0
- package/src/backup/__tests__/backup-worker.test.ts +2 -13
- package/src/backup/backup-worker.ts +3 -15
- package/src/bundler/app-compiler.ts +84 -1
- package/src/calls/call-state.ts +2 -2
- package/src/channels/__tests__/types.test.ts +3 -3
- package/src/channels/types.ts +6 -4
- package/src/cli/__tests__/notifications.test.ts +87 -211
- package/src/cli/commands/__tests__/backup.test.ts +1 -1
- package/src/cli/commands/__tests__/image-generation.test.ts +255 -35
- package/src/cli/commands/__tests__/inference-send.test.ts +12 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +12 -0
- package/src/cli/commands/backup.ts +2 -2
- package/src/cli/commands/clients.ts +138 -0
- package/src/cli/commands/completions.ts +2 -9
- package/src/cli/commands/conversations.ts +55 -7
- package/src/cli/commands/image-generation.ts +33 -34
- package/src/cli/commands/notifications.ts +68 -103
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -1
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
- package/src/cli/commands/oauth/connect.ts +2 -2
- package/src/cli/commands/oauth/providers.ts +176 -8
- package/src/cli/commands/oauth/status.ts +46 -36
- package/src/cli/commands/skills.ts +3 -4
- package/src/cli/program.ts +25 -29
- package/src/config/__tests__/backup-schema.test.ts +7 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +10 -10
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +66 -87
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +28 -51
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +22 -40
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -1
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +23 -39
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +207 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +12 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +58 -0
- package/src/config/bundled-skills/schedule/SKILL.md +8 -3
- package/src/config/bundled-skills/schedule/TOOLS.json +15 -7
- package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +59 -0
- package/src/config/bundled-tool-registry.ts +0 -15
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/schema.ts +19 -0
- package/src/config/schemas/backup.ts +1 -1
- package/src/config/schemas/conversations.ts +16 -0
- package/src/config/schemas/llm.ts +2 -3
- package/src/config/schemas/security.ts +6 -6
- package/src/config/schemas/tts.ts +11 -0
- package/src/config/skill-state.ts +6 -2
- package/src/config/skills.ts +94 -5
- package/src/context/__tests__/compact-prompt.test.ts +27 -9
- package/src/context/prompts/compact.md +26 -12
- package/src/context/tool-result-truncation.ts +3 -63
- package/src/context/window-manager.ts +190 -16
- package/src/credential-health/credential-health-service.ts +19 -6
- package/src/daemon/__tests__/conversation-feed-event.test.ts +317 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +4 -12
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +14 -15
- package/src/daemon/config-watcher.ts +0 -2
- package/src/daemon/context-overflow-policy.ts +4 -13
- package/src/daemon/conversation-agent-loop-handlers.ts +83 -22
- package/src/daemon/conversation-agent-loop.ts +984 -683
- package/src/daemon/conversation-history.ts +10 -19
- package/src/daemon/conversation-lifecycle.ts +37 -19
- package/src/daemon/conversation-notifiers.ts +2 -110
- package/src/daemon/conversation-process.ts +14 -7
- package/src/daemon/conversation-runtime-assembly.ts +532 -411
- package/src/daemon/conversation-tool-setup.ts +41 -4
- package/src/daemon/conversation.ts +80 -35
- package/src/daemon/external-plugins-bootstrap.ts +478 -0
- package/src/daemon/first-greeting.ts +191 -14
- package/src/daemon/handlers/config-model.ts +11 -0
- package/src/daemon/handlers/skills.ts +5 -1
- package/src/daemon/lifecycle.ts +33 -68
- package/src/daemon/message-types/computer-use.ts +2 -34
- package/src/daemon/message-types/conversations.ts +49 -0
- package/src/daemon/message-types/messages.ts +12 -0
- package/src/daemon/server.ts +5 -3
- package/src/daemon/shutdown-handlers.ts +2 -12
- package/src/daemon/tool-side-effects.ts +14 -56
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +160 -0
- package/src/heartbeat/heartbeat-service.ts +24 -1
- package/src/home/__tests__/feed-population-integration.test.ts +312 -0
- package/src/home/emit-feed-event.ts +7 -0
- package/src/home/feed-types.ts +41 -2
- package/src/home/rewrite-command-preview.ts +66 -0
- package/src/ipc/__tests__/socket-path.test.ts +11 -50
- package/src/ipc/cli-client.ts +1 -1
- package/src/ipc/cli-server.ts +3 -3
- package/src/ipc/gateway-client.ts +4 -1
- package/src/ipc/routes/browser-context.ts +2 -0
- package/src/ipc/routes/browser.ts +1 -0
- package/src/ipc/routes/get-contact.ts +16 -0
- package/src/ipc/routes/index.ts +14 -0
- package/src/ipc/routes/list-clients.ts +31 -0
- package/src/ipc/routes/merge-contacts.ts +17 -0
- package/src/ipc/routes/notification.ts +133 -0
- package/src/ipc/routes/rename-conversation.ts +59 -0
- package/src/ipc/routes/search-contacts.ts +19 -0
- package/src/ipc/routes/upsert-contact.ts +25 -0
- package/src/ipc/socket-path.ts +14 -38
- package/src/media/app-icon-generator.ts +23 -46
- package/src/media/avatar-router.ts +26 -41
- package/src/media/gemini-image-service.ts +8 -41
- package/src/media/image-credentials.ts +73 -0
- package/src/media/image-service.ts +85 -0
- package/src/media/openai-image-service.ts +131 -0
- package/src/media/types.ts +46 -0
- package/src/memory/conversation-crud.ts +48 -18
- package/src/memory/conversation-queries.ts +57 -4
- package/src/memory/conversation-title-service.ts +25 -0
- package/src/memory/db-init.ts +8 -0
- package/src/memory/embedding-gemini.test.ts +41 -2
- package/src/memory/embedding-gemini.ts +6 -1
- package/src/memory/graph/bootstrap.test.ts +282 -0
- package/src/memory/graph/bootstrap.ts +8 -5
- package/src/memory/graph/extraction.ts +10 -2
- package/src/memory/graph/graph-search.test.ts +1 -0
- package/src/memory/graph/inspect.ts +2 -2
- package/src/memory/graph/retriever.ts +10 -3
- package/src/memory/migrations/041-approval-prompt-ts-tracker.ts +26 -0
- package/src/memory/migrations/149-oauth-tables.ts +1 -0
- package/src/memory/migrations/223-schedule-script-column.ts +11 -0
- package/src/memory/migrations/224-oauth-providers-managed-service-is-paid.ts +24 -0
- package/src/memory/migrations/225-oauth-providers-available-scopes.ts +13 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/pkb/pkb-index.test.ts +1 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +1 -0
- package/src/memory/pkb/pkb-search.test.ts +65 -4
- package/src/memory/pkb/pkb-search.ts +40 -18
- package/src/memory/qdrant-client.test.ts +60 -0
- package/src/memory/qdrant-client.ts +25 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/schema/oauth.ts +4 -1
- package/src/messaging/providers/slack/render-transcript.test.ts +77 -29
- package/src/messaging/providers/slack/render-transcript.ts +58 -0
- package/src/notifications/conversation-pairing.ts +78 -19
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +1 -2
- package/src/oauth/AGENTS.md +1 -1
- package/src/oauth/__tests__/identity-verifier.test.ts +2 -1
- package/src/oauth/connect-orchestrator.ts +8 -34
- package/src/oauth/connect-types.ts +6 -10
- package/src/oauth/manual-token-connection.ts +23 -0
- package/src/oauth/oauth-store.ts +30 -14
- package/src/oauth/provider-serializer.ts +6 -1
- package/src/oauth/seed-providers.ts +56 -108
- package/src/outbound-proxy/http-forwarder.ts +9 -0
- package/src/permissions/approval-policy.test.ts +293 -18
- package/src/permissions/approval-policy.ts +110 -58
- package/src/permissions/arg-parser.test.ts +161 -0
- package/src/permissions/arg-parser.ts +141 -0
- package/src/permissions/bash-risk-classifier.test.ts +414 -2
- package/src/permissions/bash-risk-classifier.ts +303 -60
- package/src/permissions/checker.ts +157 -29
- package/src/permissions/command-registry.test.ts +239 -0
- package/src/permissions/command-registry.ts +234 -54
- package/src/permissions/defaults.ts +5 -4
- package/src/permissions/gateway-threshold-reader.ts +196 -0
- package/src/permissions/prompter.ts +4 -0
- package/src/permissions/risk-types.ts +61 -4
- package/src/permissions/schedule-risk-classifier.test.ts +129 -0
- package/src/permissions/schedule-risk-classifier.ts +85 -0
- package/src/permissions/shell-identity.ts +2 -42
- package/src/permissions/types.ts +2 -0
- package/src/permissions/workspace-policy.ts +8 -3
- package/src/plugins/defaults/circuit-breaker.ts +146 -0
- package/src/plugins/defaults/compaction.ts +145 -0
- package/src/plugins/defaults/empty-response.ts +126 -0
- package/src/plugins/defaults/history-repair.ts +85 -0
- package/src/plugins/defaults/index.ts +116 -0
- package/src/plugins/defaults/injectors.ts +491 -0
- package/src/plugins/defaults/llm-call.ts +82 -0
- package/src/plugins/defaults/memory-retrieval.ts +226 -0
- package/src/plugins/defaults/overflow-reduce.ts +181 -0
- package/src/plugins/defaults/persistence.ts +129 -0
- package/src/plugins/defaults/title-generate.ts +95 -0
- package/src/plugins/defaults/token-estimate.ts +104 -0
- package/src/plugins/defaults/tool-error.ts +126 -0
- package/src/plugins/defaults/tool-execute.ts +89 -0
- package/src/plugins/defaults/tool-result-truncate.ts +88 -0
- package/src/plugins/pipeline.ts +316 -0
- package/src/plugins/plugin-skill-contributions.ts +292 -0
- package/src/plugins/registry.ts +241 -0
- package/src/plugins/types.ts +1134 -0
- package/src/plugins/user-loader.ts +177 -0
- package/src/prompts/templates/BOOTSTRAP.md +27 -77
- package/src/providers/model-catalog.ts +52 -29
- package/src/providers/model-intents.ts +1 -1
- package/src/providers/openrouter/client.ts +5 -1
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +61 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +57 -0
- package/src/providers/speech-to-text/xai-realtime.test.ts +72 -4
- package/src/providers/speech-to-text/xai-realtime.ts +39 -14
- package/src/runtime/AGENTS.md +25 -16
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +3 -3
- package/src/runtime/__tests__/client-registry.test.ts +293 -0
- package/src/runtime/client-registry.ts +261 -0
- package/src/runtime/http-server.ts +77 -8
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/migrations/vbundle-builder.ts +1 -22
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +51 -31
- package/src/runtime/routes/approval-routes.ts +17 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +27 -8
- package/src/runtime/routes/conversation-routes.ts +223 -116
- package/src/runtime/routes/inbound-message-handler.ts +88 -13
- package/src/runtime/routes/memory-item-routes.test.ts +1 -0
- package/src/runtime/routes/migration-routes.ts +0 -3
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +284 -0
- package/src/runtime/routes/playground/__tests__/guard.test.ts +80 -0
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +294 -0
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +271 -0
- package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +202 -0
- package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +309 -0
- package/src/runtime/routes/playground/__tests__/state.test.ts +224 -0
- package/src/runtime/routes/playground/conversation-not-found.ts +29 -0
- package/src/runtime/routes/playground/deps.ts +56 -0
- package/src/runtime/routes/playground/force-compact.ts +73 -0
- package/src/runtime/routes/playground/guard.ts +37 -0
- package/src/runtime/routes/playground/index.ts +28 -0
- package/src/runtime/routes/playground/inject-failures.ts +159 -0
- package/src/runtime/routes/playground/reset-circuit.ts +115 -0
- package/src/runtime/routes/playground/seed-conversation.ts +139 -0
- package/src/runtime/routes/playground/seeded-conversations.ts +78 -0
- package/src/runtime/routes/playground/state.ts +78 -0
- package/src/runtime/routes/schedule-routes.ts +89 -8
- package/src/runtime/skill-route-registry.ts +75 -15
- package/src/schedule/run-script.ts +68 -0
- package/src/schedule/schedule-store.ts +7 -1
- package/src/schedule/scheduler.ts +48 -8
- package/src/skills/catalog-cache.ts +12 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +189 -0
- package/src/tools/browser/browser-execution.ts +88 -19
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +230 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +146 -3
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +54 -3
- package/src/tools/browser/cdp-client/factory.ts +15 -4
- package/src/tools/executor.ts +126 -74
- package/src/tools/network/script-proxy/session-manager.ts +37 -1
- package/src/tools/permission-checker.ts +98 -49
- package/src/tools/policy-context.ts +4 -0
- package/src/tools/registry.ts +140 -3
- package/src/tools/schedule/create.ts +23 -8
- package/src/tools/schedule/update.ts +3 -1
- package/src/tools/secret-detection-handler.ts +0 -51
- package/src/tools/system/avatar-generator.ts +6 -2
- package/src/tools/types.ts +28 -2
- package/src/util/platform.ts +7 -2
- package/src/util/pricing.ts +26 -3
- package/src/workspace/migrations/006-services-config.ts +2 -4
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +2 -3
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +3 -4
- package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +108 -0
- package/src/workspace/migrations/047-remove-watch-callsites.ts +54 -0
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +81 -0
- package/src/workspace/migrations/049-release-notes-default-sonnet.ts +80 -0
- package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +86 -0
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +128 -0
- package/src/workspace/migrations/registry.ts +12 -0
- package/tsconfig.json +1 -1
- package/hook-templates/debug-prompt-logger/hook.json +0 -7
- package/hook-templates/debug-prompt-logger/run.sh +0 -66
- package/src/__tests__/compaction-circuit-breaker.test.ts +0 -336
- package/src/__tests__/context-overflow-approval.test.ts +0 -156
- package/src/__tests__/hooks-blocking.test.ts +0 -178
- package/src/__tests__/hooks-cli.test.ts +0 -182
- package/src/__tests__/hooks-config.test.ts +0 -108
- package/src/__tests__/hooks-discovery.test.ts +0 -211
- package/src/__tests__/hooks-integration.test.ts +0 -196
- package/src/__tests__/hooks-manager.test.ts +0 -226
- package/src/__tests__/hooks-runner.test.ts +0 -175
- package/src/__tests__/hooks-settings.test.ts +0 -160
- package/src/__tests__/hooks-templates.test.ts +0 -169
- package/src/__tests__/hooks-ts-runner.test.ts +0 -170
- package/src/__tests__/hooks-watch.test.ts +0 -112
- package/src/__tests__/notification-schedule-dedup.test.ts +0 -213
- package/src/__tests__/oauth-scope-policy.test.ts +0 -180
- package/src/__tests__/send-notification-tool.test.ts +0 -83
- package/src/cli/commands/shotgun.ts +0 -266
- package/src/config/bundled-skills/conversations/SKILL.md +0 -20
- package/src/config/bundled-skills/conversations/TOOLS.json +0 -23
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +0 -88
- package/src/config/bundled-skills/heartbeat/SKILL.md +0 -43
- package/src/config/bundled-skills/notifications/SKILL.md +0 -40
- package/src/config/bundled-skills/notifications/TOOLS.json +0 -80
- package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -152
- package/src/config/bundled-skills/notifications/tools/shared.ts +0 -13
- package/src/config/bundled-skills/screen-watch/SKILL.md +0 -27
- package/src/config/bundled-skills/screen-watch/TOOLS.json +0 -35
- package/src/config/bundled-skills/screen-watch/tools/start-screen-watch.ts +0 -12
- package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -84
- package/src/daemon/context-overflow-approval.ts +0 -52
- package/src/daemon/watch-handler.ts +0 -399
- package/src/hooks/cli.ts +0 -253
- package/src/hooks/config.ts +0 -100
- package/src/hooks/discovery.ts +0 -135
- package/src/hooks/manager.ts +0 -179
- package/src/hooks/runner.ts +0 -117
- package/src/hooks/templates.ts +0 -77
- package/src/hooks/types.ts +0 -75
- package/src/oauth/scope-policy.ts +0 -89
- package/src/runtime/gateway-internal-client.ts +0 -94
- package/src/runtime/routes/watch-routes.ts +0 -156
- package/src/signals/shotgun.ts +0 -203
- package/src/tools/watch/screen-watch.ts +0 -144
- package/src/tools/watch/watch-state.ts +0 -142
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Module mocks — must be in place before dynamic imports resolve transitive
|
|
5
|
+
// dependencies (emit-feed-event -> feed-writer -> assistant-event-hub).
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
const publishSpy = mock<(event: unknown) => Promise<void>>(async () => {});
|
|
9
|
+
|
|
10
|
+
mock.module("../../runtime/assistant-event-hub.js", () => ({
|
|
11
|
+
assistantEventHub: {
|
|
12
|
+
publish: publishSpy,
|
|
13
|
+
subscribe: () => () => {},
|
|
14
|
+
},
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// Capture emitFeedEvent calls without hitting the real persistence layer.
|
|
18
|
+
const emitFeedEventCalls: Array<{
|
|
19
|
+
source: string;
|
|
20
|
+
title: string;
|
|
21
|
+
summary: string;
|
|
22
|
+
dedupKey?: string;
|
|
23
|
+
urgency?: string;
|
|
24
|
+
}> = [];
|
|
25
|
+
|
|
26
|
+
mock.module("../../home/emit-feed-event.js", () => ({
|
|
27
|
+
emitFeedEvent: async (params: {
|
|
28
|
+
source: string;
|
|
29
|
+
title: string;
|
|
30
|
+
summary: string;
|
|
31
|
+
dedupKey?: string;
|
|
32
|
+
urgency?: string;
|
|
33
|
+
}) => {
|
|
34
|
+
emitFeedEventCalls.push(params);
|
|
35
|
+
return { id: params.dedupKey ?? "mock-id", ...params };
|
|
36
|
+
},
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
// Stub heavy transitive dependencies that the resolvers import so the
|
|
40
|
+
// test can load the module without standing up a full daemon environment.
|
|
41
|
+
|
|
42
|
+
mock.module("../../calls/call-domain.js", () => ({
|
|
43
|
+
answerCall: async () => ({ ok: true }),
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
mock.module("../../config/env.js", () => ({
|
|
47
|
+
getGatewayInternalBaseUrl: () => "http://localhost:0",
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
mock.module("../../contacts/contact-store.js", () => ({
|
|
51
|
+
findContactChannel: () => null,
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
mock.module("../../contacts/contacts-write.js", () => ({
|
|
55
|
+
upsertContactChannel: () => {},
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
mock.module("../../memory/canonical-guardian-store.js", () => ({
|
|
59
|
+
getCanonicalGuardianRequest: () => null,
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
mock.module("../../notifications/emit-signal.js", () => ({
|
|
63
|
+
emitNotificationSignal: async () => {},
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
mock.module("../../notifications/signal.js", () => ({
|
|
67
|
+
isNotificationSourceChannel: () => false,
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
mock.module("../../permissions/trust-store.js", () => ({
|
|
71
|
+
addRule: () => {},
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
mock.module("../../permissions/v2-consent-policy.js", () => ({
|
|
75
|
+
isPermissionControlsV2Enabled: () => false,
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
mock.module("../../runtime/assistant-scope.js", () => ({
|
|
79
|
+
DAEMON_INTERNAL_ASSISTANT_ID: "self",
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
mock.module("../../runtime/auth/token-service.js", () => ({
|
|
83
|
+
mintDaemonDeliveryToken: () => "mock-token",
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
mock.module("../../runtime/channel-approval-types.js", () => ({}));
|
|
87
|
+
|
|
88
|
+
mock.module("../../runtime/channel-verification-service.js", () => ({
|
|
89
|
+
createOutboundSession: () => ({
|
|
90
|
+
sessionId: "mock-session",
|
|
91
|
+
secret: "123456",
|
|
92
|
+
}),
|
|
93
|
+
}));
|
|
94
|
+
|
|
95
|
+
mock.module("../../runtime/gateway-client.js", () => ({
|
|
96
|
+
deliverChannelReply: async () => {},
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
// Stub pending-interactions so tool_approval resolver can find/resolve.
|
|
100
|
+
let mockInteraction: Record<string, unknown> | null = null;
|
|
101
|
+
let mockResolved: Record<string, unknown> | null = null;
|
|
102
|
+
|
|
103
|
+
mock.module("../../runtime/pending-interactions.js", () => ({
|
|
104
|
+
get: () => mockInteraction,
|
|
105
|
+
resolve: () => mockResolved,
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
mock.module("../../tools/registry.js", () => ({
|
|
109
|
+
getTool: () => null,
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
mock.module("../../tools/tool-approval-handler.js", () => ({
|
|
113
|
+
TC_GRANT_WAIT_MAX_MS: 30_000,
|
|
114
|
+
}));
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Import the resolvers after all mocks are in place.
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
const { getResolver } = await import("../guardian-request-resolvers.js");
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Helpers
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
function makeRequest(overrides: Record<string, unknown> = {}) {
|
|
127
|
+
return {
|
|
128
|
+
id: "req-001",
|
|
129
|
+
kind: "tool_approval",
|
|
130
|
+
status: "pending",
|
|
131
|
+
conversationId: "conv-abc",
|
|
132
|
+
toolName: "web_fetch",
|
|
133
|
+
sourceChannel: "vellum",
|
|
134
|
+
createdAt: new Date().toISOString(),
|
|
135
|
+
updatedAt: new Date().toISOString(),
|
|
136
|
+
...overrides,
|
|
137
|
+
} as never;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function makeCtx(
|
|
141
|
+
request: ReturnType<typeof makeRequest>,
|
|
142
|
+
decision: { action: string; userText?: string },
|
|
143
|
+
) {
|
|
144
|
+
return {
|
|
145
|
+
request,
|
|
146
|
+
decision,
|
|
147
|
+
actor: {
|
|
148
|
+
actorPrincipalId: "principal-1",
|
|
149
|
+
actorExternalUserId: undefined,
|
|
150
|
+
channel: "vellum",
|
|
151
|
+
guardianPrincipalId: "principal-1",
|
|
152
|
+
},
|
|
153
|
+
} as never;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
// Tests
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
describe("guardian approval feed events", () => {
|
|
161
|
+
beforeEach(() => {
|
|
162
|
+
emitFeedEventCalls.length = 0;
|
|
163
|
+
mockInteraction = {
|
|
164
|
+
confirmationDetails: { toolName: "web_fetch" },
|
|
165
|
+
};
|
|
166
|
+
mockResolved = {
|
|
167
|
+
conversation: {
|
|
168
|
+
handleConfirmationResponse: () => {},
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
afterEach(() => {
|
|
174
|
+
mockInteraction = null;
|
|
175
|
+
mockResolved = null;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// -----------------------------------------------------------------------
|
|
179
|
+
// tool_approval (pendingInteractionResolver)
|
|
180
|
+
// -----------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
describe("tool_approval", () => {
|
|
183
|
+
test("approval emits with title 'Tool Request Approved'", async () => {
|
|
184
|
+
const resolver = getResolver("tool_approval")!;
|
|
185
|
+
expect(resolver).toBeDefined();
|
|
186
|
+
|
|
187
|
+
const request = makeRequest();
|
|
188
|
+
const ctx = makeCtx(request, { action: "approve_once" });
|
|
189
|
+
await resolver.resolve(ctx);
|
|
190
|
+
|
|
191
|
+
// Allow microtask for the void promise to settle.
|
|
192
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
193
|
+
|
|
194
|
+
const call = emitFeedEventCalls.find(
|
|
195
|
+
(c) => c.title === "Tool Request Approved",
|
|
196
|
+
);
|
|
197
|
+
expect(call).toBeDefined();
|
|
198
|
+
expect(call!.source).toBe("assistant");
|
|
199
|
+
expect(call!.summary).toContain("Approved");
|
|
200
|
+
expect(call!.summary).toContain("web_fetch");
|
|
201
|
+
expect(call!.urgency).toBeUndefined();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("rejection emits with title 'Tool Request Denied' and urgency 'medium'", async () => {
|
|
205
|
+
const resolver = getResolver("tool_approval")!;
|
|
206
|
+
|
|
207
|
+
const request = makeRequest();
|
|
208
|
+
const ctx = makeCtx(request, { action: "reject" });
|
|
209
|
+
await resolver.resolve(ctx);
|
|
210
|
+
|
|
211
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
212
|
+
|
|
213
|
+
const call = emitFeedEventCalls.find(
|
|
214
|
+
(c) => c.title === "Tool Request Denied",
|
|
215
|
+
);
|
|
216
|
+
expect(call).toBeDefined();
|
|
217
|
+
expect(call!.urgency).toBe("medium");
|
|
218
|
+
expect(call!.summary).toContain("Denied");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("dedupKey includes request ID", async () => {
|
|
222
|
+
const resolver = getResolver("tool_approval")!;
|
|
223
|
+
|
|
224
|
+
const request = makeRequest({ id: "req-unique-42" });
|
|
225
|
+
const ctx = makeCtx(request, { action: "approve_once" });
|
|
226
|
+
await resolver.resolve(ctx);
|
|
227
|
+
|
|
228
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
229
|
+
|
|
230
|
+
const call = emitFeedEventCalls[0];
|
|
231
|
+
expect(call).toBeDefined();
|
|
232
|
+
expect(call!.dedupKey).toBe("guardian-approval:req-unique-42");
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// -----------------------------------------------------------------------
|
|
237
|
+
// access_request (accessRequestResolver)
|
|
238
|
+
// -----------------------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
describe("access_request", () => {
|
|
241
|
+
test("approval emits with title 'Access Request Approved'", async () => {
|
|
242
|
+
const resolver = getResolver("access_request")!;
|
|
243
|
+
expect(resolver).toBeDefined();
|
|
244
|
+
|
|
245
|
+
const request = makeRequest({
|
|
246
|
+
kind: "access_request",
|
|
247
|
+
requesterExternalUserId: "user-123",
|
|
248
|
+
requesterChatId: "chat-456",
|
|
249
|
+
});
|
|
250
|
+
const ctx = makeCtx(request, { action: "approve_once" });
|
|
251
|
+
await resolver.resolve(ctx);
|
|
252
|
+
|
|
253
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
254
|
+
|
|
255
|
+
const call = emitFeedEventCalls.find(
|
|
256
|
+
(c) => c.title === "Access Request Approved",
|
|
257
|
+
);
|
|
258
|
+
expect(call).toBeDefined();
|
|
259
|
+
expect(call!.source).toBe("assistant");
|
|
260
|
+
expect(call!.summary).toContain("Granted");
|
|
261
|
+
expect(call!.urgency).toBeUndefined();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("denial emits with title 'Access Request Denied' and urgency 'medium'", async () => {
|
|
265
|
+
const resolver = getResolver("access_request")!;
|
|
266
|
+
|
|
267
|
+
const request = makeRequest({
|
|
268
|
+
kind: "access_request",
|
|
269
|
+
requesterExternalUserId: "user-123",
|
|
270
|
+
requesterChatId: "chat-456",
|
|
271
|
+
});
|
|
272
|
+
const ctx = makeCtx(request, { action: "reject" });
|
|
273
|
+
await resolver.resolve(ctx);
|
|
274
|
+
|
|
275
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
276
|
+
|
|
277
|
+
const call = emitFeedEventCalls.find(
|
|
278
|
+
(c) => c.title === "Access Request Denied",
|
|
279
|
+
);
|
|
280
|
+
expect(call).toBeDefined();
|
|
281
|
+
expect(call!.urgency).toBe("medium");
|
|
282
|
+
expect(call!.summary).toContain("Denied");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("dedupKey includes request ID", async () => {
|
|
286
|
+
const resolver = getResolver("access_request")!;
|
|
287
|
+
|
|
288
|
+
const request = makeRequest({
|
|
289
|
+
id: "req-access-99",
|
|
290
|
+
kind: "access_request",
|
|
291
|
+
requesterExternalUserId: "user-123",
|
|
292
|
+
requesterChatId: "chat-456",
|
|
293
|
+
});
|
|
294
|
+
const ctx = makeCtx(request, { action: "approve_once" });
|
|
295
|
+
await resolver.resolve(ctx);
|
|
296
|
+
|
|
297
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
298
|
+
|
|
299
|
+
const call = emitFeedEventCalls[0];
|
|
300
|
+
expect(call).toBeDefined();
|
|
301
|
+
expect(call!.dedupKey).toBe("guardian-access:req-access-99");
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
});
|
|
@@ -15,6 +15,7 @@ import { answerCall } from "../calls/call-domain.js";
|
|
|
15
15
|
import { getGatewayInternalBaseUrl } from "../config/env.js";
|
|
16
16
|
import { findContactChannel } from "../contacts/contact-store.js";
|
|
17
17
|
import { upsertContactChannel } from "../contacts/contacts-write.js";
|
|
18
|
+
import { emitFeedEvent } from "../home/emit-feed-event.js";
|
|
18
19
|
import {
|
|
19
20
|
type CanonicalGuardianRequest,
|
|
20
21
|
getCanonicalGuardianRequest,
|
|
@@ -266,6 +267,20 @@ const pendingInteractionResolver: GuardianRequestResolver = {
|
|
|
266
267
|
ctx.emissionContext,
|
|
267
268
|
);
|
|
268
269
|
|
|
270
|
+
const approved = decision.action !== "reject";
|
|
271
|
+
void emitFeedEvent({
|
|
272
|
+
source: "assistant",
|
|
273
|
+
title: approved ? "Tool Request Approved" : "Tool Request Denied",
|
|
274
|
+
summary: `${approved ? "Approved" : "Denied"} access to ${request.toolName ?? "unknown tool"}.`,
|
|
275
|
+
dedupKey: `guardian-approval:${request.id}`,
|
|
276
|
+
urgency: approved ? undefined : "medium",
|
|
277
|
+
}).catch((err) => {
|
|
278
|
+
log.warn(
|
|
279
|
+
{ err, requestId: request.id },
|
|
280
|
+
"Failed to emit guardian approval feed event",
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
|
|
269
284
|
log.info(
|
|
270
285
|
{
|
|
271
286
|
event: "resolver_tool_approval_applied",
|
|
@@ -518,6 +533,19 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
518
533
|
}
|
|
519
534
|
}
|
|
520
535
|
|
|
536
|
+
void emitFeedEvent({
|
|
537
|
+
source: "assistant",
|
|
538
|
+
title: "Access Request Denied",
|
|
539
|
+
summary: `Denied access request.`,
|
|
540
|
+
dedupKey: `guardian-access:${request.id}`,
|
|
541
|
+
urgency: "medium",
|
|
542
|
+
}).catch((err) => {
|
|
543
|
+
log.warn(
|
|
544
|
+
{ err, requestId: request.id },
|
|
545
|
+
"Failed to emit access request feed event",
|
|
546
|
+
);
|
|
547
|
+
});
|
|
548
|
+
|
|
521
549
|
return {
|
|
522
550
|
ok: true,
|
|
523
551
|
applied: true,
|
|
@@ -558,6 +586,19 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
558
586
|
"Access request resolver: voice approval — direct trusted-contact activation (no verification session)",
|
|
559
587
|
);
|
|
560
588
|
|
|
589
|
+
void emitFeedEvent({
|
|
590
|
+
source: "assistant",
|
|
591
|
+
title: "Access Request Approved",
|
|
592
|
+
summary: `Granted access request.`,
|
|
593
|
+
dedupKey: `guardian-access:${request.id}`,
|
|
594
|
+
urgency: undefined,
|
|
595
|
+
}).catch((err) => {
|
|
596
|
+
log.warn(
|
|
597
|
+
{ err, requestId: request.id },
|
|
598
|
+
"Failed to emit access request feed event",
|
|
599
|
+
);
|
|
600
|
+
});
|
|
601
|
+
|
|
561
602
|
return { ok: true, applied: true };
|
|
562
603
|
}
|
|
563
604
|
|
|
@@ -793,6 +834,19 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
793
834
|
? `Access approved for ${requesterLabel}. Give them this verification code: \`${session.secret}\`. The code expires in 10 minutes.`
|
|
794
835
|
: `Access approved for ${requesterLabel}. Give them this verification code: \`${session.secret}\`. The code expires in 10 minutes. I could not notify them automatically, so please tell them to send the code manually.`;
|
|
795
836
|
|
|
837
|
+
void emitFeedEvent({
|
|
838
|
+
source: "assistant",
|
|
839
|
+
title: "Access Request Approved",
|
|
840
|
+
summary: `Granted access request.`,
|
|
841
|
+
dedupKey: `guardian-access:${request.id}`,
|
|
842
|
+
urgency: undefined,
|
|
843
|
+
}).catch((err) => {
|
|
844
|
+
log.warn(
|
|
845
|
+
{ err, requestId: request.id },
|
|
846
|
+
"Failed to emit access request feed event",
|
|
847
|
+
);
|
|
848
|
+
});
|
|
849
|
+
|
|
796
850
|
return {
|
|
797
851
|
ok: true,
|
|
798
852
|
applied: true,
|
|
@@ -867,6 +921,19 @@ const toolGrantRequestResolver: GuardianRequestResolver = {
|
|
|
867
921
|
}
|
|
868
922
|
}
|
|
869
923
|
|
|
924
|
+
void emitFeedEvent({
|
|
925
|
+
source: "assistant",
|
|
926
|
+
title: "Tool Grant Denied",
|
|
927
|
+
summary: `Denied grant request for ${request.toolName ?? "unknown tool"}.`,
|
|
928
|
+
dedupKey: `guardian-grant:${request.id}`,
|
|
929
|
+
urgency: "medium",
|
|
930
|
+
}).catch((err) => {
|
|
931
|
+
log.warn(
|
|
932
|
+
{ err, requestId: request.id },
|
|
933
|
+
"Failed to emit tool grant denial feed event",
|
|
934
|
+
);
|
|
935
|
+
});
|
|
936
|
+
|
|
870
937
|
return { ok: true, applied: true };
|
|
871
938
|
}
|
|
872
939
|
|
|
@@ -965,6 +1032,19 @@ const toolGrantRequestResolver: GuardianRequestResolver = {
|
|
|
965
1032
|
}
|
|
966
1033
|
}
|
|
967
1034
|
|
|
1035
|
+
void emitFeedEvent({
|
|
1036
|
+
source: "assistant",
|
|
1037
|
+
title: "Tool Grant Approved",
|
|
1038
|
+
summary: `Approved grant request for ${request.toolName ?? "unknown tool"}.`,
|
|
1039
|
+
dedupKey: `guardian-grant:${request.id}`,
|
|
1040
|
+
urgency: undefined,
|
|
1041
|
+
}).catch((err) => {
|
|
1042
|
+
log.warn(
|
|
1043
|
+
{ err, requestId: request.id },
|
|
1044
|
+
"Failed to emit tool grant approval feed event",
|
|
1045
|
+
);
|
|
1046
|
+
});
|
|
1047
|
+
|
|
968
1048
|
return { ok: true, applied: true, grantMinted: false };
|
|
969
1049
|
},
|
|
970
1050
|
};
|
|
@@ -20,23 +20,13 @@ import {
|
|
|
20
20
|
import { unlink, writeFile } from "node:fs/promises";
|
|
21
21
|
import { tmpdir } from "node:os";
|
|
22
22
|
import { join } from "node:path";
|
|
23
|
-
import {
|
|
24
|
-
afterEach,
|
|
25
|
-
beforeEach,
|
|
26
|
-
describe,
|
|
27
|
-
expect,
|
|
28
|
-
mock,
|
|
29
|
-
test,
|
|
30
|
-
} from "bun:test";
|
|
23
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
31
24
|
|
|
32
25
|
import type { BackupConfig, BackupDestination } from "../../config/schema.js";
|
|
33
26
|
import { BackupConfigSchema } from "../../config/schema.js";
|
|
34
27
|
import type { StreamExportVBundleResult } from "../../runtime/migrations/vbundle-builder.js";
|
|
35
28
|
import type { BackupDeps } from "../backup-worker.js";
|
|
36
|
-
import {
|
|
37
|
-
createSnapshotNow,
|
|
38
|
-
runBackupTick,
|
|
39
|
-
} from "../backup-worker.js";
|
|
29
|
+
import { createSnapshotNow, runBackupTick } from "../backup-worker.js";
|
|
40
30
|
|
|
41
31
|
// ---------------------------------------------------------------------------
|
|
42
32
|
// Test fixtures
|
|
@@ -190,7 +180,6 @@ describe("runBackupTick — gating", () => {
|
|
|
190
180
|
snapshotLockPath: join(ROOT, ".snapshot.lock"),
|
|
191
181
|
// Explicit plaintext to avoid touching the key file
|
|
192
182
|
trustPath: join(ROOT, "trust.json"),
|
|
193
|
-
hooksDir: join(ROOT, "hooks"),
|
|
194
183
|
});
|
|
195
184
|
|
|
196
185
|
expect(result).toBeNull();
|
|
@@ -42,14 +42,10 @@ import {
|
|
|
42
42
|
getDbPath,
|
|
43
43
|
getProtectedDir,
|
|
44
44
|
getWorkspaceDir,
|
|
45
|
-
getWorkspaceHooksDir,
|
|
46
45
|
} from "../util/platform.js";
|
|
47
46
|
import { ensureBackupKey as realEnsureBackupKey } from "./backup-key.js";
|
|
48
47
|
import type { SnapshotEntry } from "./list-snapshots.js";
|
|
49
|
-
import {
|
|
50
|
-
pruneLocalSnapshots,
|
|
51
|
-
writeLocalSnapshot,
|
|
52
|
-
} from "./local-writer.js";
|
|
48
|
+
import { pruneLocalSnapshots, writeLocalSnapshot } from "./local-writer.js";
|
|
53
49
|
import type { OffsiteWriteResult } from "./offsite-writer.js";
|
|
54
50
|
import {
|
|
55
51
|
pruneOffsiteSnapshotsInAll,
|
|
@@ -60,10 +56,7 @@ import {
|
|
|
60
56
|
getLocalBackupsDir,
|
|
61
57
|
resolveOffsiteDestinations,
|
|
62
58
|
} from "./paths.js";
|
|
63
|
-
import {
|
|
64
|
-
acquireSnapshotLock,
|
|
65
|
-
getSnapshotLockPath,
|
|
66
|
-
} from "./snapshot-lock.js";
|
|
59
|
+
import { acquireSnapshotLock, getSnapshotLockPath } from "./snapshot-lock.js";
|
|
67
60
|
|
|
68
61
|
const log = getLogger("backup-worker");
|
|
69
62
|
|
|
@@ -118,8 +111,6 @@ export interface BackupDeps {
|
|
|
118
111
|
localDir?: string;
|
|
119
112
|
/** Override for the trust.json path (tests). */
|
|
120
113
|
trustPath?: string;
|
|
121
|
-
/** Override for the hooks directory (tests). */
|
|
122
|
-
hooksDir?: string;
|
|
123
114
|
/** Override for the backup key file path (tests). */
|
|
124
115
|
backupKeyPath?: string;
|
|
125
116
|
/**
|
|
@@ -187,9 +178,7 @@ async function performBackup(
|
|
|
187
178
|
const ensureKey = deps.ensureBackupKey ?? realEnsureBackupKey;
|
|
188
179
|
const workspaceDir = deps.workspaceDir ?? getWorkspaceDir();
|
|
189
180
|
const localDir = deps.localDir ?? getLocalBackupsDir(config.localDirectory);
|
|
190
|
-
const trustPath =
|
|
191
|
-
deps.trustPath ?? join(getProtectedDir(), "trust.json");
|
|
192
|
-
const hooksDir = deps.hooksDir ?? getWorkspaceHooksDir();
|
|
181
|
+
const trustPath = deps.trustPath ?? join(getProtectedDir(), "trust.json");
|
|
193
182
|
const backupKeyPath = deps.backupKeyPath ?? getBackupKeyPath();
|
|
194
183
|
|
|
195
184
|
const startTimestamp = Date.now();
|
|
@@ -207,7 +196,6 @@ async function performBackup(
|
|
|
207
196
|
const result = await streamExport({
|
|
208
197
|
workspaceDir,
|
|
209
198
|
trustPath,
|
|
210
|
-
hooksDir,
|
|
211
199
|
source: "backup-worker",
|
|
212
200
|
description: "Automated backup snapshot",
|
|
213
201
|
checkpoint: () => {
|
|
@@ -268,14 +268,97 @@ async function resolveAppImports(srcDir: string): Promise<void> {
|
|
|
268
268
|
}
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Per-appDir compile serialisation.
|
|
273
|
+
*
|
|
274
|
+
* compileApp() begins by `rm -rf dist/`, so two concurrent compiles on the
|
|
275
|
+
* same appDir can wipe each other's intermediate output. To prevent that
|
|
276
|
+
* while still picking up source edits that arrive mid-build, we track a
|
|
277
|
+
* two-slot queue per appDir:
|
|
278
|
+
*
|
|
279
|
+
* - `current`: the compile that is currently writing to dist/.
|
|
280
|
+
* - `pending`: at most one coalesced follow-up compile queued because a new
|
|
281
|
+
* caller arrived while `current` was running. Additional callers arriving
|
|
282
|
+
* during that window share `pending` — they do not spawn yet another run.
|
|
283
|
+
* Once `current` settles, `pending` is promoted to `current` and begins
|
|
284
|
+
* executing; new callers arriving after promotion queue a fresh `pending`.
|
|
285
|
+
*
|
|
286
|
+
* This keeps dist/ consistent under concurrency while guaranteeing that any
|
|
287
|
+
* source mutation observed after a compile starts will be reflected in a
|
|
288
|
+
* subsequent compile pass rather than silently dropped.
|
|
289
|
+
*/
|
|
290
|
+
interface CompileSlot {
|
|
291
|
+
current: Promise<CompileResult>;
|
|
292
|
+
pending?: Promise<CompileResult>;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const compileSlots = new Map<string, CompileSlot>();
|
|
296
|
+
|
|
271
297
|
/**
|
|
272
298
|
* Compile a TSX app from appDir/src/ into appDir/dist/.
|
|
273
299
|
*
|
|
274
300
|
* Expects appDir/src/main.tsx as the entry point and appDir/src/index.html
|
|
275
301
|
* as the HTML shell. Produces appDir/dist/main.js and appDir/dist/index.html
|
|
276
302
|
* (with script and optional stylesheet tags injected).
|
|
303
|
+
*
|
|
304
|
+
* Concurrent calls for the same appDir are serialised (see `compileSlots`
|
|
305
|
+
* above). Callers never see a partial or racing dist/ write; callers that
|
|
306
|
+
* represent work requested after a compile started always get a subsequent
|
|
307
|
+
* fresh compile.
|
|
277
308
|
*/
|
|
278
|
-
export
|
|
309
|
+
export function compileApp(appDir: string): Promise<CompileResult> {
|
|
310
|
+
const slot = compileSlots.get(appDir);
|
|
311
|
+
|
|
312
|
+
if (!slot) {
|
|
313
|
+
const current = runCompile(appDir);
|
|
314
|
+
const onSettled = () => slotCompileSettled(appDir, current);
|
|
315
|
+
current.then(onSettled, onSettled);
|
|
316
|
+
compileSlots.set(appDir, { current });
|
|
317
|
+
return current;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (slot.pending) return slot.pending;
|
|
321
|
+
|
|
322
|
+
// A second distinct caller arrived while `current` is running. Queue a
|
|
323
|
+
// follow-up that starts once `current` settles (success or failure) so
|
|
324
|
+
// any source edits that happened mid-build still get compiled.
|
|
325
|
+
const rerun = async (): Promise<CompileResult> => {
|
|
326
|
+
try {
|
|
327
|
+
await slot.current;
|
|
328
|
+
} catch {
|
|
329
|
+
// Ignore: we want to rerun regardless of the prior compile's outcome.
|
|
330
|
+
}
|
|
331
|
+
return runCompile(appDir);
|
|
332
|
+
};
|
|
333
|
+
const pending = rerun();
|
|
334
|
+
const onSettled = () => slotCompileSettled(appDir, pending);
|
|
335
|
+
pending.then(onSettled, onSettled);
|
|
336
|
+
slot.pending = pending;
|
|
337
|
+
return pending;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function slotCompileSettled(
|
|
341
|
+
appDir: string,
|
|
342
|
+
finished: Promise<CompileResult>,
|
|
343
|
+
): void {
|
|
344
|
+
const slot = compileSlots.get(appDir);
|
|
345
|
+
if (!slot) return;
|
|
346
|
+
|
|
347
|
+
if (slot.current !== finished) {
|
|
348
|
+
// finished is a rerun that hasn't been promoted yet, or a stale entry.
|
|
349
|
+
// Promotion happens below when `current` settles; there is nothing to do
|
|
350
|
+
// here because a subsequent slotCompileSettled(current) will run first.
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (slot.pending) {
|
|
355
|
+
compileSlots.set(appDir, { current: slot.pending });
|
|
356
|
+
} else {
|
|
357
|
+
compileSlots.delete(appDir);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function runCompile(appDir: string): Promise<CompileResult> {
|
|
279
362
|
const start = performance.now();
|
|
280
363
|
const srcDir = join(appDir, "src");
|
|
281
364
|
const distDir = join(appDir, "dist");
|
package/src/calls/call-state.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Call session notifiers and controller registry.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Uses module-level Maps with register/unregister/fire helpers keyed by
|
|
5
|
+
* conversationId.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { getLogger } from "../util/logger.js";
|
|
@@ -58,7 +58,7 @@ describe("isInterfaceId", () => {
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
describe("supportsHostProxy", () => {
|
|
61
|
-
// ── macOS: supports
|
|
61
|
+
// ── macOS: supports all four host proxy capabilities. ──
|
|
62
62
|
test("macos returns true (no capability)", () => {
|
|
63
63
|
expect(supportsHostProxy("macos")).toBe(true);
|
|
64
64
|
});
|
|
@@ -75,8 +75,8 @@ describe("supportsHostProxy", () => {
|
|
|
75
75
|
expect(supportsHostProxy("macos", "host_cu")).toBe(true);
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
test("macos returns
|
|
79
|
-
expect(supportsHostProxy("macos", "host_browser")).toBe(
|
|
78
|
+
test("macos returns true for host_browser", () => {
|
|
79
|
+
expect(supportsHostProxy("macos", "host_browser")).toBe(true);
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
// ── chrome-extension: only host_browser. ──
|
package/src/channels/types.ts
CHANGED
|
@@ -137,10 +137,12 @@ export function supportsHostProxy(
|
|
|
137
137
|
id: InterfaceId,
|
|
138
138
|
capability?: HostProxyCapability,
|
|
139
139
|
): boolean {
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
|
|
140
|
+
// macOS supports all four host proxy capabilities including host_browser.
|
|
141
|
+
// The host_browser proxy is provisioned via the SSE sender path (or via the
|
|
142
|
+
// ChromeExtensionRegistry when an extension connection is present). When no
|
|
143
|
+
// extension is connected, browser tools fall through to cdp-inspect/local
|
|
144
|
+
// via the CDP factory's candidate chain.
|
|
145
|
+
if (id === "macos") return true;
|
|
144
146
|
if (id === "chrome-extension" && capability === "host_browser") return true;
|
|
145
147
|
return false;
|
|
146
148
|
}
|