@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
package/src/hooks/config.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
|
|
4
|
-
import { ensureDir, readTextFileSync } from "../util/fs.js";
|
|
5
|
-
import { getLogger } from "../util/logger.js";
|
|
6
|
-
import { getWorkspaceHooksDir } from "../util/platform.js";
|
|
7
|
-
import type { HookConfig, HookConfigEntry, HookManifest } from "./types.js";
|
|
8
|
-
|
|
9
|
-
const log = getLogger("hooks-config");
|
|
10
|
-
|
|
11
|
-
const HOOKS_CONFIG_VERSION = 1;
|
|
12
|
-
|
|
13
|
-
function getConfigPath(): string {
|
|
14
|
-
return join(getWorkspaceHooksDir(), "config.json");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function loadHooksConfig(): HookConfig {
|
|
18
|
-
const configPath = getConfigPath();
|
|
19
|
-
const raw = readTextFileSync(configPath);
|
|
20
|
-
if (raw == null) {
|
|
21
|
-
return { version: HOOKS_CONFIG_VERSION, hooks: {} };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
const parsed = JSON.parse(raw) as HookConfig;
|
|
26
|
-
if (
|
|
27
|
-
typeof parsed.version !== "number" ||
|
|
28
|
-
typeof parsed.hooks !== "object" ||
|
|
29
|
-
parsed.hooks == null
|
|
30
|
-
) {
|
|
31
|
-
log.warn({ configPath }, "Invalid hooks config, using defaults");
|
|
32
|
-
return { version: HOOKS_CONFIG_VERSION, hooks: {} };
|
|
33
|
-
}
|
|
34
|
-
return parsed;
|
|
35
|
-
} catch (err) {
|
|
36
|
-
log.warn(
|
|
37
|
-
{ err, configPath },
|
|
38
|
-
"Failed to read hooks config, using defaults",
|
|
39
|
-
);
|
|
40
|
-
return { version: HOOKS_CONFIG_VERSION, hooks: {} };
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function saveHooksConfig(config: HookConfig): void {
|
|
45
|
-
const configPath = getConfigPath();
|
|
46
|
-
ensureDir(dirname(configPath));
|
|
47
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function isHookEnabled(hookName: string): boolean {
|
|
51
|
-
const config = loadHooksConfig();
|
|
52
|
-
return config.hooks[hookName]?.enabled ?? false;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function setHookEnabled(hookName: string, enabled: boolean): void {
|
|
56
|
-
const config = loadHooksConfig();
|
|
57
|
-
config.hooks[hookName] = { ...config.hooks[hookName], enabled };
|
|
58
|
-
saveHooksConfig(config);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function ensureHookInConfig(
|
|
62
|
-
hookName: string,
|
|
63
|
-
entry: HookConfigEntry,
|
|
64
|
-
): void {
|
|
65
|
-
const config = loadHooksConfig();
|
|
66
|
-
if (hookName in config.hooks) return;
|
|
67
|
-
config.hooks[hookName] = entry;
|
|
68
|
-
saveHooksConfig(config);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function removeHook(hookName: string): void {
|
|
72
|
-
const config = loadHooksConfig();
|
|
73
|
-
delete config.hooks[hookName];
|
|
74
|
-
saveHooksConfig(config);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Get merged settings for a hook. Manifest defaults are used as the base,
|
|
79
|
-
* then user overrides from config.json are applied on top.
|
|
80
|
-
*/
|
|
81
|
-
export function getHookSettings(
|
|
82
|
-
hookName: string,
|
|
83
|
-
manifest: HookManifest,
|
|
84
|
-
): Record<string, unknown> {
|
|
85
|
-
// Start with defaults from manifest schema
|
|
86
|
-
const defaults: Record<string, unknown> = {};
|
|
87
|
-
if (manifest.settingsSchema) {
|
|
88
|
-
for (const [key, schema] of Object.entries(manifest.settingsSchema)) {
|
|
89
|
-
if (schema.default !== undefined) {
|
|
90
|
-
defaults[key] = schema.default;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Merge user overrides from config
|
|
96
|
-
const config = loadHooksConfig();
|
|
97
|
-
const userSettings = config.hooks[hookName]?.settings ?? {};
|
|
98
|
-
|
|
99
|
-
return { ...defaults, ...userSettings };
|
|
100
|
-
}
|
package/src/hooks/discovery.ts
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { type Dirent, readdirSync, readFileSync } from "node:fs";
|
|
2
|
-
import { join, relative, resolve } from "node:path";
|
|
3
|
-
|
|
4
|
-
import { pathExists } from "../util/fs.js";
|
|
5
|
-
import { getLogger } from "../util/logger.js";
|
|
6
|
-
import { getWorkspaceHooksDir } from "../util/platform.js";
|
|
7
|
-
import { loadHooksConfig } from "./config.js";
|
|
8
|
-
import type { DiscoveredHook, HookManifest } from "./types.js";
|
|
9
|
-
|
|
10
|
-
const log = getLogger("hooks-discovery");
|
|
11
|
-
|
|
12
|
-
const VALID_EVENTS = new Set<string>([
|
|
13
|
-
"daemon-start",
|
|
14
|
-
"daemon-stop",
|
|
15
|
-
"conversation-start",
|
|
16
|
-
"conversation-end",
|
|
17
|
-
// Legacy aliases — existing user hooks may still reference the old names.
|
|
18
|
-
"session-start",
|
|
19
|
-
"session-end",
|
|
20
|
-
"pre-llm-call",
|
|
21
|
-
"post-llm-call",
|
|
22
|
-
"pre-tool-execute",
|
|
23
|
-
"post-tool-execute",
|
|
24
|
-
"permission-request",
|
|
25
|
-
"permission-resolve",
|
|
26
|
-
"pre-message",
|
|
27
|
-
"post-message",
|
|
28
|
-
"on-error",
|
|
29
|
-
]);
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Validates the core manifest fields required for discovery.
|
|
33
|
-
* `description` and `version` are optional here so that legacy hook manifests
|
|
34
|
-
* (created before those fields were added) continue to be discovered.
|
|
35
|
-
*/
|
|
36
|
-
export function isValidManifest(manifest: unknown): manifest is HookManifest {
|
|
37
|
-
if (typeof manifest !== "object" || manifest == null) return false;
|
|
38
|
-
const m = manifest as Record<string, unknown>;
|
|
39
|
-
if (typeof m.name !== "string" || !m.name) return false;
|
|
40
|
-
if (typeof m.script !== "string" || !m.script) return false;
|
|
41
|
-
if (!Array.isArray(m.events) || m.events.length === 0) return false;
|
|
42
|
-
for (const e of m.events) {
|
|
43
|
-
if (typeof e !== "string" || !VALID_EVENTS.has(e)) return false;
|
|
44
|
-
}
|
|
45
|
-
// Optional fields: allow if present but must be strings
|
|
46
|
-
if (m.description !== undefined && typeof m.description !== "string")
|
|
47
|
-
return false;
|
|
48
|
-
if (m.version !== undefined && typeof m.version !== "string") return false;
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Stricter validation for installing new hooks.
|
|
54
|
-
* Requires `description` and `version` in addition to the core fields.
|
|
55
|
-
*/
|
|
56
|
-
export function isValidInstallManifest(
|
|
57
|
-
manifest: unknown,
|
|
58
|
-
): manifest is HookManifest & { description: string; version: string } {
|
|
59
|
-
if (!isValidManifest(manifest)) return false;
|
|
60
|
-
if (typeof manifest.description !== "string" || !manifest.description)
|
|
61
|
-
return false;
|
|
62
|
-
if (typeof manifest.version !== "string" || !manifest.version) return false;
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function discoverHooks(hooksDir?: string): DiscoveredHook[] {
|
|
67
|
-
const dir = hooksDir ?? getWorkspaceHooksDir();
|
|
68
|
-
if (!pathExists(dir)) return [];
|
|
69
|
-
|
|
70
|
-
const config = loadHooksConfig();
|
|
71
|
-
const hooks: DiscoveredHook[] = [];
|
|
72
|
-
|
|
73
|
-
let entries: Dirent[];
|
|
74
|
-
try {
|
|
75
|
-
entries = readdirSync(dir, { withFileTypes: true }) as Dirent[];
|
|
76
|
-
} catch (err) {
|
|
77
|
-
log.warn({ err, dir }, "Failed to read hooks directory");
|
|
78
|
-
return [];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
for (const entry of entries) {
|
|
82
|
-
if (!entry.isDirectory()) continue;
|
|
83
|
-
|
|
84
|
-
const hookDir = join(dir, entry.name);
|
|
85
|
-
const manifestPath = join(hookDir, "hook.json");
|
|
86
|
-
if (!pathExists(manifestPath)) continue;
|
|
87
|
-
|
|
88
|
-
let manifest: unknown;
|
|
89
|
-
try {
|
|
90
|
-
manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
91
|
-
} catch (err) {
|
|
92
|
-
log.warn({ err, hookDir }, "Failed to parse hook manifest");
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (!isValidManifest(manifest)) {
|
|
97
|
-
log.warn({ hookDir }, "Invalid hook manifest, skipping");
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const scriptPath = resolve(hookDir, manifest.script);
|
|
102
|
-
const rel = relative(hookDir, scriptPath);
|
|
103
|
-
// Normalize backslashes so Windows-style traversal (e.g. `..\\..\\evil.js`) is
|
|
104
|
-
// also caught. This project targets macOS, but we check for defense in depth.
|
|
105
|
-
const normalizedRel = rel.replaceAll("\\", "/");
|
|
106
|
-
if (
|
|
107
|
-
normalizedRel.startsWith("../") ||
|
|
108
|
-
normalizedRel === ".." ||
|
|
109
|
-
resolve(hookDir, rel) !== scriptPath
|
|
110
|
-
) {
|
|
111
|
-
log.warn(
|
|
112
|
-
{ hookDir, script: manifest.script },
|
|
113
|
-
"Hook script path traversal detected, skipping",
|
|
114
|
-
);
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
if (!pathExists(scriptPath)) {
|
|
118
|
-
log.warn(
|
|
119
|
-
{ hookDir, script: manifest.script },
|
|
120
|
-
"Hook script not found, skipping",
|
|
121
|
-
);
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
hooks.push({
|
|
126
|
-
name: entry.name,
|
|
127
|
-
dir: hookDir,
|
|
128
|
-
manifest,
|
|
129
|
-
scriptPath,
|
|
130
|
-
enabled: config.hooks[entry.name]?.enabled ?? false,
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return hooks.sort((a, b) => a.name.localeCompare(b.name));
|
|
135
|
-
}
|
package/src/hooks/manager.ts
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import { type FSWatcher, watch } from "node:fs";
|
|
2
|
-
|
|
3
|
-
import { getIsContainerized } from "../config/env-registry.js";
|
|
4
|
-
import { Debouncer } from "../util/debounce.js";
|
|
5
|
-
import { pathExists } from "../util/fs.js";
|
|
6
|
-
import { getLogger } from "../util/logger.js";
|
|
7
|
-
import { getWorkspaceHooksDir } from "../util/platform.js";
|
|
8
|
-
import { discoverHooks } from "./discovery.js";
|
|
9
|
-
import { runHookScript } from "./runner.js";
|
|
10
|
-
import type {
|
|
11
|
-
DiscoveredHook,
|
|
12
|
-
HookEventData,
|
|
13
|
-
HookEventName,
|
|
14
|
-
HookTriggerResult,
|
|
15
|
-
} from "./types.js";
|
|
16
|
-
|
|
17
|
-
const log = getLogger("hooks-manager");
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Legacy event name aliases so existing user hooks that reference the old
|
|
21
|
-
* session-based names still fire when the corresponding conversation event
|
|
22
|
-
* is triggered.
|
|
23
|
-
*/
|
|
24
|
-
const LEGACY_EVENT_ALIASES: Partial<Record<HookEventName, HookEventName[]>> = {
|
|
25
|
-
"conversation-start": ["session-start"],
|
|
26
|
-
"conversation-end": ["session-end"],
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export class HookManager {
|
|
30
|
-
private hooks: DiscoveredHook[] = [];
|
|
31
|
-
private eventIndex = new Map<HookEventName, DiscoveredHook[]>();
|
|
32
|
-
private watcher: FSWatcher | null = null;
|
|
33
|
-
private readonly debouncer = new Debouncer(500);
|
|
34
|
-
|
|
35
|
-
initialize(): void {
|
|
36
|
-
if (getIsContainerized()) {
|
|
37
|
-
log.info("Hooks disabled in containerized mode");
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
this.hooks = discoverHooks();
|
|
41
|
-
this.buildEventIndex();
|
|
42
|
-
const enabled = this.hooks.filter((h) => h.enabled).length;
|
|
43
|
-
if (this.hooks.length > 0) {
|
|
44
|
-
log.info({ enabled, total: this.hooks.length }, "Hooks discovered");
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private buildEventIndex(): void {
|
|
49
|
-
this.eventIndex.clear();
|
|
50
|
-
for (const hook of this.hooks) {
|
|
51
|
-
if (!hook.enabled) continue;
|
|
52
|
-
for (const event of hook.manifest.events) {
|
|
53
|
-
const list = this.eventIndex.get(event) ?? [];
|
|
54
|
-
list.push(hook);
|
|
55
|
-
this.eventIndex.set(event, list);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
// Sort alphabetically by name for deterministic ordering
|
|
59
|
-
for (const [, list] of this.eventIndex) {
|
|
60
|
-
list.sort((a, b) => a.name.localeCompare(b.name));
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async trigger(
|
|
65
|
-
event: HookEventName,
|
|
66
|
-
data: Record<string, unknown>,
|
|
67
|
-
): Promise<HookTriggerResult> {
|
|
68
|
-
// Collect hooks registered under the canonical event name and any
|
|
69
|
-
// legacy aliases (e.g. session-start -> conversation-start).
|
|
70
|
-
const primaryHooks = this.eventIndex.get(event) ?? [];
|
|
71
|
-
const legacyAliases = LEGACY_EVENT_ALIASES[event] ?? [];
|
|
72
|
-
const aliasHooks = legacyAliases.flatMap(
|
|
73
|
-
(alias) => this.eventIndex.get(alias) ?? [],
|
|
74
|
-
);
|
|
75
|
-
// Deduplicate in case a hook subscribes to both old and new names.
|
|
76
|
-
const seen = new Set<string>();
|
|
77
|
-
const hooks: DiscoveredHook[] = [];
|
|
78
|
-
for (const h of [...primaryHooks, ...aliasHooks]) {
|
|
79
|
-
if (!seen.has(h.name)) {
|
|
80
|
-
seen.add(h.name);
|
|
81
|
-
hooks.push(h);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (hooks.length === 0) return { blocked: false };
|
|
85
|
-
|
|
86
|
-
const isPreEvent = event.startsWith("pre-");
|
|
87
|
-
const eventData: HookEventData = { ...data, event };
|
|
88
|
-
|
|
89
|
-
for (const hook of hooks) {
|
|
90
|
-
try {
|
|
91
|
-
const result = await runHookScript(hook, eventData);
|
|
92
|
-
if (result.exitCode != null && result.exitCode !== 0) {
|
|
93
|
-
// Blocking hooks on pre-* events cancel the action
|
|
94
|
-
if (isPreEvent && hook.manifest.blocking) {
|
|
95
|
-
log.info(
|
|
96
|
-
{ hook: hook.name, event, exitCode: result.exitCode },
|
|
97
|
-
"Blocking hook rejected action",
|
|
98
|
-
);
|
|
99
|
-
return { blocked: true, blockedBy: hook.name };
|
|
100
|
-
}
|
|
101
|
-
log.warn(
|
|
102
|
-
{ hook: hook.name, event, exitCode: result.exitCode },
|
|
103
|
-
"Hook exited with non-zero code",
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
} catch (err) {
|
|
107
|
-
log.warn({ err, hook: hook.name, event }, "Hook execution failed");
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return { blocked: false };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
reload(): void {
|
|
115
|
-
if (getIsContainerized()) return;
|
|
116
|
-
this.hooks = discoverHooks();
|
|
117
|
-
this.buildEventIndex();
|
|
118
|
-
const enabled = this.hooks.filter((h) => h.enabled).length;
|
|
119
|
-
log.info({ enabled, total: this.hooks.length }, "Hooks reloaded");
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
watch(): void {
|
|
123
|
-
if (getIsContainerized()) return;
|
|
124
|
-
const hooksDir = getWorkspaceHooksDir();
|
|
125
|
-
if (!pathExists(hooksDir)) return;
|
|
126
|
-
|
|
127
|
-
this.stopWatching();
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
this.watcher = watch(
|
|
131
|
-
hooksDir,
|
|
132
|
-
{ recursive: true },
|
|
133
|
-
(_eventType, filename) => {
|
|
134
|
-
this.debouncer.schedule(() => {
|
|
135
|
-
log.info(
|
|
136
|
-
{ filename: String(filename ?? "") },
|
|
137
|
-
"Hooks directory changed, reloading",
|
|
138
|
-
);
|
|
139
|
-
this.reload();
|
|
140
|
-
});
|
|
141
|
-
},
|
|
142
|
-
);
|
|
143
|
-
log.info({ dir: hooksDir }, "Watching hooks directory for changes");
|
|
144
|
-
} catch (err) {
|
|
145
|
-
log.warn(
|
|
146
|
-
{ err, dir: hooksDir },
|
|
147
|
-
"Failed to watch hooks directory. Hot-reload will be unavailable.",
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
stopWatching(): void {
|
|
153
|
-
this.debouncer.cancel();
|
|
154
|
-
if (this.watcher) {
|
|
155
|
-
this.watcher.close();
|
|
156
|
-
this.watcher = null;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
getDiscoveredHooks(): DiscoveredHook[] {
|
|
161
|
-
return [...this.hooks];
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
let instance: HookManager | null = null;
|
|
166
|
-
|
|
167
|
-
export function getHookManager(): HookManager {
|
|
168
|
-
if (!instance) {
|
|
169
|
-
instance = new HookManager();
|
|
170
|
-
instance.initialize();
|
|
171
|
-
}
|
|
172
|
-
return instance;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/** Reset the singleton (for testing) */
|
|
176
|
-
export function resetHookManager(): void {
|
|
177
|
-
instance?.stopWatching();
|
|
178
|
-
instance = null;
|
|
179
|
-
}
|
package/src/hooks/runner.ts
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { extname, join } from "node:path";
|
|
4
|
-
|
|
5
|
-
import { ensureBun } from "../util/bun-runtime.js";
|
|
6
|
-
import { getWorkspaceDir } from "../util/platform.js";
|
|
7
|
-
import { getHookSettings } from "./config.js";
|
|
8
|
-
import type { DiscoveredHook, HookEventData } from "./types.js";
|
|
9
|
-
|
|
10
|
-
async function getSpawnArgs(
|
|
11
|
-
scriptPath: string,
|
|
12
|
-
): Promise<{ command: string; args: string[] }> {
|
|
13
|
-
const ext = extname(scriptPath);
|
|
14
|
-
if (ext === ".ts") {
|
|
15
|
-
const bunPath = await ensureBun();
|
|
16
|
-
return { command: bunPath, args: ["run", scriptPath] };
|
|
17
|
-
}
|
|
18
|
-
return { command: scriptPath, args: [] };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface HookRunResult {
|
|
22
|
-
exitCode: number | null;
|
|
23
|
-
stdout: string;
|
|
24
|
-
stderr: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function runHookScript(
|
|
28
|
-
hook: DiscoveredHook,
|
|
29
|
-
eventData: HookEventData,
|
|
30
|
-
options?: { timeoutMs?: number },
|
|
31
|
-
): Promise<HookRunResult> {
|
|
32
|
-
const timeoutMs = options?.timeoutMs ?? 5000;
|
|
33
|
-
|
|
34
|
-
let spawnResult: { command: string; args: string[] };
|
|
35
|
-
try {
|
|
36
|
-
spawnResult = await getSpawnArgs(hook.scriptPath);
|
|
37
|
-
} catch (err) {
|
|
38
|
-
return { exitCode: null, stdout: "", stderr: (err as Error).message };
|
|
39
|
-
}
|
|
40
|
-
const { command, args } = spawnResult;
|
|
41
|
-
|
|
42
|
-
return new Promise<HookRunResult>((resolve) => {
|
|
43
|
-
const child = spawn(command, args, {
|
|
44
|
-
cwd: hook.dir,
|
|
45
|
-
env: {
|
|
46
|
-
...process.env,
|
|
47
|
-
VELLUM_HOOK_EVENT: eventData.event,
|
|
48
|
-
VELLUM_HOOK_NAME: hook.name,
|
|
49
|
-
// @deprecated — usage of VELLUM_ROOT_DIR by hook scripts is deprecated.
|
|
50
|
-
// Removing this requires an LLM-based migration or declarative migration
|
|
51
|
-
// file to update existing user-authored hooks to use VELLUM_WORKSPACE_DIR.
|
|
52
|
-
//
|
|
53
|
-
// VELLUM_ROOT_DIR is kept at the legacy `~/.vellum` value even when
|
|
54
|
-
// vellumRoot() resolves per-instance via BASE_DATA_DIR. User hook
|
|
55
|
-
// scripts written against this env var expected the legacy path;
|
|
56
|
-
// changing it would be a silent contract break. Hooks that need the
|
|
57
|
-
// per-instance root should read BASE_DATA_DIR themselves or use the
|
|
58
|
-
// new env vars the environment-layout plan adds.
|
|
59
|
-
VELLUM_ROOT_DIR: join(homedir(), ".vellum"),
|
|
60
|
-
VELLUM_WORKSPACE_DIR: getWorkspaceDir(),
|
|
61
|
-
VELLUM_HOOK_SETTINGS: JSON.stringify(
|
|
62
|
-
getHookSettings(hook.name, hook.manifest),
|
|
63
|
-
),
|
|
64
|
-
},
|
|
65
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
let stdout = "";
|
|
69
|
-
let stderr = "";
|
|
70
|
-
let settled = false;
|
|
71
|
-
|
|
72
|
-
child.stdout.on("data", (chunk: Buffer) => {
|
|
73
|
-
stdout += chunk.toString();
|
|
74
|
-
});
|
|
75
|
-
child.stderr.on("data", (chunk: Buffer) => {
|
|
76
|
-
stderr += chunk.toString();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
const timer = setTimeout(() => {
|
|
80
|
-
if (settled) return;
|
|
81
|
-
settled = true;
|
|
82
|
-
child.kill("SIGTERM");
|
|
83
|
-
// Give the process a short grace period to exit after SIGTERM, then SIGKILL
|
|
84
|
-
const killTimer = setTimeout(() => {
|
|
85
|
-
child.kill("SIGKILL");
|
|
86
|
-
}, 2000);
|
|
87
|
-
child.once("close", () => {
|
|
88
|
-
clearTimeout(killTimer);
|
|
89
|
-
resolve({
|
|
90
|
-
exitCode: null,
|
|
91
|
-
stdout,
|
|
92
|
-
stderr: stderr + "\nHook timed out",
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
}, timeoutMs);
|
|
96
|
-
|
|
97
|
-
child.on("close", (code) => {
|
|
98
|
-
if (settled) return;
|
|
99
|
-
settled = true;
|
|
100
|
-
clearTimeout(timer);
|
|
101
|
-
resolve({ exitCode: code, stdout, stderr });
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
child.on("error", (err) => {
|
|
105
|
-
if (settled) return;
|
|
106
|
-
settled = true;
|
|
107
|
-
clearTimeout(timer);
|
|
108
|
-
resolve({ exitCode: null, stdout, stderr: stderr + "\n" + err.message });
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// Suppress unhandled EPIPE errors if the child exits before we finish writing
|
|
112
|
-
child.stdin.on("error", () => {});
|
|
113
|
-
// Write event data to stdin and close
|
|
114
|
-
child.stdin.write(JSON.stringify(eventData));
|
|
115
|
-
child.stdin.end();
|
|
116
|
-
});
|
|
117
|
-
}
|
package/src/hooks/templates.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
chmodSync,
|
|
3
|
-
cpSync,
|
|
4
|
-
type Dirent,
|
|
5
|
-
readdirSync,
|
|
6
|
-
readFileSync,
|
|
7
|
-
rmSync,
|
|
8
|
-
} from "node:fs";
|
|
9
|
-
import { join } from "node:path";
|
|
10
|
-
|
|
11
|
-
import { resolveBundledDir } from "../util/bundled-asset.js";
|
|
12
|
-
import { pathExists } from "../util/fs.js";
|
|
13
|
-
import { getLogger } from "../util/logger.js";
|
|
14
|
-
import { getWorkspaceHooksDir } from "../util/platform.js";
|
|
15
|
-
import { ensureHookInConfig } from "./config.js";
|
|
16
|
-
|
|
17
|
-
const log = getLogger("hooks-templates");
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Install bundled hook templates into the user's hooks directory.
|
|
21
|
-
* Templates are copied from `assistant/hook-templates/` to `~/.vellum/hooks/`.
|
|
22
|
-
* - Never overwrites existing hooks (user modifications are preserved).
|
|
23
|
-
* - Newly installed hooks are disabled by default.
|
|
24
|
-
*/
|
|
25
|
-
export function installTemplates(): void {
|
|
26
|
-
const templatesDir = resolveBundledDir(
|
|
27
|
-
import.meta.dirname ?? __dirname,
|
|
28
|
-
"../../hook-templates",
|
|
29
|
-
"hook-templates",
|
|
30
|
-
);
|
|
31
|
-
if (!pathExists(templatesDir)) return;
|
|
32
|
-
|
|
33
|
-
const hooksDir = getWorkspaceHooksDir();
|
|
34
|
-
const entries = readdirSync(templatesDir, {
|
|
35
|
-
withFileTypes: true,
|
|
36
|
-
}) as Dirent[];
|
|
37
|
-
|
|
38
|
-
for (const entry of entries) {
|
|
39
|
-
if (!entry.isDirectory()) continue;
|
|
40
|
-
|
|
41
|
-
const targetDir = join(hooksDir, entry.name);
|
|
42
|
-
if (pathExists(targetDir)) continue; // Never overwrite user hooks
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
// Copy template directory
|
|
46
|
-
cpSync(join(templatesDir, entry.name), targetDir, { recursive: true });
|
|
47
|
-
|
|
48
|
-
// Make script executable
|
|
49
|
-
const manifestPath = join(targetDir, "hook.json");
|
|
50
|
-
if (pathExists(manifestPath)) {
|
|
51
|
-
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
52
|
-
if (manifest.script) {
|
|
53
|
-
chmodSync(join(targetDir, manifest.script), 0o755);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Add to config (disabled by default)
|
|
58
|
-
ensureHookInConfig(entry.name, { enabled: false });
|
|
59
|
-
|
|
60
|
-
log.info(
|
|
61
|
-
{ hook: entry.name },
|
|
62
|
-
"Installed hook template (disabled by default)",
|
|
63
|
-
);
|
|
64
|
-
} catch (err) {
|
|
65
|
-
// Clean up partially-copied directory so the next restart can retry
|
|
66
|
-
try {
|
|
67
|
-
rmSync(targetDir, { recursive: true, force: true });
|
|
68
|
-
} catch (e) {
|
|
69
|
-
log.debug(
|
|
70
|
-
{ err: e },
|
|
71
|
-
"Cleanup of partial hook template directory failed",
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
log.warn({ err, hook: entry.name }, "Failed to install hook template");
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
package/src/hooks/types.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
export type HookEventName =
|
|
2
|
-
// Daemon lifecycle
|
|
3
|
-
| "daemon-start"
|
|
4
|
-
| "daemon-stop"
|
|
5
|
-
// Conversation lifecycle
|
|
6
|
-
| "conversation-start"
|
|
7
|
-
| "conversation-end"
|
|
8
|
-
// Legacy aliases (backwards compat for existing user hooks)
|
|
9
|
-
| "session-start"
|
|
10
|
-
| "session-end"
|
|
11
|
-
// LLM call lifecycle
|
|
12
|
-
| "pre-llm-call"
|
|
13
|
-
| "post-llm-call"
|
|
14
|
-
// Tool execution lifecycle
|
|
15
|
-
| "pre-tool-execute"
|
|
16
|
-
| "post-tool-execute"
|
|
17
|
-
// Permission lifecycle
|
|
18
|
-
| "permission-request"
|
|
19
|
-
| "permission-resolve"
|
|
20
|
-
// Message lifecycle
|
|
21
|
-
| "pre-message"
|
|
22
|
-
| "post-message"
|
|
23
|
-
// Error
|
|
24
|
-
| "on-error";
|
|
25
|
-
|
|
26
|
-
export interface HookSettingsSchemaEntry {
|
|
27
|
-
type: string;
|
|
28
|
-
default?: unknown;
|
|
29
|
-
description?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface HookManifest {
|
|
33
|
-
name: string;
|
|
34
|
-
description?: string;
|
|
35
|
-
version?: string;
|
|
36
|
-
events: HookEventName[];
|
|
37
|
-
script: string;
|
|
38
|
-
/** When true, non-zero exit from this hook cancels pre-* actions. Default false. */
|
|
39
|
-
blocking?: boolean;
|
|
40
|
-
/** Schema for per-hook settings with defaults and descriptions. */
|
|
41
|
-
settingsSchema?: Record<string, HookSettingsSchemaEntry>;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface HookConfigEntry {
|
|
45
|
-
enabled: boolean;
|
|
46
|
-
/** Per-hook user settings that override manifest defaults. */
|
|
47
|
-
settings?: Record<string, unknown>;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface HookConfig {
|
|
51
|
-
version: number;
|
|
52
|
-
hooks: Record<string, HookConfigEntry>;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface DiscoveredHook {
|
|
56
|
-
name: string;
|
|
57
|
-
/** Absolute path to hook directory */
|
|
58
|
-
dir: string;
|
|
59
|
-
manifest: HookManifest;
|
|
60
|
-
/** Absolute path to the script */
|
|
61
|
-
scriptPath: string;
|
|
62
|
-
enabled: boolean;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface HookEventData {
|
|
66
|
-
event: HookEventName;
|
|
67
|
-
[key: string]: unknown;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface HookTriggerResult {
|
|
71
|
-
/** True if a blocking hook rejected the action (non-zero exit). */
|
|
72
|
-
blocked: boolean;
|
|
73
|
-
/** Name of the hook that blocked, if any. */
|
|
74
|
-
blockedBy?: string;
|
|
75
|
-
}
|