@vellumai/assistant 0.7.1 → 0.7.3
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 +48 -50
- package/Dockerfile +1 -0
- package/README.md +1 -2
- package/__tests__/permissions/gateway-threshold-reader.test.ts +9 -3
- package/bun.lock +26 -26
- package/docs/architecture/memory.md +5 -2
- package/docs/architecture/security.md +20 -0
- package/docs/plugins.md +7 -9
- package/knip.json +1 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +52 -5
- package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
- package/node_modules/@vellumai/service-contracts/package.json +2 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
- package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
- package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
- package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
- package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
- package/node_modules/@vellumai/twilio-client/package.json +18 -0
- package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
- package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
- package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
- package/openapi.yaml +1020 -40
- package/package.json +6 -3
- package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
- package/src/__tests__/app-bundler.test.ts +170 -1
- package/src/__tests__/app-control-flow.test.ts +384 -0
- package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
- package/src/__tests__/app-executors.test.ts +30 -43
- package/src/__tests__/approval-routes-http.test.ts +23 -6
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
- package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
- package/src/__tests__/assistant-event-hub.test.ts +157 -2
- package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -7
- package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
- package/src/__tests__/background-shell-host-bash.test.ts +14 -15
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
- package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
- package/src/__tests__/btw-routes.test.ts +13 -4
- package/src/__tests__/call-controller.test.ts +49 -1
- package/src/__tests__/call-conversation-messages.test.ts +8 -2
- package/src/__tests__/call-domain.test.ts +0 -2
- package/src/__tests__/call-routes-http.test.ts +0 -2
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
- package/src/__tests__/channel-readiness-service.test.ts +62 -2
- package/src/__tests__/checker.test.ts +3 -4
- package/src/__tests__/config-loader-backfill.test.ts +461 -147
- package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/config-set-platform-guard.test.ts +48 -4
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +20 -11
- package/src/__tests__/config-watcher.test.ts +142 -71
- package/src/__tests__/context-search-agent-runner.test.ts +61 -3
- package/src/__tests__/context-search-conversations-source.test.ts +0 -24
- package/src/__tests__/context-search-fanout.test.ts +0 -1
- package/src/__tests__/context-search-memory-source.test.ts +3 -7
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop.test.ts +454 -5
- package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-lifecycle.test.ts +36 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
- package/src/__tests__/conversation-process-callsite.test.ts +43 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +6 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -72
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
- package/src/__tests__/conversation-slash-commands.test.ts +0 -4
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
- package/src/__tests__/conversation-speed-override.test.ts +0 -3
- package/src/__tests__/conversation-store.test.ts +0 -18
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +328 -0
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
- package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
- package/src/__tests__/credentials-cli.test.ts +12 -12
- package/src/__tests__/cu-unified-flow.test.ts +351 -23
- package/src/__tests__/daemon-credential-client.test.ts +101 -19
- package/src/__tests__/date-context.test.ts +164 -2
- package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
- package/src/__tests__/disk-pressure-guard.test.ts +262 -0
- package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
- package/src/__tests__/disk-pressure-policy.test.ts +241 -0
- package/src/__tests__/disk-pressure-routes.test.ts +379 -0
- package/src/__tests__/disk-pressure-tools.test.ts +277 -0
- package/src/__tests__/disk-usage.test.ts +150 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/events-client-registration.test.ts +52 -0
- package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
- package/src/__tests__/file-write-tool.test.ts +4 -10
- package/src/__tests__/filing-service.test.ts +3 -4
- package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -2
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
- package/src/__tests__/heartbeat-service.test.ts +968 -2
- package/src/__tests__/helpers/call-route-handler.ts +7 -1
- package/src/__tests__/host-app-control-proxy.test.ts +772 -0
- package/src/__tests__/host-app-control-routes.test.ts +263 -0
- package/src/__tests__/host-bash-proxy.test.ts +439 -47
- package/src/__tests__/host-bash-routes.test.ts +459 -0
- package/src/__tests__/host-browser-proxy.test.ts +24 -22
- package/src/__tests__/host-browser-routes.test.ts +39 -13
- package/src/__tests__/host-cu-proxy.test.ts +248 -52
- package/src/__tests__/host-cu-routes-targeted.test.ts +429 -0
- package/src/__tests__/host-file-edit-tool.test.ts +47 -1
- package/src/__tests__/host-file-proxy-targeted.test.ts +378 -0
- package/src/__tests__/host-file-proxy.test.ts +301 -45
- package/src/__tests__/host-file-read-tool.test.ts +17 -0
- package/src/__tests__/host-file-routes-targeted.test.ts +420 -0
- package/src/__tests__/host-file-write-tool.test.ts +42 -1
- package/src/__tests__/host-proxy-base.test.ts +312 -0
- package/src/__tests__/host-shell-tool.test.ts +22 -4
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +932 -0
- package/src/__tests__/host-transfer-proxy.test.ts +121 -22
- package/src/__tests__/host-transfer-routes-targeted.test.ts +662 -0
- package/src/__tests__/http-user-message-parity.test.ts +108 -1
- package/src/__tests__/identity-intro-cache.test.ts +29 -0
- package/src/__tests__/identity-routes.test.ts +103 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
- package/src/__tests__/injector-chain.test.ts +18 -6
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- package/src/__tests__/inline-command-runner.test.ts +0 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +5 -11
- package/src/__tests__/integration-status.test.ts +85 -5
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
- package/src/__tests__/managed-profile-guard.test.ts +18 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
- package/src/__tests__/mcp-abort-signal.test.ts +130 -0
- package/src/__tests__/mcp-auth-routes.test.ts +197 -0
- package/src/__tests__/mcp-cli.test.ts +338 -2
- package/src/__tests__/memory-admin-recall.test.ts +3 -11
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
- package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
- package/src/__tests__/migration-import-commit-http.test.ts +108 -2
- package/src/__tests__/mock-gateway-ipc.ts +1 -0
- package/src/__tests__/normalize-onboarding.test.ts +180 -0
- package/src/__tests__/oauth-cli.test.ts +0 -2
- package/src/__tests__/oauth-connect-routes.test.ts +316 -0
- package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
- package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
- package/src/__tests__/onboarding-persona-write.test.ts +308 -0
- package/src/__tests__/openai-provider.test.ts +45 -8
- package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
- package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
- package/src/__tests__/platform-callback-registration.test.ts +21 -4
- package/src/__tests__/platform.test.ts +2 -1
- package/src/__tests__/playbook-execution.test.ts +0 -43
- package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +214 -25
- package/src/__tests__/process-message-background-slack.test.ts +2 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
- package/src/__tests__/provider-tool-name.test.ts +23 -0
- package/src/__tests__/public-ingress-urls.test.ts +97 -0
- package/src/__tests__/relay-server.test.ts +15 -4
- package/src/__tests__/require-fresh-approval.test.ts +0 -1
- package/src/__tests__/retry-backoff.test.ts +87 -0
- package/src/__tests__/runtime-events-sse.test.ts +2 -2
- package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
- package/src/__tests__/schedule-retry.test.ts +715 -0
- package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
- package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
- package/src/__tests__/secret-ingress-http.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
- package/src/__tests__/skill-feature-flags.test.ts +43 -41
- package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
- package/src/__tests__/skill-load-inline-command.test.ts +0 -51
- package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
- package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
- package/src/__tests__/slack-channel-config.test.ts +9 -14
- package/src/__tests__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-config.test.ts +0 -1
- package/src/__tests__/test-preload.ts +8 -0
- package/src/__tests__/tool-approval-handler.test.ts +3 -4
- package/src/__tests__/tool-audit-listener.test.ts +48 -0
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -1
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/twilio-config.test.ts +3 -16
- package/src/__tests__/twilio-routes.test.ts +3 -5
- package/src/__tests__/twilio-validation.test.ts +93 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -4
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -4
- package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
- package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
- package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
- package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
- package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
- package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
- package/src/approvals/guardian-decision-primitive.ts +13 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -17
- package/src/backup/__tests__/paths.test.ts +0 -22
- package/src/backup/__tests__/restore.test.ts +51 -151
- package/src/backup/paths.ts +2 -18
- package/src/backup/restore.ts +107 -231
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/app-bundler.ts +51 -3
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-conversation-messages.ts +46 -10
- package/src/calls/relay-server.ts +4 -44
- package/src/calls/twilio-config.ts +2 -17
- package/src/calls/twilio-rest.ts +33 -105
- package/src/calls/twilio-routes.ts +11 -12
- package/src/channels/types.ts +8 -7
- package/src/cli/commands/__tests__/backup.test.ts +6 -277
- package/src/cli/commands/__tests__/gateway.test.ts +288 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +4 -0
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -5
- package/src/cli/commands/backup.ts +6 -331
- package/src/cli/commands/bash.ts +35 -108
- package/src/cli/commands/clients.ts +36 -37
- package/src/cli/commands/contacts.ts +137 -25
- package/src/cli/commands/conversations.ts +2 -5
- package/src/cli/commands/credentials.ts +71 -7
- package/src/cli/commands/domain.ts +66 -15
- package/src/cli/commands/gateway.ts +183 -0
- package/src/cli/commands/keys.ts +9 -6
- package/src/cli/commands/mcp.ts +116 -156
- package/src/cli/commands/memory-v2.ts +303 -7
- package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
- package/src/cli/commands/oauth/connect.ts +127 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -4
- package/src/cli/commands/platform/__tests__/connect.test.ts +7 -3
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -3
- package/src/cli/commands/platform/__tests__/status.test.ts +116 -21
- package/src/cli/commands/platform/disconnect.ts +5 -4
- package/src/cli/commands/platform/index.ts +16 -25
- package/src/cli/commands/status.ts +57 -0
- package/src/cli/lib/daemon-credential-client.ts +110 -28
- package/src/cli/program.ts +6 -2
- package/src/config/assistant-feature-flags.ts +79 -12
- package/src/config/bundled-skills/acp/SKILL.md +6 -0
- package/src/config/bundled-skills/acp/TOOLS.json +1 -22
- package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
- package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
- package/src/config/bundled-skills/app-control/SKILL.md +75 -0
- package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
- package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
- package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
- package/src/config/bundled-skills/document/TOOLS.json +0 -8
- package/src/config/bundled-skills/followups/TOOLS.json +0 -12
- package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
- package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
- package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
- package/src/config/bundled-skills/messaging/TOOLS.json +0 -40
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
- package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +25 -4
- package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
- package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
- package/src/config/bundled-skills/settings/SKILL.md +4 -0
- package/src/config/bundled-skills/settings/TOOLS.json +0 -12
- package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
- package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
- package/src/config/bundled-skills/subagent/SKILL.md +6 -2
- package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
- package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
- package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
- package/src/config/bundled-tool-registry.ts +21 -0
- package/src/config/env-registry.ts +0 -2
- package/src/config/env.ts +19 -20
- package/src/config/feature-flag-registry.json +47 -135
- package/src/config/loader.ts +197 -104
- package/src/config/sanitize-for-transfer.ts +2 -0
- package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +17 -9
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/calls.ts +0 -9
- package/src/config/schemas/channels.ts +0 -5
- package/src/config/schemas/heartbeat.ts +64 -1
- package/src/config/schemas/ingress.ts +10 -6
- package/src/config/schemas/llm.ts +7 -10
- package/src/config/schemas/memory-lifecycle.ts +90 -24
- package/src/config/schemas/memory-v2.ts +121 -13
- package/src/config/schemas/platform.ts +49 -3
- package/src/config/schemas/services.ts +29 -15
- package/src/config/schemas/skills.ts +0 -6
- package/src/config/seed-inference-profiles.ts +230 -33
- package/src/contacts/contact-store.ts +0 -55
- package/src/contacts/contacts-write.ts +0 -27
- package/src/context/window-manager.ts +1 -2
- package/src/credential-execution/feature-gates.ts +10 -10
- package/src/credential-execution/process-manager.ts +12 -41
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +187 -5
- package/src/daemon/assistant-attachments.ts +4 -4
- package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
- package/src/daemon/config-watcher.ts +89 -60
- package/src/daemon/conversation-agent-loop-handlers.ts +27 -3
- package/src/daemon/conversation-agent-loop.ts +202 -61
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +9 -4
- package/src/daemon/conversation-process.ts +24 -11
- package/src/daemon/conversation-runtime-assembly.ts +28 -2
- package/src/daemon/conversation-store.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +305 -4
- package/src/daemon/conversation-tool-setup.ts +66 -62
- package/src/daemon/conversation.ts +38 -24
- package/src/daemon/date-context.ts +71 -22
- package/src/daemon/disk-pressure-background-gate.ts +73 -0
- package/src/daemon/disk-pressure-guard.ts +343 -0
- package/src/daemon/disk-pressure-policy.ts +163 -0
- package/src/daemon/doordash-steps.ts +1 -1
- package/src/daemon/handlers/shared.ts +4 -2
- package/src/daemon/handlers/skills.ts +3 -4
- package/src/daemon/host-app-control-proxy.ts +389 -0
- package/src/daemon/host-bash-proxy.ts +117 -82
- package/src/daemon/host-browser-proxy.ts +67 -82
- package/src/daemon/host-cu-proxy.ts +127 -86
- package/src/daemon/host-file-proxy.ts +129 -69
- package/src/daemon/host-proxy-base.ts +294 -0
- package/src/daemon/host-proxy-preactivation.ts +82 -0
- package/src/daemon/host-transfer-proxy.ts +338 -129
- package/src/daemon/lifecycle.ts +194 -145
- package/src/daemon/meet-host-supervisor.ts +4 -4
- package/src/daemon/meet-manifest-loader.ts +0 -1
- package/src/daemon/memory-v2-startup.ts +14 -4
- package/src/daemon/message-protocol.ts +6 -8
- package/src/daemon/message-types/contacts.ts +23 -1
- package/src/daemon/message-types/conversations.ts +15 -8
- package/src/daemon/message-types/disk-pressure.ts +9 -0
- package/src/daemon/message-types/host-app-control.ts +150 -0
- package/src/daemon/message-types/host-bash.ts +4 -0
- package/src/daemon/message-types/host-cu.ts +2 -0
- package/src/daemon/message-types/host-file.ts +4 -0
- package/src/daemon/message-types/host-transfer.ts +3 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/message-types/schedules.ts +8 -3
- package/src/daemon/message-types/skills.ts +2 -2
- package/src/daemon/process-message.ts +18 -1
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/shutdown-handlers.ts +0 -3
- package/src/daemon/tool-setup-types.ts +51 -0
- package/src/daemon/tool-side-effects.ts +1 -1
- package/src/documents/document-store.ts +85 -0
- package/src/events/tool-audit-listener.ts +2 -1
- package/src/filing/filing-service.ts +30 -5
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +24 -23
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +252 -0
- package/src/heartbeat/heartbeat-run-store.ts +249 -0
- package/src/heartbeat/heartbeat-service.ts +459 -54
- package/src/home/__tests__/post-connect-feed.test.ts +99 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +11 -9
- package/src/home/__tests__/suggested-prompts.test.ts +89 -0
- package/src/home/feed-scheduler.ts +18 -0
- package/src/home/post-connect-feed.ts +68 -0
- package/src/home/relationship-state-writer.ts +17 -92
- package/src/home/suggested-prompts.ts +46 -10
- package/src/inbound/platform-callback-registration.ts +8 -15
- package/src/inbound/public-ingress-urls.ts +32 -34
- package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
- package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
- package/src/ipc/assistant-server.ts +70 -3
- package/src/ipc/cli-client.ts +32 -1
- package/src/ipc/gateway-client.ts +37 -3
- package/src/live-voice/live-voice-archive.ts +4 -4
- package/src/live-voice/live-voice-metrics.ts +10 -10
- package/src/live-voice/protocol.ts +5 -7
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
- package/src/mcp/mcp-auth-orchestrator.ts +213 -0
- package/src/mcp/mcp-auth-state.ts +133 -0
- package/src/mcp/mcp-oauth-provider.ts +19 -0
- package/src/media/image-service.ts +1 -7
- package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
- package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
- package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
- package/src/memory/admin.ts +5 -9
- package/src/memory/anisotropy.test.ts +247 -0
- package/src/memory/anisotropy.ts +443 -0
- package/src/memory/auto-analysis-constants.ts +17 -0
- package/src/memory/auto-analysis-guard.ts +5 -15
- package/src/memory/canonical-guardian-store.ts +7 -7
- package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
- package/src/memory/context-search/agent-protocol.ts +6 -6
- package/src/memory/context-search/agent-runner.ts +51 -9
- package/src/memory/context-search/sources/conversations.ts +2 -11
- package/src/memory/context-search/sources/memory-v2.ts +22 -9
- package/src/memory/context-search/sources/memory.ts +0 -1
- package/src/memory/context-search/types.ts +0 -1
- package/src/memory/conversation-crud.ts +5 -13
- package/src/memory/conversation-key-store.ts +2 -15
- package/src/memory/db-init.ts +6 -0
- package/src/memory/embedding-backend.ts +9 -21
- package/src/memory/embedding-runtime-manager.ts +119 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +81 -25
- package/src/memory/graph/conversation-graph-memory.ts +43 -78
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +10 -67
- package/src/memory/graph/graph-search.ts +9 -20
- package/src/memory/graph/retriever.test.ts +6 -0
- package/src/memory/graph/retriever.ts +34 -10
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/indexer.ts +54 -45
- package/src/memory/job-handlers/backfill.ts +2 -11
- package/src/memory/job-handlers/cleanup.ts +43 -0
- package/src/memory/job-handlers/embedding.ts +6 -8
- package/src/memory/job-handlers/summarization.ts +2 -7
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
- package/src/memory/jobs/embed-concept-page.ts +28 -2
- package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
- package/src/memory/jobs-store.ts +114 -22
- package/src/memory/jobs-worker.ts +193 -106
- package/src/memory/memory-v2-activation-log-store.ts +33 -15
- package/src/memory/memory-v2-concept-frequency.ts +169 -0
- package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
- package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
- package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/pkb/pkb-search.test.ts +6 -0
- package/src/memory/pkb/pkb-search.ts +7 -0
- package/src/memory/qdrant-client.ts +49 -32
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/schema/infrastructure.ts +15 -0
- package/src/memory/search/semantic.ts +13 -67
- package/src/memory/sparse-tokenize.ts +49 -0
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +387 -344
- package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
- package/src/memory/v2/__tests__/injection.test.ts +181 -169
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +154 -188
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
- package/src/memory/v2/__tests__/static-context.test.ts +76 -2
- package/src/memory/v2/activation.ts +213 -239
- package/src/memory/v2/consolidation-job.ts +65 -17
- package/src/memory/v2/constants.ts +7 -0
- package/src/memory/v2/injection.ts +123 -103
- package/src/memory/v2/prompts/consolidation.ts +348 -92
- package/src/memory/v2/qdrant.ts +198 -1
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +113 -77
- package/src/memory/v2/skill-content.ts +4 -3
- package/src/memory/v2/skill-store.ts +91 -53
- package/src/memory/v2/sparse-bm25.ts +245 -0
- package/src/memory/v2/static-context.ts +28 -5
- package/src/memory/v2/types.ts +10 -10
- package/src/messaging/providers/gmail/types.ts +0 -49
- package/src/messaging/providers/slack/adapter.ts +1 -31
- package/src/messaging/providers/slack/types.ts +0 -32
- package/src/notifications/README.md +10 -10
- package/src/notifications/broadcaster.ts +1 -1
- package/src/notifications/copy-composer.ts +13 -0
- package/src/notifications/guardian-question-mode.ts +5 -5
- package/src/notifications/signal.ts +4 -0
- package/src/oauth/AGENTS.md +3 -1
- package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
- package/src/oauth/connect-orchestrator.ts +6 -0
- package/src/oauth/connection-resolver.test.ts +66 -1
- package/src/oauth/connection-resolver.ts +55 -1
- package/src/oauth/credential-token-resolver.ts +1 -3
- package/src/oauth/manual-token-connection.ts +0 -4
- package/src/oauth/oauth-connect-state.ts +77 -0
- package/src/oauth/seed-providers.ts +58 -1
- package/src/outbound-proxy/index.ts +1 -37
- package/src/outbound-proxy/logging.ts +1 -1
- package/src/outbound-proxy/policy.ts +6 -5
- package/src/outbound-proxy/router.ts +2 -1
- package/src/permissions/approval-policy.test.ts +6 -275
- package/src/permissions/approval-policy.ts +0 -51
- package/src/permissions/checker.test.ts +0 -1
- package/src/permissions/checker.ts +3 -17
- package/src/permissions/gateway-threshold-reader.ts +2 -0
- package/src/permissions/prompter.ts +34 -1
- package/src/permissions/secret-prompter.ts +6 -2
- package/src/plugins/defaults/injectors.ts +35 -2
- package/src/plugins/defaults/memory-retrieval.ts +5 -6
- package/src/plugins/types.ts +7 -0
- package/src/proactive-artifact/aux-message-injector.ts +74 -0
- package/src/proactive-artifact/decision.test.ts +226 -0
- package/src/proactive-artifact/decision.ts +165 -0
- package/src/proactive-artifact/index.ts +7 -0
- package/src/proactive-artifact/job.test.ts +867 -0
- package/src/proactive-artifact/job.ts +352 -0
- package/src/proactive-artifact/message-copy.ts +41 -0
- package/src/proactive-artifact/trigger-state.test.ts +277 -0
- package/src/proactive-artifact/trigger-state.ts +119 -0
- package/src/prompts/bootstrap-cleanup.ts +27 -0
- package/src/prompts/normalize-onboarding.ts +80 -0
- package/src/prompts/persona-resolver.ts +101 -9
- package/src/prompts/system-prompt.ts +23 -24
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/prompts/templates/SOUL.md +13 -1
- package/src/providers/__tests__/retry-callsite.test.ts +222 -1
- package/src/providers/model-intents.ts +7 -0
- package/src/providers/openrouter/client.ts +8 -0
- package/src/providers/retry.ts +50 -0
- package/src/providers/speech-to-text/provider-catalog.ts +7 -8
- package/src/providers/types.ts +1 -0
- package/src/runtime/__tests__/agent-wake.test.ts +456 -3
- package/src/runtime/agent-wake.ts +238 -100
- package/src/runtime/assistant-event-hub.ts +151 -99
- package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
- package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
- package/src/runtime/auth/middleware.ts +0 -96
- package/src/runtime/auth/route-policy.ts +32 -0
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/btw-sidechain.ts +2 -3
- package/src/runtime/channel-invite-transport.ts +2 -48
- package/src/runtime/channel-invite-transports/email.ts +1 -1
- package/src/runtime/channel-invite-transports/slack.ts +1 -1
- package/src/runtime/channel-invite-transports/telegram.ts +1 -1
- package/src/runtime/channel-invite-transports/voice.ts +1 -1
- package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
- package/src/runtime/channel-invite-types.ts +54 -0
- package/src/runtime/channel-readiness-service.ts +32 -13
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/guardian-reply-router.ts +10 -0
- package/src/runtime/http-server.ts +3 -329
- package/src/runtime/http-types.ts +0 -5
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
- package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +153 -1
- package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +51 -1
- package/src/runtime/migrations/migration-transport.ts +7 -7
- package/src/runtime/migrations/vbundle-builder.ts +327 -60
- package/src/runtime/migrations/vbundle-import-analyzer.ts +4 -4
- package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
- package/src/runtime/migrations/vbundle-importer.ts +245 -68
- package/src/runtime/migrations/vbundle-streaming-importer.ts +326 -35
- package/src/runtime/migrations/vbundle-streaming-validator.ts +157 -4
- package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
- package/src/runtime/migrations/vbundle-validator.ts +114 -0
- package/src/runtime/pending-interactions.ts +43 -9
- package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
- package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -5
- package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
- package/src/runtime/routes/approval-interception-types.ts +13 -0
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
- package/src/runtime/routes/backup-routes.ts +15 -38
- package/src/runtime/routes/btw-routes.ts +14 -37
- package/src/runtime/routes/client-routes.ts +21 -2
- package/src/runtime/routes/contact-prompt-routes.ts +183 -0
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-query-routes.ts +36 -1
- package/src/runtime/routes/conversation-routes.ts +65 -39
- package/src/runtime/routes/debug-bash-routes.ts +163 -0
- package/src/runtime/routes/disk-pressure-routes.ts +121 -0
- package/src/runtime/routes/document-pdf-renderer.ts +169 -0
- package/src/runtime/routes/documents-routes.ts +32 -75
- package/src/runtime/routes/errors.ts +19 -4
- package/src/runtime/routes/events-routes.ts +38 -0
- package/src/runtime/routes/gateway-log-routes.ts +79 -0
- package/src/runtime/routes/guardian-approval-interception.ts +2 -8
- package/src/runtime/routes/heartbeat-routes.ts +103 -38
- package/src/runtime/routes/host-app-control-routes.ts +134 -0
- package/src/runtime/routes/host-bash-routes.ts +56 -6
- package/src/runtime/routes/host-browser-routes.ts +108 -13
- package/src/runtime/routes/host-cu-routes.ts +66 -9
- package/src/runtime/routes/host-file-routes.ts +54 -5
- package/src/runtime/routes/host-transfer-routes.ts +122 -19
- package/src/runtime/routes/http-adapter.ts +1 -0
- package/src/runtime/routes/identity-intro-cache.ts +30 -0
- package/src/runtime/routes/identity-routes.ts +21 -180
- package/src/runtime/routes/inbound-message-handler.ts +78 -21
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -7
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
- package/src/runtime/routes/index.ts +14 -0
- package/src/runtime/routes/mcp-auth-routes.ts +132 -0
- package/src/runtime/routes/memory-item-routes.test.ts +41 -15
- package/src/runtime/routes/memory-item-routes.ts +10 -12
- package/src/runtime/routes/memory-v2-routes.ts +474 -1
- package/src/runtime/routes/migration-routes.ts +96 -0
- package/src/runtime/routes/oauth-connect-routes.ts +153 -0
- package/src/runtime/routes/schedule-routes.ts +7 -0
- package/src/runtime/verification-outbound-actions.ts +4 -4
- package/src/runtime/verification-templates.ts +4 -7
- package/src/schedule/integration-status.ts +66 -2
- package/src/schedule/recurrence-engine.ts +4 -1
- package/src/schedule/retry-backoff.ts +18 -0
- package/src/schedule/retry-policy.ts +82 -0
- package/src/schedule/run-script.ts +37 -5
- package/src/schedule/schedule-recovery.ts +64 -0
- package/src/schedule/schedule-store.ts +106 -2
- package/src/schedule/scheduler-types.ts +25 -0
- package/src/schedule/scheduler.ts +83 -39
- package/src/security/encrypted-store.ts +2 -0
- package/src/security/oauth-callback-registry.ts +8 -0
- package/src/security/secure-keys.ts +55 -0
- package/src/sequence/analytics.ts +5 -5
- package/src/sequence/engine.ts +1 -1
- package/src/skills/catalog-files.ts +2 -8
- package/src/skills/include-graph.ts +5 -5
- package/src/skills/remote-skill-policy.ts +10 -16
- package/src/skills/skill-file-provider.ts +1 -1
- package/src/skills/skill-file-types.ts +13 -0
- package/src/skills/skillssh-audit-types.ts +28 -0
- package/src/skills/skillssh-registry.ts +8 -21
- package/src/subagent/index.ts +1 -7
- package/src/subagent/manager.ts +1 -15
- package/src/tasks/task-runner.ts +0 -1
- package/src/tasks/task-store.ts +0 -3
- package/src/telemetry/types.ts +2 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +21 -0
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
- package/src/tools/apps/executors.ts +56 -69
- package/src/tools/background-tool-registry.ts +17 -3
- package/src/tools/browser/__tests__/browser-status.test.ts +21 -18
- package/src/tools/browser/browser-execution.ts +2 -2
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +55 -4
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
- package/src/tools/browser/cdp-client/factory.ts +23 -24
- package/src/tools/browser/cdp-client/index.ts +1 -14
- package/src/tools/computer-use/definitions.ts +42 -20
- package/src/tools/executor.ts +2 -0
- package/src/tools/host-filesystem/edit.test.ts +151 -0
- package/src/tools/host-filesystem/edit.ts +68 -0
- package/src/tools/host-filesystem/read.test.ts +129 -0
- package/src/tools/host-filesystem/read.ts +68 -0
- package/src/tools/host-filesystem/transfer.test.ts +127 -2
- package/src/tools/host-filesystem/transfer.ts +78 -3
- package/src/tools/host-filesystem/write.test.ts +134 -0
- package/src/tools/host-filesystem/write.ts +68 -0
- package/src/tools/host-terminal/host-shell.ts +66 -1
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/memory/register.test.ts +12 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/schedule/create.ts +6 -0
- package/src/tools/schedule/list.ts +2 -0
- package/src/tools/schedule/update.ts +10 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
- package/src/tools/shared/filesystem/path-policy.ts +25 -1
- package/src/tools/skills/load.ts +0 -32
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +32 -11
- package/src/tools/types.ts +28 -2
- package/src/tts/provider-catalog.ts +3 -5
- package/src/usage/pricing.ts +1 -1
- package/src/util/disk-usage.ts +138 -0
- package/src/util/platform.ts +21 -11
- package/src/util/process-liveness.ts +26 -0
- package/src/workspace/hatched-date.ts +86 -0
- package/src/workspace/heartbeat-service.ts +19 -0
- package/src/workspace/migrations/003-seed-device-id.ts +1 -1
- package/src/workspace/migrations/006-services-config.ts +8 -5
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
- package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +2 -1
- package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +3 -8
- package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
- package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
- package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/utils.ts +21 -0
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
- package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -443
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -226
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -427
- package/src/__tests__/twilio-rest.test.ts +0 -34
- package/src/backup/__tests__/backup-key.test.ts +0 -152
- package/src/backup/__tests__/backup-worker.test.ts +0 -782
- package/src/backup/__tests__/offsite-writer.test.ts +0 -641
- package/src/backup/__tests__/stream-crypt.test.ts +0 -228
- package/src/backup/backup-key.ts +0 -137
- package/src/backup/backup-worker.ts +0 -472
- package/src/backup/offsite-writer.ts +0 -222
- package/src/backup/stream-crypt.ts +0 -263
- package/src/daemon/message-types/pairing.ts +0 -58
- package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
- package/src/memory/v2/skill-qdrant.ts +0 -395
- package/src/outbound-proxy/config.ts +0 -20
- package/src/outbound-proxy/health.ts +0 -18
- package/src/outbound-proxy/types.ts +0 -150
- package/src/runtime/capability-tokens.ts +0 -190
- package/src/signals/bash.ts +0 -198
- package/src/signals/mcp-reload.ts +0 -18
|
@@ -2,21 +2,38 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
4
|
import { getConfig } from "../config/loader.js";
|
|
5
|
+
import type { HeartbeatConfig } from "../config/schemas/heartbeat.js";
|
|
6
|
+
import {
|
|
7
|
+
checkDiskPressureBackgroundGate,
|
|
8
|
+
diskPressureBackgroundSkipLogFields,
|
|
9
|
+
shouldLogDiskPressureBackgroundSkip,
|
|
10
|
+
} from "../daemon/disk-pressure-background-gate.js";
|
|
5
11
|
import type { HeartbeatAlert } from "../daemon/message-protocol.js";
|
|
6
12
|
import { processMessage } from "../daemon/process-message.js";
|
|
7
13
|
import { emitFeedEvent } from "../home/emit-feed-event.js";
|
|
8
14
|
import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
|
|
9
|
-
import { getConversation } from "../memory/conversation-crud.js";
|
|
15
|
+
import { getConversation, getMessages } from "../memory/conversation-crud.js";
|
|
10
16
|
import { GENERATING_TITLE } from "../memory/conversation-title-service.js";
|
|
11
17
|
import {
|
|
12
18
|
GUARDIAN_PERSONA_TEMPLATE,
|
|
13
19
|
resolveGuardianPersona,
|
|
14
20
|
} from "../prompts/persona-resolver.js";
|
|
15
21
|
import { isTemplateContent } from "../prompts/system-prompt.js";
|
|
22
|
+
import { computeNextRunAt } from "../schedule/recurrence-engine.js";
|
|
16
23
|
import { readTextFileSync } from "../util/fs.js";
|
|
17
24
|
import { getLogger } from "../util/logger.js";
|
|
18
25
|
import { getWorkspaceDir, getWorkspacePromptPath } from "../util/platform.js";
|
|
19
26
|
import { stripCommentLines } from "../util/strip-comment-lines.js";
|
|
27
|
+
import {
|
|
28
|
+
completeHeartbeatRun,
|
|
29
|
+
countCompletedHeartbeatRuns,
|
|
30
|
+
insertPendingHeartbeatRun,
|
|
31
|
+
markStaleRunningAsError,
|
|
32
|
+
markStaleRunsAsMissed,
|
|
33
|
+
skipHeartbeatRun,
|
|
34
|
+
startHeartbeatRun,
|
|
35
|
+
supersedePendingRun,
|
|
36
|
+
} from "./heartbeat-run-store.js";
|
|
20
37
|
|
|
21
38
|
const log = getLogger("heartbeat-check");
|
|
22
39
|
|
|
@@ -27,8 +44,12 @@ const DEFAULT_CHECKLIST = `- Check in with yourself. Read NOW.md. Is it still ac
|
|
|
27
44
|
- If you have a thought worth sharing, send it. A follow-up, a useful find, a check-in. Not every beat, but when it feels right.
|
|
28
45
|
- If something has happened since your last journal entry, write one. Even a few sentences. The journal is how future-you stays connected.`;
|
|
29
46
|
|
|
47
|
+
const EARLY_HEARTBEAT_THRESHOLD = 3;
|
|
30
48
|
const REENGAGEMENT_COOLDOWN_MS = 18 * 60 * 60 * 1000; // 18 hours
|
|
31
49
|
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;
|
|
32
53
|
|
|
33
54
|
// Stripped-comment form of the guardian persona scaffold. Computed
|
|
34
55
|
// once at module load because stripping comment lines is deterministic
|
|
@@ -81,6 +102,69 @@ function recordReengagementTimestamp(): void {
|
|
|
81
102
|
}
|
|
82
103
|
}
|
|
83
104
|
|
|
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
|
+
|
|
84
168
|
export interface HeartbeatDeps {
|
|
85
169
|
alerter: (alert: HeartbeatAlert) => void;
|
|
86
170
|
onConversationCreated?: (info: {
|
|
@@ -100,10 +184,20 @@ export class HeartbeatService {
|
|
|
100
184
|
}
|
|
101
185
|
|
|
102
186
|
private readonly deps: HeartbeatDeps;
|
|
103
|
-
private timer:
|
|
187
|
+
private timer:
|
|
188
|
+
| ReturnType<typeof setInterval>
|
|
189
|
+
| ReturnType<typeof setTimeout>
|
|
190
|
+
| null = null;
|
|
104
191
|
private activeRun: Promise<void> | null = null;
|
|
105
192
|
private _lastRunAt: number | null = null;
|
|
106
193
|
private _nextRunAt: number | null = null;
|
|
194
|
+
private cronMode = false;
|
|
195
|
+
private stopped = false;
|
|
196
|
+
private configEpoch = 0;
|
|
197
|
+
private _pendingRunId: string | null = null;
|
|
198
|
+
private _startupMissedCount = 0;
|
|
199
|
+
private _startupCrashedCount = 0;
|
|
200
|
+
private _hasRunStartupRecovery = false;
|
|
107
201
|
|
|
108
202
|
constructor(deps: HeartbeatDeps) {
|
|
109
203
|
this.deps = deps;
|
|
@@ -121,6 +215,7 @@ export class HeartbeatService {
|
|
|
121
215
|
}
|
|
122
216
|
|
|
123
217
|
start(): void {
|
|
218
|
+
this.stopped = false;
|
|
124
219
|
const config = getConfig().heartbeat;
|
|
125
220
|
if (!config.enabled) {
|
|
126
221
|
log.info("Heartbeat disabled by config");
|
|
@@ -129,7 +224,61 @@ export class HeartbeatService {
|
|
|
129
224
|
}
|
|
130
225
|
if (this.timer) return;
|
|
131
226
|
|
|
132
|
-
|
|
227
|
+
if (!this._hasRunStartupRecovery) {
|
|
228
|
+
this._hasRunStartupRecovery = true;
|
|
229
|
+
try {
|
|
230
|
+
this._startupMissedCount = markStaleRunsAsMissed();
|
|
231
|
+
this._startupCrashedCount = markStaleRunningAsError();
|
|
232
|
+
} catch (err) {
|
|
233
|
+
log.error({ err }, "Failed to recover stale heartbeat runs on startup");
|
|
234
|
+
}
|
|
235
|
+
if (this._startupMissedCount > 0 || this._startupCrashedCount > 0) {
|
|
236
|
+
log.info(
|
|
237
|
+
{
|
|
238
|
+
missedCount: this._startupMissedCount,
|
|
239
|
+
crashedCount: this._startupCrashedCount,
|
|
240
|
+
},
|
|
241
|
+
"Recovered stale heartbeat runs on startup",
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
if (!isDiskPressureBackgroundLocked("heartbeat-startup")) {
|
|
245
|
+
const total = this._startupMissedCount + this._startupCrashedCount;
|
|
246
|
+
const today = new Date().toISOString().split("T")[0];
|
|
247
|
+
void emitFeedEvent({
|
|
248
|
+
source: "assistant",
|
|
249
|
+
title: "Heartbeat Runs Missed",
|
|
250
|
+
summary: `${total} heartbeat run${
|
|
251
|
+
total > 1 ? "s were" : " was"
|
|
252
|
+
} missed while the assistant was offline.`,
|
|
253
|
+
dedupKey: `heartbeat:missed:${today}`,
|
|
254
|
+
priority: 55,
|
|
255
|
+
urgency: "high",
|
|
256
|
+
}).catch((err) => {
|
|
257
|
+
log.warn({ err }, "Failed to emit missed heartbeat feed event");
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (config.cronExpression != null) {
|
|
264
|
+
this.cronMode = true;
|
|
265
|
+
this.scheduleNextCronRun(config);
|
|
266
|
+
} else {
|
|
267
|
+
this.startIntervalMode(config);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private startIntervalMode(config: HeartbeatConfig): void {
|
|
272
|
+
this.cronMode = false;
|
|
273
|
+
if (this.timer) {
|
|
274
|
+
clearTimeout(this.timer as ReturnType<typeof setTimeout>);
|
|
275
|
+
clearInterval(this.timer as ReturnType<typeof setInterval>);
|
|
276
|
+
this.timer = null;
|
|
277
|
+
}
|
|
278
|
+
log.info(
|
|
279
|
+
{ intervalMs: config.intervalMs },
|
|
280
|
+
"Heartbeat service started (interval mode)",
|
|
281
|
+
);
|
|
133
282
|
this.scheduleNextRun(config.intervalMs);
|
|
134
283
|
this.timer = setInterval(() => {
|
|
135
284
|
this.runOnce().catch((err) => {
|
|
@@ -138,13 +287,69 @@ export class HeartbeatService {
|
|
|
138
287
|
}, config.intervalMs);
|
|
139
288
|
}
|
|
140
289
|
|
|
290
|
+
private scheduleNextCronRun(config: HeartbeatConfig): void {
|
|
291
|
+
if (this.stopped) return;
|
|
292
|
+
try {
|
|
293
|
+
const nextRunAt = computeNextRunAt({
|
|
294
|
+
syntax: "cron",
|
|
295
|
+
expression: config.cronExpression!,
|
|
296
|
+
timezone: config.timezone,
|
|
297
|
+
});
|
|
298
|
+
this._nextRunAt = nextRunAt;
|
|
299
|
+
if (this.timer) {
|
|
300
|
+
clearTimeout(this.timer as ReturnType<typeof setTimeout>);
|
|
301
|
+
clearInterval(this.timer as ReturnType<typeof setInterval>);
|
|
302
|
+
this.timer = null;
|
|
303
|
+
}
|
|
304
|
+
const MAX_TIMEOUT_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
305
|
+
const delayMs = Math.max(0, nextRunAt - Date.now());
|
|
306
|
+
const epoch = this.configEpoch;
|
|
307
|
+
if (delayMs > MAX_TIMEOUT_MS) {
|
|
308
|
+
// Re-evaluate after 24h — the actual cron time is still far away
|
|
309
|
+
this.timer = setTimeout(() => {
|
|
310
|
+
if (this.configEpoch === epoch) {
|
|
311
|
+
this.scheduleNextCronRun(getConfig().heartbeat);
|
|
312
|
+
}
|
|
313
|
+
}, MAX_TIMEOUT_MS);
|
|
314
|
+
} else {
|
|
315
|
+
this.timer = setTimeout(() => {
|
|
316
|
+
this.runOnce()
|
|
317
|
+
.catch((err) => log.error({ err }, "Cron heartbeat failed"))
|
|
318
|
+
.finally(() => {
|
|
319
|
+
if (this.configEpoch === epoch) {
|
|
320
|
+
this.scheduleNextCronRun(getConfig().heartbeat);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
}, delayMs);
|
|
324
|
+
}
|
|
325
|
+
(this.timer as ReturnType<typeof setTimeout>).unref();
|
|
326
|
+
log.info(
|
|
327
|
+
{ nextRunAt: new Date(nextRunAt).toISOString(), delayMs },
|
|
328
|
+
"Heartbeat cron run scheduled",
|
|
329
|
+
);
|
|
330
|
+
} catch (err) {
|
|
331
|
+
log.warn(
|
|
332
|
+
{ err },
|
|
333
|
+
"Failed to compute next cron run, falling back to interval mode",
|
|
334
|
+
);
|
|
335
|
+
this.startIntervalMode(config);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
141
339
|
/** Restart the timer with the latest config (e.g. after settings change). */
|
|
142
340
|
reconfigure(): void {
|
|
341
|
+
this.configEpoch++;
|
|
342
|
+
if (this._pendingRunId) {
|
|
343
|
+
supersedePendingRun(this._pendingRunId);
|
|
344
|
+
this._pendingRunId = null;
|
|
345
|
+
}
|
|
143
346
|
if (this.timer) {
|
|
144
|
-
|
|
347
|
+
clearTimeout(this.timer as ReturnType<typeof setTimeout>);
|
|
348
|
+
clearInterval(this.timer as ReturnType<typeof setInterval>);
|
|
145
349
|
this.timer = null;
|
|
146
350
|
}
|
|
147
351
|
this._nextRunAt = null;
|
|
352
|
+
this.cronMode = false;
|
|
148
353
|
this.start();
|
|
149
354
|
}
|
|
150
355
|
|
|
@@ -155,8 +360,15 @@ export class HeartbeatService {
|
|
|
155
360
|
*/
|
|
156
361
|
resetTimer(): void {
|
|
157
362
|
if (!this.timer) return;
|
|
363
|
+
if (this.cronMode) {
|
|
364
|
+
clearTimeout(this.timer as ReturnType<typeof setTimeout>);
|
|
365
|
+
clearInterval(this.timer as ReturnType<typeof setInterval>);
|
|
366
|
+
this.timer = null;
|
|
367
|
+
this.scheduleNextCronRun(getConfig().heartbeat);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
158
370
|
const config = getConfig().heartbeat;
|
|
159
|
-
clearInterval(this.timer);
|
|
371
|
+
clearInterval(this.timer as ReturnType<typeof setInterval>);
|
|
160
372
|
this.scheduleNextRun(config.intervalMs);
|
|
161
373
|
this.timer = setInterval(() => {
|
|
162
374
|
this.runOnce().catch((err) => {
|
|
@@ -166,10 +378,16 @@ export class HeartbeatService {
|
|
|
166
378
|
}
|
|
167
379
|
|
|
168
380
|
async stop(): Promise<void> {
|
|
381
|
+
this.stopped = true;
|
|
169
382
|
if (this.timer) {
|
|
170
|
-
|
|
383
|
+
clearTimeout(this.timer as ReturnType<typeof setTimeout>);
|
|
384
|
+
clearInterval(this.timer as ReturnType<typeof setInterval>);
|
|
171
385
|
this.timer = null;
|
|
172
386
|
}
|
|
387
|
+
if (this._pendingRunId) {
|
|
388
|
+
supersedePendingRun(this._pendingRunId);
|
|
389
|
+
this._pendingRunId = null;
|
|
390
|
+
}
|
|
173
391
|
this._nextRunAt = null;
|
|
174
392
|
if (this.activeRun) {
|
|
175
393
|
let timerId: ReturnType<typeof setTimeout>;
|
|
@@ -186,7 +404,26 @@ export class HeartbeatService {
|
|
|
186
404
|
* When `force` is true (e.g. manual "Run Now"), skip enabled & active-hours guards. */
|
|
187
405
|
async runOnce({ force = false }: { force?: boolean } = {}): Promise<boolean> {
|
|
188
406
|
const config = getConfig().heartbeat;
|
|
189
|
-
|
|
407
|
+
|
|
408
|
+
if (!force && isDiskPressureBackgroundLocked("heartbeat")) {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
let runId: string | null;
|
|
413
|
+
let scheduledFor: number;
|
|
414
|
+
if (force) {
|
|
415
|
+
scheduledFor = Date.now();
|
|
416
|
+
runId = insertPendingHeartbeatRun(scheduledFor);
|
|
417
|
+
} else {
|
|
418
|
+
runId = this._pendingRunId;
|
|
419
|
+
scheduledFor = this._nextRunAt ?? Date.now();
|
|
420
|
+
this._pendingRunId = null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (!force && !config.enabled) {
|
|
424
|
+
if (runId) skipHeartbeatRun(runId, "disabled");
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
190
427
|
|
|
191
428
|
// Active hours guard — only applied when both bounds are set.
|
|
192
429
|
// The schema rejects configs where only one bound is provided.
|
|
@@ -195,7 +432,17 @@ export class HeartbeatService {
|
|
|
195
432
|
config.activeHoursStart != null &&
|
|
196
433
|
config.activeHoursEnd != null
|
|
197
434
|
) {
|
|
198
|
-
|
|
435
|
+
let hour: number;
|
|
436
|
+
if (this.cronMode && config.timezone) {
|
|
437
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
438
|
+
timeZone: config.timezone,
|
|
439
|
+
hourCycle: "h23",
|
|
440
|
+
hour: "numeric",
|
|
441
|
+
}).formatToParts(new Date());
|
|
442
|
+
hour = Number(parts.find((p) => p.type === "hour")!.value);
|
|
443
|
+
} else {
|
|
444
|
+
hour = this.deps.getCurrentHour?.() ?? new Date().getHours();
|
|
445
|
+
}
|
|
199
446
|
if (
|
|
200
447
|
!isWithinActiveHours(
|
|
201
448
|
hour,
|
|
@@ -211,7 +458,10 @@ export class HeartbeatService {
|
|
|
211
458
|
},
|
|
212
459
|
"Outside active hours, skipping",
|
|
213
460
|
);
|
|
214
|
-
|
|
461
|
+
if (runId) skipHeartbeatRun(runId, "outside_active_hours");
|
|
462
|
+
if (!this.cronMode) {
|
|
463
|
+
this.scheduleNextRun(config.intervalMs);
|
|
464
|
+
}
|
|
215
465
|
return false;
|
|
216
466
|
}
|
|
217
467
|
}
|
|
@@ -219,10 +469,14 @@ export class HeartbeatService {
|
|
|
219
469
|
// Overlap prevention
|
|
220
470
|
if (this.activeRun) {
|
|
221
471
|
log.debug("Previous heartbeat run still active, skipping");
|
|
472
|
+
if (runId) skipHeartbeatRun(runId, "overlap");
|
|
222
473
|
return false;
|
|
223
474
|
}
|
|
224
475
|
|
|
225
|
-
|
|
476
|
+
if (!runId) {
|
|
477
|
+
runId = insertPendingHeartbeatRun(scheduledFor);
|
|
478
|
+
}
|
|
479
|
+
const run = this.executeRun(runId, scheduledFor);
|
|
226
480
|
this.activeRun = run;
|
|
227
481
|
// Clear activeRun once executeRun finishes. On timeout, runOnce releases
|
|
228
482
|
// activeRun separately (see catch block below) so future runs aren't
|
|
@@ -252,16 +506,39 @@ export class HeartbeatService {
|
|
|
252
506
|
// Release activeRun so the overlap guard doesn't permanently block
|
|
253
507
|
// future heartbeat runs when executeRun hangs past the timeout.
|
|
254
508
|
this.activeRun = null;
|
|
509
|
+
const transitioned = runId
|
|
510
|
+
? completeHeartbeatRun(runId, {
|
|
511
|
+
status: "timeout",
|
|
512
|
+
error: "Heartbeat execution exceeded the 30-minute timeout",
|
|
513
|
+
})
|
|
514
|
+
: false;
|
|
515
|
+
if (transitioned) {
|
|
516
|
+
const today = new Date().toISOString().split("T")[0];
|
|
517
|
+
void emitFeedEvent({
|
|
518
|
+
source: "assistant",
|
|
519
|
+
title: "Heartbeat Timed Out",
|
|
520
|
+
summary: "Heartbeat execution exceeded the 30-minute timeout.",
|
|
521
|
+
dedupKey: `heartbeat:timeout:${today}`,
|
|
522
|
+
priority: 55,
|
|
523
|
+
urgency: "high",
|
|
524
|
+
}).catch(() => {});
|
|
525
|
+
}
|
|
255
526
|
} finally {
|
|
256
527
|
clearTimeout(timerId);
|
|
257
528
|
this._lastRunAt = Date.now();
|
|
258
|
-
this.
|
|
529
|
+
if (!this.cronMode) {
|
|
530
|
+
this.scheduleNextRun(getConfig().heartbeat.intervalMs);
|
|
531
|
+
}
|
|
259
532
|
}
|
|
260
533
|
return true;
|
|
261
534
|
}
|
|
262
535
|
|
|
263
536
|
private scheduleNextRun(intervalMs: number): void {
|
|
537
|
+
if (this._pendingRunId) {
|
|
538
|
+
supersedePendingRun(this._pendingRunId);
|
|
539
|
+
}
|
|
264
540
|
this._nextRunAt = Date.now() + intervalMs;
|
|
541
|
+
this._pendingRunId = insertPendingHeartbeatRun(this._nextRunAt);
|
|
265
542
|
}
|
|
266
543
|
|
|
267
544
|
/**
|
|
@@ -383,19 +660,86 @@ export class HeartbeatService {
|
|
|
383
660
|
}
|
|
384
661
|
}
|
|
385
662
|
|
|
386
|
-
private
|
|
663
|
+
private getLatestAssistantMessage(
|
|
664
|
+
conversationId: string,
|
|
665
|
+
): { id: string; text: string } | null {
|
|
666
|
+
try {
|
|
667
|
+
const messages = getMessages(conversationId);
|
|
668
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
669
|
+
const message = messages[i]!;
|
|
670
|
+
if (message.role !== "assistant") continue;
|
|
671
|
+
return {
|
|
672
|
+
id: message.id,
|
|
673
|
+
text: extractVisibleTextFromStoredMessageContent(message.content),
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
} catch (err) {
|
|
677
|
+
log.warn(
|
|
678
|
+
{ err, conversationId },
|
|
679
|
+
"Failed to read heartbeat assistant message",
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
private async emitHeartbeatAlertNotification(params: {
|
|
686
|
+
runId: string;
|
|
687
|
+
conversationId: string;
|
|
688
|
+
messageId?: string;
|
|
689
|
+
conversationTitle: string;
|
|
690
|
+
summary: string;
|
|
691
|
+
}): Promise<void> {
|
|
692
|
+
const { emitNotificationSignal } =
|
|
693
|
+
await import("../notifications/emit-signal.js");
|
|
694
|
+
|
|
695
|
+
await emitNotificationSignal({
|
|
696
|
+
sourceEventName: "heartbeat.alert",
|
|
697
|
+
sourceChannel: "watcher",
|
|
698
|
+
sourceContextId: params.runId,
|
|
699
|
+
dedupeKey: `heartbeat:alert:${params.runId}`,
|
|
700
|
+
attentionHints: {
|
|
701
|
+
requiresAction: true,
|
|
702
|
+
urgency: "medium",
|
|
703
|
+
isAsyncBackground: true,
|
|
704
|
+
visibleInSourceNow: false,
|
|
705
|
+
},
|
|
706
|
+
contextPayload: {
|
|
707
|
+
title: "Heartbeat Alert",
|
|
708
|
+
summary: params.summary,
|
|
709
|
+
conversationTitle: params.conversationTitle,
|
|
710
|
+
conversationId: params.conversationId,
|
|
711
|
+
messageId: params.messageId,
|
|
712
|
+
},
|
|
713
|
+
routingIntent: "single_channel",
|
|
714
|
+
conversationAffinityHint: { vellum: params.conversationId },
|
|
715
|
+
conversationMetadata: {
|
|
716
|
+
source: "heartbeat",
|
|
717
|
+
groupId: "system:background",
|
|
718
|
+
},
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
private async executeRun(runId: string, scheduledFor: number): Promise<void> {
|
|
387
723
|
log.info("Running heartbeat");
|
|
388
724
|
|
|
725
|
+
startHeartbeatRun(runId);
|
|
726
|
+
|
|
727
|
+
const latenessMs = Date.now() - scheduledFor;
|
|
728
|
+
const LATE_THRESHOLD_MS = 5 * 60 * 1000;
|
|
729
|
+
|
|
389
730
|
// Credential health check — surface broken credentials proactively
|
|
390
731
|
// before the LLM heartbeat prompt runs. Returns unhealthy provider
|
|
391
732
|
// names so the prompt can instruct the LLM to skip those providers.
|
|
392
733
|
const unhealthyProviders = await this.runCredentialHealthCheck();
|
|
393
734
|
|
|
735
|
+
let conversationId: string | undefined;
|
|
394
736
|
try {
|
|
395
737
|
const checklist = this.readChecklist();
|
|
738
|
+
const completedRunCount = countCompletedHeartbeatRuns();
|
|
396
739
|
const { prompt, includedReengagement } = this.buildPrompt(
|
|
397
740
|
checklist,
|
|
398
741
|
unhealthyProviders,
|
|
742
|
+
completedRunCount,
|
|
399
743
|
);
|
|
400
744
|
|
|
401
745
|
const conversation = bootstrapConversation({
|
|
@@ -405,11 +749,7 @@ export class HeartbeatService {
|
|
|
405
749
|
origin: "heartbeat",
|
|
406
750
|
systemHint: "Heartbeat",
|
|
407
751
|
});
|
|
408
|
-
|
|
409
|
-
this.deps.onConversationCreated?.({
|
|
410
|
-
conversationId: conversation.id,
|
|
411
|
-
title: "Heartbeat",
|
|
412
|
-
});
|
|
752
|
+
conversationId = conversation.id;
|
|
413
753
|
|
|
414
754
|
await processMessage(conversation.id, prompt, undefined, {
|
|
415
755
|
trustContext: {
|
|
@@ -425,50 +765,90 @@ export class HeartbeatService {
|
|
|
425
765
|
|
|
426
766
|
log.info({ conversationId: conversation.id }, "Heartbeat completed");
|
|
427
767
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
768
|
+
const transitioned = completeHeartbeatRun(runId, {
|
|
769
|
+
status: "ok",
|
|
770
|
+
conversationId,
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
if (transitioned) {
|
|
774
|
+
let title = "Heartbeat";
|
|
775
|
+
try {
|
|
776
|
+
const row = getConversation(conversation.id);
|
|
777
|
+
if (row?.title && row.title !== GENERATING_TITLE) {
|
|
778
|
+
title = row.title;
|
|
779
|
+
}
|
|
780
|
+
} catch {
|
|
781
|
+
// Best-effort; fall back to generic title.
|
|
433
782
|
}
|
|
434
|
-
} catch {
|
|
435
|
-
// Best-effort; fall back to generic title.
|
|
436
|
-
}
|
|
437
783
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
source: "assistant",
|
|
441
|
-
title,
|
|
442
|
-
summary: "Periodic check completed. Tap to see details.",
|
|
443
|
-
dedupKey: `heartbeat:ok:${today}`,
|
|
444
|
-
priority: 30,
|
|
445
|
-
}).catch((err) => {
|
|
446
|
-
log.warn(
|
|
447
|
-
{ err, conversationId: conversation.id },
|
|
448
|
-
"Failed to emit heartbeat feed event",
|
|
784
|
+
const assistantMessage = this.getLatestAssistantMessage(
|
|
785
|
+
conversation.id,
|
|
449
786
|
);
|
|
450
|
-
|
|
787
|
+
const disposition = parseHeartbeatDisposition(
|
|
788
|
+
assistantMessage?.text ?? null,
|
|
789
|
+
);
|
|
790
|
+
if (disposition === "alert") {
|
|
791
|
+
this.deps.onConversationCreated?.({
|
|
792
|
+
conversationId: conversation.id,
|
|
793
|
+
title,
|
|
794
|
+
});
|
|
795
|
+
void this.emitHeartbeatAlertNotification({
|
|
796
|
+
runId,
|
|
797
|
+
conversationId: conversation.id,
|
|
798
|
+
messageId: assistantMessage?.id,
|
|
799
|
+
conversationTitle: title,
|
|
800
|
+
summary: buildHeartbeatAlertSummary(assistantMessage?.text ?? null),
|
|
801
|
+
}).catch((err) => {
|
|
802
|
+
log.warn(
|
|
803
|
+
{ err, conversationId: conversation.id },
|
|
804
|
+
"Failed to emit heartbeat alert notification",
|
|
805
|
+
);
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const today = new Date().toISOString().split("T")[0];
|
|
810
|
+
if (latenessMs > LATE_THRESHOLD_MS) {
|
|
811
|
+
const lateMinutes = Math.round(latenessMs / 60_000);
|
|
812
|
+
void emitFeedEvent({
|
|
813
|
+
source: "assistant",
|
|
814
|
+
title: "Heartbeat Ran Late",
|
|
815
|
+
summary: `Heartbeat ran ${lateMinutes} minutes late (scheduled for ${new Date(scheduledFor).toLocaleTimeString()}).`,
|
|
816
|
+
dedupKey: `heartbeat:late:${today}`,
|
|
817
|
+
priority: 45,
|
|
818
|
+
urgency: "medium",
|
|
819
|
+
}).catch(() => {});
|
|
820
|
+
}
|
|
821
|
+
}
|
|
451
822
|
} catch (err) {
|
|
452
823
|
log.error({ err }, "Heartbeat failed");
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
824
|
+
|
|
825
|
+
const transitioned = completeHeartbeatRun(runId, {
|
|
826
|
+
status: "error",
|
|
827
|
+
conversationId,
|
|
828
|
+
error: err instanceof Error ? err.message : String(err),
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
if (transitioned) {
|
|
832
|
+
try {
|
|
833
|
+
this.deps.alerter({
|
|
834
|
+
type: "heartbeat_alert",
|
|
835
|
+
title: "Heartbeat Failed",
|
|
836
|
+
body: err instanceof Error ? err.message : String(err),
|
|
837
|
+
});
|
|
838
|
+
} catch (alertErr) {
|
|
839
|
+
log.error({ alertErr }, "Failed to broadcast heartbeat alert");
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
const today = new Date().toISOString().split("T")[0];
|
|
843
|
+
void emitFeedEvent({
|
|
844
|
+
source: "assistant",
|
|
456
845
|
title: "Heartbeat Failed",
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
846
|
+
summary: `Heartbeat check failed: ${(err instanceof Error ? err.message : String(err)).slice(0, 200)}`,
|
|
847
|
+
dedupKey: `heartbeat:fail:${today}`,
|
|
848
|
+
priority: 55,
|
|
849
|
+
urgency: "high",
|
|
850
|
+
}).catch(() => {});
|
|
461
851
|
}
|
|
462
|
-
|
|
463
|
-
const today = new Date().toISOString().split("T")[0];
|
|
464
|
-
void emitFeedEvent({
|
|
465
|
-
source: "assistant",
|
|
466
|
-
title: "Heartbeat",
|
|
467
|
-
summary: "Heartbeat check failed. Check logs for details.",
|
|
468
|
-
dedupKey: `heartbeat:fail:${today}`,
|
|
469
|
-
priority: 55,
|
|
470
|
-
urgency: "medium",
|
|
471
|
-
}).catch(() => {});
|
|
472
852
|
}
|
|
473
853
|
}
|
|
474
854
|
|
|
@@ -483,6 +863,7 @@ export class HeartbeatService {
|
|
|
483
863
|
buildPrompt(
|
|
484
864
|
checklist: string,
|
|
485
865
|
unhealthyProviders: string[] = [],
|
|
866
|
+
completedRunCount: number = Infinity,
|
|
486
867
|
): { prompt: string; includedReengagement: boolean } {
|
|
487
868
|
let prompt = `You are running a periodic heartbeat check. Review the following checklist and take any necessary actions.
|
|
488
869
|
|
|
@@ -499,11 +880,20 @@ Do NOT attempt to use tools for these providers — they will fail. Skip any che
|
|
|
499
880
|
}
|
|
500
881
|
|
|
501
882
|
prompt += `\n\n<heartbeat-disposition>
|
|
883
|
+
This heartbeat runs frequently. Do not manufacture a report just because it ran.
|
|
884
|
+
If there is nothing genuinely useful, actionable, or interesting to surface, keep the response brief and end with HEARTBEAT_OK.
|
|
885
|
+
If there is something worth interrupting the guardian for, write a concise guardian-facing note first: what happened, why it matters, and the recommended next step. Then end with HEARTBEAT_ALERT. That note may be used as notification copy.
|
|
502
886
|
After completing your review, end your response with one of:
|
|
503
887
|
- HEARTBEAT_OK — if everything looks good, no action needed
|
|
504
888
|
- HEARTBEAT_ALERT — if you found issues that need attention (describe them before this marker)
|
|
505
889
|
</heartbeat-disposition>`;
|
|
506
890
|
|
|
891
|
+
if (completedRunCount < EARLY_HEARTBEAT_THRESHOLD) {
|
|
892
|
+
prompt += `\n\n<early-heartbeat>
|
|
893
|
+
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 HEARTBEAT_ALERT this time. First impressions matter.
|
|
894
|
+
</early-heartbeat>`;
|
|
895
|
+
}
|
|
896
|
+
|
|
507
897
|
let includedReengagement = false;
|
|
508
898
|
if (isShallowProfile() && isReengagementCooldownElapsed()) {
|
|
509
899
|
includedReengagement = true;
|
|
@@ -514,6 +904,21 @@ After completing your review, end your response with one of:
|
|
|
514
904
|
}
|
|
515
905
|
}
|
|
516
906
|
|
|
907
|
+
function isDiskPressureBackgroundLocked(logKey: string): boolean {
|
|
908
|
+
const diskPressureGate = checkDiskPressureBackgroundGate("background-work");
|
|
909
|
+
if (diskPressureGate.action === "allow") return false;
|
|
910
|
+
if (shouldLogDiskPressureBackgroundSkip(logKey)) {
|
|
911
|
+
log.warn(
|
|
912
|
+
{
|
|
913
|
+
source: "heartbeat",
|
|
914
|
+
...diskPressureBackgroundSkipLogFields(diskPressureGate),
|
|
915
|
+
},
|
|
916
|
+
"Heartbeat skipped during disk pressure cleanup mode",
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
return true;
|
|
920
|
+
}
|
|
921
|
+
|
|
517
922
|
/**
|
|
518
923
|
* Check if the given hour falls within the active window.
|
|
519
924
|
* Handles overnight windows (e.g. start=22, end=6).
|