@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,160 @@
|
|
|
1
|
+
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
5
|
+
|
|
6
|
+
// Stub the in-process SSE hub so the writer's publish path is a
|
|
7
|
+
// no-op in these tests.
|
|
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
|
+
// Stub workspace prompt reads so the heartbeat service doesn't try to
|
|
18
|
+
// read real workspace files.
|
|
19
|
+
mock.module("../../util/platform.js", () => ({
|
|
20
|
+
getWorkspaceDir: () => workspaceDir,
|
|
21
|
+
getWorkspacePromptPath: (name: string) => join(workspaceDir, name),
|
|
22
|
+
vellumRoot: () => workspaceDir,
|
|
23
|
+
getDataDir: () => join(workspaceDir, "data"),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// Stub config so heartbeat is enabled.
|
|
27
|
+
mock.module("../../config/loader.js", () => ({
|
|
28
|
+
getConfig: () => ({
|
|
29
|
+
heartbeat: {
|
|
30
|
+
enabled: true,
|
|
31
|
+
intervalMs: 60_000,
|
|
32
|
+
activeHoursStart: null,
|
|
33
|
+
activeHoursEnd: null,
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
// Stub conversation bootstrap.
|
|
39
|
+
const lastConversationId = "conv-heartbeat-test";
|
|
40
|
+
mock.module("../../memory/conversation-bootstrap.js", () => ({
|
|
41
|
+
bootstrapConversation: () => ({ id: lastConversationId }),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
// Stub prompt helpers.
|
|
45
|
+
mock.module("../../prompts/persona-resolver.js", () => ({
|
|
46
|
+
GUARDIAN_PERSONA_TEMPLATE: "",
|
|
47
|
+
resolveGuardianPersona: () => null,
|
|
48
|
+
}));
|
|
49
|
+
mock.module("../../prompts/system-prompt.js", () => ({
|
|
50
|
+
isTemplateContent: () => false,
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
const { getHomeFeedPath } = await import("../../home/feed-writer.js");
|
|
54
|
+
const { HeartbeatService } = await import("../heartbeat-service.js");
|
|
55
|
+
|
|
56
|
+
interface OnDiskItem {
|
|
57
|
+
id: string;
|
|
58
|
+
type: string;
|
|
59
|
+
source?: string;
|
|
60
|
+
title: string;
|
|
61
|
+
summary: string;
|
|
62
|
+
priority: number;
|
|
63
|
+
status: string;
|
|
64
|
+
author: string;
|
|
65
|
+
urgency?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function readFeedItems(): OnDiskItem[] {
|
|
69
|
+
const raw = JSON.parse(readFileSync(getHomeFeedPath(), "utf-8"));
|
|
70
|
+
return raw.items as OnDiskItem[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let workspaceDir: string;
|
|
74
|
+
let origWorkspaceDir: string | undefined;
|
|
75
|
+
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
workspaceDir = mkdtempSync(join(tmpdir(), "vellum-hb-feed-"));
|
|
78
|
+
origWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
|
|
79
|
+
process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
|
|
80
|
+
publishSpy.mockClear();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
afterEach(() => {
|
|
84
|
+
if (origWorkspaceDir === undefined) {
|
|
85
|
+
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
86
|
+
} else {
|
|
87
|
+
process.env.VELLUM_WORKSPACE_DIR = origWorkspaceDir;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
91
|
+
} catch {
|
|
92
|
+
// best-effort
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("heartbeat feed events", () => {
|
|
97
|
+
test("successful heartbeat emits feed event with priority 30 and no urgency", async () => {
|
|
98
|
+
const service = new HeartbeatService({
|
|
99
|
+
processMessage: async () => ({ messageId: "msg-1" }),
|
|
100
|
+
alerter: () => {},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await service.runOnce({ force: true });
|
|
104
|
+
|
|
105
|
+
// Give the fire-and-forget emitFeedEvent time to flush.
|
|
106
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
107
|
+
|
|
108
|
+
const items = readFeedItems();
|
|
109
|
+
const heartbeatItem = items.find((i) => i.title === "Heartbeat");
|
|
110
|
+
expect(heartbeatItem).toBeDefined();
|
|
111
|
+
expect(heartbeatItem!.summary).toBe("Heartbeat check completed.");
|
|
112
|
+
expect(heartbeatItem!.priority).toBe(30);
|
|
113
|
+
expect(heartbeatItem!.urgency).toBeUndefined();
|
|
114
|
+
expect(heartbeatItem!.source).toBe("assistant");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("failed heartbeat emits feed event with priority 55 and urgency medium", async () => {
|
|
118
|
+
const service = new HeartbeatService({
|
|
119
|
+
processMessage: async () => {
|
|
120
|
+
throw new Error("LLM call failed");
|
|
121
|
+
},
|
|
122
|
+
alerter: () => {},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
await service.runOnce({ force: true });
|
|
126
|
+
|
|
127
|
+
// Give the fire-and-forget emitFeedEvent time to flush.
|
|
128
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
129
|
+
|
|
130
|
+
const items = readFeedItems();
|
|
131
|
+
const heartbeatItem = items.find((i) => i.title === "Heartbeat");
|
|
132
|
+
expect(heartbeatItem).toBeDefined();
|
|
133
|
+
expect(heartbeatItem!.summary).toBe(
|
|
134
|
+
"Heartbeat check failed. Check logs for details.",
|
|
135
|
+
);
|
|
136
|
+
expect(heartbeatItem!.priority).toBe(55);
|
|
137
|
+
expect(heartbeatItem!.urgency).toBe("medium");
|
|
138
|
+
expect(heartbeatItem!.source).toBe("assistant");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("dedupKey uses date for daily dedup", async () => {
|
|
142
|
+
const service = new HeartbeatService({
|
|
143
|
+
processMessage: async () => ({ messageId: "msg-1" }),
|
|
144
|
+
alerter: () => {},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Run twice — same day should dedup to one item.
|
|
148
|
+
await service.runOnce({ force: true });
|
|
149
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
150
|
+
await service.runOnce({ force: true });
|
|
151
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
152
|
+
|
|
153
|
+
const items = readFeedItems();
|
|
154
|
+
const heartbeatItems = items.filter((i) => i.title === "Heartbeat");
|
|
155
|
+
expect(heartbeatItems).toHaveLength(1);
|
|
156
|
+
|
|
157
|
+
const today = new Date().toISOString().split("T")[0];
|
|
158
|
+
expect(heartbeatItems[0]!.id).toBe(`emit:assistant:heartbeat:ok:${today}`);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { getConfig } from "../config/loader.js";
|
|
5
5
|
import type { LLMCallSite } from "../config/schemas/llm.js";
|
|
6
6
|
import type { HeartbeatAlert } from "../daemon/message-protocol.js";
|
|
7
|
+
import { emitFeedEvent } from "../home/emit-feed-event.js";
|
|
7
8
|
import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
|
|
8
9
|
import {
|
|
9
10
|
GUARDIAN_PERSONA_TEMPLATE,
|
|
@@ -396,6 +397,19 @@ export class HeartbeatService {
|
|
|
396
397
|
}
|
|
397
398
|
|
|
398
399
|
log.info({ conversationId: conversation.id }, "Heartbeat completed");
|
|
400
|
+
|
|
401
|
+
void emitFeedEvent({
|
|
402
|
+
source: "assistant",
|
|
403
|
+
title: "Heartbeat",
|
|
404
|
+
summary: "Heartbeat check completed.",
|
|
405
|
+
dedupKey: `heartbeat:ok:${new Date().toISOString().split("T")[0]}`,
|
|
406
|
+
priority: 30,
|
|
407
|
+
}).catch((err) => {
|
|
408
|
+
log.warn(
|
|
409
|
+
{ err, conversationId: conversation.id },
|
|
410
|
+
"Failed to emit heartbeat feed event",
|
|
411
|
+
);
|
|
412
|
+
});
|
|
399
413
|
} catch (err) {
|
|
400
414
|
log.error({ err }, "Heartbeat failed");
|
|
401
415
|
try {
|
|
@@ -407,6 +421,15 @@ export class HeartbeatService {
|
|
|
407
421
|
} catch (alertErr) {
|
|
408
422
|
log.error({ alertErr }, "Failed to broadcast heartbeat alert");
|
|
409
423
|
}
|
|
424
|
+
|
|
425
|
+
void emitFeedEvent({
|
|
426
|
+
source: "assistant",
|
|
427
|
+
title: "Heartbeat",
|
|
428
|
+
summary: "Heartbeat check failed. Check logs for details.",
|
|
429
|
+
dedupKey: `heartbeat:fail:${new Date().toISOString().split("T")[0]}`,
|
|
430
|
+
priority: 55,
|
|
431
|
+
urgency: "medium",
|
|
432
|
+
}).catch(() => {});
|
|
410
433
|
}
|
|
411
434
|
}
|
|
412
435
|
|
|
@@ -431,7 +454,7 @@ ${checklist}
|
|
|
431
454
|
if (unhealthyProviders.length > 0) {
|
|
432
455
|
const providers = unhealthyProviders.join(", ");
|
|
433
456
|
prompt += `\n\n<credential-status>
|
|
434
|
-
The following providers have broken or expired
|
|
457
|
+
The following providers have broken or expired credentials: ${providers}.
|
|
435
458
|
Do NOT attempt to use tools for these providers — they will fail. Skip any checklist items that depend on them and note the outage in your summary.
|
|
436
459
|
</credential-status>`;
|
|
437
460
|
}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
5
|
+
|
|
6
|
+
// Stub the in-process SSE hub so the writer's publish path is a
|
|
7
|
+
// no-op in these tests. Must be in place before the writer module is
|
|
8
|
+
// imported (directly or transitively) so the dynamic import below
|
|
9
|
+
// picks it up.
|
|
10
|
+
const publishSpy = mock<(event: unknown) => Promise<void>>(async () => {});
|
|
11
|
+
|
|
12
|
+
mock.module("../../runtime/assistant-event-hub.js", () => ({
|
|
13
|
+
assistantEventHub: {
|
|
14
|
+
publish: publishSpy,
|
|
15
|
+
subscribe: () => () => {},
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
const { emitFeedEvent } = await import("../emit-feed-event.js");
|
|
20
|
+
const { readHomeFeed, MAX_ACTIONS_PER_SOURCE } =
|
|
21
|
+
await import("../feed-writer.js");
|
|
22
|
+
|
|
23
|
+
type FeedItemSource = "gmail" | "slack" | "calendar" | "assistant";
|
|
24
|
+
|
|
25
|
+
const ALL_SOURCES: FeedItemSource[] = [
|
|
26
|
+
"gmail",
|
|
27
|
+
"slack",
|
|
28
|
+
"calendar",
|
|
29
|
+
"assistant",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
let workspaceDir: string;
|
|
33
|
+
let origWorkspaceDir: string | undefined;
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
workspaceDir = mkdtempSync(join(tmpdir(), "vellum-feed-integ-"));
|
|
37
|
+
origWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
|
|
38
|
+
process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
|
|
39
|
+
publishSpy.mockClear();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
if (origWorkspaceDir === undefined) {
|
|
44
|
+
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
45
|
+
} else {
|
|
46
|
+
process.env.VELLUM_WORKSPACE_DIR = origWorkspaceDir;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
50
|
+
} catch {
|
|
51
|
+
// best-effort
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("feed population integration", () => {
|
|
56
|
+
test("items from all 4 sources coexist and are sorted by priority", async () => {
|
|
57
|
+
// Emit one item per source with distinct priorities.
|
|
58
|
+
await emitFeedEvent({
|
|
59
|
+
source: "gmail",
|
|
60
|
+
title: "Gmail action",
|
|
61
|
+
summary: "Replied to a thread.",
|
|
62
|
+
priority: 30,
|
|
63
|
+
dedupKey: "gmail-1",
|
|
64
|
+
});
|
|
65
|
+
await emitFeedEvent({
|
|
66
|
+
source: "slack",
|
|
67
|
+
title: "Slack action",
|
|
68
|
+
summary: "Sent a message in #general.",
|
|
69
|
+
priority: 70,
|
|
70
|
+
dedupKey: "slack-1",
|
|
71
|
+
});
|
|
72
|
+
await emitFeedEvent({
|
|
73
|
+
source: "calendar",
|
|
74
|
+
title: "Calendar action",
|
|
75
|
+
summary: "Meeting prep reminder.",
|
|
76
|
+
priority: 50,
|
|
77
|
+
dedupKey: "cal-1",
|
|
78
|
+
});
|
|
79
|
+
await emitFeedEvent({
|
|
80
|
+
source: "assistant",
|
|
81
|
+
title: "Assistant action",
|
|
82
|
+
summary: "Ran weekly review.",
|
|
83
|
+
priority: 90,
|
|
84
|
+
dedupKey: "asst-1",
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const feed = readHomeFeed();
|
|
88
|
+
expect(feed.items).toHaveLength(4);
|
|
89
|
+
|
|
90
|
+
// Verify all four sources are present.
|
|
91
|
+
const sources = new Set(feed.items.map((i) => i.source));
|
|
92
|
+
for (const s of ALL_SOURCES) {
|
|
93
|
+
expect(sources.has(s)).toBe(true);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Items should be sorted by priority DESC.
|
|
97
|
+
const priorities = feed.items.map((i) => i.priority);
|
|
98
|
+
for (let i = 1; i < priorities.length; i++) {
|
|
99
|
+
expect(priorities[i - 1]!).toBeGreaterThanOrEqual(priorities[i]!);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Spot-check the ordering: assistant (90) first, gmail (30) last.
|
|
103
|
+
expect(feed.items[0]!.source).toBe("assistant");
|
|
104
|
+
expect(feed.items[feed.items.length - 1]!.source).toBe("gmail");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("per-source cap holds across mixed sources", async () => {
|
|
108
|
+
// Emit MAX + 5 items for gmail and MAX + 5 for slack, plus a
|
|
109
|
+
// handful from calendar and assistant. Only the capped sources
|
|
110
|
+
// should be pruned.
|
|
111
|
+
const overflow = MAX_ACTIONS_PER_SOURCE + 5;
|
|
112
|
+
|
|
113
|
+
for (let i = 0; i < overflow; i++) {
|
|
114
|
+
await emitFeedEvent({
|
|
115
|
+
source: "gmail",
|
|
116
|
+
title: `Gmail item ${i}`,
|
|
117
|
+
summary: `Gmail summary ${i}`,
|
|
118
|
+
priority: 50,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for (let i = 0; i < overflow; i++) {
|
|
123
|
+
await emitFeedEvent({
|
|
124
|
+
source: "slack",
|
|
125
|
+
title: `Slack item ${i}`,
|
|
126
|
+
summary: `Slack summary ${i}`,
|
|
127
|
+
priority: 50,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// A few items from the other two sources — should be untouched.
|
|
132
|
+
for (let i = 0; i < 3; i++) {
|
|
133
|
+
await emitFeedEvent({
|
|
134
|
+
source: "calendar",
|
|
135
|
+
title: `Calendar item ${i}`,
|
|
136
|
+
summary: `Calendar summary ${i}`,
|
|
137
|
+
priority: 50,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
for (let i = 0; i < 2; i++) {
|
|
141
|
+
await emitFeedEvent({
|
|
142
|
+
source: "assistant",
|
|
143
|
+
title: `Assistant item ${i}`,
|
|
144
|
+
summary: `Assistant summary ${i}`,
|
|
145
|
+
priority: 50,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const feed = readHomeFeed();
|
|
150
|
+
|
|
151
|
+
const gmailItems = feed.items.filter((i) => i.source === "gmail");
|
|
152
|
+
const slackItems = feed.items.filter((i) => i.source === "slack");
|
|
153
|
+
const calendarItems = feed.items.filter((i) => i.source === "calendar");
|
|
154
|
+
const assistantItems = feed.items.filter((i) => i.source === "assistant");
|
|
155
|
+
|
|
156
|
+
expect(gmailItems).toHaveLength(MAX_ACTIONS_PER_SOURCE);
|
|
157
|
+
expect(slackItems).toHaveLength(MAX_ACTIONS_PER_SOURCE);
|
|
158
|
+
expect(calendarItems).toHaveLength(3);
|
|
159
|
+
expect(assistantItems).toHaveLength(2);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("expired items are filtered out at read time", async () => {
|
|
163
|
+
// One item that expired in the past, one that is still valid.
|
|
164
|
+
const pastDate = new Date(Date.now() - 60_000).toISOString();
|
|
165
|
+
const futureDate = new Date(Date.now() + 3_600_000).toISOString();
|
|
166
|
+
|
|
167
|
+
await emitFeedEvent({
|
|
168
|
+
source: "gmail",
|
|
169
|
+
title: "Expired item",
|
|
170
|
+
summary: "Should not appear.",
|
|
171
|
+
expiresAt: pastDate,
|
|
172
|
+
dedupKey: "expired-1",
|
|
173
|
+
});
|
|
174
|
+
await emitFeedEvent({
|
|
175
|
+
source: "slack",
|
|
176
|
+
title: "Still valid",
|
|
177
|
+
summary: "Should appear.",
|
|
178
|
+
expiresAt: futureDate,
|
|
179
|
+
dedupKey: "valid-1",
|
|
180
|
+
});
|
|
181
|
+
await emitFeedEvent({
|
|
182
|
+
source: "calendar",
|
|
183
|
+
title: "No expiry",
|
|
184
|
+
summary: "Should also appear.",
|
|
185
|
+
dedupKey: "no-expiry-1",
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const feed = readHomeFeed();
|
|
189
|
+
expect(feed.items).toHaveLength(2);
|
|
190
|
+
|
|
191
|
+
const titles = feed.items.map((i) => i.title);
|
|
192
|
+
expect(titles).toContain("Still valid");
|
|
193
|
+
expect(titles).toContain("No expiry");
|
|
194
|
+
expect(titles).not.toContain("Expired item");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("dedup: two items with the same dedupKey produce only one entry", async () => {
|
|
198
|
+
await emitFeedEvent({
|
|
199
|
+
source: "gmail",
|
|
200
|
+
title: "First version",
|
|
201
|
+
summary: "Original summary.",
|
|
202
|
+
dedupKey: "shared-key",
|
|
203
|
+
});
|
|
204
|
+
await emitFeedEvent({
|
|
205
|
+
source: "gmail",
|
|
206
|
+
title: "Second version",
|
|
207
|
+
summary: "Updated summary.",
|
|
208
|
+
dedupKey: "shared-key",
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const feed = readHomeFeed();
|
|
212
|
+
const matching = feed.items.filter((i) => i.id === "emit:gmail:shared-key");
|
|
213
|
+
expect(matching).toHaveLength(1);
|
|
214
|
+
expect(matching[0]!.title).toBe("Second version");
|
|
215
|
+
expect(matching[0]!.summary).toBe("Updated summary.");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("dedup works across reads — no phantom duplicates", async () => {
|
|
219
|
+
await emitFeedEvent({
|
|
220
|
+
source: "assistant",
|
|
221
|
+
title: "Version 1",
|
|
222
|
+
summary: "First emit.",
|
|
223
|
+
dedupKey: "cross-read",
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Read once to confirm the item is there.
|
|
227
|
+
const feed1 = readHomeFeed();
|
|
228
|
+
expect(
|
|
229
|
+
feed1.items.filter((i) => i.id === "emit:assistant:cross-read"),
|
|
230
|
+
).toHaveLength(1);
|
|
231
|
+
|
|
232
|
+
// Emit again with the same dedupKey.
|
|
233
|
+
await emitFeedEvent({
|
|
234
|
+
source: "assistant",
|
|
235
|
+
title: "Version 2",
|
|
236
|
+
summary: "Second emit.",
|
|
237
|
+
dedupKey: "cross-read",
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const feed2 = readHomeFeed();
|
|
241
|
+
const matching = feed2.items.filter(
|
|
242
|
+
(i) => i.id === "emit:assistant:cross-read",
|
|
243
|
+
);
|
|
244
|
+
expect(matching).toHaveLength(1);
|
|
245
|
+
expect(matching[0]!.title).toBe("Version 2");
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("mixed priorities and urgencies sort correctly", async () => {
|
|
249
|
+
const events: Array<{
|
|
250
|
+
source: FeedItemSource;
|
|
251
|
+
title: string;
|
|
252
|
+
priority: number;
|
|
253
|
+
dedupKey: string;
|
|
254
|
+
}> = [
|
|
255
|
+
{
|
|
256
|
+
source: "gmail",
|
|
257
|
+
title: "Low priority gmail",
|
|
258
|
+
priority: 10,
|
|
259
|
+
dedupKey: "g-low",
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
source: "slack",
|
|
263
|
+
title: "High priority slack",
|
|
264
|
+
priority: 95,
|
|
265
|
+
dedupKey: "s-high",
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
source: "calendar",
|
|
269
|
+
title: "Mid priority calendar",
|
|
270
|
+
priority: 50,
|
|
271
|
+
dedupKey: "c-mid",
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
source: "assistant",
|
|
275
|
+
title: "High priority assistant",
|
|
276
|
+
priority: 95,
|
|
277
|
+
dedupKey: "a-high",
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
source: "gmail",
|
|
281
|
+
title: "Mid priority gmail",
|
|
282
|
+
priority: 50,
|
|
283
|
+
dedupKey: "g-mid",
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
source: "slack",
|
|
287
|
+
title: "Low priority slack",
|
|
288
|
+
priority: 20,
|
|
289
|
+
dedupKey: "s-low",
|
|
290
|
+
},
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
for (const e of events) {
|
|
294
|
+
await emitFeedEvent({
|
|
295
|
+
source: e.source,
|
|
296
|
+
title: e.title,
|
|
297
|
+
summary: `Summary for ${e.title}`,
|
|
298
|
+
priority: e.priority,
|
|
299
|
+
dedupKey: e.dedupKey,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const feed = readHomeFeed();
|
|
304
|
+
expect(feed.items).toHaveLength(6);
|
|
305
|
+
|
|
306
|
+
// Verify descending priority order.
|
|
307
|
+
const priorities = feed.items.map((i) => i.priority);
|
|
308
|
+
for (let i = 1; i < priorities.length; i++) {
|
|
309
|
+
expect(priorities[i - 1]!).toBeGreaterThanOrEqual(priorities[i]!);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
});
|
|
@@ -41,6 +41,7 @@ import { randomUUID } from "node:crypto";
|
|
|
41
41
|
import {
|
|
42
42
|
type FeedAction,
|
|
43
43
|
type FeedItem,
|
|
44
|
+
type FeedItemDetailPanel,
|
|
44
45
|
feedItemSchema,
|
|
45
46
|
type FeedItemSource,
|
|
46
47
|
type FeedItemUrgency,
|
|
@@ -96,6 +97,10 @@ export interface EmitFeedEventParams {
|
|
|
96
97
|
expiresAt?: string;
|
|
97
98
|
/** Visual urgency treatment — controls badge color independently of sort priority. */
|
|
98
99
|
urgency?: FeedItemUrgency;
|
|
100
|
+
/** Optional conversation this feed item is associated with. */
|
|
101
|
+
conversationId?: string;
|
|
102
|
+
/** Server-driven detail panel descriptor; when present, the client opens this panel kind. */
|
|
103
|
+
detailPanel?: FeedItemDetailPanel;
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
/**
|
|
@@ -148,6 +153,8 @@ export async function emitFeedEvent(
|
|
|
148
153
|
createdAt: now,
|
|
149
154
|
actions: params.actions,
|
|
150
155
|
urgency: params.urgency,
|
|
156
|
+
conversationId: params.conversationId,
|
|
157
|
+
detailPanel: params.detailPanel,
|
|
151
158
|
minTimeAway: params.minTimeAway,
|
|
152
159
|
expiresAt: params.expiresAt,
|
|
153
160
|
};
|
package/src/home/feed-types.ts
CHANGED
|
@@ -34,7 +34,12 @@ export type FeedItemStatus = "new" | "seen" | "acted_on" | "dismissed";
|
|
|
34
34
|
* stays exhaustive. Future sources will be added explicitly rather
|
|
35
35
|
* than letting arbitrary strings slip through.
|
|
36
36
|
*/
|
|
37
|
-
export type FeedItemSource =
|
|
37
|
+
export type FeedItemSource =
|
|
38
|
+
| "gmail"
|
|
39
|
+
| "slack"
|
|
40
|
+
| "calendar"
|
|
41
|
+
| "assistant"
|
|
42
|
+
| "telegram";
|
|
38
43
|
|
|
39
44
|
/**
|
|
40
45
|
* Internal field used by the hybrid authoring resolver (PR 5 writer).
|
|
@@ -62,6 +67,20 @@ export interface FeedAction {
|
|
|
62
67
|
prompt: string;
|
|
63
68
|
}
|
|
64
69
|
|
|
70
|
+
/** Which detail panel the macOS client should open for this feed item. */
|
|
71
|
+
export type FeedItemDetailPanelKind =
|
|
72
|
+
| "emailDraft"
|
|
73
|
+
| "documentPreview"
|
|
74
|
+
| "permissionChat"
|
|
75
|
+
| "paymentAuth"
|
|
76
|
+
| "toolPermission"
|
|
77
|
+
| "updatesList";
|
|
78
|
+
|
|
79
|
+
/** Server-driven detail panel descriptor attached to a feed item. */
|
|
80
|
+
export interface FeedItemDetailPanel {
|
|
81
|
+
kind: FeedItemDetailPanelKind;
|
|
82
|
+
}
|
|
83
|
+
|
|
65
84
|
/**
|
|
66
85
|
* A single item rendered in the Home feed.
|
|
67
86
|
*
|
|
@@ -81,7 +100,7 @@ export interface FeedItem {
|
|
|
81
100
|
priority: number;
|
|
82
101
|
title: string;
|
|
83
102
|
summary: string;
|
|
84
|
-
/** Optional; when present must be one of the
|
|
103
|
+
/** Optional; when present must be one of the v1 sources. */
|
|
85
104
|
source?: FeedItemSource;
|
|
86
105
|
/** Event time (ISO-8601). */
|
|
87
106
|
timestamp: string;
|
|
@@ -94,6 +113,10 @@ export interface FeedItem {
|
|
|
94
113
|
actions?: FeedAction[];
|
|
95
114
|
/** Visual urgency treatment — controls badge color independently of sort priority. */
|
|
96
115
|
urgency?: FeedItemUrgency;
|
|
116
|
+
/** Optional conversation this feed item is associated with. */
|
|
117
|
+
conversationId?: string;
|
|
118
|
+
/** Server-driven detail panel descriptor; when present, the client opens this panel kind. */
|
|
119
|
+
detailPanel?: FeedItemDetailPanel;
|
|
97
120
|
/** Internal: who authored this item. */
|
|
98
121
|
author: FeedItemAuthor;
|
|
99
122
|
/** Internal: ISO-8601 writer-record time, used for ordering + TTL. */
|
|
@@ -157,6 +180,7 @@ const feedItemSourceSchema = z.enum([
|
|
|
157
180
|
"slack",
|
|
158
181
|
"calendar",
|
|
159
182
|
"assistant",
|
|
183
|
+
"telegram",
|
|
160
184
|
]);
|
|
161
185
|
|
|
162
186
|
const feedItemAuthorSchema = z.enum(["assistant", "platform"]);
|
|
@@ -169,6 +193,19 @@ const feedActionSchema = z.object({
|
|
|
169
193
|
prompt: z.string(),
|
|
170
194
|
});
|
|
171
195
|
|
|
196
|
+
const feedItemDetailPanelKindSchema = z.enum([
|
|
197
|
+
"emailDraft",
|
|
198
|
+
"documentPreview",
|
|
199
|
+
"permissionChat",
|
|
200
|
+
"paymentAuth",
|
|
201
|
+
"toolPermission",
|
|
202
|
+
"updatesList",
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
const feedItemDetailPanelSchema = z.object({
|
|
206
|
+
kind: feedItemDetailPanelKindSchema,
|
|
207
|
+
});
|
|
208
|
+
|
|
172
209
|
/**
|
|
173
210
|
* Schema for a single `FeedItem`.
|
|
174
211
|
*
|
|
@@ -196,6 +233,8 @@ export const feedItemSchema = z.object({
|
|
|
196
233
|
minTimeAway: z.number().int().min(0).optional(),
|
|
197
234
|
actions: z.array(feedActionSchema).optional(),
|
|
198
235
|
urgency: feedItemUrgencySchema.optional(),
|
|
236
|
+
conversationId: z.string().optional(),
|
|
237
|
+
detailPanel: feedItemDetailPanelSchema.optional(),
|
|
199
238
|
author: feedItemAuthorSchema,
|
|
200
239
|
createdAt: z.string(),
|
|
201
240
|
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { getConfiguredProvider } from "../providers/provider-send-message.js";
|
|
2
|
+
import { getLogger } from "../util/logger.js";
|
|
3
|
+
|
|
4
|
+
const log = getLogger("command-preview-rewriter");
|
|
5
|
+
const REWRITE_TIMEOUT_MS = 3000;
|
|
6
|
+
const REWRITE_MAX_TOKENS = 100;
|
|
7
|
+
|
|
8
|
+
const SYSTEM_PROMPT = `You rewrite technical computer commands into simple, human-readable descriptions.
|
|
9
|
+
Output ONLY the rewritten description — no quotes, no explanation, no preamble.
|
|
10
|
+
Keep it under 15 words. Use plain language a non-technical person would understand.
|
|
11
|
+
Examples:
|
|
12
|
+
- "ls -la ~/Desktop" → "View files on the desktop"
|
|
13
|
+
- "cat ~/.bashrc" → "Read shell configuration file"
|
|
14
|
+
- "rm -rf /tmp/cache" → "Delete temporary cache files"
|
|
15
|
+
- "grep -r 'password' ." → "Search files for the word 'password'"
|
|
16
|
+
- "curl https://api.example.com/users" → "Fetch user data from an API"`;
|
|
17
|
+
|
|
18
|
+
export async function rewriteCommandPreview(
|
|
19
|
+
toolName: string,
|
|
20
|
+
commandPreview: string,
|
|
21
|
+
): Promise<string | null> {
|
|
22
|
+
try {
|
|
23
|
+
const provider = await getConfiguredProvider("feedEventCopy");
|
|
24
|
+
if (!provider) return null;
|
|
25
|
+
|
|
26
|
+
const response = await provider.sendMessage(
|
|
27
|
+
[
|
|
28
|
+
{
|
|
29
|
+
role: "user",
|
|
30
|
+
content: [
|
|
31
|
+
{
|
|
32
|
+
type: "text",
|
|
33
|
+
text: `Tool: ${toolName}\nCommand: ${commandPreview}`,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
[],
|
|
39
|
+
SYSTEM_PROMPT,
|
|
40
|
+
{
|
|
41
|
+
config: {
|
|
42
|
+
max_tokens: REWRITE_MAX_TOKENS,
|
|
43
|
+
callSite: "feedEventCopy",
|
|
44
|
+
},
|
|
45
|
+
signal: AbortSignal.timeout(REWRITE_TIMEOUT_MS),
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const block = response.content.find((entry) => entry.type === "text");
|
|
50
|
+
const text =
|
|
51
|
+
block && "text" in block ? (block as { text: string }).text.trim() : "";
|
|
52
|
+
if (!text) return null;
|
|
53
|
+
return (
|
|
54
|
+
text
|
|
55
|
+
.replace(/^["'`]+/, "")
|
|
56
|
+
.replace(/["'`]+$/, "")
|
|
57
|
+
.trim() || null
|
|
58
|
+
);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
log.warn(
|
|
61
|
+
{ err, toolName, commandPreview },
|
|
62
|
+
"Command preview rewrite failed",
|
|
63
|
+
);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|