@vellumai/assistant 0.8.2 → 0.8.4
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/ARCHITECTURE.md +11 -12
- package/docker-entrypoint.sh +13 -2
- package/docker-init-apt-root.sh +79 -6
- package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
- package/openapi.yaml +945 -36
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
- package/src/__tests__/agent-loop.test.ts +88 -3
- package/src/__tests__/anthropic-provider.test.ts +272 -0
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
- package/src/__tests__/channel-delivery-store.test.ts +193 -0
- package/src/__tests__/channel-reply-delivery.test.ts +284 -5
- package/src/__tests__/channel-retry-sweep.test.ts +274 -1
- package/src/__tests__/compaction-events.test.ts +1 -1
- package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
- package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
- package/src/__tests__/config-get-vision-flag.test.ts +136 -0
- package/src/__tests__/config-loader-backfill.test.ts +115 -18
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/context-token-estimator.test.ts +112 -57
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
- package/src/__tests__/conversation-agent-loop.test.ts +77 -3
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-clean-command.test.ts +137 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +161 -0
- package/src/__tests__/conversation-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-media-retry.test.ts +19 -8
- package/src/__tests__/conversation-pairing.test.ts +2 -2
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
- package/src/__tests__/conversation-seed-composer.test.ts +66 -4
- package/src/__tests__/conversation-slash-commands.test.ts +36 -8
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +6 -0
- package/src/__tests__/cu-unified-flow.test.ts +10 -1
- package/src/__tests__/date-context.test.ts +45 -0
- package/src/__tests__/dm-backfill.test.ts +64 -0
- package/src/__tests__/dm-persistence.test.ts +33 -0
- package/src/__tests__/document-find-replace.test.ts +501 -0
- package/src/__tests__/external-plugin-loader.test.ts +91 -19
- package/src/__tests__/first-greeting.test.ts +23 -2
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
- package/src/__tests__/guardian-dispatch.test.ts +1 -0
- package/src/__tests__/headless-browser-navigate.test.ts +172 -0
- package/src/__tests__/heartbeat-service.test.ts +24 -164
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/host-app-control-proxy.test.ts +241 -0
- package/src/__tests__/host-bash-proxy.test.ts +6 -0
- package/src/__tests__/host-browser-proxy.test.ts +10 -0
- package/src/__tests__/host-cu-proxy.test.ts +8 -1
- package/src/__tests__/host-file-proxy.test.ts +8 -1
- package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
- package/src/__tests__/host-transfer-proxy.test.ts +8 -1
- package/src/__tests__/identity-routes.test.ts +57 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
- package/src/__tests__/injector-background-turn.test.ts +153 -0
- package/src/__tests__/injector-chain.test.ts +7 -0
- package/src/__tests__/injector-document-comments.test.ts +378 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
- package/src/__tests__/list-messages-attachments.test.ts +21 -17
- package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
- package/src/__tests__/list-messages-page-latest.test.ts +130 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
- package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
- package/src/__tests__/llm-catalog-parity.test.ts +3 -0
- package/src/__tests__/llm-context-normalization.test.ts +0 -2
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
- package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
- package/src/__tests__/llm-resolver.test.ts +340 -3
- package/src/__tests__/log-export-routes.test.ts +99 -2
- package/src/__tests__/managed-profile-guard.test.ts +10 -0
- package/src/__tests__/message-queue-steer.test.ts +114 -0
- package/src/__tests__/notification-decision-fallback.test.ts +0 -91
- package/src/__tests__/notification-decision-strategy.test.ts +14 -31
- package/src/__tests__/notification-deep-link.test.ts +15 -0
- package/src/__tests__/notification-guardian-path.test.ts +1 -2
- package/src/__tests__/notification-platform-adapter.test.ts +5 -4
- package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
- package/src/__tests__/openai-provider.test.ts +323 -3
- package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
- package/src/__tests__/openai-responses-provider.test.ts +4 -4
- package/src/__tests__/openrouter-provider-only.test.ts +51 -3
- package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
- package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
- package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
- package/src/__tests__/platform-proxy-context.test.ts +6 -1
- package/src/__tests__/platform.test.ts +0 -3
- package/src/__tests__/plugin-source-watcher.test.ts +302 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
- package/src/__tests__/plugin-types.test.ts +2 -2
- package/src/__tests__/process-message-background-slack.test.ts +1 -51
- package/src/__tests__/process-message-display-content.test.ts +21 -16
- package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +83 -4
- package/src/__tests__/steer-tool-repair.test.ts +249 -0
- package/src/__tests__/system-prompt.test.ts +57 -101
- package/src/__tests__/terminal-tools.test.ts +11 -1
- package/src/__tests__/thinking-block-replay.test.ts +113 -0
- package/src/__tests__/thread-backfill.test.ts +370 -22
- package/src/__tests__/tool-executor.test.ts +90 -1
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/web-fetch.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +88 -5
- package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
- package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
- package/src/a2a/__tests__/agent-card.test.ts +98 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
- package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
- package/src/a2a/__tests__/task-store.test.ts +246 -0
- package/src/a2a/agent-card.ts +58 -0
- package/src/a2a/feature-gate.ts +8 -0
- package/src/a2a/protocol-constants.ts +21 -0
- package/src/a2a/protocol-errors.ts +50 -0
- package/src/a2a/protocol-types.ts +162 -0
- package/src/a2a/task-store.ts +168 -0
- package/src/agent/attachments.ts +1 -0
- package/src/agent/loop.ts +208 -22
- package/src/background-wake/next-wake.test.ts +289 -0
- package/src/background-wake/next-wake.ts +172 -0
- package/src/browser/operations.ts +15 -0
- package/src/channels/config.ts +9 -0
- package/src/channels/types.ts +14 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
- package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
- package/src/cli/commands/__tests__/schedules.test.ts +469 -0
- package/src/cli/commands/conversations.ts +128 -1
- package/src/cli/commands/inference-providers.ts +147 -1
- package/src/cli/commands/memory-v2.ts +308 -0
- package/src/cli/commands/notifications.ts +89 -37
- package/src/cli/commands/plugins.ts +67 -0
- package/src/cli/commands/schedules.ts +297 -5
- package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
- package/src/cli/lib/install-from-github.ts +8 -9
- package/src/cli/lib/search-plugins.ts +163 -0
- package/src/cli/program.ts +14 -0
- package/src/cli/utils/conversation-id.ts +17 -5
- package/src/config/assistant-feature-flags.ts +24 -54
- package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
- package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
- package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
- package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/SKILL.md +8 -0
- package/src/config/bundled-tool-registry.ts +22 -12
- package/src/config/call-site-defaults.ts +124 -0
- package/src/config/feature-flag-registry.json +111 -23
- package/src/config/llm-resolver.ts +66 -1
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
- package/src/config/schemas/call-site-catalog.ts +21 -0
- package/src/config/schemas/channels.ts +9 -0
- package/src/config/schemas/conversations.ts +10 -0
- package/src/config/schemas/heartbeat.ts +14 -0
- package/src/config/schemas/llm.ts +4 -3
- package/src/config/schemas/memory-retrospective.ts +1 -1
- package/src/config/schemas/memory-v2.ts +51 -4
- package/src/config/schemas/memory.ts +3 -1
- package/src/config/seed-inference-profiles.ts +99 -29
- package/src/context/compactor.ts +80 -13
- package/src/context/token-estimator.ts +72 -31
- package/src/context/window-manager.ts +25 -0
- package/src/credential-health/credential-health-service.ts +34 -19
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
- package/src/daemon/conversation-agent-loop.ts +252 -56
- package/src/daemon/conversation-lifecycle.ts +142 -116
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +273 -0
- package/src/daemon/conversation-queue-manager.ts +14 -0
- package/src/daemon/conversation-runtime-assembly.ts +144 -75
- package/src/daemon/conversation-slash.ts +37 -5
- package/src/daemon/conversation-surfaces.ts +45 -2
- package/src/daemon/conversation-tool-setup.ts +7 -0
- package/src/daemon/conversation.ts +42 -12
- package/src/daemon/date-context.ts +40 -0
- package/src/daemon/first-greeting.ts +10 -0
- package/src/daemon/guardian-action-generators.ts +1 -125
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
- package/src/daemon/handlers/config-a2a.ts +449 -0
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +80 -0
- package/src/daemon/handlers/shared.ts +92 -29
- package/src/daemon/host-app-control-proxy.ts +69 -18
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/host-file-proxy.ts +1 -1
- package/src/daemon/host-proxy-preactivation.ts +85 -18
- package/src/daemon/host-transfer-proxy.ts +1 -1
- package/src/daemon/lifecycle.ts +67 -65
- package/src/daemon/memory-v2-startup.ts +49 -13
- package/src/daemon/message-protocol.ts +4 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/document-comments.ts +50 -0
- package/src/daemon/message-types/messages.ts +68 -1
- package/src/daemon/message-types/notifications.ts +21 -0
- package/src/daemon/message-types/surfaces.ts +3 -1
- package/src/daemon/message-types/web-activity.ts +57 -0
- package/src/daemon/pkb-reminder-builder.test.ts +10 -53
- package/src/daemon/pkb-reminder-builder.ts +4 -19
- package/src/daemon/plugin-source-watcher.ts +135 -3
- package/src/daemon/process-message.ts +72 -12
- package/src/daemon/query-complexity-router.ts +75 -0
- package/src/daemon/skill-memory-refresh.ts +5 -1
- package/src/daemon/trust-context.ts +6 -0
- package/src/daemon/wake-target-adapter.ts +2 -0
- package/src/documents/document-comments-store.test.ts +338 -0
- package/src/documents/document-comments-store.ts +237 -0
- package/src/documents/document-store.ts +202 -0
- package/src/export/__tests__/transcript-formatter.test.ts +121 -0
- package/src/export/transcript-formatter.ts +54 -20
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
- package/src/heartbeat/heartbeat-service.ts +35 -191
- package/src/home/__tests__/feed-types.test.ts +40 -0
- package/src/home/__tests__/suggested-prompts.test.ts +33 -2
- package/src/home/feed-types.ts +20 -3
- package/src/home/home-content-refresh.ts +52 -0
- package/src/home/home-greeting-cache.ts +69 -0
- package/src/home/home-greeting.ts +94 -0
- package/src/home/suggested-prompts.ts +177 -9
- package/src/ipc/cli-client.ts +147 -45
- package/src/memory/__tests__/conversation-queries.test.ts +220 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
- package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
- package/src/memory/conversation-crud.ts +133 -43
- package/src/memory/conversation-queries.ts +87 -1
- package/src/memory/conversation-title-service.ts +26 -4
- package/src/memory/db-init.ts +22 -0
- package/src/memory/delivery-crud.ts +41 -0
- package/src/memory/delivery-status.ts +141 -15
- package/src/memory/external-conversation-store.ts +32 -1
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
- package/src/memory/graph/conversation-graph-memory.ts +18 -6
- package/src/memory/graph/tools.ts +6 -37
- package/src/memory/invite-store.ts +53 -0
- package/src/memory/jobs-worker.ts +21 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
- package/src/memory/llm-request-log-store.ts +92 -1
- package/src/memory/memory-retrospective-constants.ts +28 -0
- package/src/memory/memory-retrospective-enqueue.ts +4 -22
- package/src/memory/memory-retrospective-job.ts +438 -21
- package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
- package/src/memory/memory-v2-activation-log-store.ts +26 -8
- package/src/memory/migrations/100-core-tables.ts +1 -0
- package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
- package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
- package/src/memory/migrations/251-a2a-tasks.ts +49 -0
- package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
- package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
- package/src/memory/migrations/253-document-comments.ts +47 -0
- package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
- package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
- package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
- package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
- package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
- package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
- package/src/memory/migrations/index.ts +20 -0
- package/src/memory/migrations/registry.ts +33 -0
- package/src/memory/onboarding-events-store.ts +7 -0
- package/src/memory/schema/a2a.ts +15 -0
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/inference.ts +2 -0
- package/src/memory/schema/infrastructure.ts +2 -0
- package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
- package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
- package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
- package/src/memory/v2/__tests__/injection.test.ts +221 -17
- package/src/memory/v2/__tests__/page-index.test.ts +365 -1
- package/src/memory/v2/__tests__/router.test.ts +489 -1
- package/src/memory/v2/__tests__/static-context.test.ts +12 -1
- package/src/memory/v2/activation-store.ts +14 -16
- package/src/memory/v2/cli-command-content.ts +19 -0
- package/src/memory/v2/cli-command-store.ts +304 -0
- package/src/memory/v2/consolidation-job.ts +14 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -1
- package/src/memory/v2/injection-events.ts +101 -0
- package/src/memory/v2/injection.ts +69 -29
- package/src/memory/v2/page-index.ts +246 -19
- package/src/memory/v2/page-store.ts +18 -0
- package/src/memory/v2/router.ts +209 -55
- package/src/memory/v2/static-context.ts +4 -4
- package/src/memory/v2/types.ts +23 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
- package/src/messaging/providers/a2a/deliver.ts +156 -0
- package/src/messaging/providers/gmail/client.ts +9 -2
- package/src/messaging/providers/index.ts +18 -3
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
- package/src/messaging/providers/slack/adapter.ts +178 -25
- package/src/messaging/providers/slack/api.test.ts +54 -0
- package/src/messaging/providers/slack/api.ts +119 -3
- package/src/messaging/providers/slack/client.ts +12 -0
- package/src/messaging/providers/slack/deep-link.ts +20 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
- package/src/messaging/providers/slack/message-metadata.ts +156 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
- package/src/messaging/providers/slack/render-transcript.ts +176 -49
- package/src/messaging/providers/slack/send.test.ts +77 -0
- package/src/messaging/providers/slack/send.ts +8 -2
- package/src/messaging/providers/slack/types.ts +14 -0
- package/src/notifications/__tests__/broadcaster.test.ts +203 -0
- package/src/notifications/__tests__/decision-engine.test.ts +283 -0
- package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
- package/src/notifications/adapters/macos.ts +12 -2
- package/src/notifications/broadcaster.ts +29 -4
- package/src/notifications/conversation-seed-composer.ts +14 -2
- package/src/notifications/copy-composer.ts +17 -64
- package/src/notifications/decision-engine.ts +111 -44
- package/src/notifications/deferred-emit.ts +135 -0
- package/src/notifications/deterministic-checks.ts +96 -0
- package/src/notifications/emit-signal.ts +10 -1
- package/src/notifications/home-feed-side-effect.ts +136 -27
- package/src/notifications/signal.ts +0 -4
- package/src/notifications/types.ts +8 -0
- package/src/oauth/connect-orchestrator.ts +3 -0
- package/src/oauth/credential-token-resolver.ts +2 -0
- package/src/oauth/manual-token-connection.ts +19 -0
- package/src/oauth/oauth-store.ts +12 -0
- package/src/oauth/platform-connection.test.ts +43 -3
- package/src/oauth/platform-connection.ts +13 -4
- package/src/oauth/seed-providers.ts +22 -0
- package/src/permissions/prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +4 -1
- package/src/plugins/defaults/injectors.ts +118 -26
- package/src/plugins/external-plugin-loader.ts +82 -10
- package/src/plugins/types.ts +16 -7
- package/src/prompts/__tests__/system-prompt.test.ts +44 -45
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
- package/src/prompts/normalize-onboarding.ts +40 -0
- package/src/prompts/sections.ts +32 -14
- package/src/prompts/system-prompt.ts +105 -76
- package/src/prompts/template-detection.ts +37 -0
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/prompts/templates/VOICE.md +3 -0
- package/src/prompts/templates/system-sections.ts +51 -10
- package/src/providers/__tests__/inference.test.ts +2 -0
- package/src/providers/anthropic/client.ts +132 -5
- package/src/providers/call-site-routing.ts +24 -6
- package/src/providers/connection-resolution.ts +63 -13
- package/src/providers/fireworks/client.ts +20 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
- package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
- package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
- package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
- package/src/providers/inference/adapter-factory.ts +24 -21
- package/src/providers/inference/auth.ts +15 -3
- package/src/providers/inference/backfill.ts +14 -1
- package/src/providers/inference/codex-token-refresh.ts +128 -0
- package/src/providers/inference/connections.ts +85 -5
- package/src/providers/inference/resolve-auth.ts +50 -5
- package/src/providers/model-catalog.ts +244 -242
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
- package/src/providers/openai/chat-completions-provider.ts +215 -25
- package/src/providers/openai/responses-provider.ts +9 -3
- package/src/providers/openrouter/client.ts +46 -4
- package/src/providers/platform-proxy/constants.ts +3 -4
- package/src/providers/provider-catalog-visibility.ts +3 -1
- package/src/providers/provider-send-message.ts +27 -12
- package/src/providers/registry.ts +30 -1
- package/src/providers/types.ts +25 -0
- package/src/runtime/__tests__/agent-wake.test.ts +214 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
- package/src/runtime/agent-wake.ts +212 -57
- package/src/runtime/auth/route-policy.ts +20 -3
- package/src/runtime/background-job-runner.ts +26 -0
- package/src/runtime/channel-reply-delivery.ts +182 -47
- package/src/runtime/channel-retry-sweep.ts +141 -16
- package/src/runtime/http-server.ts +7 -16
- package/src/runtime/http-types.ts +7 -51
- package/src/runtime/pending-interactions.ts +51 -8
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
- package/src/runtime/routes/approval-routes.ts +4 -1
- package/src/runtime/routes/channel-availability-routes.ts +5 -0
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
- package/src/runtime/routes/consolidation-routes.ts +100 -0
- package/src/runtime/routes/content-source-routes.ts +78 -0
- package/src/runtime/routes/conversation-cli-routes.ts +146 -1
- package/src/runtime/routes/conversation-query-routes.ts +130 -12
- package/src/runtime/routes/conversation-routes.ts +288 -76
- package/src/runtime/routes/document-comments-routes.ts +287 -0
- package/src/runtime/routes/documents-routes.ts +33 -0
- package/src/runtime/routes/home-feed-routes.ts +6 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-browser-routes.ts +8 -1
- package/src/runtime/routes/identity-routes.ts +21 -0
- package/src/runtime/routes/inbound-message-handler.ts +288 -58
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
- package/src/runtime/routes/index.ts +14 -4
- package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
- package/src/runtime/routes/integrations/a2a.ts +294 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
- package/src/runtime/routes/log-export-routes.ts +39 -0
- package/src/runtime/routes/memory-v2-routes.ts +217 -0
- package/src/runtime/routes/notification-routes.ts +19 -2
- package/src/runtime/routes/question-routes.ts +4 -1
- package/src/runtime/routes/sanity-routes.ts +159 -0
- package/src/runtime/routes/slack-channel-routes.ts +187 -0
- package/src/runtime/routes/subagents-routes.ts +41 -0
- package/src/runtime/services/conversation-serializer.ts +30 -4
- package/src/schedule/integration-status.ts +3 -1
- package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
- package/src/security/oauth2-device-code.ts +307 -0
- package/src/security/oauth2.ts +26 -9
- package/src/security/secure-keys.ts +5 -0
- package/src/skills/catalog-install.ts +6 -2
- package/src/subagent/manager.ts +2 -0
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
- package/src/tools/browser/browser-execution.ts +93 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
- package/src/tools/browser/cdp-client/factory.ts +87 -3
- package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
- package/src/tools/browser/cdp-client/types.ts +36 -0
- package/src/tools/browser/pinned-tabs.ts +90 -0
- package/src/tools/document/document-comment-tool.test.ts +379 -0
- package/src/tools/document/document-comment-tool.ts +156 -0
- package/src/tools/document/document-tool.ts +128 -2
- package/src/tools/memory/register.ts +1 -9
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
- package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
- package/src/tools/network/domain-normalize.ts +17 -0
- package/src/tools/network/web-fetch.ts +213 -64
- package/src/tools/network/web-search.ts +191 -66
- package/src/tools/registry.ts +2 -2
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/tool-approval-handler.ts +19 -12
- package/src/tools/types.ts +41 -2
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/types/onboarding-context.ts +4 -0
- package/src/util/__tests__/favicon.test.ts +84 -0
- package/src/util/favicon.ts +40 -0
- package/src/util/platform.ts +0 -5
- package/src/workspace/git-service.ts +75 -4
- package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
- package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
- package/src/config/bundled-skills/document/SKILL.md +0 -54
- package/src/config/bundled-skills/document/TOOLS.json +0 -106
- package/src/daemon/seed-files.ts +0 -18
- package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
- package/src/runtime/guardian-action-conversation-turn.ts +0 -99
- package/src/runtime/routes/interface-routes.ts +0 -43
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
|
@@ -9,8 +9,6 @@ import {
|
|
|
9
9
|
shouldLogDiskPressureBackgroundSkip,
|
|
10
10
|
} from "../daemon/disk-pressure-background-gate.js";
|
|
11
11
|
import type { HeartbeatAlert } from "../daemon/message-protocol.js";
|
|
12
|
-
import { getConversation, getMessages } from "../memory/conversation-crud.js";
|
|
13
|
-
import { GENERATING_TITLE } from "../memory/conversation-title-service.js";
|
|
14
12
|
import { emitNotificationSignal } from "../notifications/emit-signal.js";
|
|
15
13
|
import {
|
|
16
14
|
GUARDIAN_PERSONA_TEMPLATE,
|
|
@@ -47,9 +45,6 @@ const DEFAULT_CHECKLIST = `- Check in with yourself. Read NOW.md. Is it still ac
|
|
|
47
45
|
const EARLY_HEARTBEAT_THRESHOLD = 3;
|
|
48
46
|
const REENGAGEMENT_COOLDOWN_MS = 18 * 60 * 60 * 1000; // 18 hours
|
|
49
47
|
const HEARTBEAT_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
50
|
-
const HEARTBEAT_ALERT_MARKER = "HEARTBEAT_ALERT";
|
|
51
|
-
const HEARTBEAT_OK_MARKER = "HEARTBEAT_OK";
|
|
52
|
-
const HEARTBEAT_ALERT_SUMMARY_MAX_CHARS = 700;
|
|
53
48
|
|
|
54
49
|
// Stripped-comment form of the guardian persona scaffold. Computed
|
|
55
50
|
// once at module load because stripping comment lines is deterministic
|
|
@@ -102,69 +97,6 @@ function recordReengagementTimestamp(): void {
|
|
|
102
97
|
}
|
|
103
98
|
}
|
|
104
99
|
|
|
105
|
-
type HeartbeatDisposition = "alert" | "ok" | "unknown";
|
|
106
|
-
|
|
107
|
-
function parseHeartbeatDisposition(text: string | null): HeartbeatDisposition {
|
|
108
|
-
if (!text) return "unknown";
|
|
109
|
-
const lines = text
|
|
110
|
-
.trim()
|
|
111
|
-
.split(/\r?\n/)
|
|
112
|
-
.map((line) => line.trim())
|
|
113
|
-
.filter((line) => line.length > 0);
|
|
114
|
-
const lastLine = lines.at(-1);
|
|
115
|
-
if (lastLine === HEARTBEAT_ALERT_MARKER) return "alert";
|
|
116
|
-
if (lastLine === HEARTBEAT_OK_MARKER) return "ok";
|
|
117
|
-
return "unknown";
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function stripHeartbeatDispositionMarkers(text: string): string {
|
|
121
|
-
return text
|
|
122
|
-
.replace(
|
|
123
|
-
new RegExp(
|
|
124
|
-
`(?:\\r?\\n)?\\s*(?:${HEARTBEAT_ALERT_MARKER}|${HEARTBEAT_OK_MARKER})\\s*$`,
|
|
125
|
-
),
|
|
126
|
-
"",
|
|
127
|
-
)
|
|
128
|
-
.trim();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function truncateSummary(text: string, maxChars: number): string {
|
|
132
|
-
if (text.length <= maxChars) return text;
|
|
133
|
-
return `${text.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function buildHeartbeatAlertSummary(text: string | null): string {
|
|
137
|
-
const summary = text ? stripHeartbeatDispositionMarkers(text) : "";
|
|
138
|
-
return truncateSummary(
|
|
139
|
-
summary || "Your assistant found something worth your attention.",
|
|
140
|
-
HEARTBEAT_ALERT_SUMMARY_MAX_CHARS,
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function extractVisibleTextFromStoredMessageContent(raw: string): string {
|
|
145
|
-
try {
|
|
146
|
-
const parsed = JSON.parse(raw) as unknown;
|
|
147
|
-
if (typeof parsed === "string") return parsed;
|
|
148
|
-
if (!Array.isArray(parsed)) return "";
|
|
149
|
-
const texts: string[] = [];
|
|
150
|
-
for (const block of parsed) {
|
|
151
|
-
if (
|
|
152
|
-
block != null &&
|
|
153
|
-
typeof block === "object" &&
|
|
154
|
-
"type" in block &&
|
|
155
|
-
block.type === "text" &&
|
|
156
|
-
"text" in block &&
|
|
157
|
-
typeof block.text === "string"
|
|
158
|
-
) {
|
|
159
|
-
texts.push(block.text);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return texts.join("\n").trim();
|
|
163
|
-
} catch {
|
|
164
|
-
return raw;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
100
|
export interface HeartbeatDeps {
|
|
169
101
|
alerter: (alert: HeartbeatAlert) => void;
|
|
170
102
|
onConversationCreated?: (info: {
|
|
@@ -202,6 +134,9 @@ export class HeartbeatService {
|
|
|
202
134
|
// Reset by resetTimer (guardian message), reconfigure, and stop. Force runs
|
|
203
135
|
// bypass the cap and do not increment.
|
|
204
136
|
private _consecutiveRuns = 0;
|
|
137
|
+
// Bumped every time the counter is reset so an in-flight run that finishes
|
|
138
|
+
// after a guardian message can detect the reset and skip its increment.
|
|
139
|
+
private _resetGeneration = 0;
|
|
205
140
|
|
|
206
141
|
constructor(deps: HeartbeatDeps) {
|
|
207
142
|
this.deps = deps;
|
|
@@ -360,6 +295,7 @@ export class HeartbeatService {
|
|
|
360
295
|
/** Restart the timer with the latest config (e.g. after settings change). */
|
|
361
296
|
reconfigure(): void {
|
|
362
297
|
this._consecutiveRuns = 0;
|
|
298
|
+
this._resetGeneration++;
|
|
363
299
|
this.configEpoch++;
|
|
364
300
|
if (this._pendingRunId) {
|
|
365
301
|
supersedePendingRun(this._pendingRunId);
|
|
@@ -384,6 +320,7 @@ export class HeartbeatService {
|
|
|
384
320
|
// Counter resets even when the timer is null so a guardian message during
|
|
385
321
|
// a stopped window still clears the count.
|
|
386
322
|
this._consecutiveRuns = 0;
|
|
323
|
+
this._resetGeneration++;
|
|
387
324
|
if (!this.timer) return;
|
|
388
325
|
if (this.cronMode) {
|
|
389
326
|
clearTimeout(this.timer as ReturnType<typeof setTimeout>);
|
|
@@ -404,6 +341,7 @@ export class HeartbeatService {
|
|
|
404
341
|
|
|
405
342
|
async stop(): Promise<void> {
|
|
406
343
|
this._consecutiveRuns = 0;
|
|
344
|
+
this._resetGeneration++;
|
|
407
345
|
this.stopped = true;
|
|
408
346
|
if (this.timer) {
|
|
409
347
|
clearTimeout(this.timer as ReturnType<typeof setTimeout>);
|
|
@@ -552,6 +490,11 @@ export class HeartbeatService {
|
|
|
552
490
|
}
|
|
553
491
|
const run = this.executeRun(runId, scheduledFor);
|
|
554
492
|
this.activeRun = run;
|
|
493
|
+
// Snapshot the reset generation so we can detect whether a reset (guardian
|
|
494
|
+
// message, reconfigure, stop) happened while this run was in flight. If it
|
|
495
|
+
// did, the counter was already zeroed and we must not undo that reset by
|
|
496
|
+
// incrementing in `finally`.
|
|
497
|
+
const startGeneration = this._resetGeneration;
|
|
555
498
|
try {
|
|
556
499
|
await run;
|
|
557
500
|
} catch (err) {
|
|
@@ -561,7 +504,7 @@ export class HeartbeatService {
|
|
|
561
504
|
this.activeRun = null;
|
|
562
505
|
}
|
|
563
506
|
this._lastRunAt = Date.now();
|
|
564
|
-
if (!force) {
|
|
507
|
+
if (!force && this._resetGeneration === startGeneration) {
|
|
565
508
|
this._consecutiveRuns++;
|
|
566
509
|
}
|
|
567
510
|
if (!this.cronMode) {
|
|
@@ -699,66 +642,6 @@ export class HeartbeatService {
|
|
|
699
642
|
}
|
|
700
643
|
}
|
|
701
644
|
|
|
702
|
-
private getLatestAssistantMessage(
|
|
703
|
-
conversationId: string,
|
|
704
|
-
): { id: string; text: string } | null {
|
|
705
|
-
try {
|
|
706
|
-
const messages = getMessages(conversationId);
|
|
707
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
708
|
-
const message = messages[i]!;
|
|
709
|
-
if (message.role !== "assistant") continue;
|
|
710
|
-
return {
|
|
711
|
-
id: message.id,
|
|
712
|
-
text: extractVisibleTextFromStoredMessageContent(message.content),
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
} catch (err) {
|
|
716
|
-
log.warn(
|
|
717
|
-
{ err, conversationId },
|
|
718
|
-
"Failed to read heartbeat assistant message",
|
|
719
|
-
);
|
|
720
|
-
}
|
|
721
|
-
return null;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
private async emitHeartbeatAlertNotification(params: {
|
|
725
|
-
runId: string;
|
|
726
|
-
conversationId: string;
|
|
727
|
-
messageId?: string;
|
|
728
|
-
conversationTitle: string;
|
|
729
|
-
summary: string;
|
|
730
|
-
}): Promise<void> {
|
|
731
|
-
const { emitNotificationSignal } =
|
|
732
|
-
await import("../notifications/emit-signal.js");
|
|
733
|
-
|
|
734
|
-
await emitNotificationSignal({
|
|
735
|
-
sourceEventName: "heartbeat.alert",
|
|
736
|
-
sourceChannel: "watcher",
|
|
737
|
-
sourceContextId: params.runId,
|
|
738
|
-
dedupeKey: `heartbeat:alert:${params.runId}`,
|
|
739
|
-
attentionHints: {
|
|
740
|
-
requiresAction: true,
|
|
741
|
-
urgency: "medium",
|
|
742
|
-
isAsyncBackground: true,
|
|
743
|
-
visibleInSourceNow: false,
|
|
744
|
-
},
|
|
745
|
-
contextPayload: {
|
|
746
|
-
title: "Heartbeat Alert",
|
|
747
|
-
summary: params.summary,
|
|
748
|
-
conversationTitle: params.conversationTitle,
|
|
749
|
-
conversationId: params.conversationId,
|
|
750
|
-
messageId: params.messageId,
|
|
751
|
-
},
|
|
752
|
-
routingIntent: "single_channel",
|
|
753
|
-
conversationAffinityHint: { vellum: params.conversationId },
|
|
754
|
-
conversationMetadata: {
|
|
755
|
-
source: "heartbeat",
|
|
756
|
-
groupId: "system:background",
|
|
757
|
-
conversationType: "background",
|
|
758
|
-
},
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
|
|
762
645
|
private async executeRun(runId: string, scheduledFor: number): Promise<void> {
|
|
763
646
|
log.info("Running heartbeat");
|
|
764
647
|
|
|
@@ -787,9 +670,9 @@ export class HeartbeatService {
|
|
|
787
670
|
// The runner fires `onConversationCreated` synchronously after
|
|
788
671
|
// bootstrap so the macOS sidebar gets the new conversation
|
|
789
672
|
// immediately rather than waiting up to HEARTBEAT_TIMEOUT_MS for
|
|
790
|
-
// the LLM turn to finish.
|
|
791
|
-
//
|
|
792
|
-
//
|
|
673
|
+
// the LLM turn to finish. If the model judges the run worth
|
|
674
|
+
// surfacing to the guardian, it calls the `notifications` skill
|
|
675
|
+
// directly — no in-band marker.
|
|
793
676
|
let conversationId: string | undefined;
|
|
794
677
|
const result = await runBackgroundJob({
|
|
795
678
|
jobName: "heartbeat",
|
|
@@ -803,6 +686,7 @@ export class HeartbeatService {
|
|
|
803
686
|
callSite: "heartbeatAgent",
|
|
804
687
|
timeoutMs: HEARTBEAT_TIMEOUT_MS,
|
|
805
688
|
origin: "heartbeat",
|
|
689
|
+
deferNotifications: true,
|
|
806
690
|
onConversationCreated: (newConversationId) => {
|
|
807
691
|
conversationId = newConversationId;
|
|
808
692
|
this.deps.onConversationCreated?.({
|
|
@@ -821,62 +705,26 @@ export class HeartbeatService {
|
|
|
821
705
|
"Heartbeat completed",
|
|
822
706
|
);
|
|
823
707
|
|
|
824
|
-
// Mark the run record as ok
|
|
825
|
-
// alert the
|
|
826
|
-
//
|
|
827
|
-
//
|
|
828
|
-
// contents.
|
|
708
|
+
// Mark the run record as ok. The runner owns failure emission via
|
|
709
|
+
// `activity.failed`; any user-facing alert the model decided to
|
|
710
|
+
// raise was emitted in-band via the `notifications` skill during
|
|
711
|
+
// the turn itself.
|
|
829
712
|
const transitioned = completeHeartbeatRun(runId, {
|
|
830
713
|
status: "ok",
|
|
831
714
|
conversationId: result.conversationId,
|
|
832
715
|
});
|
|
833
716
|
|
|
834
|
-
if (transitioned) {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
} catch {
|
|
842
|
-
// Best-effort; fall back to generic title.
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
const assistantMessage = this.getLatestAssistantMessage(
|
|
846
|
-
result.conversationId,
|
|
847
|
-
);
|
|
848
|
-
const disposition = parseHeartbeatDisposition(
|
|
849
|
-
assistantMessage?.text ?? null,
|
|
850
|
-
);
|
|
851
|
-
if (disposition === "alert") {
|
|
852
|
-
// Conversation was already surfaced via the runner's bootstrap
|
|
853
|
-
// callback above; alert just needs to emit the notification.
|
|
854
|
-
void this.emitHeartbeatAlertNotification({
|
|
717
|
+
if (transitioned && latenessMs > LATE_THRESHOLD_MS) {
|
|
718
|
+
const lateMinutes = Math.round(latenessMs / 60_000);
|
|
719
|
+
log.warn(
|
|
720
|
+
{
|
|
721
|
+
latenessMs,
|
|
722
|
+
lateMinutes,
|
|
723
|
+
scheduledFor,
|
|
855
724
|
runId,
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
summary: buildHeartbeatAlertSummary(assistantMessage?.text ?? null),
|
|
860
|
-
}).catch((err) => {
|
|
861
|
-
log.warn(
|
|
862
|
-
{ err, conversationId: result.conversationId },
|
|
863
|
-
"Failed to emit heartbeat alert notification",
|
|
864
|
-
);
|
|
865
|
-
});
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
if (latenessMs > LATE_THRESHOLD_MS) {
|
|
869
|
-
const lateMinutes = Math.round(latenessMs / 60_000);
|
|
870
|
-
log.warn(
|
|
871
|
-
{
|
|
872
|
-
latenessMs,
|
|
873
|
-
lateMinutes,
|
|
874
|
-
scheduledFor,
|
|
875
|
-
runId,
|
|
876
|
-
},
|
|
877
|
-
"Heartbeat ran late",
|
|
878
|
-
);
|
|
879
|
-
}
|
|
725
|
+
},
|
|
726
|
+
"Heartbeat ran late",
|
|
727
|
+
);
|
|
880
728
|
}
|
|
881
729
|
return;
|
|
882
730
|
}
|
|
@@ -941,18 +789,14 @@ Do NOT attempt to use tools for these providers — they will fail. Skip any che
|
|
|
941
789
|
</credential-status>`;
|
|
942
790
|
}
|
|
943
791
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
After completing your review, end your response with one of:
|
|
949
|
-
- HEARTBEAT_OK — if everything looks good, no action needed
|
|
950
|
-
- HEARTBEAT_ALERT — if you found issues that need attention (describe them before this marker)
|
|
951
|
-
</heartbeat-disposition>`;
|
|
792
|
+
const disposition = getConfig().heartbeat.disposition;
|
|
793
|
+
if (disposition) {
|
|
794
|
+
prompt += `\n\n<heartbeat-disposition>\n${disposition}\n</heartbeat-disposition>`;
|
|
795
|
+
}
|
|
952
796
|
|
|
953
797
|
if (completedRunCount < EARLY_HEARTBEAT_THRESHOLD) {
|
|
954
798
|
prompt += `\n\n<early-heartbeat>
|
|
955
|
-
This is one of your first heartbeats. Your user hasn't heard from you yet and may not know you're here. Find something genuinely useful to share — a follow-up from a recent conversation, something you noticed, or a quick check-in. Lean toward
|
|
799
|
+
This is one of your first heartbeats. Your user hasn't heard from you yet and may not know you're here. Find something genuinely useful to share — a follow-up from a recent conversation, something you noticed, or a quick check-in. Lean toward surfacing it via the notifications skill this time. First impressions matter.
|
|
956
800
|
</early-heartbeat>`;
|
|
957
801
|
}
|
|
958
802
|
|
|
@@ -116,6 +116,28 @@ describe("feedItemSchema — valid minimal items", () => {
|
|
|
116
116
|
expect(parsed.category).toBeUndefined();
|
|
117
117
|
expect(parsed.metadata).toBeUndefined();
|
|
118
118
|
});
|
|
119
|
+
|
|
120
|
+
test("noteworthy field passes through when present", () => {
|
|
121
|
+
const parsed = feedItemSchema.parse({
|
|
122
|
+
...minimalNotification(),
|
|
123
|
+
noteworthy: true,
|
|
124
|
+
});
|
|
125
|
+
expect(parsed.noteworthy).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("items without noteworthy field still parse (backward compat)", () => {
|
|
129
|
+
const parsed = feedItemSchema.parse(minimalNotification());
|
|
130
|
+
expect(parsed.noteworthy).toBeUndefined();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("title is optional and may be omitted", () => {
|
|
134
|
+
const { title: _omitted, ...rest } = minimalNotification();
|
|
135
|
+
const parsed = feedItemSchema.parse(rest);
|
|
136
|
+
expect(parsed.title).toBeUndefined();
|
|
137
|
+
expect(parsed.summary).toBe(
|
|
138
|
+
"You mentioned wanting to review the onboarding designs.",
|
|
139
|
+
);
|
|
140
|
+
});
|
|
119
141
|
});
|
|
120
142
|
|
|
121
143
|
// ---------------------------------------------------------------------------
|
|
@@ -244,4 +266,22 @@ describe("parseFeedFile", () => {
|
|
|
244
266
|
}),
|
|
245
267
|
).toThrow();
|
|
246
268
|
});
|
|
269
|
+
|
|
270
|
+
test("accepts a file with a noteworthy item", () => {
|
|
271
|
+
const parsed = parseFeedFile({
|
|
272
|
+
version: 2,
|
|
273
|
+
items: [{ ...minimalNotification(), noteworthy: true }],
|
|
274
|
+
updatedAt: NOW_ISO,
|
|
275
|
+
});
|
|
276
|
+
expect(parsed.items[0]?.noteworthy).toBe(true);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("accepts a file whose items omit noteworthy (backward compat)", () => {
|
|
280
|
+
const parsed = parseFeedFile({
|
|
281
|
+
version: 2,
|
|
282
|
+
items: [minimalNotification()],
|
|
283
|
+
updatedAt: NOW_ISO,
|
|
284
|
+
});
|
|
285
|
+
expect(parsed.items[0]?.noteworthy).toBeUndefined();
|
|
286
|
+
});
|
|
247
287
|
});
|
|
@@ -5,8 +5,6 @@ import { describe, expect, mock, test } from "bun:test";
|
|
|
5
5
|
let mockConnectedProviders = new Set<string>();
|
|
6
6
|
|
|
7
7
|
mock.module("../../oauth/oauth-store.js", () => ({
|
|
8
|
-
isProviderConnected: async (provider: string) =>
|
|
9
|
-
mockConnectedProviders.has(provider),
|
|
10
8
|
listProviders: () => [
|
|
11
9
|
{ provider: "google" },
|
|
12
10
|
{ provider: "slack" },
|
|
@@ -16,6 +14,11 @@ mock.module("../../oauth/oauth-store.js", () => ({
|
|
|
16
14
|
],
|
|
17
15
|
}));
|
|
18
16
|
|
|
17
|
+
mock.module("../../schedule/integration-status.js", () => ({
|
|
18
|
+
isOAuthProviderConnected: async (provider: string) =>
|
|
19
|
+
mockConnectedProviders.has(provider),
|
|
20
|
+
}));
|
|
21
|
+
|
|
19
22
|
mock.module("../../util/logger.js", () => ({
|
|
20
23
|
getLogger: () =>
|
|
21
24
|
new Proxy({} as Record<string, unknown>, {
|
|
@@ -23,6 +26,34 @@ mock.module("../../util/logger.js", () => ({
|
|
|
23
26
|
}),
|
|
24
27
|
}));
|
|
25
28
|
|
|
29
|
+
mock.module("../../config/loader.js", () => ({
|
|
30
|
+
getConfig: () => ({ llm: {} }),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
mock.module("../../config/llm-resolver.js", () => ({
|
|
34
|
+
resolveCallSiteConfig: () => ({ provider: "mock" }),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
mock.module("../../providers/provider-send-message.js", () => ({
|
|
38
|
+
getConfiguredProvider: async () => ({}),
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
mock.module("../../prompts/persona-resolver.js", () => ({
|
|
42
|
+
resolvePersonaContext: () => ({
|
|
43
|
+
userPersona: null,
|
|
44
|
+
userSlug: null,
|
|
45
|
+
channelPersona: null,
|
|
46
|
+
}),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
mock.module("../../prompts/system-prompt.js", () => ({
|
|
50
|
+
buildSystemPrompt: () => "mock system prompt",
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
mock.module("../../runtime/btw-sidechain.js", () => ({
|
|
54
|
+
runBtwSidechain: async () => ({ text: "" }),
|
|
55
|
+
}));
|
|
56
|
+
|
|
26
57
|
const { getSuggestedPrompts } = await import("../suggested-prompts.js");
|
|
27
58
|
|
|
28
59
|
// ─── Tests ─────────────────────────────────────────────────────────────
|
package/src/home/feed-types.ts
CHANGED
|
@@ -89,7 +89,13 @@ export interface FeedItem {
|
|
|
89
89
|
type: FeedItemType;
|
|
90
90
|
/** Integer in [0, 100]; higher values sort earlier. */
|
|
91
91
|
priority: number;
|
|
92
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Optional short header. Omit when the source did not supply one — the
|
|
94
|
+
* notification pipeline never manufactures a title from rendered copy
|
|
95
|
+
* (LLM-echoed bodies stutter against `summary`). Clients fall back to
|
|
96
|
+
* `summary` when rendering a row.
|
|
97
|
+
*/
|
|
98
|
+
title?: string;
|
|
93
99
|
summary: string;
|
|
94
100
|
/** Event time (ISO-8601). */
|
|
95
101
|
timestamp: string;
|
|
@@ -100,12 +106,21 @@ export interface FeedItem {
|
|
|
100
106
|
actions?: FeedAction[];
|
|
101
107
|
/** Visual urgency treatment — controls badge color independently of sort priority. */
|
|
102
108
|
urgency?: FeedItemUrgency;
|
|
103
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* Source conversation that emitted this notification, when known. Used by
|
|
111
|
+
* the "Go to Thread" affordance in the detail panel. Omitted when the
|
|
112
|
+
* source context is not a navigable conversation (scheduler job ids,
|
|
113
|
+
* watcher event ids, CLI tool-call ids).
|
|
114
|
+
*/
|
|
104
115
|
conversationId?: string;
|
|
105
116
|
/** Server-driven detail panel descriptor; when present, the client opens this panel kind. */
|
|
106
117
|
detailPanel?: FeedItemDetailPanel;
|
|
107
118
|
/** Broad category for grouping and filtering feed items. */
|
|
108
119
|
category?: FeedItemCategory;
|
|
120
|
+
/** True when this item represents an assistant-initiated share or a high-importance system event. Used by clients to split inbox vs activity surfaces. */
|
|
121
|
+
noteworthy?: boolean;
|
|
122
|
+
/** True when the assistant herself emitted this item (e.g. via the `notifications send` skill). Drives clients to swap the row's leading icon for the persona avatar; system-generated items keep the category icon. */
|
|
123
|
+
fromAssistant?: boolean;
|
|
109
124
|
/** Arbitrary structured data the detail panel or other consumers can use. */
|
|
110
125
|
metadata?: Record<string, unknown>;
|
|
111
126
|
/** Internal: ISO-8601 writer-record time, used for ordering + TTL. */
|
|
@@ -198,7 +213,7 @@ export const feedItemSchema = z.object({
|
|
|
198
213
|
id: z.string(),
|
|
199
214
|
type: feedItemTypeSchema,
|
|
200
215
|
priority: z.number().int().min(0).max(100),
|
|
201
|
-
title: z.string(),
|
|
216
|
+
title: z.string().optional(),
|
|
202
217
|
summary: z.string(),
|
|
203
218
|
timestamp: z.string(),
|
|
204
219
|
status: feedItemStatusSchema.default("new"),
|
|
@@ -208,6 +223,8 @@ export const feedItemSchema = z.object({
|
|
|
208
223
|
conversationId: z.string().optional(),
|
|
209
224
|
detailPanel: feedItemDetailPanelSchema.optional(),
|
|
210
225
|
category: feedItemCategorySchema.optional(),
|
|
226
|
+
noteworthy: z.boolean().optional(),
|
|
227
|
+
fromAssistant: z.boolean().optional(),
|
|
211
228
|
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
212
229
|
createdAt: z.string(),
|
|
213
230
|
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background refresh timer for LLM-generated home page content.
|
|
3
|
+
*
|
|
4
|
+
* Keeps the personalized greeting and assistant-generated suggestion
|
|
5
|
+
* prompts warm in their caches so the GET handler never triggers LLM
|
|
6
|
+
* calls or database writes (see `src/runtime/AGENTS.md` — GET handler
|
|
7
|
+
* idempotency rule).
|
|
8
|
+
*
|
|
9
|
+
* Call `startHomeContentRefresh()` once during daemon startup; the
|
|
10
|
+
* timer handles periodic re-generation automatically.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { getLogger } from "../util/logger.js";
|
|
14
|
+
import { refreshPersonalizedGreeting } from "./home-greeting.js";
|
|
15
|
+
import { refreshAssistantSuggestedPrompts } from "./suggested-prompts.js";
|
|
16
|
+
|
|
17
|
+
const log = getLogger("home-content-refresh");
|
|
18
|
+
|
|
19
|
+
const REFRESH_INTERVAL_MS = 30 * 60 * 1000; // 30 minutes
|
|
20
|
+
|
|
21
|
+
let refreshTimer: ReturnType<typeof setInterval> | null = null;
|
|
22
|
+
|
|
23
|
+
async function refreshAll(): Promise<void> {
|
|
24
|
+
await Promise.all([
|
|
25
|
+
refreshPersonalizedGreeting(),
|
|
26
|
+
refreshAssistantSuggestedPrompts(),
|
|
27
|
+
]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Start periodic background refresh of home page LLM content.
|
|
32
|
+
* Runs an initial generation immediately (fire-and-forget) and
|
|
33
|
+
* schedules re-generation every 30 minutes.
|
|
34
|
+
*/
|
|
35
|
+
export function startHomeContentRefresh(): void {
|
|
36
|
+
void refreshAll().catch((err) =>
|
|
37
|
+
log.warn({ err }, "Initial home content refresh failed"),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
refreshTimer = setInterval(() => {
|
|
41
|
+
void refreshAll().catch((err) =>
|
|
42
|
+
log.warn({ err }, "Periodic home content refresh failed"),
|
|
43
|
+
);
|
|
44
|
+
}, REFRESH_INTERVAL_MS);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function stopHomeContentRefresh(): void {
|
|
48
|
+
if (refreshTimer) {
|
|
49
|
+
clearInterval(refreshTimer);
|
|
50
|
+
refreshTimer = null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caching layer for the LLM-generated personalized home greeting.
|
|
3
|
+
*
|
|
4
|
+
* The greeting (a short persona-flavored "here's what's been going on" line)
|
|
5
|
+
* is generated via `runBtwSidechain` and displayed at the top of the Home
|
|
6
|
+
* page. To avoid redundant LLM calls on every feed fetch, we cache the
|
|
7
|
+
* result for 4 hours with content-hash-based invalidation: when IDENTITY.md,
|
|
8
|
+
* SOUL.md, or the guardian persona change, the cache is busted.
|
|
9
|
+
*
|
|
10
|
+
* Storage uses the existing `memory_checkpoints` table (simple key-value store).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
getMemoryCheckpoint,
|
|
15
|
+
setMemoryCheckpoint,
|
|
16
|
+
} from "../memory/checkpoints.js";
|
|
17
|
+
import { computeIdentityContentHash } from "../runtime/routes/identity-intro-cache.js";
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Constants
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
const CACHE_TTL_MS = 4 * 60 * 60 * 1000; // 4 hours
|
|
24
|
+
|
|
25
|
+
const CHECKPOINT_KEY_TEXT = "home:greeting:text";
|
|
26
|
+
const CHECKPOINT_KEY_HASH = "home:greeting:content_hash";
|
|
27
|
+
const CHECKPOINT_KEY_TIMESTAMP = "home:greeting:cached_at";
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Public API
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
export function getCachedHomeGreeting(): string | null {
|
|
34
|
+
try {
|
|
35
|
+
const text = getMemoryCheckpoint(CHECKPOINT_KEY_TEXT);
|
|
36
|
+
const hash = getMemoryCheckpoint(CHECKPOINT_KEY_HASH);
|
|
37
|
+
const timestampStr = getMemoryCheckpoint(CHECKPOINT_KEY_TIMESTAMP);
|
|
38
|
+
|
|
39
|
+
if (!text || !hash || !timestampStr) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const cachedAt = Number(timestampStr);
|
|
44
|
+
if (isNaN(cachedAt) || Date.now() - cachedAt > CACHE_TTL_MS) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const currentHash = computeIdentityContentHash();
|
|
49
|
+
if (currentHash !== hash) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return text;
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function setCachedHomeGreeting(text: string): void {
|
|
60
|
+
try {
|
|
61
|
+
const hash = computeIdentityContentHash();
|
|
62
|
+
const now = String(Date.now());
|
|
63
|
+
setMemoryCheckpoint(CHECKPOINT_KEY_TEXT, text);
|
|
64
|
+
setMemoryCheckpoint(CHECKPOINT_KEY_HASH, hash);
|
|
65
|
+
setMemoryCheckpoint(CHECKPOINT_KEY_TIMESTAMP, now);
|
|
66
|
+
} catch {
|
|
67
|
+
// Cache write failure is non-fatal — next request will regenerate.
|
|
68
|
+
}
|
|
69
|
+
}
|