@aitne/daemon 0.1.0
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/LICENSE +21 -0
- package/dist/adapters/composite-dashboard-stream.d.ts +42 -0
- package/dist/adapters/composite-dashboard-stream.d.ts.map +1 -0
- package/dist/adapters/composite-dashboard-stream.js +49 -0
- package/dist/adapters/composite-dashboard-stream.js.map +1 -0
- package/dist/adapters/dashboard-adapter.d.ts +104 -0
- package/dist/adapters/dashboard-adapter.d.ts.map +1 -0
- package/dist/adapters/dashboard-adapter.js +216 -0
- package/dist/adapters/dashboard-adapter.js.map +1 -0
- package/dist/adapters/discord.d.ts +77 -0
- package/dist/adapters/discord.d.ts.map +1 -0
- package/dist/adapters/discord.js +339 -0
- package/dist/adapters/discord.js.map +1 -0
- package/dist/adapters/docs-qa-adapter.d.ts +123 -0
- package/dist/adapters/docs-qa-adapter.d.ts.map +1 -0
- package/dist/adapters/docs-qa-adapter.js +218 -0
- package/dist/adapters/docs-qa-adapter.js.map +1 -0
- package/dist/adapters/message-hub.d.ts +70 -0
- package/dist/adapters/message-hub.d.ts.map +1 -0
- package/dist/adapters/message-hub.js +359 -0
- package/dist/adapters/message-hub.js.map +1 -0
- package/dist/adapters/notification-manager.d.ts +99 -0
- package/dist/adapters/notification-manager.d.ts.map +1 -0
- package/dist/adapters/notification-manager.js +498 -0
- package/dist/adapters/notification-manager.js.map +1 -0
- package/dist/adapters/outbound-text.d.ts +28 -0
- package/dist/adapters/outbound-text.d.ts.map +1 -0
- package/dist/adapters/outbound-text.js +58 -0
- package/dist/adapters/outbound-text.js.map +1 -0
- package/dist/adapters/slack-adapter.d.ts +82 -0
- package/dist/adapters/slack-adapter.d.ts.map +1 -0
- package/dist/adapters/slack-adapter.js +359 -0
- package/dist/adapters/slack-adapter.js.map +1 -0
- package/dist/adapters/telegram-adapter.d.ts +107 -0
- package/dist/adapters/telegram-adapter.d.ts.map +1 -0
- package/dist/adapters/telegram-adapter.js +477 -0
- package/dist/adapters/telegram-adapter.js.map +1 -0
- package/dist/adapters/types.d.ts +92 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/adapters/whatsapp-adapter.d.ts +213 -0
- package/dist/adapters/whatsapp-adapter.d.ts.map +1 -0
- package/dist/adapters/whatsapp-adapter.js +1216 -0
- package/dist/adapters/whatsapp-adapter.js.map +1 -0
- package/dist/api/chat-binding-query.d.ts +36 -0
- package/dist/api/chat-binding-query.d.ts.map +1 -0
- package/dist/api/chat-binding-query.js +63 -0
- package/dist/api/chat-binding-query.js.map +1 -0
- package/dist/api/chat-session-resume.d.ts +12 -0
- package/dist/api/chat-session-resume.d.ts.map +1 -0
- package/dist/api/chat-session-resume.js +21 -0
- package/dist/api/chat-session-resume.js.map +1 -0
- package/dist/api/delegated-proxy-helper.d.ts +33 -0
- package/dist/api/delegated-proxy-helper.d.ts.map +1 -0
- package/dist/api/delegated-proxy-helper.js +54 -0
- package/dist/api/delegated-proxy-helper.js.map +1 -0
- package/dist/api/directory-picker.d.ts +38 -0
- package/dist/api/directory-picker.d.ts.map +1 -0
- package/dist/api/directory-picker.js +278 -0
- package/dist/api/directory-picker.js.map +1 -0
- package/dist/api/env-writer.d.ts +25 -0
- package/dist/api/env-writer.d.ts.map +1 -0
- package/dist/api/env-writer.js +421 -0
- package/dist/api/env-writer.js.map +1 -0
- package/dist/api/integration-route-gate.d.ts +60 -0
- package/dist/api/integration-route-gate.d.ts.map +1 -0
- package/dist/api/integration-route-gate.js +83 -0
- package/dist/api/integration-route-gate.js.map +1 -0
- package/dist/api/json-body.d.ts +29 -0
- package/dist/api/json-body.d.ts.map +1 -0
- package/dist/api/json-body.js +87 -0
- package/dist/api/json-body.js.map +1 -0
- package/dist/api/routes/activity-sources.d.ts +20 -0
- package/dist/api/routes/activity-sources.d.ts.map +1 -0
- package/dist/api/routes/activity-sources.js +18 -0
- package/dist/api/routes/activity-sources.js.map +1 -0
- package/dist/api/routes/agent.d.ts +4 -0
- package/dist/api/routes/agent.d.ts.map +1 -0
- package/dist/api/routes/agent.js +619 -0
- package/dist/api/routes/agent.js.map +1 -0
- package/dist/api/routes/apple-calendar.d.ts +31 -0
- package/dist/api/routes/apple-calendar.d.ts.map +1 -0
- package/dist/api/routes/apple-calendar.js +310 -0
- package/dist/api/routes/apple-calendar.js.map +1 -0
- package/dist/api/routes/attachments.d.ts +36 -0
- package/dist/api/routes/attachments.d.ts.map +1 -0
- package/dist/api/routes/attachments.js +305 -0
- package/dist/api/routes/attachments.js.map +1 -0
- package/dist/api/routes/backends.d.ts +4 -0
- package/dist/api/routes/backends.d.ts.map +1 -0
- package/dist/api/routes/backends.js +1132 -0
- package/dist/api/routes/backends.js.map +1 -0
- package/dist/api/routes/books.d.ts +63 -0
- package/dist/api/routes/books.d.ts.map +1 -0
- package/dist/api/routes/books.js +467 -0
- package/dist/api/routes/books.js.map +1 -0
- package/dist/api/routes/calendar.d.ts +36 -0
- package/dist/api/routes/calendar.d.ts.map +1 -0
- package/dist/api/routes/calendar.js +351 -0
- package/dist/api/routes/calendar.js.map +1 -0
- package/dist/api/routes/commands.d.ts +4 -0
- package/dist/api/routes/commands.d.ts.map +1 -0
- package/dist/api/routes/commands.js +251 -0
- package/dist/api/routes/commands.js.map +1 -0
- package/dist/api/routes/context.d.ts +57 -0
- package/dist/api/routes/context.d.ts.map +1 -0
- package/dist/api/routes/context.js +1765 -0
- package/dist/api/routes/context.js.map +1 -0
- package/dist/api/routes/dashboard.d.ts +29 -0
- package/dist/api/routes/dashboard.d.ts.map +1 -0
- package/dist/api/routes/dashboard.js +2062 -0
- package/dist/api/routes/dashboard.js.map +1 -0
- package/dist/api/routes/delegated-sync.d.ts +4 -0
- package/dist/api/routes/delegated-sync.d.ts.map +1 -0
- package/dist/api/routes/delegated-sync.js +192 -0
- package/dist/api/routes/delegated-sync.js.map +1 -0
- package/dist/api/routes/delegated.d.ts +42 -0
- package/dist/api/routes/delegated.d.ts.map +1 -0
- package/dist/api/routes/delegated.js +250 -0
- package/dist/api/routes/delegated.js.map +1 -0
- package/dist/api/routes/docs.d.ts +34 -0
- package/dist/api/routes/docs.d.ts.map +1 -0
- package/dist/api/routes/docs.js +580 -0
- package/dist/api/routes/docs.js.map +1 -0
- package/dist/api/routes/entities.d.ts +9 -0
- package/dist/api/routes/entities.d.ts.map +1 -0
- package/dist/api/routes/entities.js +176 -0
- package/dist/api/routes/entities.js.map +1 -0
- package/dist/api/routes/git-accounts.d.ts +23 -0
- package/dist/api/routes/git-accounts.d.ts.map +1 -0
- package/dist/api/routes/git-accounts.js +227 -0
- package/dist/api/routes/git-accounts.js.map +1 -0
- package/dist/api/routes/git-templates.d.ts +50 -0
- package/dist/api/routes/git-templates.d.ts.map +1 -0
- package/dist/api/routes/git-templates.js +276 -0
- package/dist/api/routes/git-templates.js.map +1 -0
- package/dist/api/routes/git.d.ts +34 -0
- package/dist/api/routes/git.d.ts.map +1 -0
- package/dist/api/routes/git.js +126 -0
- package/dist/api/routes/git.js.map +1 -0
- package/dist/api/routes/github.d.ts +34 -0
- package/dist/api/routes/github.d.ts.map +1 -0
- package/dist/api/routes/github.js +465 -0
- package/dist/api/routes/github.js.map +1 -0
- package/dist/api/routes/health.d.ts +4 -0
- package/dist/api/routes/health.d.ts.map +1 -0
- package/dist/api/routes/health.js +257 -0
- package/dist/api/routes/health.js.map +1 -0
- package/dist/api/routes/integrations-reconcile.d.ts +33 -0
- package/dist/api/routes/integrations-reconcile.d.ts.map +1 -0
- package/dist/api/routes/integrations-reconcile.js +463 -0
- package/dist/api/routes/integrations-reconcile.js.map +1 -0
- package/dist/api/routes/integrations.d.ts +19 -0
- package/dist/api/routes/integrations.d.ts.map +1 -0
- package/dist/api/routes/integrations.js +1384 -0
- package/dist/api/routes/integrations.js.map +1 -0
- package/dist/api/routes/knowledge.d.ts +4 -0
- package/dist/api/routes/knowledge.d.ts.map +1 -0
- package/dist/api/routes/knowledge.js +224 -0
- package/dist/api/routes/knowledge.js.map +1 -0
- package/dist/api/routes/mail.d.ts +39 -0
- package/dist/api/routes/mail.d.ts.map +1 -0
- package/dist/api/routes/mail.js +1406 -0
- package/dist/api/routes/mail.js.map +1 -0
- package/dist/api/routes/managed-tasks.d.ts +48 -0
- package/dist/api/routes/managed-tasks.d.ts.map +1 -0
- package/dist/api/routes/managed-tasks.js +844 -0
- package/dist/api/routes/managed-tasks.js.map +1 -0
- package/dist/api/routes/mcp.d.ts +50 -0
- package/dist/api/routes/mcp.d.ts.map +1 -0
- package/dist/api/routes/mcp.js +470 -0
- package/dist/api/routes/mcp.js.map +1 -0
- package/dist/api/routes/metrics.d.ts +13 -0
- package/dist/api/routes/metrics.d.ts.map +1 -0
- package/dist/api/routes/metrics.js +117 -0
- package/dist/api/routes/metrics.js.map +1 -0
- package/dist/api/routes/notion.d.ts +35 -0
- package/dist/api/routes/notion.d.ts.map +1 -0
- package/dist/api/routes/notion.js +442 -0
- package/dist/api/routes/notion.js.map +1 -0
- package/dist/api/routes/observations.d.ts +4 -0
- package/dist/api/routes/observations.d.ts.map +1 -0
- package/dist/api/routes/observations.js +177 -0
- package/dist/api/routes/observations.js.map +1 -0
- package/dist/api/routes/obsidian.d.ts +16 -0
- package/dist/api/routes/obsidian.d.ts.map +1 -0
- package/dist/api/routes/obsidian.js +321 -0
- package/dist/api/routes/obsidian.js.map +1 -0
- package/dist/api/routes/profile-questions.d.ts +17 -0
- package/dist/api/routes/profile-questions.d.ts.map +1 -0
- package/dist/api/routes/profile-questions.js +115 -0
- package/dist/api/routes/profile-questions.js.map +1 -0
- package/dist/api/routes/receipts.d.ts +4 -0
- package/dist/api/routes/receipts.d.ts.map +1 -0
- package/dist/api/routes/receipts.js +155 -0
- package/dist/api/routes/receipts.js.map +1 -0
- package/dist/api/routes/recurring-schedules.d.ts +4 -0
- package/dist/api/routes/recurring-schedules.d.ts.map +1 -0
- package/dist/api/routes/recurring-schedules.js +137 -0
- package/dist/api/routes/recurring-schedules.js.map +1 -0
- package/dist/api/routes/repositories.d.ts +40 -0
- package/dist/api/routes/repositories.d.ts.map +1 -0
- package/dist/api/routes/repositories.js +857 -0
- package/dist/api/routes/repositories.js.map +1 -0
- package/dist/api/routes/setup-migrate.d.ts +74 -0
- package/dist/api/routes/setup-migrate.d.ts.map +1 -0
- package/dist/api/routes/setup-migrate.js +944 -0
- package/dist/api/routes/setup-migrate.js.map +1 -0
- package/dist/api/routes/setup.d.ts +4 -0
- package/dist/api/routes/setup.d.ts.map +1 -0
- package/dist/api/routes/setup.js +443 -0
- package/dist/api/routes/setup.js.map +1 -0
- package/dist/api/routes/skill-curation.d.ts +5 -0
- package/dist/api/routes/skill-curation.d.ts.map +1 -0
- package/dist/api/routes/skill-curation.js +728 -0
- package/dist/api/routes/skill-curation.js.map +1 -0
- package/dist/api/routes/skills.d.ts +52 -0
- package/dist/api/routes/skills.d.ts.map +1 -0
- package/dist/api/routes/skills.js +429 -0
- package/dist/api/routes/skills.js.map +1 -0
- package/dist/api/routes/sot-bindings.d.ts +20 -0
- package/dist/api/routes/sot-bindings.d.ts.map +1 -0
- package/dist/api/routes/sot-bindings.js +163 -0
- package/dist/api/routes/sot-bindings.js.map +1 -0
- package/dist/api/routes/sse.d.ts +86 -0
- package/dist/api/routes/sse.d.ts.map +1 -0
- package/dist/api/routes/sse.js +378 -0
- package/dist/api/routes/sse.js.map +1 -0
- package/dist/api/routes/system.d.ts +4 -0
- package/dist/api/routes/system.d.ts.map +1 -0
- package/dist/api/routes/system.js +207 -0
- package/dist/api/routes/system.js.map +1 -0
- package/dist/api/routes/task-flows.d.ts +30 -0
- package/dist/api/routes/task-flows.d.ts.map +1 -0
- package/dist/api/routes/task-flows.js +155 -0
- package/dist/api/routes/task-flows.js.map +1 -0
- package/dist/api/routes/travel-bookings.d.ts +4 -0
- package/dist/api/routes/travel-bookings.d.ts.map +1 -0
- package/dist/api/routes/travel-bookings.js +142 -0
- package/dist/api/routes/travel-bookings.js.map +1 -0
- package/dist/api/routes/travel-time.d.ts +8 -0
- package/dist/api/routes/travel-time.d.ts.map +1 -0
- package/dist/api/routes/travel-time.js +87 -0
- package/dist/api/routes/travel-time.js.map +1 -0
- package/dist/api/routes/triggers.d.ts +4 -0
- package/dist/api/routes/triggers.d.ts.map +1 -0
- package/dist/api/routes/triggers.js +101 -0
- package/dist/api/routes/triggers.js.map +1 -0
- package/dist/api/routes/voice.d.ts +48 -0
- package/dist/api/routes/voice.d.ts.map +1 -0
- package/dist/api/routes/voice.js +232 -0
- package/dist/api/routes/voice.js.map +1 -0
- package/dist/api/server.d.ts +428 -0
- package/dist/api/server.d.ts.map +1 -0
- package/dist/api/server.js +558 -0
- package/dist/api/server.js.map +1 -0
- package/dist/config.d.ts +136 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +699 -0
- package/dist/config.js.map +1 -0
- package/dist/core/agent-core.d.ts +517 -0
- package/dist/core/agent-core.d.ts.map +1 -0
- package/dist/core/agent-core.js +102 -0
- package/dist/core/agent-core.js.map +1 -0
- package/dist/core/alerts.d.ts +86 -0
- package/dist/core/alerts.d.ts.map +1 -0
- package/dist/core/alerts.js +304 -0
- package/dist/core/alerts.js.map +1 -0
- package/dist/core/atomic-write.d.ts +51 -0
- package/dist/core/atomic-write.d.ts.map +1 -0
- package/dist/core/atomic-write.js +135 -0
- package/dist/core/atomic-write.js.map +1 -0
- package/dist/core/backends/api-key-probe.d.ts +40 -0
- package/dist/core/backends/api-key-probe.d.ts.map +1 -0
- package/dist/core/backends/api-key-probe.js +116 -0
- package/dist/core/backends/api-key-probe.js.map +1 -0
- package/dist/core/backends/auth-health-monitor.d.ts +373 -0
- package/dist/core/backends/auth-health-monitor.d.ts.map +1 -0
- package/dist/core/backends/auth-health-monitor.js +950 -0
- package/dist/core/backends/auth-health-monitor.js.map +1 -0
- package/dist/core/backends/auth-recovery.d.ts +263 -0
- package/dist/core/backends/auth-recovery.d.ts.map +1 -0
- package/dist/core/backends/auth-recovery.js +1086 -0
- package/dist/core/backends/auth-recovery.js.map +1 -0
- package/dist/core/backends/auth-telemetry.d.ts +81 -0
- package/dist/core/backends/auth-telemetry.d.ts.map +1 -0
- package/dist/core/backends/auth-telemetry.js +108 -0
- package/dist/core/backends/auth-telemetry.js.map +1 -0
- package/dist/core/backends/backend-router.d.ts +272 -0
- package/dist/core/backends/backend-router.d.ts.map +1 -0
- package/dist/core/backends/backend-router.js +759 -0
- package/dist/core/backends/backend-router.js.map +1 -0
- package/dist/core/backends/claude-code-core.d.ts +299 -0
- package/dist/core/backends/claude-code-core.d.ts.map +1 -0
- package/dist/core/backends/claude-code-core.js +2541 -0
- package/dist/core/backends/claude-code-core.js.map +1 -0
- package/dist/core/backends/claude-credentials-store.d.ts +83 -0
- package/dist/core/backends/claude-credentials-store.d.ts.map +1 -0
- package/dist/core/backends/claude-credentials-store.js +243 -0
- package/dist/core/backends/claude-credentials-store.js.map +1 -0
- package/dist/core/backends/cli-utils.d.ts +95 -0
- package/dist/core/backends/cli-utils.d.ts.map +1 -0
- package/dist/core/backends/cli-utils.js +464 -0
- package/dist/core/backends/cli-utils.js.map +1 -0
- package/dist/core/backends/codex-core.d.ts +127 -0
- package/dist/core/backends/codex-core.d.ts.map +1 -0
- package/dist/core/backends/codex-core.js +1693 -0
- package/dist/core/backends/codex-core.js.map +1 -0
- package/dist/core/backends/gemini-cli-core.d.ts +367 -0
- package/dist/core/backends/gemini-cli-core.d.ts.map +1 -0
- package/dist/core/backends/gemini-cli-core.js +2331 -0
- package/dist/core/backends/gemini-cli-core.js.map +1 -0
- package/dist/core/backends/idle-watchdog.d.ts +77 -0
- package/dist/core/backends/idle-watchdog.d.ts.map +1 -0
- package/dist/core/backends/idle-watchdog.js +94 -0
- package/dist/core/backends/idle-watchdog.js.map +1 -0
- package/dist/core/backends/install-methods.d.ts +93 -0
- package/dist/core/backends/install-methods.d.ts.map +1 -0
- package/dist/core/backends/install-methods.js +267 -0
- package/dist/core/backends/install-methods.js.map +1 -0
- package/dist/core/backends/model-registry.d.ts +58 -0
- package/dist/core/backends/model-registry.d.ts.map +1 -0
- package/dist/core/backends/model-registry.js +539 -0
- package/dist/core/backends/model-registry.js.map +1 -0
- package/dist/core/backends/plan-presets.d.ts +123 -0
- package/dist/core/backends/plan-presets.d.ts.map +1 -0
- package/dist/core/backends/plan-presets.js +235 -0
- package/dist/core/backends/plan-presets.js.map +1 -0
- package/dist/core/backends/price-fetcher.d.ts +48 -0
- package/dist/core/backends/price-fetcher.d.ts.map +1 -0
- package/dist/core/backends/price-fetcher.js +248 -0
- package/dist/core/backends/price-fetcher.js.map +1 -0
- package/dist/core/backends/process-config-cascade.d.ts +68 -0
- package/dist/core/backends/process-config-cascade.d.ts.map +1 -0
- package/dist/core/backends/process-config-cascade.js +173 -0
- package/dist/core/backends/process-config-cascade.js.map +1 -0
- package/dist/core/backends/prompt-utils.d.ts +6 -0
- package/dist/core/backends/prompt-utils.d.ts.map +1 -0
- package/dist/core/backends/prompt-utils.js +80 -0
- package/dist/core/backends/prompt-utils.js.map +1 -0
- package/dist/core/backends/proxy-model-registry.d.ts +110 -0
- package/dist/core/backends/proxy-model-registry.d.ts.map +1 -0
- package/dist/core/backends/proxy-model-registry.js +195 -0
- package/dist/core/backends/proxy-model-registry.js.map +1 -0
- package/dist/core/backends/silent-api-error-detector.d.ts +31 -0
- package/dist/core/backends/silent-api-error-detector.d.ts.map +1 -0
- package/dist/core/backends/silent-api-error-detector.js +44 -0
- package/dist/core/backends/silent-api-error-detector.js.map +1 -0
- package/dist/core/bang-commands/commands-cost.d.ts +13 -0
- package/dist/core/bang-commands/commands-cost.d.ts.map +1 -0
- package/dist/core/bang-commands/commands-cost.js +91 -0
- package/dist/core/bang-commands/commands-cost.js.map +1 -0
- package/dist/core/bang-commands/commands-report.d.ts +18 -0
- package/dist/core/bang-commands/commands-report.d.ts.map +1 -0
- package/dist/core/bang-commands/commands-report.js +105 -0
- package/dist/core/bang-commands/commands-report.js.map +1 -0
- package/dist/core/bang-commands/commands-stop-start.d.ts +4 -0
- package/dist/core/bang-commands/commands-stop-start.d.ts.map +1 -0
- package/dist/core/bang-commands/commands-stop-start.js +88 -0
- package/dist/core/bang-commands/commands-stop-start.js.map +1 -0
- package/dist/core/bang-commands/format-utils.d.ts +34 -0
- package/dist/core/bang-commands/format-utils.d.ts.map +1 -0
- package/dist/core/bang-commands/format-utils.js +118 -0
- package/dist/core/bang-commands/format-utils.js.map +1 -0
- package/dist/core/bang-commands/index.d.ts +20 -0
- package/dist/core/bang-commands/index.d.ts.map +1 -0
- package/dist/core/bang-commands/index.js +31 -0
- package/dist/core/bang-commands/index.js.map +1 -0
- package/dist/core/bang-commands/registry.d.ts +72 -0
- package/dist/core/bang-commands/registry.d.ts.map +1 -0
- package/dist/core/bang-commands/registry.js +174 -0
- package/dist/core/bang-commands/registry.js.map +1 -0
- package/dist/core/bang-commands/user-commands.d.ts +86 -0
- package/dist/core/bang-commands/user-commands.d.ts.map +1 -0
- package/dist/core/bang-commands/user-commands.js +212 -0
- package/dist/core/bang-commands/user-commands.js.map +1 -0
- package/dist/core/channel-timeline.d.ts +28 -0
- package/dist/core/channel-timeline.d.ts.map +1 -0
- package/dist/core/channel-timeline.js +117 -0
- package/dist/core/channel-timeline.js.map +1 -0
- package/dist/core/character-block.d.ts +37 -0
- package/dist/core/character-block.d.ts.map +1 -0
- package/dist/core/character-block.js +162 -0
- package/dist/core/character-block.js.map +1 -0
- package/dist/core/context/activity-sources.d.ts +37 -0
- package/dist/core/context/activity-sources.d.ts.map +1 -0
- package/dist/core/context/activity-sources.js +69 -0
- package/dist/core/context/activity-sources.js.map +1 -0
- package/dist/core/context/activity-view-reconciler.d.ts +110 -0
- package/dist/core/context/activity-view-reconciler.d.ts.map +1 -0
- package/dist/core/context/activity-view-reconciler.js +252 -0
- package/dist/core/context/activity-view-reconciler.js.map +1 -0
- package/dist/core/context/activity-view-runner.d.ts +38 -0
- package/dist/core/context/activity-view-runner.d.ts.map +1 -0
- package/dist/core/context/activity-view-runner.js +402 -0
- package/dist/core/context/activity-view-runner.js.map +1 -0
- package/dist/core/context/default-schedules-reconciler.d.ts +85 -0
- package/dist/core/context/default-schedules-reconciler.d.ts.map +1 -0
- package/dist/core/context/default-schedules-reconciler.js +153 -0
- package/dist/core/context/default-schedules-reconciler.js.map +1 -0
- package/dist/core/context/default-schedules-runner.d.ts +40 -0
- package/dist/core/context/default-schedules-runner.d.ts.map +1 -0
- package/dist/core/context/default-schedules-runner.js +233 -0
- package/dist/core/context/default-schedules-runner.js.map +1 -0
- package/dist/core/context/domain-index-reconciler.d.ts +81 -0
- package/dist/core/context/domain-index-reconciler.d.ts.map +1 -0
- package/dist/core/context/domain-index-reconciler.js +199 -0
- package/dist/core/context/domain-index-reconciler.js.map +1 -0
- package/dist/core/context/domain-index-runner.d.ts +35 -0
- package/dist/core/context/domain-index-runner.d.ts.map +1 -0
- package/dist/core/context/domain-index-runner.js +223 -0
- package/dist/core/context/domain-index-runner.js.map +1 -0
- package/dist/core/context/entity-mirror.d.ts +227 -0
- package/dist/core/context/entity-mirror.d.ts.map +1 -0
- package/dist/core/context/entity-mirror.js +629 -0
- package/dist/core/context/entity-mirror.js.map +1 -0
- package/dist/core/context/entity-source-rename.d.ts +61 -0
- package/dist/core/context/entity-source-rename.d.ts.map +1 -0
- package/dist/core/context/entity-source-rename.js +237 -0
- package/dist/core/context/entity-source-rename.js.map +1 -0
- package/dist/core/context/index-reconciler.d.ts +61 -0
- package/dist/core/context/index-reconciler.d.ts.map +1 -0
- package/dist/core/context/index-reconciler.js +329 -0
- package/dist/core/context/index-reconciler.js.map +1 -0
- package/dist/core/context/policy-index-reconciler.d.ts +102 -0
- package/dist/core/context/policy-index-reconciler.d.ts.map +1 -0
- package/dist/core/context/policy-index-reconciler.js +202 -0
- package/dist/core/context/policy-index-reconciler.js.map +1 -0
- package/dist/core/context/policy-index-runner.d.ts +66 -0
- package/dist/core/context/policy-index-runner.d.ts.map +1 -0
- package/dist/core/context/policy-index-runner.js +406 -0
- package/dist/core/context/policy-index-runner.js.map +1 -0
- package/dist/core/context/reconciler-runner.d.ts +44 -0
- package/dist/core/context/reconciler-runner.d.ts.map +1 -0
- package/dist/core/context/reconciler-runner.js +273 -0
- package/dist/core/context/reconciler-runner.js.map +1 -0
- package/dist/core/context-builder.d.ts +115 -0
- package/dist/core/context-builder.d.ts.map +1 -0
- package/dist/core/context-builder.js +1148 -0
- package/dist/core/context-builder.js.map +1 -0
- package/dist/core/context-frontmatter-backfill.d.ts +33 -0
- package/dist/core/context-frontmatter-backfill.d.ts.map +1 -0
- package/dist/core/context-frontmatter-backfill.js +111 -0
- package/dist/core/context-frontmatter-backfill.js.map +1 -0
- package/dist/core/context-frontmatter.d.ts +13 -0
- package/dist/core/context-frontmatter.d.ts.map +1 -0
- package/dist/core/context-frontmatter.js +325 -0
- package/dist/core/context-frontmatter.js.map +1 -0
- package/dist/core/context-health.d.ts +51 -0
- package/dist/core/context-health.d.ts.map +1 -0
- package/dist/core/context-health.js +304 -0
- package/dist/core/context-health.js.map +1 -0
- package/dist/core/context-paths.d.ts +183 -0
- package/dist/core/context-paths.d.ts.map +1 -0
- package/dist/core/context-paths.js +241 -0
- package/dist/core/context-paths.js.map +1 -0
- package/dist/core/context-staleness.d.ts +45 -0
- package/dist/core/context-staleness.d.ts.map +1 -0
- package/dist/core/context-staleness.js +88 -0
- package/dist/core/context-staleness.js.map +1 -0
- package/dist/core/custom-routine-scheduler.d.ts +151 -0
- package/dist/core/custom-routine-scheduler.d.ts.map +1 -0
- package/dist/core/custom-routine-scheduler.js +335 -0
- package/dist/core/custom-routine-scheduler.js.map +1 -0
- package/dist/core/daemon-api-cli.d.ts +33 -0
- package/dist/core/daemon-api-cli.d.ts.map +1 -0
- package/dist/core/daemon-api-cli.js +614 -0
- package/dist/core/daemon-api-cli.js.map +1 -0
- package/dist/core/dashboard-session-cleanup.d.ts +39 -0
- package/dist/core/dashboard-session-cleanup.d.ts.map +1 -0
- package/dist/core/dashboard-session-cleanup.js +108 -0
- package/dist/core/dashboard-session-cleanup.js.map +1 -0
- package/dist/core/dashboard-session-controls.d.ts +41 -0
- package/dist/core/dashboard-session-controls.d.ts.map +1 -0
- package/dist/core/dashboard-session-controls.js +154 -0
- package/dist/core/dashboard-session-controls.js.map +1 -0
- package/dist/core/delegated-connector-health.d.ts +63 -0
- package/dist/core/delegated-connector-health.d.ts.map +1 -0
- package/dist/core/delegated-connector-health.js +157 -0
- package/dist/core/delegated-connector-health.js.map +1 -0
- package/dist/core/dispatcher.d.ts +999 -0
- package/dist/core/dispatcher.d.ts.map +1 -0
- package/dist/core/dispatcher.js +4378 -0
- package/dist/core/dispatcher.js.map +1 -0
- package/dist/core/dm-freshness-metrics.d.ts +73 -0
- package/dist/core/dm-freshness-metrics.d.ts.map +1 -0
- package/dist/core/dm-freshness-metrics.js +138 -0
- package/dist/core/dm-freshness-metrics.js.map +1 -0
- package/dist/core/docs/citation-validator.d.ts +73 -0
- package/dist/core/docs/citation-validator.d.ts.map +1 -0
- package/dist/core/docs/citation-validator.js +195 -0
- package/dist/core/docs/citation-validator.js.map +1 -0
- package/dist/core/docs/extract-terms.d.ts +78 -0
- package/dist/core/docs/extract-terms.d.ts.map +1 -0
- package/dist/core/docs/extract-terms.js +147 -0
- package/dist/core/docs/extract-terms.js.map +1 -0
- package/dist/core/docs/indexer.d.ts +104 -0
- package/dist/core/docs/indexer.d.ts.map +1 -0
- package/dist/core/docs/indexer.js +340 -0
- package/dist/core/docs/indexer.js.map +1 -0
- package/dist/core/drift-effects.d.ts +30 -0
- package/dist/core/drift-effects.d.ts.map +1 -0
- package/dist/core/drift-effects.js +384 -0
- package/dist/core/drift-effects.js.map +1 -0
- package/dist/core/event-bus.d.ts +56 -0
- package/dist/core/event-bus.d.ts.map +1 -0
- package/dist/core/event-bus.js +135 -0
- package/dist/core/event-bus.js.map +1 -0
- package/dist/core/git-project-docs.d.ts +77 -0
- package/dist/core/git-project-docs.d.ts.map +1 -0
- package/dist/core/git-project-docs.js +439 -0
- package/dist/core/git-project-docs.js.map +1 -0
- package/dist/core/health-monitor.d.ts +57 -0
- package/dist/core/health-monitor.d.ts.map +1 -0
- package/dist/core/health-monitor.js +137 -0
- package/dist/core/health-monitor.js.map +1 -0
- package/dist/core/heartbeat.d.ts +26 -0
- package/dist/core/heartbeat.d.ts.map +1 -0
- package/dist/core/heartbeat.js +48 -0
- package/dist/core/heartbeat.js.map +1 -0
- package/dist/core/integration-health.d.ts +49 -0
- package/dist/core/integration-health.d.ts.map +1 -0
- package/dist/core/integration-health.js +89 -0
- package/dist/core/integration-health.js.map +1 -0
- package/dist/core/integration-lifecycle.d.ts +79 -0
- package/dist/core/integration-lifecycle.d.ts.map +1 -0
- package/dist/core/integration-lifecycle.js +153 -0
- package/dist/core/integration-lifecycle.js.map +1 -0
- package/dist/core/integration-main-backend.d.ts +36 -0
- package/dist/core/integration-main-backend.d.ts.map +1 -0
- package/dist/core/integration-main-backend.js +59 -0
- package/dist/core/integration-main-backend.js.map +1 -0
- package/dist/core/integration-probe.d.ts +98 -0
- package/dist/core/integration-probe.d.ts.map +1 -0
- package/dist/core/integration-probe.js +152 -0
- package/dist/core/integration-probe.js.map +1 -0
- package/dist/core/management-md-write-lock.d.ts +68 -0
- package/dist/core/management-md-write-lock.d.ts.map +1 -0
- package/dist/core/management-md-write-lock.js +93 -0
- package/dist/core/management-md-write-lock.js.map +1 -0
- package/dist/core/management-md.d.ts +186 -0
- package/dist/core/management-md.d.ts.map +1 -0
- package/dist/core/management-md.js +652 -0
- package/dist/core/management-md.js.map +1 -0
- package/dist/core/management-registry.d.ts +245 -0
- package/dist/core/management-registry.d.ts.map +1 -0
- package/dist/core/management-registry.js +906 -0
- package/dist/core/management-registry.js.map +1 -0
- package/dist/core/management-telemetry.d.ts +100 -0
- package/dist/core/management-telemetry.d.ts.map +1 -0
- package/dist/core/management-telemetry.js +156 -0
- package/dist/core/management-telemetry.js.map +1 -0
- package/dist/core/message-recorder.d.ts +38 -0
- package/dist/core/message-recorder.d.ts.map +1 -0
- package/dist/core/message-recorder.js +88 -0
- package/dist/core/message-recorder.js.map +1 -0
- package/dist/core/metrics.d.ts +338 -0
- package/dist/core/metrics.d.ts.map +1 -0
- package/dist/core/metrics.js +747 -0
- package/dist/core/metrics.js.map +1 -0
- package/dist/core/migration-backup.d.ts +218 -0
- package/dist/core/migration-backup.d.ts.map +1 -0
- package/dist/core/migration-backup.js +934 -0
- package/dist/core/migration-backup.js.map +1 -0
- package/dist/core/overview-write-lock.d.ts +48 -0
- package/dist/core/overview-write-lock.d.ts.map +1 -0
- package/dist/core/overview-write-lock.js +56 -0
- package/dist/core/overview-write-lock.js.map +1 -0
- package/dist/core/path-compat.d.ts +22 -0
- package/dist/core/path-compat.d.ts.map +1 -0
- package/dist/core/path-compat.js +67 -0
- package/dist/core/path-compat.js.map +1 -0
- package/dist/core/path-rewrite.d.ts +58 -0
- package/dist/core/path-rewrite.d.ts.map +1 -0
- package/dist/core/path-rewrite.js +141 -0
- package/dist/core/path-rewrite.js.map +1 -0
- package/dist/core/policy-files.d.ts +108 -0
- package/dist/core/policy-files.d.ts.map +1 -0
- package/dist/core/policy-files.js +198 -0
- package/dist/core/policy-files.js.map +1 -0
- package/dist/core/profile-questions/seed.d.ts +44 -0
- package/dist/core/profile-questions/seed.d.ts.map +1 -0
- package/dist/core/profile-questions/seed.js +173 -0
- package/dist/core/profile-questions/seed.js.map +1 -0
- package/dist/core/profile-questions/slot-filled.d.ts +51 -0
- package/dist/core/profile-questions/slot-filled.d.ts.map +1 -0
- package/dist/core/profile-questions/slot-filled.js +118 -0
- package/dist/core/profile-questions/slot-filled.js.map +1 -0
- package/dist/core/prompts.d.ts +111 -0
- package/dist/core/prompts.d.ts.map +1 -0
- package/dist/core/prompts.js +267 -0
- package/dist/core/prompts.js.map +1 -0
- package/dist/core/quiet-hours-sync.d.ts +15 -0
- package/dist/core/quiet-hours-sync.d.ts.map +1 -0
- package/dist/core/quiet-hours-sync.js +51 -0
- package/dist/core/quiet-hours-sync.js.map +1 -0
- package/dist/core/read-sensitive-token-manager.d.ts +19 -0
- package/dist/core/read-sensitive-token-manager.d.ts.map +1 -0
- package/dist/core/read-sensitive-token-manager.js +29 -0
- package/dist/core/read-sensitive-token-manager.js.map +1 -0
- package/dist/core/recurrence.d.ts +24 -0
- package/dist/core/recurrence.d.ts.map +1 -0
- package/dist/core/recurrence.js +162 -0
- package/dist/core/recurrence.js.map +1 -0
- package/dist/core/reinstall.d.ts +107 -0
- package/dist/core/reinstall.d.ts.map +1 -0
- package/dist/core/reinstall.js +163 -0
- package/dist/core/reinstall.js.map +1 -0
- package/dist/core/release-assets.d.ts +106 -0
- package/dist/core/release-assets.d.ts.map +1 -0
- package/dist/core/release-assets.js +434 -0
- package/dist/core/release-assets.js.map +1 -0
- package/dist/core/repository-management-docs.d.ts +216 -0
- package/dist/core/repository-management-docs.d.ts.map +1 -0
- package/dist/core/repository-management-docs.js +855 -0
- package/dist/core/repository-management-docs.js.map +1 -0
- package/dist/core/retention.d.ts +164 -0
- package/dist/core/retention.d.ts.map +1 -0
- package/dist/core/retention.js +1008 -0
- package/dist/core/retention.js.map +1 -0
- package/dist/core/review-context.d.ts +48 -0
- package/dist/core/review-context.d.ts.map +1 -0
- package/dist/core/review-context.js +282 -0
- package/dist/core/review-context.js.map +1 -0
- package/dist/core/roadmap-horizon.d.ts +48 -0
- package/dist/core/roadmap-horizon.d.ts.map +1 -0
- package/dist/core/roadmap-horizon.js +213 -0
- package/dist/core/roadmap-horizon.js.map +1 -0
- package/dist/core/roadmap-ids.d.ts +57 -0
- package/dist/core/roadmap-ids.d.ts.map +1 -0
- package/dist/core/roadmap-ids.js +118 -0
- package/dist/core/roadmap-ids.js.map +1 -0
- package/dist/core/roadmap-merge.d.ts +7 -0
- package/dist/core/roadmap-merge.d.ts.map +1 -0
- package/dist/core/roadmap-merge.js +187 -0
- package/dist/core/roadmap-merge.js.map +1 -0
- package/dist/core/roadmap-refresh-triggers.d.ts +32 -0
- package/dist/core/roadmap-refresh-triggers.d.ts.map +1 -0
- package/dist/core/roadmap-refresh-triggers.js +51 -0
- package/dist/core/roadmap-refresh-triggers.js.map +1 -0
- package/dist/core/roadmap-truncate.d.ts +49 -0
- package/dist/core/roadmap-truncate.d.ts.map +1 -0
- package/dist/core/roadmap-truncate.js +152 -0
- package/dist/core/roadmap-truncate.js.map +1 -0
- package/dist/core/roadmap-validate.d.ts +31 -0
- package/dist/core/roadmap-validate.d.ts.map +1 -0
- package/dist/core/roadmap-validate.js +403 -0
- package/dist/core/roadmap-validate.js.map +1 -0
- package/dist/core/roadmap-write-lock.d.ts +53 -0
- package/dist/core/roadmap-write-lock.d.ts.map +1 -0
- package/dist/core/roadmap-write-lock.js +59 -0
- package/dist/core/roadmap-write-lock.js.map +1 -0
- package/dist/core/schedule-insert-helper.d.ts +46 -0
- package/dist/core/schedule-insert-helper.d.ts.map +1 -0
- package/dist/core/schedule-insert-helper.js +52 -0
- package/dist/core/schedule-insert-helper.js.map +1 -0
- package/dist/core/schedule-maintenance.d.ts +22 -0
- package/dist/core/schedule-maintenance.d.ts.map +1 -0
- package/dist/core/schedule-maintenance.js +57 -0
- package/dist/core/schedule-maintenance.js.map +1 -0
- package/dist/core/scheduler.d.ts +208 -0
- package/dist/core/scheduler.d.ts.map +1 -0
- package/dist/core/scheduler.js +896 -0
- package/dist/core/scheduler.js.map +1 -0
- package/dist/core/semaphore.d.ts +13 -0
- package/dist/core/semaphore.d.ts.map +1 -0
- package/dist/core/semaphore.js +31 -0
- package/dist/core/semaphore.js.map +1 -0
- package/dist/core/session-gate.d.ts +37 -0
- package/dist/core/session-gate.d.ts.map +1 -0
- package/dist/core/session-gate.js +69 -0
- package/dist/core/session-gate.js.map +1 -0
- package/dist/core/session-manager.d.ts +252 -0
- package/dist/core/session-manager.d.ts.map +1 -0
- package/dist/core/session-manager.js +716 -0
- package/dist/core/session-manager.js.map +1 -0
- package/dist/core/signal-detector.d.ts +97 -0
- package/dist/core/signal-detector.d.ts.map +1 -0
- package/dist/core/signal-detector.js +215 -0
- package/dist/core/signal-detector.js.map +1 -0
- package/dist/core/skeleton.d.ts +83 -0
- package/dist/core/skeleton.d.ts.map +1 -0
- package/dist/core/skeleton.js +255 -0
- package/dist/core/skeleton.js.map +1 -0
- package/dist/core/skill-curation/apply-proposal.d.ts +71 -0
- package/dist/core/skill-curation/apply-proposal.d.ts.map +1 -0
- package/dist/core/skill-curation/apply-proposal.js +175 -0
- package/dist/core/skill-curation/apply-proposal.js.map +1 -0
- package/dist/core/skill-curation/auto-revert.d.ts +43 -0
- package/dist/core/skill-curation/auto-revert.d.ts.map +1 -0
- package/dist/core/skill-curation/auto-revert.js +155 -0
- package/dist/core/skill-curation/auto-revert.js.map +1 -0
- package/dist/core/skill-curation/classify-diff.d.ts +27 -0
- package/dist/core/skill-curation/classify-diff.d.ts.map +1 -0
- package/dist/core/skill-curation/classify-diff.js +0 -0
- package/dist/core/skill-curation/classify-diff.js.map +1 -0
- package/dist/core/skill-curation/declarations.d.ts +32 -0
- package/dist/core/skill-curation/declarations.d.ts.map +1 -0
- package/dist/core/skill-curation/declarations.js +171 -0
- package/dist/core/skill-curation/declarations.js.map +1 -0
- package/dist/core/skill-curation/knowledge-map.d.ts +26 -0
- package/dist/core/skill-curation/knowledge-map.d.ts.map +1 -0
- package/dist/core/skill-curation/knowledge-map.js +154 -0
- package/dist/core/skill-curation/knowledge-map.js.map +1 -0
- package/dist/core/skill-curation/orphan-overlay.d.ts +35 -0
- package/dist/core/skill-curation/orphan-overlay.d.ts.map +1 -0
- package/dist/core/skill-curation/orphan-overlay.js +167 -0
- package/dist/core/skill-curation/orphan-overlay.js.map +1 -0
- package/dist/core/skill-curation/overlay-store.d.ts +41 -0
- package/dist/core/skill-curation/overlay-store.d.ts.map +1 -0
- package/dist/core/skill-curation/overlay-store.js +143 -0
- package/dist/core/skill-curation/overlay-store.js.map +1 -0
- package/dist/core/skill-curation/render/convention-notes.d.ts +4 -0
- package/dist/core/skill-curation/render/convention-notes.d.ts.map +1 -0
- package/dist/core/skill-curation/render/convention-notes.js +13 -0
- package/dist/core/skill-curation/render/convention-notes.js.map +1 -0
- package/dist/core/skill-curation/render/cross-references.d.ts +4 -0
- package/dist/core/skill-curation/render/cross-references.d.ts.map +1 -0
- package/dist/core/skill-curation/render/cross-references.js +10 -0
- package/dist/core/skill-curation/render/cross-references.js.map +1 -0
- package/dist/core/skill-curation/render/frontmatter-schema.d.ts +4 -0
- package/dist/core/skill-curation/render/frontmatter-schema.d.ts.map +1 -0
- package/dist/core/skill-curation/render/frontmatter-schema.js +25 -0
- package/dist/core/skill-curation/render/frontmatter-schema.js.map +1 -0
- package/dist/core/skill-curation/render/index.d.ts +5 -0
- package/dist/core/skill-curation/render/index.d.ts.map +1 -0
- package/dist/core/skill-curation/render/index.js +42 -0
- package/dist/core/skill-curation/render/index.js.map +1 -0
- package/dist/core/skill-curation/render/knowledge-layout.d.ts +4 -0
- package/dist/core/skill-curation/render/knowledge-layout.d.ts.map +1 -0
- package/dist/core/skill-curation/render/knowledge-layout.js +36 -0
- package/dist/core/skill-curation/render/knowledge-layout.js.map +1 -0
- package/dist/core/skill-curation/render/routing-table.d.ts +4 -0
- package/dist/core/skill-curation/render/routing-table.d.ts.map +1 -0
- package/dist/core/skill-curation/render/routing-table.js +37 -0
- package/dist/core/skill-curation/render/routing-table.js.map +1 -0
- package/dist/core/skill-curation/render/search-recipes.d.ts +4 -0
- package/dist/core/skill-curation/render/search-recipes.d.ts.map +1 -0
- package/dist/core/skill-curation/render/search-recipes.js +39 -0
- package/dist/core/skill-curation/render/search-recipes.js.map +1 -0
- package/dist/core/skill-curation/run-token.d.ts +27 -0
- package/dist/core/skill-curation/run-token.d.ts.map +1 -0
- package/dist/core/skill-curation/run-token.js +81 -0
- package/dist/core/skill-curation/run-token.js.map +1 -0
- package/dist/core/skill-curation/signals.d.ts +49 -0
- package/dist/core/skill-curation/signals.d.ts.map +1 -0
- package/dist/core/skill-curation/signals.js +149 -0
- package/dist/core/skill-curation/signals.js.map +1 -0
- package/dist/core/skill-curation/smoke-test.d.ts +39 -0
- package/dist/core/skill-curation/smoke-test.d.ts.map +1 -0
- package/dist/core/skill-curation/smoke-test.js +313 -0
- package/dist/core/skill-curation/smoke-test.js.map +1 -0
- package/dist/core/skill-curation/splicer.d.ts +16 -0
- package/dist/core/skill-curation/splicer.d.ts.map +1 -0
- package/dist/core/skill-curation/splicer.js +78 -0
- package/dist/core/skill-curation/splicer.js.map +1 -0
- package/dist/core/skill-curation/workdir.d.ts +40 -0
- package/dist/core/skill-curation/workdir.d.ts.map +1 -0
- package/dist/core/skill-curation/workdir.js +242 -0
- package/dist/core/skill-curation/workdir.js.map +1 -0
- package/dist/core/skills-compiler.d.ts +391 -0
- package/dist/core/skills-compiler.d.ts.map +1 -0
- package/dist/core/skills-compiler.js +1271 -0
- package/dist/core/skills-compiler.js.map +1 -0
- package/dist/core/skills-manifest.d.ts +8 -0
- package/dist/core/skills-manifest.d.ts.map +1 -0
- package/dist/core/skills-manifest.js +408 -0
- package/dist/core/skills-manifest.js.map +1 -0
- package/dist/core/system-reset.d.ts +268 -0
- package/dist/core/system-reset.d.ts.map +1 -0
- package/dist/core/system-reset.js +816 -0
- package/dist/core/system-reset.js.map +1 -0
- package/dist/core/template-store.d.ts +170 -0
- package/dist/core/template-store.d.ts.map +1 -0
- package/dist/core/template-store.js +388 -0
- package/dist/core/template-store.js.map +1 -0
- package/dist/core/template-versions.d.ts +95 -0
- package/dist/core/template-versions.d.ts.map +1 -0
- package/dist/core/template-versions.js +175 -0
- package/dist/core/template-versions.js.map +1 -0
- package/dist/core/today-agent-plan.d.ts +33 -0
- package/dist/core/today-agent-plan.d.ts.map +1 -0
- package/dist/core/today-agent-plan.js +120 -0
- package/dist/core/today-agent-plan.js.map +1 -0
- package/dist/core/today-direct-writer.d.ts +62 -0
- package/dist/core/today-direct-writer.d.ts.map +1 -0
- package/dist/core/today-direct-writer.js +132 -0
- package/dist/core/today-direct-writer.js.map +1 -0
- package/dist/core/today-write-lock.d.ts +89 -0
- package/dist/core/today-write-lock.d.ts.map +1 -0
- package/dist/core/today-write-lock.js +154 -0
- package/dist/core/today-write-lock.js.map +1 -0
- package/dist/core/trigger-dispatch.d.ts +31 -0
- package/dist/core/trigger-dispatch.d.ts.map +1 -0
- package/dist/core/trigger-dispatch.js +100 -0
- package/dist/core/trigger-dispatch.js.map +1 -0
- package/dist/core/trigger-evaluator.d.ts +59 -0
- package/dist/core/trigger-evaluator.d.ts.map +1 -0
- package/dist/core/trigger-evaluator.js +243 -0
- package/dist/core/trigger-evaluator.js.map +1 -0
- package/dist/core/workdir.d.ts +241 -0
- package/dist/core/workdir.d.ts.map +1 -0
- package/dist/core/workdir.js +565 -0
- package/dist/core/workdir.js.map +1 -0
- package/dist/db/automation-triggers.d.ts +90 -0
- package/dist/db/automation-triggers.d.ts.map +1 -0
- package/dist/db/automation-triggers.js +199 -0
- package/dist/db/automation-triggers.js.map +1 -0
- package/dist/db/client.d.ts +6 -0
- package/dist/db/client.d.ts.map +1 -0
- package/dist/db/client.js +47 -0
- package/dist/db/client.js.map +1 -0
- package/dist/db/entities-store.d.ts +92 -0
- package/dist/db/entities-store.d.ts.map +1 -0
- package/dist/db/entities-store.js +180 -0
- package/dist/db/entities-store.js.map +1 -0
- package/dist/db/hourly-check-signals.d.ts +78 -0
- package/dist/db/hourly-check-signals.d.ts.map +1 -0
- package/dist/db/hourly-check-signals.js +289 -0
- package/dist/db/hourly-check-signals.js.map +1 -0
- package/dist/db/integration-probe-store.d.ts +27 -0
- package/dist/db/integration-probe-store.d.ts.map +1 -0
- package/dist/db/integration-probe-store.js +75 -0
- package/dist/db/integration-probe-store.js.map +1 -0
- package/dist/db/integrations-store.d.ts +19 -0
- package/dist/db/integrations-store.d.ts.map +1 -0
- package/dist/db/integrations-store.js +85 -0
- package/dist/db/integrations-store.js.map +1 -0
- package/dist/db/managed-tasks-store.d.ts +130 -0
- package/dist/db/managed-tasks-store.d.ts.map +1 -0
- package/dist/db/managed-tasks-store.js +238 -0
- package/dist/db/managed-tasks-store.js.map +1 -0
- package/dist/db/management-parse-failures-store.d.ts +45 -0
- package/dist/db/management-parse-failures-store.d.ts.map +1 -0
- package/dist/db/management-parse-failures-store.js +36 -0
- package/dist/db/management-parse-failures-store.js.map +1 -0
- package/dist/db/observations.d.ts +145 -0
- package/dist/db/observations.d.ts.map +1 -0
- package/dist/db/observations.js +287 -0
- package/dist/db/observations.js.map +1 -0
- package/dist/db/recurring-schedules.d.ts +70 -0
- package/dist/db/recurring-schedules.d.ts.map +1 -0
- package/dist/db/recurring-schedules.js +213 -0
- package/dist/db/recurring-schedules.js.map +1 -0
- package/dist/db/repositories-store.d.ts +296 -0
- package/dist/db/repositories-store.d.ts.map +1 -0
- package/dist/db/repositories-store.js +754 -0
- package/dist/db/repositories-store.js.map +1 -0
- package/dist/db/runtime-state.d.ts +61 -0
- package/dist/db/runtime-state.d.ts.map +1 -0
- package/dist/db/runtime-state.js +104 -0
- package/dist/db/runtime-state.js.map +1 -0
- package/dist/db/schema.d.ts +4 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +1338 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/sot-bindings-store.d.ts +41 -0
- package/dist/db/sot-bindings-store.d.ts.map +1 -0
- package/dist/db/sot-bindings-store.js +64 -0
- package/dist/db/sot-bindings-store.js.map +1 -0
- package/dist/db/test-schemas.d.ts +23 -0
- package/dist/db/test-schemas.d.ts.map +1 -0
- package/dist/db/test-schemas.js +111 -0
- package/dist/db/test-schemas.js.map +1 -0
- package/dist/db/voice-transcripts-store.d.ts +28 -0
- package/dist/db/voice-transcripts-store.d.ts.map +1 -0
- package/dist/db/voice-transcripts-store.js +43 -0
- package/dist/db/voice-transcripts-store.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2913 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +7 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +32 -0
- package/dist/init.js.map +1 -0
- package/dist/log-buffer.d.ts +71 -0
- package/dist/log-buffer.d.ts.map +1 -0
- package/dist/log-buffer.js +201 -0
- package/dist/log-buffer.js.map +1 -0
- package/dist/logging.d.ts +5 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +130 -0
- package/dist/logging.js.map +1 -0
- package/dist/management-rules.d.ts +2 -0
- package/dist/management-rules.d.ts.map +1 -0
- package/dist/management-rules.js +62 -0
- package/dist/management-rules.js.map +1 -0
- package/dist/messaging/constants.d.ts +33 -0
- package/dist/messaging/constants.d.ts.map +1 -0
- package/dist/messaging/constants.js +52 -0
- package/dist/messaging/constants.js.map +1 -0
- package/dist/messaging/magic-phrase.d.ts +16 -0
- package/dist/messaging/magic-phrase.d.ts.map +1 -0
- package/dist/messaging/magic-phrase.js +103 -0
- package/dist/messaging/magic-phrase.js.map +1 -0
- package/dist/messaging/owner-channels.d.ts +20 -0
- package/dist/messaging/owner-channels.d.ts.map +1 -0
- package/dist/messaging/owner-channels.js +41 -0
- package/dist/messaging/owner-channels.js.map +1 -0
- package/dist/observers/calendar-poller.d.ts +51 -0
- package/dist/observers/calendar-poller.d.ts.map +1 -0
- package/dist/observers/calendar-poller.js +128 -0
- package/dist/observers/calendar-poller.js.map +1 -0
- package/dist/observers/context-index-reconciler-observer.d.ts +72 -0
- package/dist/observers/context-index-reconciler-observer.d.ts.map +1 -0
- package/dist/observers/context-index-reconciler-observer.js +253 -0
- package/dist/observers/context-index-reconciler-observer.js.map +1 -0
- package/dist/observers/delegated-probe-observer.d.ts +83 -0
- package/dist/observers/delegated-probe-observer.d.ts.map +1 -0
- package/dist/observers/delegated-probe-observer.js +237 -0
- package/dist/observers/delegated-probe-observer.js.map +1 -0
- package/dist/observers/delegated-sync-worker.d.ts +375 -0
- package/dist/observers/delegated-sync-worker.d.ts.map +1 -0
- package/dist/observers/delegated-sync-worker.js +1087 -0
- package/dist/observers/delegated-sync-worker.js.map +1 -0
- package/dist/observers/entity-mirror-observer.d.ts +55 -0
- package/dist/observers/entity-mirror-observer.d.ts.map +1 -0
- package/dist/observers/entity-mirror-observer.js +73 -0
- package/dist/observers/entity-mirror-observer.js.map +1 -0
- package/dist/observers/git-delegated-cron.d.ts +41 -0
- package/dist/observers/git-delegated-cron.d.ts.map +1 -0
- package/dist/observers/git-delegated-cron.js +159 -0
- package/dist/observers/git-delegated-cron.js.map +1 -0
- package/dist/observers/git-event-classifier.d.ts +52 -0
- package/dist/observers/git-event-classifier.d.ts.map +1 -0
- package/dist/observers/git-event-classifier.js +70 -0
- package/dist/observers/git-event-classifier.js.map +1 -0
- package/dist/observers/git-watcher.d.ts +162 -0
- package/dist/observers/git-watcher.d.ts.map +1 -0
- package/dist/observers/git-watcher.js +768 -0
- package/dist/observers/git-watcher.js.map +1 -0
- package/dist/observers/github-poller-classifier.d.ts +101 -0
- package/dist/observers/github-poller-classifier.d.ts.map +1 -0
- package/dist/observers/github-poller-classifier.js +199 -0
- package/dist/observers/github-poller-classifier.js.map +1 -0
- package/dist/observers/github-poller.d.ts +291 -0
- package/dist/observers/github-poller.d.ts.map +1 -0
- package/dist/observers/github-poller.js +609 -0
- package/dist/observers/github-poller.js.map +1 -0
- package/dist/observers/imminent-event-scheduler.d.ts +34 -0
- package/dist/observers/imminent-event-scheduler.d.ts.map +1 -0
- package/dist/observers/imminent-event-scheduler.js +125 -0
- package/dist/observers/imminent-event-scheduler.js.map +1 -0
- package/dist/observers/mail-poller.d.ts +133 -0
- package/dist/observers/mail-poller.d.ts.map +1 -0
- package/dist/observers/mail-poller.js +563 -0
- package/dist/observers/mail-poller.js.map +1 -0
- package/dist/observers/mail-reconciliation.d.ts +87 -0
- package/dist/observers/mail-reconciliation.d.ts.map +1 -0
- package/dist/observers/mail-reconciliation.js +241 -0
- package/dist/observers/mail-reconciliation.js.map +1 -0
- package/dist/observers/manager.d.ts +67 -0
- package/dist/observers/manager.d.ts.map +1 -0
- package/dist/observers/manager.js +136 -0
- package/dist/observers/manager.js.map +1 -0
- package/dist/observers/notion-poller.d.ts +43 -0
- package/dist/observers/notion-poller.d.ts.map +1 -0
- package/dist/observers/notion-poller.js +184 -0
- package/dist/observers/notion-poller.js.map +1 -0
- package/dist/observers/observation-summarizer/index.d.ts +13 -0
- package/dist/observers/observation-summarizer/index.d.ts.map +1 -0
- package/dist/observers/observation-summarizer/index.js +13 -0
- package/dist/observers/observation-summarizer/index.js.map +1 -0
- package/dist/observers/observation-summarizer/pre-filter.d.ts +62 -0
- package/dist/observers/observation-summarizer/pre-filter.d.ts.map +1 -0
- package/dist/observers/observation-summarizer/pre-filter.js +189 -0
- package/dist/observers/observation-summarizer/pre-filter.js.map +1 -0
- package/dist/observers/observation-summarizer/response-parser.d.ts +30 -0
- package/dist/observers/observation-summarizer/response-parser.d.ts.map +1 -0
- package/dist/observers/observation-summarizer/response-parser.js +106 -0
- package/dist/observers/observation-summarizer/response-parser.js.map +1 -0
- package/dist/observers/observation-summarizer/summarizer-client.d.ts +83 -0
- package/dist/observers/observation-summarizer/summarizer-client.d.ts.map +1 -0
- package/dist/observers/observation-summarizer/summarizer-client.js +185 -0
- package/dist/observers/observation-summarizer/summarizer-client.js.map +1 -0
- package/dist/observers/observation-summarizer/summarizer-prompts.d.ts +51 -0
- package/dist/observers/observation-summarizer/summarizer-prompts.d.ts.map +1 -0
- package/dist/observers/observation-summarizer/summarizer-prompts.js +286 -0
- package/dist/observers/observation-summarizer/summarizer-prompts.js.map +1 -0
- package/dist/observers/observation-summarizer/worker.d.ts +106 -0
- package/dist/observers/observation-summarizer/worker.d.ts.map +1 -0
- package/dist/observers/observation-summarizer/worker.js +311 -0
- package/dist/observers/observation-summarizer/worker.js.map +1 -0
- package/dist/observers/obsidian-watcher.d.ts +90 -0
- package/dist/observers/obsidian-watcher.d.ts.map +1 -0
- package/dist/observers/obsidian-watcher.js +166 -0
- package/dist/observers/obsidian-watcher.js.map +1 -0
- package/dist/observers/primary-vault-watcher.d.ts +73 -0
- package/dist/observers/primary-vault-watcher.d.ts.map +1 -0
- package/dist/observers/primary-vault-watcher.js +115 -0
- package/dist/observers/primary-vault-watcher.js.map +1 -0
- package/dist/observers/repository-management-cron.d.ts +70 -0
- package/dist/observers/repository-management-cron.d.ts.map +1 -0
- package/dist/observers/repository-management-cron.js +166 -0
- package/dist/observers/repository-management-cron.js.map +1 -0
- package/dist/observers/skill-curation-walker.d.ts +33 -0
- package/dist/observers/skill-curation-walker.d.ts.map +1 -0
- package/dist/observers/skill-curation-walker.js +216 -0
- package/dist/observers/skill-curation-walker.js.map +1 -0
- package/dist/safety/absolute-block-audit.d.ts +22 -0
- package/dist/safety/absolute-block-audit.d.ts.map +1 -0
- package/dist/safety/absolute-block-audit.js +32 -0
- package/dist/safety/absolute-block-audit.js.map +1 -0
- package/dist/safety/agent-write-tracker.d.ts +42 -0
- package/dist/safety/agent-write-tracker.d.ts.map +1 -0
- package/dist/safety/agent-write-tracker.js +82 -0
- package/dist/safety/agent-write-tracker.js.map +1 -0
- package/dist/safety/always-disallowed.d.ts +66 -0
- package/dist/safety/always-disallowed.d.ts.map +1 -0
- package/dist/safety/always-disallowed.js +347 -0
- package/dist/safety/always-disallowed.js.map +1 -0
- package/dist/safety/audit.d.ts +118 -0
- package/dist/safety/audit.d.ts.map +1 -0
- package/dist/safety/audit.js +324 -0
- package/dist/safety/audit.js.map +1 -0
- package/dist/safety/integration-write-tracker.d.ts +58 -0
- package/dist/safety/integration-write-tracker.d.ts.map +1 -0
- package/dist/safety/integration-write-tracker.js +41 -0
- package/dist/safety/integration-write-tracker.js.map +1 -0
- package/dist/safety/risk-classifier.d.ts +65 -0
- package/dist/safety/risk-classifier.d.ts.map +1 -0
- package/dist/safety/risk-classifier.js +763 -0
- package/dist/safety/risk-classifier.js.map +1 -0
- package/dist/scheduler/hourly-check-gate.d.ts +73 -0
- package/dist/scheduler/hourly-check-gate.d.ts.map +1 -0
- package/dist/scheduler/hourly-check-gate.js +128 -0
- package/dist/scheduler/hourly-check-gate.js.map +1 -0
- package/dist/secrets/backend-api-key-env.d.ts +104 -0
- package/dist/secrets/backend-api-key-env.d.ts.map +1 -0
- package/dist/secrets/backend-api-key-env.js +197 -0
- package/dist/secrets/backend-api-key-env.js.map +1 -0
- package/dist/secrets/codex-home-materializer.d.ts +35 -0
- package/dist/secrets/codex-home-materializer.d.ts.map +1 -0
- package/dist/secrets/codex-home-materializer.js +76 -0
- package/dist/secrets/codex-home-materializer.js.map +1 -0
- package/dist/secrets/encrypted-blob-store.d.ts +20 -0
- package/dist/secrets/encrypted-blob-store.d.ts.map +1 -0
- package/dist/secrets/encrypted-blob-store.js +80 -0
- package/dist/secrets/encrypted-blob-store.js.map +1 -0
- package/dist/secrets/platform-secret-store.d.ts +17 -0
- package/dist/secrets/platform-secret-store.d.ts.map +1 -0
- package/dist/secrets/platform-secret-store.js +37 -0
- package/dist/secrets/platform-secret-store.js.map +1 -0
- package/dist/secrets/redaction.d.ts +2 -0
- package/dist/secrets/redaction.d.ts.map +1 -0
- package/dist/secrets/redaction.js +2 -0
- package/dist/secrets/redaction.js.map +1 -0
- package/dist/secrets/secret-broker.d.ts +61 -0
- package/dist/secrets/secret-broker.d.ts.map +1 -0
- package/dist/secrets/secret-broker.js +160 -0
- package/dist/secrets/secret-broker.js.map +1 -0
- package/dist/secrets/secret-names.d.ts +34 -0
- package/dist/secrets/secret-names.d.ts.map +1 -0
- package/dist/secrets/secret-names.js +39 -0
- package/dist/secrets/secret-names.js.map +1 -0
- package/dist/secrets/secret-store.d.ts +8 -0
- package/dist/secrets/secret-store.d.ts.map +1 -0
- package/dist/secrets/secret-store.js +2 -0
- package/dist/secrets/secret-store.js.map +1 -0
- package/dist/secrets/types.d.ts +7 -0
- package/dist/secrets/types.d.ts.map +1 -0
- package/dist/secrets/types.js +2 -0
- package/dist/secrets/types.js.map +1 -0
- package/dist/services/apple-calendar/caldav-client.d.ts +48 -0
- package/dist/services/apple-calendar/caldav-client.d.ts.map +1 -0
- package/dist/services/apple-calendar/caldav-client.js +86 -0
- package/dist/services/apple-calendar/caldav-client.js.map +1 -0
- package/dist/services/apple-calendar/caldav-codec.d.ts +67 -0
- package/dist/services/apple-calendar/caldav-codec.d.ts.map +1 -0
- package/dist/services/apple-calendar/caldav-codec.js +341 -0
- package/dist/services/apple-calendar/caldav-codec.js.map +1 -0
- package/dist/services/apple-calendar/index.d.ts +3 -0
- package/dist/services/apple-calendar/index.d.ts.map +1 -0
- package/dist/services/apple-calendar/index.js +2 -0
- package/dist/services/apple-calendar/index.js.map +1 -0
- package/dist/services/apple-calendar/service.d.ts +75 -0
- package/dist/services/apple-calendar/service.d.ts.map +1 -0
- package/dist/services/apple-calendar/service.js +374 -0
- package/dist/services/apple-calendar/service.js.map +1 -0
- package/dist/services/apple-calendar/types.d.ts +78 -0
- package/dist/services/apple-calendar/types.d.ts.map +1 -0
- package/dist/services/apple-calendar/types.js +17 -0
- package/dist/services/apple-calendar/types.js.map +1 -0
- package/dist/services/attachments/hardlink.d.ts +11 -0
- package/dist/services/attachments/hardlink.d.ts.map +1 -0
- package/dist/services/attachments/hardlink.js +56 -0
- package/dist/services/attachments/hardlink.js.map +1 -0
- package/dist/services/attachments/sanitize.d.ts +21 -0
- package/dist/services/attachments/sanitize.d.ts.map +1 -0
- package/dist/services/attachments/sanitize.js +128 -0
- package/dist/services/attachments/sanitize.js.map +1 -0
- package/dist/services/attachments/store.d.ts +146 -0
- package/dist/services/attachments/store.d.ts.map +1 -0
- package/dist/services/attachments/store.js +477 -0
- package/dist/services/attachments/store.js.map +1 -0
- package/dist/services/calendar/outlook/graph-calendar-client.d.ts +114 -0
- package/dist/services/calendar/outlook/graph-calendar-client.d.ts.map +1 -0
- package/dist/services/calendar/outlook/graph-calendar-client.js +146 -0
- package/dist/services/calendar/outlook/graph-calendar-client.js.map +1 -0
- package/dist/services/calendar.d.ts +115 -0
- package/dist/services/calendar.d.ts.map +1 -0
- package/dist/services/calendar.js +281 -0
- package/dist/services/calendar.js.map +1 -0
- package/dist/services/delegated-backend-invoker.d.ts +414 -0
- package/dist/services/delegated-backend-invoker.d.ts.map +1 -0
- package/dist/services/delegated-backend-invoker.js +2372 -0
- package/dist/services/delegated-backend-invoker.js.map +1 -0
- package/dist/services/delegated-proxy-config.d.ts +93 -0
- package/dist/services/delegated-proxy-config.d.ts.map +1 -0
- package/dist/services/delegated-proxy-config.js +98 -0
- package/dist/services/delegated-proxy-config.js.map +1 -0
- package/dist/services/delegated-task-result-cache.d.ts +176 -0
- package/dist/services/delegated-task-result-cache.d.ts.map +1 -0
- package/dist/services/delegated-task-result-cache.js +0 -0
- package/dist/services/delegated-task-result-cache.js.map +1 -0
- package/dist/services/delegated-task-runtime.d.ts +346 -0
- package/dist/services/delegated-task-runtime.d.ts.map +1 -0
- package/dist/services/delegated-task-runtime.js +589 -0
- package/dist/services/delegated-task-runtime.js.map +1 -0
- package/dist/services/delegated-task-session-pool.d.ts +182 -0
- package/dist/services/delegated-task-session-pool.d.ts.map +1 -0
- package/dist/services/delegated-task-session-pool.js +292 -0
- package/dist/services/delegated-task-session-pool.js.map +1 -0
- package/dist/services/delegated-tool-runtime.d.ts +50 -0
- package/dist/services/delegated-tool-runtime.d.ts.map +1 -0
- package/dist/services/delegated-tool-runtime.js +120 -0
- package/dist/services/delegated-tool-runtime.js.map +1 -0
- package/dist/services/fts5.d.ts +40 -0
- package/dist/services/fts5.d.ts.map +1 -0
- package/dist/services/fts5.js +54 -0
- package/dist/services/fts5.js.map +1 -0
- package/dist/services/git-account-registry.d.ts +164 -0
- package/dist/services/git-account-registry.d.ts.map +1 -0
- package/dist/services/git-account-registry.js +297 -0
- package/dist/services/git-account-registry.js.map +1 -0
- package/dist/services/github.d.ts +49 -0
- package/dist/services/github.d.ts.map +1 -0
- package/dist/services/github.js +123 -0
- package/dist/services/github.js.map +1 -0
- package/dist/services/gmail-classifier.d.ts +62 -0
- package/dist/services/gmail-classifier.d.ts.map +1 -0
- package/dist/services/gmail-classifier.js +221 -0
- package/dist/services/gmail-classifier.js.map +1 -0
- package/dist/services/gmail.d.ts +192 -0
- package/dist/services/gmail.d.ts.map +1 -0
- package/dist/services/gmail.js +678 -0
- package/dist/services/gmail.js.map +1 -0
- package/dist/services/google-auth.d.ts +16 -0
- package/dist/services/google-auth.d.ts.map +1 -0
- package/dist/services/google-auth.js +37 -0
- package/dist/services/google-auth.js.map +1 -0
- package/dist/services/google-maps.d.ts +35 -0
- package/dist/services/google-maps.d.ts.map +1 -0
- package/dist/services/google-maps.js +82 -0
- package/dist/services/google-maps.js.map +1 -0
- package/dist/services/integrations/extract-write-item-id.d.ts +64 -0
- package/dist/services/integrations/extract-write-item-id.d.ts.map +1 -0
- package/dist/services/integrations/extract-write-item-id.js +188 -0
- package/dist/services/integrations/extract-write-item-id.js.map +1 -0
- package/dist/services/integrations/reconcile.d.ts +136 -0
- package/dist/services/integrations/reconcile.d.ts.map +1 -0
- package/dist/services/integrations/reconcile.js +218 -0
- package/dist/services/integrations/reconcile.js.map +1 -0
- package/dist/services/integrations/snapshot-partitions.d.ts +40 -0
- package/dist/services/integrations/snapshot-partitions.d.ts.map +1 -0
- package/dist/services/integrations/snapshot-partitions.js +113 -0
- package/dist/services/integrations/snapshot-partitions.js.map +1 -0
- package/dist/services/journal/render.d.ts +15 -0
- package/dist/services/journal/render.d.ts.map +1 -0
- package/dist/services/journal/render.js +17 -0
- package/dist/services/journal/render.js.map +1 -0
- package/dist/services/journal/writer.d.ts +26 -0
- package/dist/services/journal/writer.d.ts.map +1 -0
- package/dist/services/journal/writer.js +50 -0
- package/dist/services/journal/writer.js.map +1 -0
- package/dist/services/mail/account-registry.d.ts +208 -0
- package/dist/services/mail/account-registry.d.ts.map +1 -0
- package/dist/services/mail/account-registry.js +554 -0
- package/dist/services/mail/account-registry.js.map +1 -0
- package/dist/services/mail/gmail/auth-failure-classifier.d.ts +24 -0
- package/dist/services/mail/gmail/auth-failure-classifier.d.ts.map +1 -0
- package/dist/services/mail/gmail/auth-failure-classifier.js +67 -0
- package/dist/services/mail/gmail/auth-failure-classifier.js.map +1 -0
- package/dist/services/mail/gmail/gmail-provider.d.ts +58 -0
- package/dist/services/mail/gmail/gmail-provider.d.ts.map +1 -0
- package/dist/services/mail/gmail/gmail-provider.js +434 -0
- package/dist/services/mail/gmail/gmail-provider.js.map +1 -0
- package/dist/services/mail/gmail/legacy-row.d.ts +24 -0
- package/dist/services/mail/gmail/legacy-row.d.ts.map +1 -0
- package/dist/services/mail/gmail/legacy-row.js +71 -0
- package/dist/services/mail/gmail/legacy-row.js.map +1 -0
- package/dist/services/mail/gmail/poll-cursor.d.ts +12 -0
- package/dist/services/mail/gmail/poll-cursor.d.ts.map +1 -0
- package/dist/services/mail/gmail/poll-cursor.js +32 -0
- package/dist/services/mail/gmail/poll-cursor.js.map +1 -0
- package/dist/services/mail/html-to-plaintext.d.ts +27 -0
- package/dist/services/mail/html-to-plaintext.d.ts.map +1 -0
- package/dist/services/mail/html-to-plaintext.js +163 -0
- package/dist/services/mail/html-to-plaintext.js.map +1 -0
- package/dist/services/mail/imap/app-password.d.ts +27 -0
- package/dist/services/mail/imap/app-password.d.ts.map +1 -0
- package/dist/services/mail/imap/app-password.js +86 -0
- package/dist/services/mail/imap/app-password.js.map +1 -0
- package/dist/services/mail/imap/auth-failure-classifier.d.ts +21 -0
- package/dist/services/mail/imap/auth-failure-classifier.d.ts.map +1 -0
- package/dist/services/mail/imap/auth-failure-classifier.js +54 -0
- package/dist/services/mail/imap/auth-failure-classifier.js.map +1 -0
- package/dist/services/mail/imap/capabilities.d.ts +30 -0
- package/dist/services/mail/imap/capabilities.d.ts.map +1 -0
- package/dist/services/mail/imap/capabilities.js +70 -0
- package/dist/services/mail/imap/capabilities.js.map +1 -0
- package/dist/services/mail/imap/client.d.ts +15 -0
- package/dist/services/mail/imap/client.d.ts.map +1 -0
- package/dist/services/mail/imap/client.js +60 -0
- package/dist/services/mail/imap/client.js.map +1 -0
- package/dist/services/mail/imap/cursor.d.ts +19 -0
- package/dist/services/mail/imap/cursor.d.ts.map +1 -0
- package/dist/services/mail/imap/cursor.js +47 -0
- package/dist/services/mail/imap/cursor.js.map +1 -0
- package/dist/services/mail/imap/folder-resolver.d.ts +24 -0
- package/dist/services/mail/imap/folder-resolver.d.ts.map +1 -0
- package/dist/services/mail/imap/folder-resolver.js +58 -0
- package/dist/services/mail/imap/folder-resolver.js.map +1 -0
- package/dist/services/mail/imap/icloud-provider.d.ts +5 -0
- package/dist/services/mail/imap/icloud-provider.d.ts.map +1 -0
- package/dist/services/mail/imap/icloud-provider.js +5 -0
- package/dist/services/mail/imap/icloud-provider.js.map +1 -0
- package/dist/services/mail/imap/imap-provider-base.d.ts +173 -0
- package/dist/services/mail/imap/imap-provider-base.d.ts.map +1 -0
- package/dist/services/mail/imap/imap-provider-base.js +1004 -0
- package/dist/services/mail/imap/imap-provider-base.js.map +1 -0
- package/dist/services/mail/imap/query-translator.d.ts +13 -0
- package/dist/services/mail/imap/query-translator.d.ts.map +1 -0
- package/dist/services/mail/imap/query-translator.js +114 -0
- package/dist/services/mail/imap/query-translator.js.map +1 -0
- package/dist/services/mail/imap/reconcile-planner.d.ts +56 -0
- package/dist/services/mail/imap/reconcile-planner.d.ts.map +1 -0
- package/dist/services/mail/imap/reconcile-planner.js +52 -0
- package/dist/services/mail/imap/reconcile-planner.js.map +1 -0
- package/dist/services/mail/imap/reply-mime.d.ts +24 -0
- package/dist/services/mail/imap/reply-mime.d.ts.map +1 -0
- package/dist/services/mail/imap/reply-mime.js +77 -0
- package/dist/services/mail/imap/reply-mime.js.map +1 -0
- package/dist/services/mail/imap/yahoo-provider.d.ts +5 -0
- package/dist/services/mail/imap/yahoo-provider.d.ts.map +1 -0
- package/dist/services/mail/imap/yahoo-provider.js +5 -0
- package/dist/services/mail/imap/yahoo-provider.js.map +1 -0
- package/dist/services/mail/mail-search.d.ts +35 -0
- package/dist/services/mail/mail-search.d.ts.map +1 -0
- package/dist/services/mail/mail-search.js +59 -0
- package/dist/services/mail/mail-search.js.map +1 -0
- package/dist/services/mail/outlook/auth-failure-classifier.d.ts +38 -0
- package/dist/services/mail/outlook/auth-failure-classifier.d.ts.map +1 -0
- package/dist/services/mail/outlook/auth-failure-classifier.js +91 -0
- package/dist/services/mail/outlook/auth-failure-classifier.js.map +1 -0
- package/dist/services/mail/outlook/client-config.d.ts +34 -0
- package/dist/services/mail/outlook/client-config.d.ts.map +1 -0
- package/dist/services/mail/outlook/client-config.js +58 -0
- package/dist/services/mail/outlook/client-config.js.map +1 -0
- package/dist/services/mail/outlook/delta-cursor.d.ts +66 -0
- package/dist/services/mail/outlook/delta-cursor.d.ts.map +1 -0
- package/dist/services/mail/outlook/delta-cursor.js +85 -0
- package/dist/services/mail/outlook/delta-cursor.js.map +1 -0
- package/dist/services/mail/outlook/graph-client.d.ts +98 -0
- package/dist/services/mail/outlook/graph-client.d.ts.map +1 -0
- package/dist/services/mail/outlook/graph-client.js +198 -0
- package/dist/services/mail/outlook/graph-client.js.map +1 -0
- package/dist/services/mail/outlook/msal-app-factory.d.ts +20 -0
- package/dist/services/mail/outlook/msal-app-factory.d.ts.map +1 -0
- package/dist/services/mail/outlook/msal-app-factory.js +62 -0
- package/dist/services/mail/outlook/msal-app-factory.js.map +1 -0
- package/dist/services/mail/outlook/msal-cache-plugin.d.ts +19 -0
- package/dist/services/mail/outlook/msal-cache-plugin.d.ts.map +1 -0
- package/dist/services/mail/outlook/msal-cache-plugin.js +30 -0
- package/dist/services/mail/outlook/msal-cache-plugin.js.map +1 -0
- package/dist/services/mail/outlook/oauth-device-code.d.ts +26 -0
- package/dist/services/mail/outlook/oauth-device-code.d.ts.map +1 -0
- package/dist/services/mail/outlook/oauth-device-code.js +32 -0
- package/dist/services/mail/outlook/oauth-device-code.js.map +1 -0
- package/dist/services/mail/outlook/oauth-loopback.d.ts +41 -0
- package/dist/services/mail/outlook/oauth-loopback.d.ts.map +1 -0
- package/dist/services/mail/outlook/oauth-loopback.js +223 -0
- package/dist/services/mail/outlook/oauth-loopback.js.map +1 -0
- package/dist/services/mail/outlook/outlook-provider.d.ts +100 -0
- package/dist/services/mail/outlook/outlook-provider.d.ts.map +1 -0
- package/dist/services/mail/outlook/outlook-provider.js +619 -0
- package/dist/services/mail/outlook/outlook-provider.js.map +1 -0
- package/dist/services/mail/outlook/query-translator.d.ts +10 -0
- package/dist/services/mail/outlook/query-translator.d.ts.map +1 -0
- package/dist/services/mail/outlook/query-translator.js +103 -0
- package/dist/services/mail/outlook/query-translator.js.map +1 -0
- package/dist/services/mail/provider.d.ts +267 -0
- package/dist/services/mail/provider.d.ts.map +1 -0
- package/dist/services/mail/provider.js +34 -0
- package/dist/services/mail/provider.js.map +1 -0
- package/dist/services/mail/query-utils.d.ts +13 -0
- package/dist/services/mail/query-utils.d.ts.map +1 -0
- package/dist/services/mail/query-utils.js +18 -0
- package/dist/services/mail/query-utils.js.map +1 -0
- package/dist/services/mail-classifier.d.ts +25 -0
- package/dist/services/mail-classifier.d.ts.map +1 -0
- package/dist/services/mail-classifier.js +52 -0
- package/dist/services/mail-classifier.js.map +1 -0
- package/dist/services/mail-ingestion.d.ts +139 -0
- package/dist/services/mail-ingestion.d.ts.map +1 -0
- package/dist/services/mail-ingestion.js +223 -0
- package/dist/services/mail-ingestion.js.map +1 -0
- package/dist/services/mcp/auto-probe.d.ts +76 -0
- package/dist/services/mcp/auto-probe.d.ts.map +1 -0
- package/dist/services/mcp/auto-probe.js +147 -0
- package/dist/services/mcp/auto-probe.js.map +1 -0
- package/dist/services/mcp/generators/claude.d.ts +18 -0
- package/dist/services/mcp/generators/claude.d.ts.map +1 -0
- package/dist/services/mcp/generators/claude.js +90 -0
- package/dist/services/mcp/generators/claude.js.map +1 -0
- package/dist/services/mcp/generators/codex.d.ts +22 -0
- package/dist/services/mcp/generators/codex.d.ts.map +1 -0
- package/dist/services/mcp/generators/codex.js +102 -0
- package/dist/services/mcp/generators/codex.js.map +1 -0
- package/dist/services/mcp/generators/gemini.d.ts +20 -0
- package/dist/services/mcp/generators/gemini.d.ts.map +1 -0
- package/dist/services/mcp/generators/gemini.js +97 -0
- package/dist/services/mcp/generators/gemini.js.map +1 -0
- package/dist/services/mcp/generators/index.d.ts +20 -0
- package/dist/services/mcp/generators/index.d.ts.map +1 -0
- package/dist/services/mcp/generators/index.js +29 -0
- package/dist/services/mcp/generators/index.js.map +1 -0
- package/dist/services/mcp/generators/types.d.ts +47 -0
- package/dist/services/mcp/generators/types.d.ts.map +1 -0
- package/dist/services/mcp/generators/types.js +40 -0
- package/dist/services/mcp/generators/types.js.map +1 -0
- package/dist/services/mcp/probe.d.ts +31 -0
- package/dist/services/mcp/probe.d.ts.map +1 -0
- package/dist/services/mcp/probe.js +437 -0
- package/dist/services/mcp/probe.js.map +1 -0
- package/dist/services/mcp/registry.d.ts +84 -0
- package/dist/services/mcp/registry.d.ts.map +1 -0
- package/dist/services/mcp/registry.js +387 -0
- package/dist/services/mcp/registry.js.map +1 -0
- package/dist/services/mcp/risk.d.ts +82 -0
- package/dist/services/mcp/risk.d.ts.map +1 -0
- package/dist/services/mcp/risk.js +126 -0
- package/dist/services/mcp/risk.js.map +1 -0
- package/dist/services/mcp/session-materializer.d.ts +123 -0
- package/dist/services/mcp/session-materializer.d.ts.map +1 -0
- package/dist/services/mcp/session-materializer.js +361 -0
- package/dist/services/mcp/session-materializer.js.map +1 -0
- package/dist/services/mcp/tool-audit.d.ts +53 -0
- package/dist/services/mcp/tool-audit.d.ts.map +1 -0
- package/dist/services/mcp/tool-audit.js +74 -0
- package/dist/services/mcp/tool-audit.js.map +1 -0
- package/dist/services/mcp/types.d.ts +88 -0
- package/dist/services/mcp/types.d.ts.map +1 -0
- package/dist/services/mcp/types.js +94 -0
- package/dist/services/mcp/types.js.map +1 -0
- package/dist/services/notion.d.ts +134 -0
- package/dist/services/notion.d.ts.map +1 -0
- package/dist/services/notion.js +350 -0
- package/dist/services/notion.js.map +1 -0
- package/dist/services/obsidian.d.ts +116 -0
- package/dist/services/obsidian.d.ts.map +1 -0
- package/dist/services/obsidian.js +305 -0
- package/dist/services/obsidian.js.map +1 -0
- package/dist/services/service-registry.d.ts +31 -0
- package/dist/services/service-registry.d.ts.map +1 -0
- package/dist/services/service-registry.js +15 -0
- package/dist/services/service-registry.js.map +1 -0
- package/dist/services/voice/transcriber-impl.d.ts +15 -0
- package/dist/services/voice/transcriber-impl.d.ts.map +1 -0
- package/dist/services/voice/transcriber-impl.js +129 -0
- package/dist/services/voice/transcriber-impl.js.map +1 -0
- package/dist/services/voice/transcriber.d.ts +117 -0
- package/dist/services/voice/transcriber.d.ts.map +1 -0
- package/dist/services/voice/transcriber.js +201 -0
- package/dist/services/voice/transcriber.js.map +1 -0
- package/dist/settings/runtime-settings.d.ts +232 -0
- package/dist/settings/runtime-settings.d.ts.map +1 -0
- package/dist/settings/runtime-settings.js +769 -0
- package/dist/settings/runtime-settings.js.map +1 -0
- package/dist/settings/settings-store.d.ts +13 -0
- package/dist/settings/settings-store.d.ts.map +1 -0
- package/dist/settings/settings-store.js +87 -0
- package/dist/settings/settings-store.js.map +1 -0
- package/package.json +85 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2913 @@
|
|
|
1
|
+
import { serve } from "@hono/node-server";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
import { loadConfig, getContextDir, isRoadmapStale, mergeRuntimeSettingsFromDb, runVaultHealthProbe, validateExternalObsidianVaultPath, } from "./config.js";
|
|
6
|
+
import { getDegradedMode, isSetupCompleted, isUserPaused, isDegraded as readDegradedMode, } from "./db/runtime-state.js";
|
|
7
|
+
import { createDefaultBangCommandRegistry } from "./core/bang-commands/index.js";
|
|
8
|
+
import { initDirectories } from "./init.js";
|
|
9
|
+
import { createDatabase } from "./db/client.js";
|
|
10
|
+
import { applySchema } from "./db/schema.js";
|
|
11
|
+
import { readIntegrations } from "./db/integrations-store.js";
|
|
12
|
+
import { getRepositoryByGithub, getRepositoryByLocalPath, listRepositories, selectGithubRepoSlugs, selectGitWatchedRepos, } from "./db/repositories-store.js";
|
|
13
|
+
import { dispatchMatchingTriggers } from "./core/trigger-dispatch.js";
|
|
14
|
+
import { EventBus } from "./core/event-bus.js";
|
|
15
|
+
import { AgentScheduler } from "./core/scheduler.js";
|
|
16
|
+
import { CustomRoutineScheduler } from "./core/custom-routine-scheduler.js";
|
|
17
|
+
import { HealthMonitor } from "./core/health-monitor.js";
|
|
18
|
+
import { Heartbeat } from "./core/heartbeat.js";
|
|
19
|
+
import { MessageHub } from "./adapters/message-hub.js";
|
|
20
|
+
import { ObserverManager } from "./observers/manager.js";
|
|
21
|
+
import { ObsidianWatcher } from "./observers/obsidian-watcher.js";
|
|
22
|
+
import { PrimaryVaultWatcher } from "./observers/primary-vault-watcher.js";
|
|
23
|
+
import { ContextIndexReconcilerObserver } from "./observers/context-index-reconciler-observer.js";
|
|
24
|
+
import { EntityMirrorObserver } from "./observers/entity-mirror-observer.js";
|
|
25
|
+
import { GitWatcher } from "./observers/git-watcher.js";
|
|
26
|
+
import { GitHubPoller } from "./observers/github-poller.js";
|
|
27
|
+
import { GitAccountRegistry } from "./services/git-account-registry.js";
|
|
28
|
+
import { GitDelegatedCronObserver, hasActiveDelegatedGitLifecycleIntegration, } from "./observers/git-delegated-cron.js";
|
|
29
|
+
import { RepositoryManagementCron } from "./observers/repository-management-cron.js";
|
|
30
|
+
import { ObservationSummarizerWorker, AnthropicSummarizerClient, UnsupportedSummarizerClient, } from "./observers/observation-summarizer/index.js";
|
|
31
|
+
import { CalendarPoller } from "./observers/calendar-poller.js";
|
|
32
|
+
import { ImminentEventScheduler } from "./observers/imminent-event-scheduler.js";
|
|
33
|
+
import { DelegatedSyncWorker, hasActiveDelegatedSyncIntegration, } from "./observers/delegated-sync-worker.js";
|
|
34
|
+
import { applyIntegrationModeChange, shouldStartObserversFor, } from "./core/integration-lifecycle.js";
|
|
35
|
+
import { NotionPoller } from "./observers/notion-poller.js";
|
|
36
|
+
import { DiscordAdapter } from "./adapters/discord.js";
|
|
37
|
+
import { SlackAdapter } from "./adapters/slack-adapter.js";
|
|
38
|
+
import { TelegramAdapter } from "./adapters/telegram-adapter.js";
|
|
39
|
+
import { DashboardAdapter } from "./adapters/dashboard-adapter.js";
|
|
40
|
+
import { WhatsAppAdapter } from "./adapters/whatsapp-adapter.js";
|
|
41
|
+
import { CalendarService } from "./services/calendar.js";
|
|
42
|
+
import { AppleCalendarService } from "./services/apple-calendar/index.js";
|
|
43
|
+
import { GmailService } from "./services/gmail.js";
|
|
44
|
+
import { detectGoogleCredentialType } from "./services/google-auth.js";
|
|
45
|
+
import { ObsidianService } from "./services/obsidian.js";
|
|
46
|
+
import { NotionService } from "./services/notion.js";
|
|
47
|
+
import { GitHubService } from "./services/github.js";
|
|
48
|
+
import { createServiceRegistry } from "./services/service-registry.js";
|
|
49
|
+
import { SignalDetector } from "./core/signal-detector.js";
|
|
50
|
+
import { EventDispatcher } from "./core/dispatcher.js";
|
|
51
|
+
import { ClaudeCodeCore } from "./core/backends/claude-code-core.js";
|
|
52
|
+
import { BackendRouter } from "./core/backends/backend-router.js";
|
|
53
|
+
import { ensureBackendMaterialized, syncAllUserSkills, buildConfiguredServices, refreshDmSessionWorkdirs, validateDelegatedStartup, } from "./core/workdir.js";
|
|
54
|
+
import { CodexCore } from "./core/backends/codex-core.js";
|
|
55
|
+
import { GeminiCliCore } from "./core/backends/gemini-cli-core.js";
|
|
56
|
+
import { PriceFetcher } from "./core/backends/price-fetcher.js";
|
|
57
|
+
import { AuthTelemetry } from "./core/backends/auth-telemetry.js";
|
|
58
|
+
import { AuthHealthMonitor, AUTH_PROBE_NOTIFICATION_CATEGORY } from "./core/backends/auth-health-monitor.js";
|
|
59
|
+
import { AuthRecovery } from "./core/backends/auth-recovery.js";
|
|
60
|
+
import { ContextBuilder } from "./core/context-builder.js";
|
|
61
|
+
import { getTaskFlow, initTaskFlows } from "./core/prompts.js";
|
|
62
|
+
import { ensureSkeletonFiles, resolveTemplatesRoot } from "./core/skeleton.js";
|
|
63
|
+
import { normalizeGitWatchedRepos, queueGitProjectUpdate, queueMissingGitProjectInits, seedGitProjectDocTemplates, } from "./core/git-project-docs.js";
|
|
64
|
+
import { reconcileTemplateAssets, recordInstructionAssetStatus, recordSkillAssetStatus, } from "./core/release-assets.js";
|
|
65
|
+
import { SessionManager } from "./core/session-manager.js";
|
|
66
|
+
import { MessageRecorder } from "./core/message-recorder.js";
|
|
67
|
+
import { ScopedReadSensitiveTokenManager } from "./core/read-sensitive-token-manager.js";
|
|
68
|
+
import { NotificationManager } from "./adapters/notification-manager.js";
|
|
69
|
+
import { recordProactiveForwardDeliveries } from "./core/channel-timeline.js";
|
|
70
|
+
import { continueDashboardSession as continueDashboardSessionFromHistory, endDashboardSession as endDashboardSessionFromChannel, markContextChanged, } from "./core/dashboard-session-controls.js";
|
|
71
|
+
import { AuditLogger } from "./safety/audit.js";
|
|
72
|
+
import { bootstrapManagementMd, migrateLegacyManagementMd, startManagementMdWatcher, } from "./core/management-md.js";
|
|
73
|
+
import { bootstrapManagementRegistry, startManagementRegistryWatcher, } from "./core/management-registry.js";
|
|
74
|
+
import { bootstrapManagedTaskSeq } from "./db/managed-tasks-store.js";
|
|
75
|
+
import { startDocsIndexer, } from "./core/docs/indexer.js";
|
|
76
|
+
import { makeDbLookup as makeDocsCitationLookup } from "./core/docs/citation-validator.js";
|
|
77
|
+
import { createDocsRoutes } from "./api/routes/docs.js";
|
|
78
|
+
import { DocsQAAdapter } from "./adapters/docs-qa-adapter.js";
|
|
79
|
+
import { CompositeDashboardStream } from "./adapters/composite-dashboard-stream.js";
|
|
80
|
+
import { createApp } from "./api/server.js";
|
|
81
|
+
import { EventBroadcaster } from "./api/routes/sse.js";
|
|
82
|
+
import { APP_NAME, createEvent, EventPriority, formatSqliteDatetime, getAgentDayBoundsUtc, getAgentDayDateStr, getAgentDayProgressMinutes, getBackendIds, nowInTimezone, parseSqliteUtcMs, } from "@aitne/shared";
|
|
83
|
+
import { getOwnerChannel } from "./messaging/owner-channels.js";
|
|
84
|
+
import { SUPPORTED_MESSAGING_PLATFORMS, } from "./messaging/constants.js";
|
|
85
|
+
import { AgentWriteTracker } from "./safety/agent-write-tracker.js";
|
|
86
|
+
import { discardStalePendingSchedules, hasActionInWindow, recoverOrphanedRunningSchedules, } from "./core/schedule-maintenance.js";
|
|
87
|
+
import { ContextWriteGate, InMemoryTodayWriteLockManager, MigrationLock, getTodayWriteLockTimeoutMs, } from "./core/today-write-lock.js";
|
|
88
|
+
import { applyPromptContextStaleness, } from "./core/context-staleness.js";
|
|
89
|
+
import { InMemoryRoadmapWriteLockManager, getRoadmapWriteLockTimeoutMs, } from "./core/roadmap-write-lock.js";
|
|
90
|
+
import { sweepExpiredMigrationBackups } from "./api/routes/setup-migrate.js";
|
|
91
|
+
import { createSettingsStore } from "./settings/settings-store.js";
|
|
92
|
+
import { PlatformSecretStore } from "./secrets/platform-secret-store.js";
|
|
93
|
+
import { FileEncryptedBlobStore } from "./secrets/encrypted-blob-store.js";
|
|
94
|
+
import { AttachmentStore } from "./services/attachments/store.js";
|
|
95
|
+
import { VoiceTranscriber } from "./services/voice/transcriber.js";
|
|
96
|
+
import { DelegatedBackendInvoker, runDelegatedTaskOrphanJanitor, runProxyTempdirJanitor, } from "./services/delegated-backend-invoker.js";
|
|
97
|
+
import { runSessionPoolTempdirJanitor } from "./services/delegated-task-session-pool.js";
|
|
98
|
+
import { MailAccountRegistry } from "./services/mail/account-registry.js";
|
|
99
|
+
import { loadOutlookClientConfig, OutlookClientConfigMissingError, } from "./services/mail/outlook/client-config.js";
|
|
100
|
+
import { createRuntimeMsalApp } from "./services/mail/outlook/msal-app-factory.js";
|
|
101
|
+
import { OutlookGraphProvider } from "./services/mail/outlook/outlook-provider.js";
|
|
102
|
+
import { parseImapAccountSecret } from "./services/mail/imap/app-password.js";
|
|
103
|
+
import { ICloudImapProvider } from "./services/mail/imap/icloud-provider.js";
|
|
104
|
+
import { YahooImapProvider } from "./services/mail/imap/yahoo-provider.js";
|
|
105
|
+
import { GmailProvider } from "./services/mail/gmail/gmail-provider.js";
|
|
106
|
+
import { ensureLegacyGmailRow } from "./services/mail/gmail/legacy-row.js";
|
|
107
|
+
import { syncLegacyGmailAccountState } from "./services/mail/gmail/legacy-row.js";
|
|
108
|
+
import { MailPoller } from "./observers/mail-poller.js";
|
|
109
|
+
import { MailReconciliationJob } from "./observers/mail-reconciliation.js";
|
|
110
|
+
import { SecretBroker } from "./secrets/secret-broker.js";
|
|
111
|
+
import { captureOriginalShellEnv, syncBackendApiKeyToEnv, } from "./secrets/backend-api-key-env.js";
|
|
112
|
+
import { createLogger, toSafeErrorMessage } from "./logging.js";
|
|
113
|
+
const logger = createLogger("daemon", {
|
|
114
|
+
transport: {
|
|
115
|
+
target: "pino-pretty",
|
|
116
|
+
options: { colorize: !process.env.PA_DAEMONIZED },
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
const startedAt = new Date();
|
|
120
|
+
async function startup() {
|
|
121
|
+
logger.info({ logLevel: logger.level }, `${APP_NAME} Daemon starting...`);
|
|
122
|
+
// Snapshot the original shell-set values for ANTHROPIC_API_KEY /
|
|
123
|
+
// OPENAI_API_KEY / GEMINI_API_KEY+GOOGLE_API_KEY *before* any keychain
|
|
124
|
+
// mirroring runs. The capture is the source of truth for "fall back to
|
|
125
|
+
// shell env" when the operator clears the keychain entry via the
|
|
126
|
+
// dashboard. See `secrets/backend-api-key-env.ts` for precedence.
|
|
127
|
+
captureOriginalShellEnv();
|
|
128
|
+
// ── 1. Configuration ──
|
|
129
|
+
const config = loadConfig();
|
|
130
|
+
const secretBroker = new SecretBroker(new PlatformSecretStore());
|
|
131
|
+
const releaseAssetBackupRoot = join(config.dataDir, "backups", "release-assets");
|
|
132
|
+
// Tighten .env permissions BEFORE the first read/write so bootstrap
|
|
133
|
+
// values aren't world-readable between env-writer's later chmod calls.
|
|
134
|
+
// Best-effort: log and continue if chmod fails (some filesystems and
|
|
135
|
+
// CI sandboxes don't honor mode bits).
|
|
136
|
+
const { getEnvFilePath: getEnvFilePathEarly, ensureEnvFilePermissions: ensureEnvFilePermissionsEarly, } = await import("./api/env-writer.js");
|
|
137
|
+
try {
|
|
138
|
+
ensureEnvFilePermissionsEarly(getEnvFilePathEarly());
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
logger.warn({ err }, "Could not chmod .env to 0o600 — secrets may be world-readable");
|
|
142
|
+
}
|
|
143
|
+
logger.info({ dataDir: config.dataDir, apiPort: config.apiPort }, "Config loaded");
|
|
144
|
+
// ── 2. Directory structure ──
|
|
145
|
+
initDirectories(config);
|
|
146
|
+
// ── 3. Database ──
|
|
147
|
+
const db = createDatabase(config);
|
|
148
|
+
applySchema(db);
|
|
149
|
+
// §12 ("managed_tasks.id collision after restore from backup") — ensure
|
|
150
|
+
// `managed_task_seq.next_id` is greater than the max existing mt id so a
|
|
151
|
+
// backup-restored DB cannot collide with the seq counter on the next
|
|
152
|
+
// POST. No-op when the table is empty (steady-state cost: one SELECT).
|
|
153
|
+
bootstrapManagedTaskSeq(db);
|
|
154
|
+
// Close any dashboard_chat sessions that were left `active` from before
|
|
155
|
+
// the daemon restart. They cannot be resumed cleanly: the SSE channel is
|
|
156
|
+
// gone, and `Dispatcher.currentSetupMode` is in-process state lost on
|
|
157
|
+
// restart, so a setup conversation half-way through would otherwise be
|
|
158
|
+
// re-adopted by `findActiveDashboardSessionId` and processed as regular
|
|
159
|
+
// chat against stale prompts. The setup wizard's resume path on the
|
|
160
|
+
// client-side checks active status; closing here flips the check so the
|
|
161
|
+
// wizard correctly falls through to a fresh /setup/start.
|
|
162
|
+
try {
|
|
163
|
+
const result = db
|
|
164
|
+
.prepare(`UPDATE conversation_sessions
|
|
165
|
+
SET status = 'closed'
|
|
166
|
+
WHERE scope = 'dashboard_chat' AND status = 'active'`)
|
|
167
|
+
.run();
|
|
168
|
+
if (result.changes > 0) {
|
|
169
|
+
logger.info({ closed: result.changes }, "Closed orphaned dashboard_chat sessions from previous run");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
logger.warn({ err }, "Failed to close orphaned dashboard_chat sessions");
|
|
174
|
+
}
|
|
175
|
+
// Chat attachment store — constructed early so adapter reload functions
|
|
176
|
+
// can reference it in closure without TypeScript "used before declaration" issues.
|
|
177
|
+
const attachmentStore = new AttachmentStore(db, config.dataDir);
|
|
178
|
+
attachmentStore.reapOrphans(24);
|
|
179
|
+
void new PriceFetcher(config.dataDir, db).refresh();
|
|
180
|
+
const settingsStore = createSettingsStore(db);
|
|
181
|
+
const persistedSettings = settingsStore.getAll();
|
|
182
|
+
mergeRuntimeSettingsFromDb(config, persistedSettings);
|
|
183
|
+
try {
|
|
184
|
+
seedGitProjectDocTemplates(config.dataDir, config.workspaceDir);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
logger.warn({ err }, "Failed to seed git project document templates");
|
|
188
|
+
}
|
|
189
|
+
// ── Default-correction for `delegatedTaskModeEnabled` ──
|
|
190
|
+
// The legacy `/integrations/:key/invoke` RPC was retired 2026-05-01
|
|
191
|
+
// and every delegated skill / task flow now goes through `/exec`,
|
|
192
|
+
// which is gated by this flag. Its Phase-1 canary default of `false`
|
|
193
|
+
// is now a footgun: any user with delegated integrations from before
|
|
194
|
+
// the runtime PATCH-time auto-enable landed will hit 503 on every
|
|
195
|
+
// /exec call.
|
|
196
|
+
//
|
|
197
|
+
// Heal once at boot, only when the operator never explicitly chose:
|
|
198
|
+
// - `delegatedTaskModeEnabled` row is *absent* from settings (the
|
|
199
|
+
// `in` check distinguishes "never set" from "explicitly false"),
|
|
200
|
+
// - and at least one integration is currently in `delegated` mode.
|
|
201
|
+
//
|
|
202
|
+
// An explicit `false` row in `settings` is treated as operator intent
|
|
203
|
+
// and respected — emergency-disable still works.
|
|
204
|
+
if (!("delegatedTaskModeEnabled" in persistedSettings)
|
|
205
|
+
&& !config.delegatedTaskModeEnabled) {
|
|
206
|
+
const integrationsAtBoot = readIntegrations(db);
|
|
207
|
+
const delegatedKeys = Object.entries(integrationsAtBoot)
|
|
208
|
+
.filter(([, state]) => state.mode === "delegated")
|
|
209
|
+
.map(([key]) => key);
|
|
210
|
+
if (delegatedKeys.length > 0) {
|
|
211
|
+
try {
|
|
212
|
+
settingsStore.set("delegatedTaskModeEnabled", true);
|
|
213
|
+
config.delegatedTaskModeEnabled = true;
|
|
214
|
+
logger.info({ delegatedKeys }, "auto-enabled delegatedTaskModeEnabled at startup — delegated integrations exist and the flag was never explicitly set");
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
logger.error({ err, delegatedKeys }, "startup auto-enable of delegatedTaskModeEnabled failed; /exec calls may continue to 503 until PATCH /api/config sets the flag");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// ── Integration Delegation Framework (Phase 1) ──
|
|
222
|
+
// Reconcile `<dataDir>/integrations.md` with the DB integrations map.
|
|
223
|
+
// Creates the file on first run, parses hand-edits if present, and
|
|
224
|
+
// re-renders unconditionally so daemon-owned columns are canonical.
|
|
225
|
+
// The watcher is started later (after the observer manager) so fs-watch
|
|
226
|
+
// errors don't block boot.
|
|
227
|
+
let managementMdWatcher = null;
|
|
228
|
+
let managementRegistryWatcher = null;
|
|
229
|
+
try {
|
|
230
|
+
migrateLegacyManagementMd(config.dataDir);
|
|
231
|
+
await bootstrapManagementMd(config.dataDir, db, config.workspaceDir, {
|
|
232
|
+
externalObsidianVaultPath: config.externalObsidianVaultPath,
|
|
233
|
+
externalObsidianWatch: config.externalObsidianWatch,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
logger.error({ err, dataDir: config.dataDir }, "integrations.md bootstrap failed; continuing with DB state");
|
|
238
|
+
}
|
|
239
|
+
// ── Docs corpus indexer (DOCS_QA_DESIGN.md P1) ──
|
|
240
|
+
// Seeds `docs/user/` from `agent-assets/docs/` on first launch, then
|
|
241
|
+
// runs a boot-scan and starts a chokidar watcher with the same 300ms
|
|
242
|
+
// debounce window the management-md watcher uses. A startup failure
|
|
243
|
+
// is non-fatal — the daemon still serves but `/api/docs/health`
|
|
244
|
+
// surfaces `status: "degraded"`.
|
|
245
|
+
let docsIndexer = null;
|
|
246
|
+
try {
|
|
247
|
+
docsIndexer = await startDocsIndexer(db, {
|
|
248
|
+
workspaceDir: config.workspaceDir,
|
|
249
|
+
backupRoot: releaseAssetBackupRoot,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
logger.error({ err }, "docs indexer failed to start");
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
recordInstructionAssetStatus(db, config.workspaceDir);
|
|
257
|
+
const skillStatus = recordSkillAssetStatus(db, config.dataDir, config.workspaceDir);
|
|
258
|
+
if (skillStatus.builtinShadowedUserSkills.length > 0) {
|
|
259
|
+
logger.warn({ slugs: skillStatus.builtinShadowedUserSkills }, "User skills are shadowed by newly-shipped built-in skills");
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
logger.warn({ err }, "release asset status scan failed");
|
|
264
|
+
}
|
|
265
|
+
// ── Management Mode startup validation (plan §5.4) ──
|
|
266
|
+
// Delegated to `runVaultHealthProbe` so startup and the 30-second timer
|
|
267
|
+
// use identical logic. The probe internally handles:
|
|
268
|
+
// - plain mode → clear any stale degraded state
|
|
269
|
+
// - obsidian mode + setup incomplete → clear (bootstrapping bypass so
|
|
270
|
+
// the DM-driven setup flow is not blocked by 503)
|
|
271
|
+
// - obsidian mode + primaryVaultPath null → degraded
|
|
272
|
+
// - obsidian mode + path unreachable → degraded
|
|
273
|
+
// - obsidian mode + path reachable → lift
|
|
274
|
+
const startupProbe = runVaultHealthProbe(config, db);
|
|
275
|
+
if (startupProbe.action === "entered") {
|
|
276
|
+
logger.warn({ reason: startupProbe.reason }, "Management Mode degraded at startup");
|
|
277
|
+
}
|
|
278
|
+
else if (startupProbe.action === "lifted") {
|
|
279
|
+
logger.info("Management Mode degraded state cleared at startup");
|
|
280
|
+
}
|
|
281
|
+
if (process.env.PA_VAULT_STRICT === "1" && readDegradedMode(db)) {
|
|
282
|
+
const state = getDegradedMode(db);
|
|
283
|
+
logger.fatal({ degradedState: state }, "PA_VAULT_STRICT=1 and degraded mode active — exiting");
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
if (isSetupCompleted(db) && !readDegradedMode(db)) {
|
|
287
|
+
const contextDir = getContextDir(config);
|
|
288
|
+
ensureSkeletonFiles(contextDir, config.workspaceDir);
|
|
289
|
+
// ── Management Registry boot reconcile (design 21 §7.2 / P2) ──
|
|
290
|
+
// Mirrors `bootstrapManagementMd` (which owns integrations.md). Reads
|
|
291
|
+
// `<contextDir>/rules/management.md` and reconciles its A-section with
|
|
292
|
+
// `settings.sot_bindings`; renders a fresh v3 file when the on-disk
|
|
293
|
+
// copy is v2 (no schema_version) or forward-incompat (>= v4). Run
|
|
294
|
+
// after `ensureSkeletonFiles` so the seeded template is the parse
|
|
295
|
+
// target on first install.
|
|
296
|
+
try {
|
|
297
|
+
await bootstrapManagementRegistry(contextDir, db);
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
logger.error({ err, contextDir }, "rules/management.md bootstrap failed; continuing with DB state");
|
|
301
|
+
}
|
|
302
|
+
// Release asset reconcile (future-proofing for format changes).
|
|
303
|
+
// Missing templates are added, unedited versioned templates are
|
|
304
|
+
// refreshed from the shipped tree, and edited files are preserved and
|
|
305
|
+
// reported in `runtime_state.templates.pending` for review.
|
|
306
|
+
try {
|
|
307
|
+
const templatesRoot = resolveTemplatesRoot(config.workspaceDir);
|
|
308
|
+
const templateStatus = reconcileTemplateAssets({
|
|
309
|
+
db,
|
|
310
|
+
templatesRoot,
|
|
311
|
+
contextDir,
|
|
312
|
+
backupRoot: releaseAssetBackupRoot,
|
|
313
|
+
});
|
|
314
|
+
if (templateStatus.pending.length > 0) {
|
|
315
|
+
logger.warn({ pending: templateStatus.pending, count: templateStatus.pending.length }, "template upgrades pending — user-edited files were preserved (inspect via /api/health.templatesPending)");
|
|
316
|
+
}
|
|
317
|
+
if (templateStatus.autoUpdated > 0 || templateStatus.added > 0) {
|
|
318
|
+
logger.info({ added: templateStatus.added, autoUpdated: templateStatus.autoUpdated }, "release template assets reconciled");
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
logger.warn({ err }, "template asset reconcile failed — continuing startup; pending snapshot unchanged");
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (config.externalObsidianVaultPath) {
|
|
326
|
+
const externalCheck = validateExternalObsidianVaultPath(config.externalObsidianVaultPath, config);
|
|
327
|
+
if (!externalCheck.ok) {
|
|
328
|
+
logger.warn({
|
|
329
|
+
path: config.externalObsidianVaultPath,
|
|
330
|
+
error: externalCheck.error,
|
|
331
|
+
message: externalCheck.message,
|
|
332
|
+
}, "externalObsidianVaultPath invalid — the Obsidian CLI skill will be degraded");
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Auto-generate API token if it does not exist in the secret store yet.
|
|
336
|
+
if (!(await secretBroker.getApiToken())) {
|
|
337
|
+
await secretBroker.set("apiToken", randomBytes(32).toString("hex"));
|
|
338
|
+
logger.info("Generated PA_API_TOKEN in secret store");
|
|
339
|
+
}
|
|
340
|
+
// Mirror keychain-stored backend API keys into process.env so the
|
|
341
|
+
// existing Claude SDK / Codex CLI / Gemini CLI subprocesses pick them
|
|
342
|
+
// up via the unchanged `process.env.ANTHROPIC_API_KEY` etc. read paths.
|
|
343
|
+
// Keychain values take precedence over shell env (the captured
|
|
344
|
+
// snapshot above acts as the fallback when the operator clears the
|
|
345
|
+
// dashboard entry).
|
|
346
|
+
for (const backendId of getBackendIds()) {
|
|
347
|
+
try {
|
|
348
|
+
const result = await syncBackendApiKeyToEnv(secretBroker, backendId, process.env, { dataDir: config.dataDir });
|
|
349
|
+
if (result.source === "keychain") {
|
|
350
|
+
logger.info({ backendId, source: result.source }, "Mirrored backend API key from keychain into process.env");
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
logger.warn({ err, backendId }, "Failed to read backend API key from keychain — backend will fall back to existing env / CLI auth");
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
logger.info("Database initialized");
|
|
358
|
+
// ── 4. Core components ──
|
|
359
|
+
const eventBus = new EventBus();
|
|
360
|
+
const writeTracker = new AgentWriteTracker();
|
|
361
|
+
// Morning-routine and roadmap write locks are constructed here (earlier
|
|
362
|
+
// than their historical home at §11) so the reconciler observer can
|
|
363
|
+
// consult the morning-routine lock when its cron trigger fires. Both
|
|
364
|
+
// locks are passed into the dispatcher later without re-construction.
|
|
365
|
+
const morningRoutineLock = new InMemoryTodayWriteLockManager(getTodayWriteLockTimeoutMs(config.executeTimeoutMinutes));
|
|
366
|
+
const roadmapWriteLock = new InMemoryRoadmapWriteLockManager(getRoadmapWriteLockTimeoutMs(config.executeTimeoutMinutes));
|
|
367
|
+
// ── 5. Messaging adapters ──
|
|
368
|
+
const messageHub = new MessageHub(config, db);
|
|
369
|
+
/**
|
|
370
|
+
* Persist a freshly-detected owner ID into .env + in-memory config and
|
|
371
|
+
* send a welcome DM. Called by Slack/Telegram/Discord adapters when
|
|
372
|
+
* discovery (or a Telegram pair token) captures the owner from the first
|
|
373
|
+
* inbound DM.
|
|
374
|
+
*
|
|
375
|
+
* Why this lives in index.ts (not the adapter): the adapter must not
|
|
376
|
+
* import env-writer directly — that would couple the adapter layer to the
|
|
377
|
+
* config-persistence layer. The adapter just calls back; index.ts owns
|
|
378
|
+
* the wiring.
|
|
379
|
+
*/
|
|
380
|
+
async function recordDetectedOwner(platform, ownerId) {
|
|
381
|
+
const fieldByPlatform = {
|
|
382
|
+
slack: "slackOwnerUserId",
|
|
383
|
+
telegram: "telegramOwnerChatId",
|
|
384
|
+
discord: "discordOwnerUserId",
|
|
385
|
+
};
|
|
386
|
+
const { applyConfigUpdates } = await import("./api/env-writer.js");
|
|
387
|
+
// No `db` option needed — owner-id pairing never touches Note Sources
|
|
388
|
+
// keys, so the §6.2 side-effect inside applyConfigUpdates is a no-op.
|
|
389
|
+
const result = await applyConfigUpdates(config, settingsStore, {
|
|
390
|
+
[fieldByPlatform[platform]]: ownerId,
|
|
391
|
+
});
|
|
392
|
+
if (Object.keys(result.errors).length > 0) {
|
|
393
|
+
logger.error({ platform, ownerId, errors: result.errors }, "Failed to persist detected owner ID to .env");
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
logger.info({ platform, ownerId }, "auto-paired owner from discovery");
|
|
397
|
+
// Greet the user so they know pairing landed. Route this through the
|
|
398
|
+
// adapter's normal owner-channel resolution path ("user") rather than
|
|
399
|
+
// assuming the captured owner identifier is itself a channel id.
|
|
400
|
+
//
|
|
401
|
+
// Why: Telegram captures a chat id, but Slack/Discord capture a user id
|
|
402
|
+
// that must be resolved to a DM channel before sending.
|
|
403
|
+
try {
|
|
404
|
+
await messageHub.sendToPlatform(platform, "user", `Pairing successful — this channel is now linked as your owner DM.`);
|
|
405
|
+
}
|
|
406
|
+
catch (err) {
|
|
407
|
+
logger.warn({ err, platform }, "Failed to deliver welcome DM after auto-pairing");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
let discordAdapter = null;
|
|
411
|
+
let slackAdapter = null;
|
|
412
|
+
let telegramAdapter = null;
|
|
413
|
+
let whatsappAdapter = null;
|
|
414
|
+
async function reloadDiscordAdapter(startNow) {
|
|
415
|
+
const botToken = await secretBroker.getDiscordBotToken();
|
|
416
|
+
const configured = !!botToken;
|
|
417
|
+
messageHub.setPlatformConfigured("discord", configured);
|
|
418
|
+
if (discordAdapter) {
|
|
419
|
+
try {
|
|
420
|
+
await discordAdapter.stop();
|
|
421
|
+
}
|
|
422
|
+
catch (err) {
|
|
423
|
+
logger.warn({ err }, "Failed to stop Discord adapter during reload");
|
|
424
|
+
}
|
|
425
|
+
messageHub.unregister("discord");
|
|
426
|
+
discordAdapter = null;
|
|
427
|
+
}
|
|
428
|
+
if (!botToken) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
discordAdapter = new DiscordAdapter({
|
|
432
|
+
botToken,
|
|
433
|
+
ownerUserId: config.discordOwnerUserId,
|
|
434
|
+
onMessage: (event) => void eventBus.put(event),
|
|
435
|
+
onOwnerDetected: (userId) => recordDetectedOwner("discord", userId),
|
|
436
|
+
attachmentStore,
|
|
437
|
+
});
|
|
438
|
+
messageHub.register(discordAdapter);
|
|
439
|
+
if (startNow) {
|
|
440
|
+
// Mark as "connecting" while the websocket handshake is in flight so
|
|
441
|
+
// /health doesn't briefly report "ok" before the adapter actually
|
|
442
|
+
// comes up (register() defaults to "ok" to keep the notification
|
|
443
|
+
// pipeline usable in unit tests; this override is the real state).
|
|
444
|
+
messageHub.setPlatformRuntimeStatus("discord", { runtimeState: "connecting", error: null });
|
|
445
|
+
try {
|
|
446
|
+
await discordAdapter.start();
|
|
447
|
+
messageHub.setPlatformRuntimeStatus("discord", { runtimeState: "ok", error: null });
|
|
448
|
+
}
|
|
449
|
+
catch (err) {
|
|
450
|
+
const message = toSafeErrorMessage(err, "Discord adapter failed to start");
|
|
451
|
+
messageHub.setPlatformRuntimeStatus("discord", { runtimeState: "error", error: message });
|
|
452
|
+
logger.error({ err }, "Failed to start Discord adapter during reload");
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
async function reloadSlackAdapter(startNow) {
|
|
457
|
+
const [botToken, appToken] = await Promise.all([
|
|
458
|
+
secretBroker.getSlackBotToken(),
|
|
459
|
+
secretBroker.getSlackAppToken(),
|
|
460
|
+
]);
|
|
461
|
+
const configured = !!(botToken && appToken);
|
|
462
|
+
messageHub.setPlatformConfigured("slack", configured);
|
|
463
|
+
if (slackAdapter) {
|
|
464
|
+
try {
|
|
465
|
+
await slackAdapter.stop();
|
|
466
|
+
}
|
|
467
|
+
catch (err) {
|
|
468
|
+
logger.warn({ err }, "Failed to stop Slack adapter during reload");
|
|
469
|
+
}
|
|
470
|
+
messageHub.unregister("slack");
|
|
471
|
+
slackAdapter = null;
|
|
472
|
+
}
|
|
473
|
+
if (!botToken || !appToken) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
slackAdapter = new SlackAdapter({
|
|
477
|
+
botToken,
|
|
478
|
+
appToken,
|
|
479
|
+
ownerUserId: config.slackOwnerUserId,
|
|
480
|
+
onMessage: (event) => void eventBus.put(event),
|
|
481
|
+
onOwnerDetected: (userId) => recordDetectedOwner("slack", userId),
|
|
482
|
+
attachmentStore,
|
|
483
|
+
});
|
|
484
|
+
messageHub.register(slackAdapter);
|
|
485
|
+
if (startNow) {
|
|
486
|
+
messageHub.setPlatformRuntimeStatus("slack", { runtimeState: "connecting", error: null });
|
|
487
|
+
try {
|
|
488
|
+
await slackAdapter.start();
|
|
489
|
+
messageHub.setPlatformRuntimeStatus("slack", { runtimeState: "ok", error: null });
|
|
490
|
+
}
|
|
491
|
+
catch (err) {
|
|
492
|
+
const message = toSafeErrorMessage(err, "Slack adapter failed to start");
|
|
493
|
+
messageHub.setPlatformRuntimeStatus("slack", { runtimeState: "error", error: message });
|
|
494
|
+
logger.error({ err }, "Failed to start Slack adapter during reload");
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
async function reloadTelegramAdapter(startNow) {
|
|
499
|
+
const botToken = await secretBroker.getTelegramBotToken();
|
|
500
|
+
const configured = !!botToken;
|
|
501
|
+
messageHub.setPlatformConfigured("telegram", configured);
|
|
502
|
+
if (telegramAdapter) {
|
|
503
|
+
try {
|
|
504
|
+
await telegramAdapter.stop();
|
|
505
|
+
}
|
|
506
|
+
catch (err) {
|
|
507
|
+
logger.warn({ err }, "Failed to stop Telegram adapter during reload");
|
|
508
|
+
}
|
|
509
|
+
messageHub.unregister("telegram");
|
|
510
|
+
telegramAdapter = null;
|
|
511
|
+
}
|
|
512
|
+
if (!botToken) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
telegramAdapter = new TelegramAdapter({
|
|
516
|
+
botToken,
|
|
517
|
+
ownerChatId: config.telegramOwnerChatId,
|
|
518
|
+
onMessage: (event) => void eventBus.put(event),
|
|
519
|
+
onOwnerDetected: (chatId) => recordDetectedOwner("telegram", chatId),
|
|
520
|
+
attachmentStore,
|
|
521
|
+
});
|
|
522
|
+
messageHub.register(telegramAdapter);
|
|
523
|
+
if (startNow) {
|
|
524
|
+
messageHub.setPlatformRuntimeStatus("telegram", { runtimeState: "connecting", error: null });
|
|
525
|
+
try {
|
|
526
|
+
await telegramAdapter.start();
|
|
527
|
+
messageHub.setPlatformRuntimeStatus("telegram", { runtimeState: "ok", error: null });
|
|
528
|
+
}
|
|
529
|
+
catch (err) {
|
|
530
|
+
const message = toSafeErrorMessage(err, "Telegram adapter failed to start");
|
|
531
|
+
messageHub.setPlatformRuntimeStatus("telegram", { runtimeState: "error", error: message });
|
|
532
|
+
logger.error({ err }, "Failed to start Telegram adapter during reload");
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
await Promise.all([
|
|
537
|
+
reloadDiscordAdapter(false),
|
|
538
|
+
reloadSlackAdapter(false),
|
|
539
|
+
reloadTelegramAdapter(false),
|
|
540
|
+
]);
|
|
541
|
+
/**
|
|
542
|
+
* Build (or rebuild) the WhatsApp adapter from current config and register
|
|
543
|
+
* it with the MessageHub. Idempotent: returns the existing adapter if config
|
|
544
|
+
* is unchanged. Throws if whatsappOwnerPhone is missing.
|
|
545
|
+
*/
|
|
546
|
+
function buildWhatsAppAdapter() {
|
|
547
|
+
if (!config.whatsappOwnerPhone) {
|
|
548
|
+
throw new Error("Cannot enable WhatsApp: PA_WHATSAPP_OWNER_PHONE is not set");
|
|
549
|
+
}
|
|
550
|
+
const existing = messageHub.getAdapter("whatsapp");
|
|
551
|
+
if (existing && whatsappAdapter && existing === whatsappAdapter) {
|
|
552
|
+
return whatsappAdapter;
|
|
553
|
+
}
|
|
554
|
+
const adapter = new WhatsAppAdapter({
|
|
555
|
+
ownerPhone: config.whatsappOwnerPhone,
|
|
556
|
+
authDir: config.whatsappAuthDir ?? join(config.dataDir, "whatsapp", "auth"),
|
|
557
|
+
onMessage: (event) => void eventBus.put(event),
|
|
558
|
+
attachmentStore,
|
|
559
|
+
onLoggedOut: async () => {
|
|
560
|
+
try {
|
|
561
|
+
await messageHub.sendToUser("WhatsApp session logged out — re-run foreground pairing");
|
|
562
|
+
}
|
|
563
|
+
catch (err) {
|
|
564
|
+
logger.error({ err }, "Failed to deliver WhatsApp logout notification via fallback channel");
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
});
|
|
568
|
+
messageHub.register(adapter);
|
|
569
|
+
whatsappAdapter = adapter;
|
|
570
|
+
return adapter;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Tear down the WhatsApp adapter completely. Used by the dashboard
|
|
574
|
+
* `whatsappEnabled=false` toggle so we don't keep a stale Baileys socket
|
|
575
|
+
* around. Logs but does not throw on socket close errors.
|
|
576
|
+
*/
|
|
577
|
+
async function teardownWhatsAppAdapter() {
|
|
578
|
+
if (!whatsappAdapter)
|
|
579
|
+
return;
|
|
580
|
+
try {
|
|
581
|
+
await whatsappAdapter.stop();
|
|
582
|
+
}
|
|
583
|
+
catch (err) {
|
|
584
|
+
logger.warn({ err }, "Error stopping WhatsApp adapter during teardown");
|
|
585
|
+
}
|
|
586
|
+
messageHub.unregister("whatsapp");
|
|
587
|
+
whatsappAdapter = null;
|
|
588
|
+
}
|
|
589
|
+
if (config.whatsappEnabled) {
|
|
590
|
+
if (!config.whatsappOwnerPhone) {
|
|
591
|
+
throw new Error("PA_WHATSAPP_ENABLED=true but PA_WHATSAPP_OWNER_PHONE is not set");
|
|
592
|
+
}
|
|
593
|
+
buildWhatsAppAdapter();
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Build the per-platform pairing helpers (`messagingControls`) for the
|
|
597
|
+
* dashboard API. Each block returns `undefined` when its adapter wasn't
|
|
598
|
+
* registered, so the dashboard route can branch on existence to render
|
|
599
|
+
* "not configured" messaging without 404'ing the user.
|
|
600
|
+
*
|
|
601
|
+
* Why a builder, not three separate consts: keeps the wiring co-located
|
|
602
|
+
* with the adapter declarations and short-circuits the cases where
|
|
603
|
+
* dynamic imports (e.g. `qrcode` for Telegram QR rendering) would
|
|
604
|
+
* otherwise be loaded for adapters the user never configured.
|
|
605
|
+
*/
|
|
606
|
+
/**
|
|
607
|
+
* Refuse to start a pairing flow on an adapter that isn't actually
|
|
608
|
+
* connected to its upstream service. Without this gate the dashboard
|
|
609
|
+
* would happily display a QR or magic phrase even though the daemon's
|
|
610
|
+
* Slack/Telegram/Discord client failed to come up — the user would scan
|
|
611
|
+
* or type the phrase forever and nothing would arrive.
|
|
612
|
+
*/
|
|
613
|
+
function assertAdapterReady(platform) {
|
|
614
|
+
const status = messageHub.getPlatformRuntimeStatus(platform);
|
|
615
|
+
if (status.runtimeState !== "ok") {
|
|
616
|
+
throw new Error(`${platform} adapter is not connected (${status.error ?? status.runtimeState}). `
|
|
617
|
+
+ `Verify the token, then save and retry.`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
function buildTelegramControls() {
|
|
621
|
+
return {
|
|
622
|
+
testToken: async (candidate) => {
|
|
623
|
+
// Prefer the candidate token from the request body so the
|
|
624
|
+
// dashboard can validate an unsaved draft. Falls back to the
|
|
625
|
+
// currently-saved token if no candidate was supplied.
|
|
626
|
+
const tokenToTest = candidate ?? await secretBroker.getTelegramBotToken();
|
|
627
|
+
if (!tokenToTest) {
|
|
628
|
+
throw new Error("No Telegram bot token provided.");
|
|
629
|
+
}
|
|
630
|
+
const info = await TelegramAdapter.fetchBotInfo(tokenToTest);
|
|
631
|
+
return {
|
|
632
|
+
ok: true,
|
|
633
|
+
id: info.id,
|
|
634
|
+
username: info.username,
|
|
635
|
+
firstName: info.firstName,
|
|
636
|
+
};
|
|
637
|
+
},
|
|
638
|
+
startPairing: async (ttlMs = 5 * 60_000) => {
|
|
639
|
+
assertAdapterReady("telegram");
|
|
640
|
+
const adapter = telegramAdapter;
|
|
641
|
+
if (!adapter) {
|
|
642
|
+
throw new Error("Telegram adapter is not initialized. Save the token and retry.");
|
|
643
|
+
}
|
|
644
|
+
const savedToken = await secretBroker.getTelegramBotToken();
|
|
645
|
+
if (!savedToken) {
|
|
646
|
+
throw new Error("No Telegram bot token provided.");
|
|
647
|
+
}
|
|
648
|
+
// Always re-fetch bot info on pair start. Caching it on adapter
|
|
649
|
+
// start meant a stale username (e.g. user renamed the bot via
|
|
650
|
+
// BotFather) would silently break the deep link.
|
|
651
|
+
const info = await TelegramAdapter.fetchBotInfo(savedToken);
|
|
652
|
+
if (!info.username) {
|
|
653
|
+
throw new Error("Telegram bot has no username — set one via @BotFather (/setname or /newbot) before pairing.");
|
|
654
|
+
}
|
|
655
|
+
// 96 bits of entropy in the pair token. The QR encodes a deep link
|
|
656
|
+
// of the form `https://t.me/<bot>?start=<token>`; when the user
|
|
657
|
+
// taps START in Telegram, the bot receives `/start <token>` and
|
|
658
|
+
// the matcher below promotes them to owner. WITHOUT a separate
|
|
659
|
+
// discovery fallback — that combination was unsafe (any DM during
|
|
660
|
+
// the window would claim the role even without the token).
|
|
661
|
+
const pairToken = randomBytes(12).toString("base64url");
|
|
662
|
+
const expiresAt = Date.now() + ttlMs;
|
|
663
|
+
// Matcher rules:
|
|
664
|
+
// - The `/start` and optional `@<botname>` prefix are matched
|
|
665
|
+
// case-INSENSITIVELY. Telegram bot usernames are case-
|
|
666
|
+
// insensitive in URLs and mentions, and clients normalize
|
|
667
|
+
// differently — `MyBot`, `mybot`, `MYBOT` must all work.
|
|
668
|
+
// - The token itself is matched case-SENSITIVELY (base64url
|
|
669
|
+
// uses both cases) so we keep the full 96-bit search space.
|
|
670
|
+
// - Anything after the token (extra args, trailing whitespace
|
|
671
|
+
// beyond what trim() handles) rejects, so we can't be tricked
|
|
672
|
+
// into matching a longer payload that happens to start with
|
|
673
|
+
// `/start <token>`.
|
|
674
|
+
const escapedUsername = info.username.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
675
|
+
const prefixRe = new RegExp(`^/start(?:@${escapedUsername})?\\s+`, "i");
|
|
676
|
+
adapter.startPairing({
|
|
677
|
+
match: (text) => {
|
|
678
|
+
const trimmed = text.trim();
|
|
679
|
+
const prefixMatch = trimmed.match(prefixRe);
|
|
680
|
+
if (!prefixMatch)
|
|
681
|
+
return false;
|
|
682
|
+
const remainder = trimmed.slice(prefixMatch[0].length);
|
|
683
|
+
return remainder === pairToken;
|
|
684
|
+
},
|
|
685
|
+
expiresAt,
|
|
686
|
+
});
|
|
687
|
+
const deepLink = `https://t.me/${info.username}?start=${pairToken}`;
|
|
688
|
+
const qrcodeMod = await import("qrcode");
|
|
689
|
+
const toDataURL = (qrcodeMod.default ?? qrcodeMod).toDataURL;
|
|
690
|
+
const qrDataUrl = await toDataURL(deepLink, { width: 320, margin: 2 });
|
|
691
|
+
return {
|
|
692
|
+
pairToken,
|
|
693
|
+
deepLink,
|
|
694
|
+
qrDataUrl,
|
|
695
|
+
expiresAt,
|
|
696
|
+
botUsername: info.username,
|
|
697
|
+
};
|
|
698
|
+
},
|
|
699
|
+
getPairingStatus: () => ({
|
|
700
|
+
paired: telegramAdapter?.getOwnerChatId() !== null,
|
|
701
|
+
ownerChatId: telegramAdapter?.getOwnerChatId() ?? null,
|
|
702
|
+
pairingActive: telegramAdapter?.isPairingActive() ?? false,
|
|
703
|
+
}),
|
|
704
|
+
cancelPairing: () => {
|
|
705
|
+
telegramAdapter?.cancelPairing();
|
|
706
|
+
},
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
function buildSlackControls() {
|
|
710
|
+
return {
|
|
711
|
+
testToken: async (candidate) => {
|
|
712
|
+
const tokenToTest = candidate ?? await secretBroker.getSlackBotToken();
|
|
713
|
+
if (!tokenToTest) {
|
|
714
|
+
throw new Error("No Slack bot token provided.");
|
|
715
|
+
}
|
|
716
|
+
const info = await SlackAdapter.fetchBotInfo(tokenToTest);
|
|
717
|
+
return {
|
|
718
|
+
ok: true,
|
|
719
|
+
botUserId: info.botUserId,
|
|
720
|
+
botName: info.botName,
|
|
721
|
+
team: info.team,
|
|
722
|
+
url: info.url,
|
|
723
|
+
};
|
|
724
|
+
},
|
|
725
|
+
startPairing: async (ttlMs = 5 * 60_000) => {
|
|
726
|
+
assertAdapterReady("slack");
|
|
727
|
+
const adapter = slackAdapter;
|
|
728
|
+
if (!adapter) {
|
|
729
|
+
throw new Error("Slack adapter is not initialized. Save the tokens and retry.");
|
|
730
|
+
}
|
|
731
|
+
// Magic-phrase pairing: the dashboard displays the phrase, the
|
|
732
|
+
// user copies/types it into their bot DM, the matcher (closed over
|
|
733
|
+
// the normalized phrase) accepts only DMs containing it. Defeats
|
|
734
|
+
// the previous "first DM wins" race entirely because attackers
|
|
735
|
+
// who can't see the dashboard can't see the phrase.
|
|
736
|
+
const { generateMagicPhrase, buildPhraseMatcher } = await import("./messaging/magic-phrase.js");
|
|
737
|
+
const phrase = generateMagicPhrase();
|
|
738
|
+
const expiresAt = Date.now() + ttlMs;
|
|
739
|
+
adapter.startPairing({
|
|
740
|
+
match: buildPhraseMatcher(phrase),
|
|
741
|
+
expiresAt,
|
|
742
|
+
});
|
|
743
|
+
return { phrase, expiresAt };
|
|
744
|
+
},
|
|
745
|
+
cancelPairing: () => {
|
|
746
|
+
slackAdapter?.cancelPairing();
|
|
747
|
+
},
|
|
748
|
+
getPairingStatus: () => ({
|
|
749
|
+
paired: slackAdapter?.getOwnerUserId() !== null,
|
|
750
|
+
ownerUserId: slackAdapter?.getOwnerUserId() ?? null,
|
|
751
|
+
pairingActive: slackAdapter?.isPairingActive() ?? false,
|
|
752
|
+
}),
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function buildDiscordControls() {
|
|
756
|
+
return {
|
|
757
|
+
testToken: async (candidate) => {
|
|
758
|
+
const tokenToTest = candidate ?? await secretBroker.getDiscordBotToken();
|
|
759
|
+
if (!tokenToTest) {
|
|
760
|
+
throw new Error("No Discord bot token provided.");
|
|
761
|
+
}
|
|
762
|
+
const info = await DiscordAdapter.fetchBotInfo(tokenToTest);
|
|
763
|
+
return {
|
|
764
|
+
ok: true,
|
|
765
|
+
id: info.id,
|
|
766
|
+
username: info.username,
|
|
767
|
+
discriminator: info.discriminator,
|
|
768
|
+
avatarUrl: info.avatarUrl,
|
|
769
|
+
};
|
|
770
|
+
},
|
|
771
|
+
startPairing: async (ttlMs = 5 * 60_000) => {
|
|
772
|
+
assertAdapterReady("discord");
|
|
773
|
+
const adapter = discordAdapter;
|
|
774
|
+
if (!adapter) {
|
|
775
|
+
throw new Error("Discord adapter is not initialized. Save the token and retry.");
|
|
776
|
+
}
|
|
777
|
+
const { generateMagicPhrase, buildPhraseMatcher } = await import("./messaging/magic-phrase.js");
|
|
778
|
+
const phrase = generateMagicPhrase();
|
|
779
|
+
const expiresAt = Date.now() + ttlMs;
|
|
780
|
+
adapter.startPairing({
|
|
781
|
+
match: buildPhraseMatcher(phrase),
|
|
782
|
+
expiresAt,
|
|
783
|
+
});
|
|
784
|
+
return { phrase, expiresAt };
|
|
785
|
+
},
|
|
786
|
+
cancelPairing: () => {
|
|
787
|
+
discordAdapter?.cancelPairing();
|
|
788
|
+
},
|
|
789
|
+
getPairingStatus: () => ({
|
|
790
|
+
paired: discordAdapter?.getOwnerUserId() !== null,
|
|
791
|
+
ownerUserId: discordAdapter?.getOwnerUserId() ?? null,
|
|
792
|
+
pairingActive: discordAdapter?.isPairingActive() ?? false,
|
|
793
|
+
}),
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
function whatsappQrResponseFromAdapter(adapter, snapshotOverride) {
|
|
797
|
+
if (!adapter) {
|
|
798
|
+
return {
|
|
799
|
+
dataUrl: null,
|
|
800
|
+
payload: null,
|
|
801
|
+
generatedAt: null,
|
|
802
|
+
expiresAt: null,
|
|
803
|
+
state: "not_initialized",
|
|
804
|
+
error: "WhatsApp adapter not enabled",
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
const snapshot = snapshotOverride !== undefined
|
|
808
|
+
? snapshotOverride
|
|
809
|
+
: adapter.getQrSnapshot();
|
|
810
|
+
return {
|
|
811
|
+
dataUrl: snapshot?.dataUrl ?? null,
|
|
812
|
+
payload: snapshot?.payload ?? null,
|
|
813
|
+
generatedAt: snapshot?.generatedAt ?? null,
|
|
814
|
+
expiresAt: snapshot?.expiresAt ?? null,
|
|
815
|
+
state: adapter.getStatus(),
|
|
816
|
+
error: adapter.getStatusError(),
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
// Dashboard adapter — always registered (activates on SSE connect)
|
|
820
|
+
const dashboardAdapter = new DashboardAdapter((event) => void eventBus.put(event));
|
|
821
|
+
messageHub.register(dashboardAdapter);
|
|
822
|
+
dashboardAdapter.setAttachmentStore(attachmentStore);
|
|
823
|
+
// ── 6. External services (mutable registry for hot-reload) ──
|
|
824
|
+
const services = createServiceRegistry();
|
|
825
|
+
const blobStore = new FileEncryptedBlobStore(resolve(config.dataDir, "secrets", "blobs"), new PlatformSecretStore());
|
|
826
|
+
// Forward-declared so the registry constructor can wire its scope-change
|
|
827
|
+
// observer before `sessionManager` / `eventBroadcaster` exist. The real
|
|
828
|
+
// implementation is assigned below once those deps are live — until then,
|
|
829
|
+
// `onMailScopeChanged` is a no-op (which is correct: no DM sessions can
|
|
830
|
+
// exist pre-dispatcher).
|
|
831
|
+
let onMailScopeChanged = () => undefined;
|
|
832
|
+
services.mail = new MailAccountRegistry({
|
|
833
|
+
db,
|
|
834
|
+
blobStore: blobStore,
|
|
835
|
+
getEnabledKinds: () => config.enabledMailProviders,
|
|
836
|
+
onScopeChanged: (reason) => onMailScopeChanged(reason),
|
|
837
|
+
providerFactories: {
|
|
838
|
+
// Gmail credentials remain on the shared Google OAuth path, so the
|
|
839
|
+
// row stores a sentinel in `secret_blob_name` and the factory reads
|
|
840
|
+
// the live GmailService from the registry instead of the blob store.
|
|
841
|
+
gmail: (account) => {
|
|
842
|
+
const service = services.gmail;
|
|
843
|
+
if (!service || !service.available) {
|
|
844
|
+
throw new Error(`Gmail service not initialized for account ${account.id}. Complete Google OAuth in the dashboard.`);
|
|
845
|
+
}
|
|
846
|
+
return new GmailProvider({ account, service });
|
|
847
|
+
},
|
|
848
|
+
outlook: async (account, ctx) => {
|
|
849
|
+
const clientConfig = await loadOutlookClientConfig(blobStore);
|
|
850
|
+
if (!clientConfig)
|
|
851
|
+
throw new OutlookClientConfigMissingError();
|
|
852
|
+
const msalApp = createRuntimeMsalApp(clientConfig, account.id, blobStore);
|
|
853
|
+
return new OutlookGraphProvider({
|
|
854
|
+
account,
|
|
855
|
+
msalApp,
|
|
856
|
+
abortSignal: ctx.signal,
|
|
857
|
+
});
|
|
858
|
+
},
|
|
859
|
+
yahoo: async (account) => {
|
|
860
|
+
const raw = await blobStore.readUtf8(`mail:${account.kind}:${account.id}`);
|
|
861
|
+
if (!raw)
|
|
862
|
+
throw new Error(`Missing IMAP secret for ${account.id}`);
|
|
863
|
+
return new YahooImapProvider({
|
|
864
|
+
account,
|
|
865
|
+
secret: parseImapAccountSecret(raw),
|
|
866
|
+
onCapabilitiesProbed: (id, caps) => {
|
|
867
|
+
services.mail?.updateCapabilities(id, caps);
|
|
868
|
+
},
|
|
869
|
+
});
|
|
870
|
+
},
|
|
871
|
+
icloud: async (account) => {
|
|
872
|
+
const raw = await blobStore.readUtf8(`mail:${account.kind}:${account.id}`);
|
|
873
|
+
if (!raw)
|
|
874
|
+
throw new Error(`Missing IMAP secret for ${account.id}`);
|
|
875
|
+
return new ICloudImapProvider({
|
|
876
|
+
account,
|
|
877
|
+
secret: parseImapAccountSecret(raw),
|
|
878
|
+
onCapabilitiesProbed: (id, caps) => {
|
|
879
|
+
services.mail?.updateCapabilities(id, caps);
|
|
880
|
+
},
|
|
881
|
+
});
|
|
882
|
+
},
|
|
883
|
+
},
|
|
884
|
+
});
|
|
885
|
+
const secretState = {
|
|
886
|
+
googleCredentialsConfigured: false,
|
|
887
|
+
googleTokenConfigured: false,
|
|
888
|
+
googleCredentialType: null,
|
|
889
|
+
notionConfigured: false,
|
|
890
|
+
githubConfigured: false,
|
|
891
|
+
githubWebhookConfigured: false,
|
|
892
|
+
};
|
|
893
|
+
async function refreshGoogleSecretState() {
|
|
894
|
+
const [credentialsRaw, tokenRaw] = await Promise.all([
|
|
895
|
+
secretBroker.getGoogleCredentialsJson(),
|
|
896
|
+
secretBroker.getGoogleTokenJson(),
|
|
897
|
+
]);
|
|
898
|
+
secretState.googleCredentialsConfigured = !!credentialsRaw;
|
|
899
|
+
secretState.googleTokenConfigured = !!tokenRaw;
|
|
900
|
+
secretState.googleCredentialType = detectGoogleCredentialType(credentialsRaw);
|
|
901
|
+
}
|
|
902
|
+
async function reloadGoogleServices() {
|
|
903
|
+
await refreshGoogleSecretState();
|
|
904
|
+
services.calendar = null;
|
|
905
|
+
services.gmail = null;
|
|
906
|
+
delete services.errors.googleCalendar;
|
|
907
|
+
delete services.errors.gmail;
|
|
908
|
+
if (!secretState.googleCredentialsConfigured) {
|
|
909
|
+
if (services.mail) {
|
|
910
|
+
syncLegacyGmailAccountState(db, services.mail, {
|
|
911
|
+
available: false,
|
|
912
|
+
error: "Google credentials are not configured.",
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
// OAuth2 pre-auth: credentials uploaded but the user has not completed the
|
|
918
|
+
// browser flow yet (no token in the keychain). Initializing the services
|
|
919
|
+
// would fail with a "missing token" error that the dashboard would then
|
|
920
|
+
// render as red "Error" under the Google card — but this is the expected
|
|
921
|
+
// mid-setup state, not a failure. Skip init and leave services.errors
|
|
922
|
+
// unset so /health reports error: null until the user finishes OAuth or a
|
|
923
|
+
// real init error occurs.
|
|
924
|
+
const oauth2PreAuth = secretState.googleCredentialType === "oauth2"
|
|
925
|
+
&& !secretState.googleTokenConfigured;
|
|
926
|
+
if (oauth2PreAuth) {
|
|
927
|
+
if (services.mail) {
|
|
928
|
+
syncLegacyGmailAccountState(db, services.mail, {
|
|
929
|
+
available: false,
|
|
930
|
+
error: "Awaiting Google OAuth authorization.",
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
const calendarService = new CalendarService(config, secretBroker);
|
|
936
|
+
try {
|
|
937
|
+
await calendarService.init();
|
|
938
|
+
services.calendar = calendarService;
|
|
939
|
+
}
|
|
940
|
+
catch (err) {
|
|
941
|
+
const msg = err.message;
|
|
942
|
+
logger.error({ error: msg }, "Calendar service init failed, continuing without it");
|
|
943
|
+
services.errors.googleCalendar = msg;
|
|
944
|
+
}
|
|
945
|
+
const gmailService = new GmailService(secretBroker);
|
|
946
|
+
try {
|
|
947
|
+
await gmailService.init();
|
|
948
|
+
services.gmail = gmailService;
|
|
949
|
+
}
|
|
950
|
+
catch (err) {
|
|
951
|
+
const msg = err.message;
|
|
952
|
+
logger.error({ error: msg }, "Gmail service init failed, continuing without it");
|
|
953
|
+
services.errors.gmail = msg;
|
|
954
|
+
}
|
|
955
|
+
// Ensure the shared-Google-OAuth Gmail identity exists as a unified
|
|
956
|
+
// mail account (idempotent; returns `exists` on subsequent boots).
|
|
957
|
+
if (services.gmail?.available) {
|
|
958
|
+
try {
|
|
959
|
+
await ensureLegacyGmailRow(db, services.gmail);
|
|
960
|
+
}
|
|
961
|
+
catch (err) {
|
|
962
|
+
logger.error({ err }, "Failed to ensure shared-Google-OAuth Gmail mail_accounts row");
|
|
963
|
+
}
|
|
964
|
+
if (services.mail) {
|
|
965
|
+
syncLegacyGmailAccountState(db, services.mail, { available: true });
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
else if (services.mail) {
|
|
969
|
+
syncLegacyGmailAccountState(db, services.mail, {
|
|
970
|
+
available: false,
|
|
971
|
+
error: services.errors.gmail ?? "Gmail is not configured.",
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
// Google Maps (F-08: commute optimization)
|
|
976
|
+
{
|
|
977
|
+
const { GoogleMapsService } = await import("./services/google-maps.js");
|
|
978
|
+
const mapsService = new GoogleMapsService(secretBroker);
|
|
979
|
+
try {
|
|
980
|
+
await mapsService.init();
|
|
981
|
+
if (mapsService.available) {
|
|
982
|
+
services.googleMaps = mapsService;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
catch (err) {
|
|
986
|
+
const msg = err.message;
|
|
987
|
+
logger.error({ error: msg }, "Google Maps service init failed, continuing without it");
|
|
988
|
+
services.errors.googleMaps = msg;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
// Obsidian
|
|
992
|
+
if (config.externalObsidianVaultName) {
|
|
993
|
+
const obsidianService = new ObsidianService(config);
|
|
994
|
+
if (obsidianService.available) {
|
|
995
|
+
logger.info({ vaultName: config.externalObsidianVaultName }, "Probing Obsidian CLI...");
|
|
996
|
+
const running = await obsidianService.isRunning();
|
|
997
|
+
if (running) {
|
|
998
|
+
services.obsidian = obsidianService;
|
|
999
|
+
logger.info({ vaultName: config.externalObsidianVaultName }, "Obsidian service available");
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
services.errors.obsidian = "Obsidian CLI not accessible — is Obsidian running?";
|
|
1003
|
+
logger.warn("Obsidian CLI not accessible — is Obsidian running?");
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
else if (config.externalObsidianVaultPath && !config.externalObsidianVaultName) {
|
|
1008
|
+
services.errors.obsidian = "externalObsidianVaultName is required for the Obsidian CLI service (externalObsidianVaultPath alone enables file watching only)";
|
|
1009
|
+
}
|
|
1010
|
+
async function reloadAppleCalendarService() {
|
|
1011
|
+
const raw = await secretBroker.getAppleCalendarCredentialsJson();
|
|
1012
|
+
services.appleCalendar = null;
|
|
1013
|
+
delete services.errors.appleCalendar;
|
|
1014
|
+
if (!raw) {
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
const service = new AppleCalendarService(secretBroker);
|
|
1018
|
+
try {
|
|
1019
|
+
await service.init();
|
|
1020
|
+
if (service.available) {
|
|
1021
|
+
services.appleCalendar = service;
|
|
1022
|
+
}
|
|
1023
|
+
else {
|
|
1024
|
+
// Surface the underlying iCloud error verbatim — the dashboard
|
|
1025
|
+
// shows it on the Connections card so the user can act
|
|
1026
|
+
// (`401 Unauthorized` → regenerate password; network error →
|
|
1027
|
+
// retry; etc.). Falls back to a generic placeholder only if
|
|
1028
|
+
// init() failed without recording a message.
|
|
1029
|
+
services.errors.appleCalendar =
|
|
1030
|
+
service.initError
|
|
1031
|
+
?? "Apple Calendar credentials present but iCloud discovery did not return a usable calendar — verify the app-specific password.";
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
catch (err) {
|
|
1035
|
+
const msg = err.message;
|
|
1036
|
+
logger.error({ error: msg }, "Apple Calendar service init failed, continuing without it");
|
|
1037
|
+
services.errors.appleCalendar = msg;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
async function reloadNotionService() {
|
|
1041
|
+
const apiKey = await secretBroker.getNotionApiKey();
|
|
1042
|
+
secretState.notionConfigured = !!apiKey;
|
|
1043
|
+
services.notion = null;
|
|
1044
|
+
delete services.errors.notion;
|
|
1045
|
+
if (!apiKey) {
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
const notionService = new NotionService(config, secretBroker);
|
|
1049
|
+
try {
|
|
1050
|
+
await notionService.init();
|
|
1051
|
+
services.notion = notionService;
|
|
1052
|
+
}
|
|
1053
|
+
catch (err) {
|
|
1054
|
+
const msg = err.message;
|
|
1055
|
+
logger.error({ error: msg }, "Notion service init failed, continuing without it");
|
|
1056
|
+
services.errors.notion = msg;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
async function reloadGitHubService() {
|
|
1060
|
+
const [token, webhookSecret] = await Promise.all([
|
|
1061
|
+
secretBroker.getGitHubToken(),
|
|
1062
|
+
secretBroker.getGitHubWebhookSecret(),
|
|
1063
|
+
]);
|
|
1064
|
+
secretState.githubConfigured = !!token;
|
|
1065
|
+
secretState.githubWebhookConfigured = !!webhookSecret;
|
|
1066
|
+
services.github = null;
|
|
1067
|
+
delete services.errors.github;
|
|
1068
|
+
if (!token) {
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
const githubService = new GitHubService(token, webhookSecret);
|
|
1072
|
+
try {
|
|
1073
|
+
await githubService.init();
|
|
1074
|
+
services.github = githubService;
|
|
1075
|
+
}
|
|
1076
|
+
catch (err) {
|
|
1077
|
+
const msg = err.message;
|
|
1078
|
+
logger.error({ error: msg }, "GitHub service init failed, continuing without it");
|
|
1079
|
+
services.errors.github = msg;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
await Promise.all([
|
|
1083
|
+
reloadGoogleServices(),
|
|
1084
|
+
reloadAppleCalendarService(),
|
|
1085
|
+
reloadNotionService(),
|
|
1086
|
+
reloadGitHubService(),
|
|
1087
|
+
]);
|
|
1088
|
+
/** Build integration status snapshot for /api/health */
|
|
1089
|
+
const getIntegrationStatus = () => {
|
|
1090
|
+
const whatsappState = config.whatsappEnabled
|
|
1091
|
+
? (whatsappAdapter?.getStatus() ?? "disabled")
|
|
1092
|
+
: "not_configured";
|
|
1093
|
+
return {
|
|
1094
|
+
google: {
|
|
1095
|
+
configured: secretState.googleCredentialsConfigured,
|
|
1096
|
+
connected: services.calendar !== null || services.gmail !== null,
|
|
1097
|
+
error: services.errors.googleCalendar
|
|
1098
|
+
? toSafeErrorMessage(services.errors.googleCalendar)
|
|
1099
|
+
: null,
|
|
1100
|
+
services: {
|
|
1101
|
+
calendar: {
|
|
1102
|
+
connected: services.calendar !== null,
|
|
1103
|
+
error: services.errors.googleCalendar
|
|
1104
|
+
? toSafeErrorMessage(services.errors.googleCalendar)
|
|
1105
|
+
: null,
|
|
1106
|
+
},
|
|
1107
|
+
gmail: {
|
|
1108
|
+
connected: services.gmail !== null,
|
|
1109
|
+
error: services.errors.gmail
|
|
1110
|
+
? toSafeErrorMessage(services.errors.gmail)
|
|
1111
|
+
: null,
|
|
1112
|
+
},
|
|
1113
|
+
},
|
|
1114
|
+
},
|
|
1115
|
+
appleCalendar: {
|
|
1116
|
+
// `configured` reflects whether credentials are stored in the
|
|
1117
|
+
// keychain; `connected` reflects whether last principal-discovery
|
|
1118
|
+
// succeeded. The Overview's "Apple selected but not connected"
|
|
1119
|
+
// banner reads `appleCalendar.configured && !appleCalendar.connected`.
|
|
1120
|
+
configured: services.appleCalendar !== null || !!services.errors.appleCalendar,
|
|
1121
|
+
connected: services.appleCalendar?.available ?? false,
|
|
1122
|
+
error: services.errors.appleCalendar
|
|
1123
|
+
? toSafeErrorMessage(services.errors.appleCalendar)
|
|
1124
|
+
: null,
|
|
1125
|
+
},
|
|
1126
|
+
obsidian: {
|
|
1127
|
+
configured: !!(config.externalObsidianVaultPath || config.externalObsidianVaultName),
|
|
1128
|
+
connected: services.obsidian !== null,
|
|
1129
|
+
error: services.errors.obsidian
|
|
1130
|
+
? toSafeErrorMessage(services.errors.obsidian)
|
|
1131
|
+
: null,
|
|
1132
|
+
},
|
|
1133
|
+
notion: {
|
|
1134
|
+
configured: secretState.notionConfigured,
|
|
1135
|
+
connected: services.notion !== null,
|
|
1136
|
+
error: services.errors.notion
|
|
1137
|
+
? toSafeErrorMessage(services.errors.notion)
|
|
1138
|
+
: null,
|
|
1139
|
+
},
|
|
1140
|
+
whatsapp: {
|
|
1141
|
+
configured: config.whatsappEnabled,
|
|
1142
|
+
connected: whatsappState === "ok",
|
|
1143
|
+
error: whatsappState === "disconnected"
|
|
1144
|
+
? "WhatsApp disconnected"
|
|
1145
|
+
: whatsappState === "logged_out"
|
|
1146
|
+
? "WhatsApp logged out"
|
|
1147
|
+
: null,
|
|
1148
|
+
state: whatsappState,
|
|
1149
|
+
},
|
|
1150
|
+
googleMaps: {
|
|
1151
|
+
configured: services.googleMaps !== null,
|
|
1152
|
+
connected: services.googleMaps !== null,
|
|
1153
|
+
error: services.errors.googleMaps
|
|
1154
|
+
? toSafeErrorMessage(services.errors.googleMaps)
|
|
1155
|
+
: null,
|
|
1156
|
+
},
|
|
1157
|
+
};
|
|
1158
|
+
};
|
|
1159
|
+
const getMessagingStatus = () => {
|
|
1160
|
+
return Object.fromEntries(SUPPORTED_MESSAGING_PLATFORMS.map((platform) => {
|
|
1161
|
+
const ownerChannel = getOwnerChannel(db, platform);
|
|
1162
|
+
const configured = messageHub.isPlatformConfigured(platform);
|
|
1163
|
+
const ownerConfigured = messageHub.isOwnerConfigured(platform);
|
|
1164
|
+
const { runtimeState, error } = messageHub.getPlatformRuntimeStatus(platform);
|
|
1165
|
+
const ownerChannelKnown = platform === "dashboard"
|
|
1166
|
+
? dashboardAdapter.getActiveChannels().length > 0 || !!ownerChannel
|
|
1167
|
+
: platform === "telegram"
|
|
1168
|
+
? !!config.telegramOwnerChatId
|
|
1169
|
+
: platform === "whatsapp"
|
|
1170
|
+
? !!config.whatsappOwnerPhone
|
|
1171
|
+
: !!ownerChannel;
|
|
1172
|
+
const notificationEligible = messageHub.isPlatformNotificationEligible(platform);
|
|
1173
|
+
return [
|
|
1174
|
+
platform,
|
|
1175
|
+
{
|
|
1176
|
+
configured,
|
|
1177
|
+
runtimeState,
|
|
1178
|
+
ownerConfigured,
|
|
1179
|
+
ownerChannelKnown,
|
|
1180
|
+
notificationEligible,
|
|
1181
|
+
lastInboundAt: ownerChannel?.last_inbound_at ?? null,
|
|
1182
|
+
error,
|
|
1183
|
+
},
|
|
1184
|
+
];
|
|
1185
|
+
}));
|
|
1186
|
+
};
|
|
1187
|
+
// ── 7. Observers ──
|
|
1188
|
+
const observerManager = new ObserverManager();
|
|
1189
|
+
// Mutable indirection: observers are constructed in this block, but the
|
|
1190
|
+
// dispatcher (owner of `emitRoadmapRefresh`) is created later in §7.3.
|
|
1191
|
+
// Pollers store this callback and only invoke it from their poll loops,
|
|
1192
|
+
// which run after `observerManager.startAll()` — by then the dispatcher
|
|
1193
|
+
// has been wired via the assignment in §7.3 and the indirection resolves.
|
|
1194
|
+
// ROADMAP-REDESIGN §3.4 RFC-C.
|
|
1195
|
+
let emitRoadmapRefreshSink = null;
|
|
1196
|
+
const triggerRoadmapRefresh = (source) => {
|
|
1197
|
+
emitRoadmapRefreshSink?.(source);
|
|
1198
|
+
};
|
|
1199
|
+
const getNormalizedGitRepos = () => normalizeGitWatchedRepos({ gitWatchedRepos: selectGitWatchedRepos(db) });
|
|
1200
|
+
// P5 multi-account — registry resolves `gitAccounts[<alias>]` to a
|
|
1201
|
+
// `{GH_TOKEN, GIT_ASKPASS, ...}` env overlay each observer applies
|
|
1202
|
+
// per-call. The `getAccounts` thunk reads `config.gitAccounts` lazily
|
|
1203
|
+
// so a `PATCH /api/config` that mutates `config` in place via
|
|
1204
|
+
// `applyConfigUpdates`'s `Object.assign(config, runtimeUpdates)`
|
|
1205
|
+
// flows through to the next resolver call without re-instantiation
|
|
1206
|
+
// — which is why `gitAccounts` is NOT in `RESTART_REQUIRED_KEY_TUPLE`.
|
|
1207
|
+
// Per-repo PAT secrets live in the OS keychain at
|
|
1208
|
+
// `git.account.<alias>` (see secret-names.ts ScopedSecretName).
|
|
1209
|
+
const gitAccountRegistry = new GitAccountRegistry({
|
|
1210
|
+
dataDir: config.dataDir,
|
|
1211
|
+
secretBroker,
|
|
1212
|
+
getAccounts: () => config.gitAccounts,
|
|
1213
|
+
});
|
|
1214
|
+
// One-time info note: github-side rows without a local clone always
|
|
1215
|
+
// poll under the default `gh` profile when the row's `github_account`
|
|
1216
|
+
// is null. Surface this if both `gitAccounts` and github-only rows
|
|
1217
|
+
// exist so a user staring at unaccounted-for traffic from "the wrong
|
|
1218
|
+
// account" sees it in `aitne logs` rather than guessing.
|
|
1219
|
+
const githubOnlyRows = selectGithubRepoSlugs(db).length;
|
|
1220
|
+
if (Object.keys(config.gitAccounts ?? {}).length > 0
|
|
1221
|
+
&& githubOnlyRows > 0) {
|
|
1222
|
+
logger.info({
|
|
1223
|
+
gitAccounts: Object.keys(config.gitAccounts).length,
|
|
1224
|
+
githubRepos: githubOnlyRows,
|
|
1225
|
+
}, "GitHub-side repository rows fall back to the default gh profile when github_account is unset. Set the row's account alias from /api/repositories or the dashboard.");
|
|
1226
|
+
}
|
|
1227
|
+
const lookupRepoAlias = (repoPath, fullName) => {
|
|
1228
|
+
const repos = getNormalizedGitRepos();
|
|
1229
|
+
if (repoPath) {
|
|
1230
|
+
const byPath = repos.find((r) => r.path === repoPath);
|
|
1231
|
+
if (byPath?.accountAlias)
|
|
1232
|
+
return byPath.accountAlias;
|
|
1233
|
+
}
|
|
1234
|
+
if (fullName) {
|
|
1235
|
+
const byOrgRepo = repos.find((r) => {
|
|
1236
|
+
if (!r.org)
|
|
1237
|
+
return false;
|
|
1238
|
+
// Repos resolved from `githubRepos` (not local paths) match by full name.
|
|
1239
|
+
return fullName.toLowerCase() === `${r.org}/${r.slug}`.toLowerCase();
|
|
1240
|
+
});
|
|
1241
|
+
if (byOrgRepo?.accountAlias)
|
|
1242
|
+
return byOrgRepo.accountAlias;
|
|
1243
|
+
}
|
|
1244
|
+
return undefined;
|
|
1245
|
+
};
|
|
1246
|
+
const queueGitProjectInitsForCurrentConfig = (source) => {
|
|
1247
|
+
if (!isSetupCompleted(db) || readDegradedMode(db))
|
|
1248
|
+
return;
|
|
1249
|
+
const repos = getNormalizedGitRepos();
|
|
1250
|
+
if (repos.length === 0)
|
|
1251
|
+
return;
|
|
1252
|
+
try {
|
|
1253
|
+
const inserted = queueMissingGitProjectInits({
|
|
1254
|
+
db,
|
|
1255
|
+
contextDir: getContextDir(config, db),
|
|
1256
|
+
dataDir: config.dataDir,
|
|
1257
|
+
workspaceDir: config.workspaceDir,
|
|
1258
|
+
repos,
|
|
1259
|
+
});
|
|
1260
|
+
if (inserted > 0) {
|
|
1261
|
+
logger.info({ inserted, source }, "Queued missing git project documentation init tasks");
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
catch (err) {
|
|
1265
|
+
logger.warn({ err, source }, "Failed to queue git project documentation init tasks");
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
const gitWatchedRepos = getNormalizedGitRepos();
|
|
1269
|
+
const gitRepoPaths = gitWatchedRepos.map((repo) => repo.path);
|
|
1270
|
+
let gitWatcher = null;
|
|
1271
|
+
const buildGitWatcher = () => {
|
|
1272
|
+
if (gitRepoPaths.length === 0)
|
|
1273
|
+
return null;
|
|
1274
|
+
// Per-row poll cadence (unified-repositories §5). Sourced from each
|
|
1275
|
+
// row's `poll_interval_sec` column; rows with null fall through to
|
|
1276
|
+
// the global `gitPollIntervalSeconds`.
|
|
1277
|
+
const repoIntervals = new Map(gitWatchedRepos.map((row) => [row.path, row.pollIntervalSec ?? null]));
|
|
1278
|
+
const watcher = new GitWatcher(gitRepoPaths, db, config.gitPollIntervalSeconds, {
|
|
1279
|
+
eventBus,
|
|
1280
|
+
pushOverdueMinutes: config.gitPushOverdueMinutes,
|
|
1281
|
+
repoIntervals,
|
|
1282
|
+
repoEnvResolver: async (repoPath) => {
|
|
1283
|
+
const alias = lookupRepoAlias(repoPath);
|
|
1284
|
+
if (!alias)
|
|
1285
|
+
return undefined;
|
|
1286
|
+
return (await gitAccountRegistry.buildSpawnEnv(alias)) ?? undefined;
|
|
1287
|
+
},
|
|
1288
|
+
onRepoBaseline: (repoPath) => {
|
|
1289
|
+
const repo = getNormalizedGitRepos().find((item) => item.path === repoPath);
|
|
1290
|
+
if (!repo)
|
|
1291
|
+
return;
|
|
1292
|
+
queueGitProjectInitsForCurrentConfig("git-baseline");
|
|
1293
|
+
},
|
|
1294
|
+
onLifecycleObservation: (classification) => {
|
|
1295
|
+
if (!isSetupCompleted(db) || readDegradedMode(db))
|
|
1296
|
+
return;
|
|
1297
|
+
const repoPath = typeof classification.payload.repoPath === "string"
|
|
1298
|
+
? classification.payload.repoPath
|
|
1299
|
+
: classification.source.replace(/^git:/, "");
|
|
1300
|
+
const repo = getNormalizedGitRepos().find((item) => item.path === repoPath);
|
|
1301
|
+
if (!repo)
|
|
1302
|
+
return;
|
|
1303
|
+
const result = queueGitProjectUpdate({
|
|
1304
|
+
db,
|
|
1305
|
+
dataDir: config.dataDir,
|
|
1306
|
+
workspaceDir: config.workspaceDir,
|
|
1307
|
+
repo,
|
|
1308
|
+
event: classification,
|
|
1309
|
+
debounceMinutes: config.gitProjectUpdateDebounceMinutes,
|
|
1310
|
+
});
|
|
1311
|
+
if (result === "queued" || result === "merged") {
|
|
1312
|
+
logger.info({
|
|
1313
|
+
repo: repo.path,
|
|
1314
|
+
eventType: classification.eventType,
|
|
1315
|
+
result,
|
|
1316
|
+
}, "Queued git project documentation update");
|
|
1317
|
+
}
|
|
1318
|
+
// Unified-repositories §4.4 — fire any per-repo triggers
|
|
1319
|
+
// configured for this lifecycle event. Triggers ride alongside
|
|
1320
|
+
// the task-flow pipeline above; they do not consume the event.
|
|
1321
|
+
const repositoryRow = getRepositoryByLocalPath(db, repoPath);
|
|
1322
|
+
if (repositoryRow) {
|
|
1323
|
+
void dispatchMatchingTriggers({ db, eventBus }, repositoryRow.id, classification.eventType, classification.payload);
|
|
1324
|
+
}
|
|
1325
|
+
},
|
|
1326
|
+
});
|
|
1327
|
+
gitWatcher = watcher;
|
|
1328
|
+
return watcher;
|
|
1329
|
+
};
|
|
1330
|
+
if (shouldStartObserversFor(db, "git")) {
|
|
1331
|
+
const watcher = buildGitWatcher();
|
|
1332
|
+
if (watcher)
|
|
1333
|
+
observerManager.register(watcher);
|
|
1334
|
+
}
|
|
1335
|
+
// GitHubPoller — daemon-side notification + workflow_run polling via the
|
|
1336
|
+
// user's `gh auth login` keychain entry. Replaces the webhook receiver
|
|
1337
|
+
// for local-first installs where exposing a public URL is impractical.
|
|
1338
|
+
// Registered only while github.mode === "direct" now that GitHub is a
|
|
1339
|
+
// first-class integration. Direct remains the default, preserving the
|
|
1340
|
+
// historical "notifications on when gh is authenticated" behaviour.
|
|
1341
|
+
const buildGithubPoller = () => {
|
|
1342
|
+
// Per-row poll cadence (unified-repositories §5). The map is keyed by
|
|
1343
|
+
// `owner/repo` to match `RepoBinding.fullName`; rows are sourced from
|
|
1344
|
+
// the unified table. Local-only rows have no `owner/repo` and fall
|
|
1345
|
+
// out of the map naturally.
|
|
1346
|
+
const repoIntervals = new Map();
|
|
1347
|
+
for (const row of listRepositories(db, { hasGithub: true })) {
|
|
1348
|
+
if (row.githubOwner && row.githubRepo) {
|
|
1349
|
+
repoIntervals.set(`${row.githubOwner}/${row.githubRepo}`, row.pollIntervalSec ?? null);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
return new GitHubPoller({
|
|
1353
|
+
db,
|
|
1354
|
+
eventBus,
|
|
1355
|
+
repoPaths: gitRepoPaths,
|
|
1356
|
+
repoFullNames: selectGithubRepoSlugs(db),
|
|
1357
|
+
pollIntervalSeconds: config.githubPollIntervalSeconds,
|
|
1358
|
+
repoIntervals,
|
|
1359
|
+
repoAccountAliasResolver: ({ localPath, fullName }) => lookupRepoAlias(localPath, fullName),
|
|
1360
|
+
accountResolver: async (binding) => {
|
|
1361
|
+
if (!binding.accountAlias)
|
|
1362
|
+
return undefined;
|
|
1363
|
+
return ((await gitAccountRegistry.buildSpawnEnv(binding.accountAlias)) ?? undefined);
|
|
1364
|
+
},
|
|
1365
|
+
// Unified-repositories §4.4 — resolve the binding's owner/repo to a
|
|
1366
|
+
// repositories.id and fire any per-repo triggers configured for
|
|
1367
|
+
// this event type. Failures are logged inside dispatch and do not
|
|
1368
|
+
// bubble out so a misconfigured trigger cannot stall the poll loop.
|
|
1369
|
+
onTriggerableEvent: async (event) => {
|
|
1370
|
+
if (!isSetupCompleted(db) || readDegradedMode(db))
|
|
1371
|
+
return;
|
|
1372
|
+
let repoRow = null;
|
|
1373
|
+
if (event.binding) {
|
|
1374
|
+
repoRow = getRepositoryByGithub(db, event.binding.owner, event.binding.repo);
|
|
1375
|
+
}
|
|
1376
|
+
if (!repoRow) {
|
|
1377
|
+
// Notifications without a `directNotificationRepos` match still
|
|
1378
|
+
// arrive here when a `repository.full_name` is in the payload.
|
|
1379
|
+
const fullName = typeof event.payload.repository === "string"
|
|
1380
|
+
? event.payload.repository
|
|
1381
|
+
: null;
|
|
1382
|
+
if (fullName) {
|
|
1383
|
+
const [owner, repo] = fullName.split("/");
|
|
1384
|
+
if (owner && repo) {
|
|
1385
|
+
repoRow = getRepositoryByGithub(db, owner, repo);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (!repoRow)
|
|
1390
|
+
return;
|
|
1391
|
+
await dispatchMatchingTriggers({ db, eventBus }, repoRow.id, event.eventType, event.payload);
|
|
1392
|
+
},
|
|
1393
|
+
});
|
|
1394
|
+
};
|
|
1395
|
+
if (shouldStartObserversFor(db, "github")) {
|
|
1396
|
+
observerManager.register(buildGithubPoller());
|
|
1397
|
+
}
|
|
1398
|
+
// Each call returns a fresh observer so the integration-lifecycle helper
|
|
1399
|
+
// re-registers a new instance after a mode flip — picking up any
|
|
1400
|
+
// gitPollIntervalSeconds / gitPushOverdueMinutes / hourlyCheckEnabled
|
|
1401
|
+
// PATCH that landed while the cron was idle.
|
|
1402
|
+
const buildGitDelegatedCronObserver = () => new GitDelegatedCronObserver({
|
|
1403
|
+
db,
|
|
1404
|
+
eventBus,
|
|
1405
|
+
repoPaths: gitRepoPaths,
|
|
1406
|
+
githubRepos: selectGithubRepoSlugs(db),
|
|
1407
|
+
cadenceSeconds: config.gitPollIntervalSeconds,
|
|
1408
|
+
pushOverdueMinutes: config.gitPushOverdueMinutes,
|
|
1409
|
+
hourlyCheckEnabled: config.hourlyCheckEnabled,
|
|
1410
|
+
});
|
|
1411
|
+
if (hasActiveDelegatedGitLifecycleIntegration(db)) {
|
|
1412
|
+
observerManager.register(buildGitDelegatedCronObserver());
|
|
1413
|
+
}
|
|
1414
|
+
// Unified-repositories daily management cron — see
|
|
1415
|
+
// docs/design/appendices/unified-repositories.md §4.5. Iterates rows
|
|
1416
|
+
// whose `repository_management.enabled = 1` and writes the required
|
|
1417
|
+
// journal/overview markdown for each row that's due.
|
|
1418
|
+
observerManager.register(new RepositoryManagementCron({
|
|
1419
|
+
db,
|
|
1420
|
+
eventBus,
|
|
1421
|
+
contextDir: () => getContextDir(config, db),
|
|
1422
|
+
timezone: config.timezone || undefined,
|
|
1423
|
+
writeTracker,
|
|
1424
|
+
}));
|
|
1425
|
+
// Coexistence note: the legacy /webhook/github handler also calls
|
|
1426
|
+
// recordObservation + EventBus.put, so a user with both webhooks AND
|
|
1427
|
+
// the poller live will receive two events per `review_requested`. This
|
|
1428
|
+
// is a low-probability scenario (webhooks need a public URL most users
|
|
1429
|
+
// don't expose), so we log a one-time warning rather than disabling
|
|
1430
|
+
// either path automatically.
|
|
1431
|
+
if (secretState.githubWebhookConfigured && shouldStartObserversFor(db, "github")) {
|
|
1432
|
+
logger.warn("GitHub webhook secret configured AND GitHubPoller running — "
|
|
1433
|
+
+ "duplicate events possible. Remove the webhook secret or unregister "
|
|
1434
|
+
+ "the GitHub webhook on github.com to silence.");
|
|
1435
|
+
}
|
|
1436
|
+
// SETUP-FLOW-REDESIGN-PLAN §6.3 — `externalObsidianWatch` is a kill
|
|
1437
|
+
// switch for the external-vault branch of the watcher. When false the
|
|
1438
|
+
// path is preserved (the Obsidian CLI skill still sees it via
|
|
1439
|
+
// `config.externalObsidianVaultPath`) but no chokidar instance is
|
|
1440
|
+
// registered. Sized for power users with very large vaults that would
|
|
1441
|
+
// otherwise produce noisy observation churn (§13 open question).
|
|
1442
|
+
if (config.externalObsidianVaultPath && config.externalObsidianWatch) {
|
|
1443
|
+
observerManager.register(new ObsidianWatcher(config.externalObsidianVaultPath, db, config.obsidianDebounceSeconds, writeTracker, { source: "obsidian:external", name: "obsidian:external" }));
|
|
1444
|
+
}
|
|
1445
|
+
// Primary-vault watcher — registered unconditionally. Stays dormant
|
|
1446
|
+
// until `setVaultPath` points it at a real directory. The migration
|
|
1447
|
+
// endpoint's `onPrimaryVaultPathChange` callback calls setVaultPath
|
|
1448
|
+
// explicitly after every commit, so plain → obsidian transitions
|
|
1449
|
+
// and obsidian-path changes both re-target without dynamic
|
|
1450
|
+
// register/unregister plumbing.
|
|
1451
|
+
const primaryVaultWatcher = new PrimaryVaultWatcher(db, config.obsidianDebounceSeconds, writeTracker);
|
|
1452
|
+
await primaryVaultWatcher.setVaultPath(config.vaultMode === "obsidian" ? config.primaryVaultPath : null);
|
|
1453
|
+
observerManager.register(primaryVaultWatcher);
|
|
1454
|
+
// Build CalendarPoller from current services. Returns null when
|
|
1455
|
+
// `services.calendar` is unavailable so the integration-lifecycle
|
|
1456
|
+
// module can no-op (for example: integration flips to direct before
|
|
1457
|
+
// OAuth is set up). The §4.5.1 gate `google_calendar.mode === "direct"`
|
|
1458
|
+
// is checked BEFORE invoking the builder.
|
|
1459
|
+
const buildCalendarPoller = () => {
|
|
1460
|
+
if (!services.calendar)
|
|
1461
|
+
return null;
|
|
1462
|
+
return new CalendarPoller(services.calendar, db, config.calendarPollIntervalSeconds, config.googleCalendarId, writeTracker, triggerRoadmapRefresh, morningRoutineLock, config.timezone);
|
|
1463
|
+
};
|
|
1464
|
+
if (services.calendar && shouldStartObserversFor(db, "google_calendar")) {
|
|
1465
|
+
const poller = buildCalendarPoller();
|
|
1466
|
+
if (poller)
|
|
1467
|
+
observerManager.register(poller);
|
|
1468
|
+
}
|
|
1469
|
+
observerManager.register(new ImminentEventScheduler(db, eventBus, config.googleCalendarId));
|
|
1470
|
+
// Build NotionPoller from current services + config. Returns null when
|
|
1471
|
+
// `services.notion` is unavailable or no databases are configured so
|
|
1472
|
+
// the integration-lifecycle module can no-op (e.g. integration flips
|
|
1473
|
+
// to direct before the API key is set up). The §4.5.1 gate
|
|
1474
|
+
// `notion.mode === "direct"` is checked BEFORE invoking the builder.
|
|
1475
|
+
const buildNotionPoller = () => {
|
|
1476
|
+
if (!services.notion)
|
|
1477
|
+
return null;
|
|
1478
|
+
if (Object.keys(config.notionDatabaseIds).length === 0)
|
|
1479
|
+
return null;
|
|
1480
|
+
return new NotionPoller({
|
|
1481
|
+
notionService: services.notion,
|
|
1482
|
+
databaseIds: config.notionDatabaseIds,
|
|
1483
|
+
pollIntervalSeconds: config.notionPollIntervalSeconds,
|
|
1484
|
+
db,
|
|
1485
|
+
writeTracker,
|
|
1486
|
+
});
|
|
1487
|
+
};
|
|
1488
|
+
if (services.notion
|
|
1489
|
+
&& Object.keys(config.notionDatabaseIds).length > 0
|
|
1490
|
+
&& shouldStartObserversFor(db, "notion")) {
|
|
1491
|
+
const poller = buildNotionPoller();
|
|
1492
|
+
if (poller)
|
|
1493
|
+
observerManager.register(poller);
|
|
1494
|
+
}
|
|
1495
|
+
if (services.mail) {
|
|
1496
|
+
observerManager.register(new MailPoller({
|
|
1497
|
+
registry: services.mail,
|
|
1498
|
+
db,
|
|
1499
|
+
writeTracker,
|
|
1500
|
+
pollIntervalSeconds: config.mailPollIntervalSeconds,
|
|
1501
|
+
maxMessagesPerPoll: config.mailMaxMessagesPerPoll,
|
|
1502
|
+
authFailureRetryHours: config.mailAuthFailureRetryHours,
|
|
1503
|
+
providerPollIntervalsSeconds: {
|
|
1504
|
+
gmail: config.gmailPollIntervalSeconds,
|
|
1505
|
+
},
|
|
1506
|
+
notifyOwner: async (message) => {
|
|
1507
|
+
await messageHub.sendToUser(message);
|
|
1508
|
+
},
|
|
1509
|
+
triggerRoadmapRefresh,
|
|
1510
|
+
}));
|
|
1511
|
+
observerManager.register(new MailReconciliationJob({
|
|
1512
|
+
registry: services.mail,
|
|
1513
|
+
db,
|
|
1514
|
+
}));
|
|
1515
|
+
}
|
|
1516
|
+
// ── 7.04 Skill-curation observers (P22 — appendix p22-skill-self-optimization.md) ──
|
|
1517
|
+
// Hourly walker accumulates `structure_diff` signals for the curation
|
|
1518
|
+
// cohort. Outcomes/feedback collection was dropped from the Preview scope
|
|
1519
|
+
// — the feature optimizes silently in the background, no metrics surface.
|
|
1520
|
+
{
|
|
1521
|
+
const { SkillCurationWalker } = await import("./observers/skill-curation-walker.js");
|
|
1522
|
+
observerManager.register(new SkillCurationWalker(db, getContextDir(config), join(config.workspaceDir, "agent-assets", "skills"), config.dataDir));
|
|
1523
|
+
// §5.4 — boot-time orphan-overlay scan. Emits one log line per orphan
|
|
1524
|
+
// and seeds `runtime_state.skill_curation.orphan_overlays` so the
|
|
1525
|
+
// dashboard banner can read it without re-walking the FS.
|
|
1526
|
+
try {
|
|
1527
|
+
const { scanAndRecordOrphanOverlays } = await import("./core/skill-curation/orphan-overlay.js");
|
|
1528
|
+
scanAndRecordOrphanOverlays(db, config.dataDir, join(config.workspaceDir, "agent-assets", "skills"));
|
|
1529
|
+
}
|
|
1530
|
+
catch (err) {
|
|
1531
|
+
logger.warn({ err }, "Skill-curation orphan-overlay scan failed at boot");
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
// ── 7.05 Context-index reconciler (B-004 Phase 2a) ──
|
|
1535
|
+
// Keeps `context/context-index.md` in sync with the filesystem so the
|
|
1536
|
+
// per-flow review-context loader can treat the index as authoritative.
|
|
1537
|
+
// Combines: startup one-shot (30 s after boot), internal chokidar
|
|
1538
|
+
// watcher on contextDir (10 s debounce), cron nightly (wired via
|
|
1539
|
+
// `AgentScheduler.setContextIndexReconcilerCallback`), and API-route
|
|
1540
|
+
// hints via `onIndexableContextChange` below.
|
|
1541
|
+
let contextIndexReconcilerPromptSink = null;
|
|
1542
|
+
const contextIndexReconciler = new ContextIndexReconcilerObserver({
|
|
1543
|
+
db,
|
|
1544
|
+
contextDir: getContextDir(config),
|
|
1545
|
+
writeTracker,
|
|
1546
|
+
onPromptContextChanged: (path, reason, tier, metadata) => {
|
|
1547
|
+
// The reconciler writes one path (`context-index.md`) — route the
|
|
1548
|
+
// prompt-cache invalidation through the same handler every other
|
|
1549
|
+
// context write uses. The real handler is installed in §11
|
|
1550
|
+
// (`onPromptContextChanged` ApiDependency); this indirection lets
|
|
1551
|
+
// the observer start at §7 before the handler exists.
|
|
1552
|
+
contextIndexReconcilerPromptSink?.(path, reason, tier, metadata);
|
|
1553
|
+
},
|
|
1554
|
+
morningRoutineLock,
|
|
1555
|
+
timezone: config.timezone || undefined,
|
|
1556
|
+
});
|
|
1557
|
+
observerManager.register(contextIndexReconciler);
|
|
1558
|
+
// docs/design/21-management-registry-and-entities.md §7.6 P5 —
|
|
1559
|
+
// entity-mirror watcher. Owns its own chokidar watcher (independent
|
|
1560
|
+
// of the context-index reconciler's debounce path) so single-file
|
|
1561
|
+
// L2 writes converge into the SQLite mirror within NFR-9's 500 ms
|
|
1562
|
+
// budget — the §7.6 lookup contract relies on this freshness at
|
|
1563
|
+
// scheduled-task fire time. Boot pass + watcher lifecycle handled
|
|
1564
|
+
// by the observer wrapper.
|
|
1565
|
+
//
|
|
1566
|
+
// §7.2 chain — fan an L2 entity delta out to the context-index
|
|
1567
|
+
// observer's `requestReconcile` so the rendered domain-index +
|
|
1568
|
+
// activity-view files refresh on the same 10 s debounce as other
|
|
1569
|
+
// fs_event triggers. Without this, those views only converged on
|
|
1570
|
+
// the nightly cron + 30 s startup pass because `shouldIndexPath`
|
|
1571
|
+
// filters L2 paths out of the context-index's own watcher
|
|
1572
|
+
// (followups item 7).
|
|
1573
|
+
observerManager.register(new EntityMirrorObserver({
|
|
1574
|
+
db,
|
|
1575
|
+
contextDir: getContextDir(config),
|
|
1576
|
+
writeTracker,
|
|
1577
|
+
onEntityChanged: () => contextIndexReconciler.requestReconcile("fs_event"),
|
|
1578
|
+
}));
|
|
1579
|
+
// ── 7.1 MCP auto-probe (B-003 Phase 4.3) ──
|
|
1580
|
+
// Walks enabled mcp_servers rows every `mcpAutoProbeIntervalMinutes` and
|
|
1581
|
+
// re-runs the probe sandbox. Set the interval to 0 to disable. The
|
|
1582
|
+
// observer never flips `enabled` on its own — failure surfaces through
|
|
1583
|
+
// the dashboard card's status dot, user decides whether to disable.
|
|
1584
|
+
{
|
|
1585
|
+
const { McpAutoProbe } = await import("./services/mcp/auto-probe.js");
|
|
1586
|
+
observerManager.register(new McpAutoProbe({
|
|
1587
|
+
db,
|
|
1588
|
+
blobStore,
|
|
1589
|
+
dataDir: config.dataDir,
|
|
1590
|
+
intervalMinutes: config.mcpAutoProbeIntervalMinutes,
|
|
1591
|
+
}));
|
|
1592
|
+
}
|
|
1593
|
+
// ── 7.2 Observation summarizer (cost-reduction-structural §A) ──
|
|
1594
|
+
// Drains pending observation rows asynchronously: pre-filter → per-
|
|
1595
|
+
// source LLM call → `summary_text` + `novelty_score` written back to
|
|
1596
|
+
// the row. The hourly_check skill consumes summaries instead of
|
|
1597
|
+
// re-fetching raw content. Disabled cleanly via
|
|
1598
|
+
// `observationSummarizerEnabled` — when off, observations stay
|
|
1599
|
+
// pending and the skill drops to legacy fetch-on-doubt.
|
|
1600
|
+
if (config.observationSummarizerEnabled) {
|
|
1601
|
+
const summarizerBinding = (() => {
|
|
1602
|
+
try {
|
|
1603
|
+
const row = db
|
|
1604
|
+
.prepare(`SELECT main_backend, main_model FROM process_backend_config WHERE process_key = 'observation.summarize'`)
|
|
1605
|
+
.get();
|
|
1606
|
+
if (!row?.main_backend || !row.main_model)
|
|
1607
|
+
return null;
|
|
1608
|
+
return { backendId: row.main_backend, modelId: row.main_model };
|
|
1609
|
+
}
|
|
1610
|
+
catch (err) {
|
|
1611
|
+
logger.debug({ err }, "Failed to read observation.summarize binding; using fallback");
|
|
1612
|
+
return null;
|
|
1613
|
+
}
|
|
1614
|
+
})();
|
|
1615
|
+
const summarizerClient = (() => {
|
|
1616
|
+
const fallbackBackend = summarizerBinding?.backendId ?? "claude";
|
|
1617
|
+
const fallbackModel = summarizerBinding?.modelId ?? "claude-haiku-4-5-20251001";
|
|
1618
|
+
if (fallbackBackend === "claude") {
|
|
1619
|
+
return new AnthropicSummarizerClient({
|
|
1620
|
+
modelId: fallbackModel,
|
|
1621
|
+
getApiKey: async () => {
|
|
1622
|
+
const direct = await secretBroker.getBackendApiKey("claude");
|
|
1623
|
+
if (direct)
|
|
1624
|
+
return direct;
|
|
1625
|
+
// Fall back to env (matches the daemon's API-key bridging policy).
|
|
1626
|
+
return process.env.ANTHROPIC_API_KEY ?? null;
|
|
1627
|
+
},
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
// Codex / Gemini summarizer support is not yet implemented; the
|
|
1631
|
+
// worker translates `unsupported_backend` into a 'skipped' row so
|
|
1632
|
+
// the hourly_check skill drops to its legacy fetch path.
|
|
1633
|
+
return new UnsupportedSummarizerClient(fallbackBackend, fallbackModel);
|
|
1634
|
+
})();
|
|
1635
|
+
observerManager.register(new ObservationSummarizerWorker({
|
|
1636
|
+
db,
|
|
1637
|
+
client: summarizerClient,
|
|
1638
|
+
concurrency: config.observationSummarizerConcurrency,
|
|
1639
|
+
perCallTimeoutMs: config.observationSummarizerTimeoutMs,
|
|
1640
|
+
maxLlmCallsPerMinute: config.observationSummarizerMaxCallsPerMinute,
|
|
1641
|
+
queueDepthLimit: config.observationSummarizerQueueLimit,
|
|
1642
|
+
preFilter: { vipMailSenders: config.vipMailSenders },
|
|
1643
|
+
}));
|
|
1644
|
+
}
|
|
1645
|
+
else {
|
|
1646
|
+
logger.info("Observation summarizer disabled — pending rows stay pending");
|
|
1647
|
+
}
|
|
1648
|
+
// ── 8. Health Monitor ──
|
|
1649
|
+
const healthMonitor = new HealthMonitor({
|
|
1650
|
+
db,
|
|
1651
|
+
config,
|
|
1652
|
+
eventBus,
|
|
1653
|
+
messageHub,
|
|
1654
|
+
observerManager,
|
|
1655
|
+
startedAt,
|
|
1656
|
+
});
|
|
1657
|
+
// Notifications Center heartbeat (see docs/design/20-notifications-center.md).
|
|
1658
|
+
// Updates an in-memory tick timestamp every 30s; surfaced via /api/health
|
|
1659
|
+
// so the dashboard can detect a frozen event loop.
|
|
1660
|
+
const heartbeat = new Heartbeat();
|
|
1661
|
+
// ── 9. Scheduler ──
|
|
1662
|
+
const scheduler = new AgentScheduler(eventBus, db, config);
|
|
1663
|
+
// ── 9.1 Custom routine scheduler (B-007 §5.8) ──
|
|
1664
|
+
// Reads `routines/custom/*.md` from the context dir and registers a
|
|
1665
|
+
// cron job per enabled routine. Reloaded from the context API whenever
|
|
1666
|
+
// the agent or dashboard edits a file under that directory.
|
|
1667
|
+
const customRoutineScheduler = new CustomRoutineScheduler({
|
|
1668
|
+
contextDir: getContextDir(config),
|
|
1669
|
+
eventBus,
|
|
1670
|
+
timezone: config.timezone || undefined,
|
|
1671
|
+
});
|
|
1672
|
+
// ── 9.5 Signal Detector ──
|
|
1673
|
+
const signalDetector = new SignalDetector(config);
|
|
1674
|
+
// ── 10. Event Processing Pipeline ──
|
|
1675
|
+
const agentCore = new ClaudeCodeCore(config, writeTracker);
|
|
1676
|
+
const codexCore = new CodexCore(config);
|
|
1677
|
+
const geminiCore = new GeminiCliCore(config, writeTracker, undefined, db);
|
|
1678
|
+
// B-003 Phase 3 — wire the MCP session context so per-session workdirs pick
|
|
1679
|
+
// up the current DB + keychain state at spawn time. Each core stays
|
|
1680
|
+
// backward-compatible: without this call it simply runs without MCP.
|
|
1681
|
+
const mcpContext = { db, blobStore };
|
|
1682
|
+
agentCore.setMcpContext(mcpContext);
|
|
1683
|
+
codexCore.setMcpContext(mcpContext);
|
|
1684
|
+
geminiCore.setMcpContext(mcpContext);
|
|
1685
|
+
// DELEGATED-PROXY-API-DESIGN.md Phase A — boot janitor sweeps stale
|
|
1686
|
+
// `agent-sessions/proxy-*` tempdirs left by SIGKILL'd proxy invocations,
|
|
1687
|
+
// then construct the invoker so /api/mail/* and /api/calendar/* route
|
|
1688
|
+
// handlers can dispatch through it once Phase B wires the route shims.
|
|
1689
|
+
const janitorRemoved = runProxyTempdirJanitor(config.dataDir);
|
|
1690
|
+
if (janitorRemoved > 0) {
|
|
1691
|
+
logger.info({ removed: janitorRemoved }, "Boot janitor cleared stale delegated-proxy tempdirs");
|
|
1692
|
+
}
|
|
1693
|
+
const poolJanitorRemoved = runSessionPoolTempdirJanitor(join(config.dataDir, "agent-sessions"));
|
|
1694
|
+
if (poolJanitorRemoved > 0) {
|
|
1695
|
+
logger.info({ removed: poolJanitorRemoved }, "Boot janitor cleared stale delegated-task pool tempdirs");
|
|
1696
|
+
}
|
|
1697
|
+
// DELEGATED-TASK-MODE-DESIGN.md §11.1 — close `delegated_task.exec`
|
|
1698
|
+
// rows that were `in_progress` when the daemon last crashed. Without
|
|
1699
|
+
// this, the dashboard's in-flight counter and audit views drift forever.
|
|
1700
|
+
const taskOrphansClosed = runDelegatedTaskOrphanJanitor(db);
|
|
1701
|
+
if (taskOrphansClosed > 0) {
|
|
1702
|
+
logger.info({ closed: taskOrphansClosed }, "Boot janitor closed orphaned delegated_task in-progress rows");
|
|
1703
|
+
}
|
|
1704
|
+
const delegatedBackendInvoker = new DelegatedBackendInvoker({
|
|
1705
|
+
db,
|
|
1706
|
+
config,
|
|
1707
|
+
cores: { claude: agentCore, codex: codexCore, gemini: geminiCore },
|
|
1708
|
+
});
|
|
1709
|
+
let delegatedSyncWorker = null;
|
|
1710
|
+
const buildDelegatedSyncWorker = () => {
|
|
1711
|
+
if (!delegatedSyncWorker) {
|
|
1712
|
+
delegatedSyncWorker = new DelegatedSyncWorker({
|
|
1713
|
+
db,
|
|
1714
|
+
invoker: delegatedBackendInvoker,
|
|
1715
|
+
calendarId: config.googleCalendarId,
|
|
1716
|
+
timezone: config.timezone,
|
|
1717
|
+
todayWriteLock: morningRoutineLock,
|
|
1718
|
+
triggerRoadmapRefresh,
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
return delegatedSyncWorker;
|
|
1722
|
+
};
|
|
1723
|
+
if (hasActiveDelegatedSyncIntegration(db)) {
|
|
1724
|
+
observerManager.register(buildDelegatedSyncWorker());
|
|
1725
|
+
}
|
|
1726
|
+
// ── Delegated probe observer (DELEGATED-MODE-V2 §7.1) ──
|
|
1727
|
+
// Hourly re-probe of delegated integrations' connector tools so the
|
|
1728
|
+
// `integration_probes` cache reflects current sign-in state. Without
|
|
1729
|
+
// this, `consultDelegatedConnectorHealth` (§4.5) has no signal to fire
|
|
1730
|
+
// on after the wizard's first probe — the §10 "user signed out hours
|
|
1731
|
+
// ago" risk row would stay silent.
|
|
1732
|
+
//
|
|
1733
|
+
// Registered here (mid-§10) rather than in §7 because it depends on the
|
|
1734
|
+
// three agent cores constructed at the top of this section. The
|
|
1735
|
+
// observerManager.startAll() call further down picks it up unchanged.
|
|
1736
|
+
{
|
|
1737
|
+
const { DelegatedProbeObserver } = await import("./observers/delegated-probe-observer.js");
|
|
1738
|
+
observerManager.register(new DelegatedProbeObserver({
|
|
1739
|
+
db,
|
|
1740
|
+
agentBackends: [agentCore, codexCore, geminiCore],
|
|
1741
|
+
intervalMinutes: config.delegatedProbeIntervalMinutes,
|
|
1742
|
+
}));
|
|
1743
|
+
}
|
|
1744
|
+
const notificationManager = new NotificationManager(messageHub, db, config);
|
|
1745
|
+
const authTelemetry = new AuthTelemetry(db);
|
|
1746
|
+
// Shared notifier factory for auth-health-monitor and auth-recovery (D3 fix).
|
|
1747
|
+
// Maps `kind` to notification_type + category so both callers share the
|
|
1748
|
+
// same routing / quiet-hours-bypass logic without duplicating 30+ lines.
|
|
1749
|
+
//
|
|
1750
|
+
// Probe-failure and recovery DMs bypass NotificationManager's own quiet-hours
|
|
1751
|
+
// + rate-limit gates via `category: "error"` (SAFETY_CATEGORIES member).
|
|
1752
|
+
// Keepalive DMs stay on the standard path. See Phase 4 self-critique B2 for
|
|
1753
|
+
// the anti-spam reasoning (clock-driven, ≤1 DM per cron tick).
|
|
1754
|
+
const makeAuthNotifier = (source) => ({
|
|
1755
|
+
send: async (message, options) => {
|
|
1756
|
+
const kind = options?.kind ?? "keepalive";
|
|
1757
|
+
const typeMap = {
|
|
1758
|
+
probe_failure: "auth.probe_failure",
|
|
1759
|
+
recovery: "auth.recovery",
|
|
1760
|
+
keepalive: "auth.keepalive_reminder",
|
|
1761
|
+
};
|
|
1762
|
+
const notificationType = typeMap[kind] ?? "auth.keepalive_reminder";
|
|
1763
|
+
// probe_failure and recovery bypass quiet-hours; keepalive does not.
|
|
1764
|
+
const category = kind === "keepalive" ? "auth-health" : AUTH_PROBE_NOTIFICATION_CATEGORY;
|
|
1765
|
+
await notificationManager.send(message, {
|
|
1766
|
+
type: notificationType,
|
|
1767
|
+
source,
|
|
1768
|
+
priority: EventPriority.NORMAL,
|
|
1769
|
+
timestamp: new Date(),
|
|
1770
|
+
data: {},
|
|
1771
|
+
correlationId: randomBytes(8).toString("hex"),
|
|
1772
|
+
}, {
|
|
1773
|
+
priority: "normal",
|
|
1774
|
+
category,
|
|
1775
|
+
destinationMode: "configured_only",
|
|
1776
|
+
});
|
|
1777
|
+
},
|
|
1778
|
+
});
|
|
1779
|
+
const authHealthMonitor = new AuthHealthMonitor(db, {
|
|
1780
|
+
claude: agentCore,
|
|
1781
|
+
codex: codexCore,
|
|
1782
|
+
gemini: geminiCore,
|
|
1783
|
+
}, authTelemetry, {
|
|
1784
|
+
notifier: makeAuthNotifier("auth-health-monitor"),
|
|
1785
|
+
// Forward references — these gate closures are registered here
|
|
1786
|
+
// but only fire once `scheduler.start()` ticks the hourly cron,
|
|
1787
|
+
// by which time `dispatcher` is fully constructed (see the
|
|
1788
|
+
// declaration further down in this file). Calling any of these
|
|
1789
|
+
// before then would hit TDZ; in practice the constructor and
|
|
1790
|
+
// `reconcilePendingRecoveries` / `runKeepaliveSweep` never do.
|
|
1791
|
+
isMorningRoutineActive: () => dispatcher.isMorningRoutineActive(),
|
|
1792
|
+
isQuietHours: () => notificationManager.isQuietHours(),
|
|
1793
|
+
probeDisabled: () => config.authProbeDisabled,
|
|
1794
|
+
});
|
|
1795
|
+
// Reset any recoveries that were in-flight when the daemon was last killed.
|
|
1796
|
+
const recovered = authHealthMonitor.reconcilePendingRecoveries();
|
|
1797
|
+
if (recovered > 0) {
|
|
1798
|
+
logger.info({ count: recovered }, "Reconciled stuck auth recoveries on startup");
|
|
1799
|
+
}
|
|
1800
|
+
// Run the 60-day keepalive sweep once on startup. Hourly probe is
|
|
1801
|
+
// registered via scheduler.setAuthProbeCallback — see §9.5.4.
|
|
1802
|
+
void authHealthMonitor.runKeepaliveSweep().catch((err) => {
|
|
1803
|
+
logger.warn({ err }, "Initial auth keepalive sweep failed");
|
|
1804
|
+
});
|
|
1805
|
+
const keepaliveTimer = setInterval(() => {
|
|
1806
|
+
void authHealthMonitor.runKeepaliveSweep().catch((err) => {
|
|
1807
|
+
logger.warn({ err }, "Periodic auth keepalive sweep failed");
|
|
1808
|
+
});
|
|
1809
|
+
}, 24 * 60 * 60 * 1000);
|
|
1810
|
+
keepaliveTimer.unref?.();
|
|
1811
|
+
// Phase 5/6: Interactive auth recovery manager. Uses the same notifier
|
|
1812
|
+
// sink as the AuthHealthMonitor so recovery DMs flow through the same
|
|
1813
|
+
// notification pipeline with the same anti-spam guarantees.
|
|
1814
|
+
const authRecovery = new AuthRecovery(db, authTelemetry, authHealthMonitor, makeAuthNotifier("auth-recovery"), {
|
|
1815
|
+
claudeRecoveryTimeoutMin: 10,
|
|
1816
|
+
codexRecoveryTimeoutMin: 15,
|
|
1817
|
+
geminiRecoveryTimeoutMin: 5,
|
|
1818
|
+
});
|
|
1819
|
+
// ── Scoped read tokens for ReadSensitive API endpoints ──
|
|
1820
|
+
// Tokens rotate per session/workdir execution and are only injected into
|
|
1821
|
+
// backend subprocess env, never into prompt-visible instruction files.
|
|
1822
|
+
const readTokenManager = new ScopedReadSensitiveTokenManager();
|
|
1823
|
+
const agentRouter = new BackendRouter(db, config, [agentCore, codexCore, geminiCore], notificationManager, authTelemetry,
|
|
1824
|
+
// Callback to materialize instruction files for a fallback backend in an
|
|
1825
|
+
// existing session workdir. Without this, a Claude→Codex heavy-tier
|
|
1826
|
+
// fallback would leave the dir with only CLAUDE.md and no AGENTS.md.
|
|
1827
|
+
// After materializing built-in files, re-sync user-authored skills so
|
|
1828
|
+
// the newly created backend-specific dir (e.g. .codex/skills/) also
|
|
1829
|
+
// receives user skills. The dispatcher's syncAllUserSkills ran BEFORE
|
|
1830
|
+
// router.execute(), so the fallback dir didn't exist at that point.
|
|
1831
|
+
(sessionDir, backendId, eventType, processKey) => {
|
|
1832
|
+
// services is the mutable ServiceRegistry — reading .calendar here
|
|
1833
|
+
// picks up the current availability (e.g. after mid-flight OAuth).
|
|
1834
|
+
// GitHub presence is read fresh from the unified `repositories` table
|
|
1835
|
+
// (rows with a github side); legacy `githubRepos` config is gone.
|
|
1836
|
+
const cfgServices = buildConfiguredServices(config, {
|
|
1837
|
+
...services,
|
|
1838
|
+
github: selectGithubRepoSlugs(db).length > 0,
|
|
1839
|
+
});
|
|
1840
|
+
const mailAccounts = services.mail?.listActiveAccounts() ?? [];
|
|
1841
|
+
ensureBackendMaterialized(config.workspaceDir, sessionDir, backendId, eventType, processKey, cfgServices, mailAccounts, readIntegrations(db), config.character);
|
|
1842
|
+
syncAllUserSkills(sessionDir, join(config.dataDir, "skills"));
|
|
1843
|
+
});
|
|
1844
|
+
// Startup validation — warn if any delegated-mode variant files are
|
|
1845
|
+
// missing. Now checks both skill and task-flow variants for every
|
|
1846
|
+
// currently-delegated integration against its configured `delegatedBackend`
|
|
1847
|
+
// (not the union of all possible backends — see validateDelegatedStartup
|
|
1848
|
+
// docstring). Never throws; the PATCH route + /health do the hard-
|
|
1849
|
+
// rejection and user-surfacing downstream.
|
|
1850
|
+
{
|
|
1851
|
+
const startupIntegrations = readIntegrations(db);
|
|
1852
|
+
const missing = validateDelegatedStartup(config.workspaceDir, startupIntegrations);
|
|
1853
|
+
if (missing.skills.length > 0 || missing.taskFlows.length > 0) {
|
|
1854
|
+
logger.warn({ missingSkills: missing.skills, missingTaskFlows: missing.taskFlows }, "Delegated-mode variant files missing — agent will fall back to SKILL.md / direct task-flow for affected entries; surface in /health.integrationModes");
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
const contextBuilder = new ContextBuilder(config, db, services);
|
|
1858
|
+
// Task flows are simple code constants — no file system, no hot-reload needed
|
|
1859
|
+
const sessionManager = new SessionManager(db, config);
|
|
1860
|
+
const messageRecorder = new MessageRecorder(db);
|
|
1861
|
+
const eventBroadcaster = new EventBroadcaster();
|
|
1862
|
+
const auditLogger = new AuditLogger(db, {
|
|
1863
|
+
// `/api/events/stream` is defined in terms of persisted agent_actions rows,
|
|
1864
|
+
// not raw EventBus payloads, so the broadcaster subscribes at the audit layer.
|
|
1865
|
+
onRowInserted: (row) => eventBroadcaster.broadcastEvent(row),
|
|
1866
|
+
});
|
|
1867
|
+
// Shared body for "something changed that affects what's baked into
|
|
1868
|
+
// active DM workdirs — refresh them in place." Used by:
|
|
1869
|
+
// - `onMailScopeChanged` (MailAccountRegistry scope-change hook)
|
|
1870
|
+
// - the integration-mode lifecycle (DELEGATED-PROXY-API-DESIGN.md
|
|
1871
|
+
// Phase F §4.8 — every mode flip re-materializes so the unified
|
|
1872
|
+
// skill body and per-backend instruction file reflect the new
|
|
1873
|
+
// state on the next turn without tearing down the SDK session)
|
|
1874
|
+
//
|
|
1875
|
+
// Returns null when there's no active DM session to refresh; otherwise
|
|
1876
|
+
// the per-session summary so callers can attach it to a broadcast or
|
|
1877
|
+
// structured log. Per-session failures stay inside the helper.
|
|
1878
|
+
const rematerializeActiveDmWorkdirs = (reason) => {
|
|
1879
|
+
const sessions = sessionManager.listActiveDmSessions();
|
|
1880
|
+
if (sessions.length === 0) {
|
|
1881
|
+
logger.debug({ reason }, "DM workdir refresh requested — no active DM sessions");
|
|
1882
|
+
return null;
|
|
1883
|
+
}
|
|
1884
|
+
const cfgServices = buildConfiguredServices(config, {
|
|
1885
|
+
...services,
|
|
1886
|
+
github: selectGithubRepoSlugs(db).length > 0,
|
|
1887
|
+
});
|
|
1888
|
+
const mailAccounts = services.mail?.listActiveAccounts() ?? [];
|
|
1889
|
+
// Read integration state fresh inside the closure so a Phase F mode
|
|
1890
|
+
// flip's pre-`writeIntegrations` row is not what gets baked. The
|
|
1891
|
+
// `SkillsCompiler` uses this for variant resolution (non-proxy
|
|
1892
|
+
// integrations like Notion need `SKILL.delegated.<backend>.md`) and
|
|
1893
|
+
// to re-emit `applyAllDeniedToolsForSkill` soft-deny prose. Passing
|
|
1894
|
+
// an empty object would silently regress to the latent staleness bug
|
|
1895
|
+
// that Phase F surfaced.
|
|
1896
|
+
const integrations = readIntegrations(db);
|
|
1897
|
+
const summary = refreshDmSessionWorkdirs({
|
|
1898
|
+
projectRoot: config.workspaceDir,
|
|
1899
|
+
dataDir: config.dataDir,
|
|
1900
|
+
sessions,
|
|
1901
|
+
configuredServices: cfgServices,
|
|
1902
|
+
mailAccounts,
|
|
1903
|
+
integrations,
|
|
1904
|
+
character: config.character,
|
|
1905
|
+
});
|
|
1906
|
+
return { summary, mailAccounts };
|
|
1907
|
+
};
|
|
1908
|
+
// Real implementation of the mail-scope-changed hook. Wired by the
|
|
1909
|
+
// MailAccountRegistry constructor above through a forward-reference so the
|
|
1910
|
+
// registry can be built before sessionManager / eventBroadcaster exist.
|
|
1911
|
+
// Re-materializes every active DM session workdir so `accounts.md` and the
|
|
1912
|
+
// `external-services` skill reflect the new scope on the next turn without
|
|
1913
|
+
// tearing down the SDK session.
|
|
1914
|
+
onMailScopeChanged = (reason) => {
|
|
1915
|
+
const result = rematerializeActiveDmWorkdirs(reason);
|
|
1916
|
+
if (!result)
|
|
1917
|
+
return;
|
|
1918
|
+
logger.info({ reason, ...result.summary }, "Mail scope changed — DM session workdirs re-materialized");
|
|
1919
|
+
eventBroadcaster.broadcastEvent({
|
|
1920
|
+
kind: "mail_scope_changed",
|
|
1921
|
+
reason,
|
|
1922
|
+
activeAccounts: result.mailAccounts.length,
|
|
1923
|
+
...result.summary,
|
|
1924
|
+
});
|
|
1925
|
+
};
|
|
1926
|
+
// Management Mode Phase 2 — shared migration primitives. Long timeout
|
|
1927
|
+
// because cross-fs copies of large vaults may legitimately run
|
|
1928
|
+
// multiple minutes; the lock itself is cheap and only blocks a
|
|
1929
|
+
// second concurrent /api/setup/migrate-context call.
|
|
1930
|
+
const migrationLock = new MigrationLock(60 * 60 * 1000);
|
|
1931
|
+
const contextWriteGate = new ContextWriteGate();
|
|
1932
|
+
initTaskFlows(config.workspaceDir, config.dataDir);
|
|
1933
|
+
const dispatcher = new EventDispatcher(eventBus, agentRouter, contextBuilder, getTaskFlow, notificationManager, sessionManager, messageRecorder, auditLogger, db, config, morningRoutineLock, services, roadmapWriteLock, writeTracker);
|
|
1934
|
+
dispatcher.setSignalDetector(signalDetector);
|
|
1935
|
+
// DOCS_QA_B7_DESIGN.md §11.1 — persistence-side citation validator
|
|
1936
|
+
// for docs_qa sessions. Lookup runs over fts_docs (indexed by the
|
|
1937
|
+
// docs indexer); the dispatcher only consults it when
|
|
1938
|
+
// `isDocsQAMessage(event)` is true, so this hook is inert for
|
|
1939
|
+
// chat/DM/routine flows and for installs that haven't ingested docs.
|
|
1940
|
+
const docsCitationLookup = makeDocsCitationLookup(db);
|
|
1941
|
+
dispatcher.setDocsCitationLookup(docsCitationLookup);
|
|
1942
|
+
// Docs-QA SSE adapter — DOCS_QA_B7_DESIGN.md §S4 / §S8. Sits
|
|
1943
|
+
// alongside `dashboardAdapter` on the same `platform="dashboard"`
|
|
1944
|
+
// surface; the `intent: "docs_qa"` discriminator on inbound events
|
|
1945
|
+
// forks dispatch into the docs-qa task flow + skill set + light
|
|
1946
|
+
// tier clamp. The adapter is exposed to `createDocsRoutes` for the
|
|
1947
|
+
// QA POST/SSE endpoints; outbound text reaches it via the dispatcher's
|
|
1948
|
+
// `IDashboardStream` slot fanned out by `CompositeDashboardStream`.
|
|
1949
|
+
//
|
|
1950
|
+
// It is intentionally NOT registered with `messageHub`: the hub keys
|
|
1951
|
+
// adapters by `platformName`, and both `DashboardAdapter` and this
|
|
1952
|
+
// adapter declare `platformName="dashboard"`. A second registration
|
|
1953
|
+
// would silently replace `dashboardAdapter` in the hub map (only a
|
|
1954
|
+
// warn-log), breaking `getAdapter("dashboard")`, the
|
|
1955
|
+
// start/stop lifecycle for the chat adapter, and any
|
|
1956
|
+
// `sendToPlatform("dashboard", ...)` caller (DocsQAAdapter.sendMessage
|
|
1957
|
+
// throws — it's streaming-only). DocsQAAdapter's lifecycle is owned
|
|
1958
|
+
// by the SSE route itself: clients register on `GET /docs/qa/stream`
|
|
1959
|
+
// and unregister on the request's `onAbort`.
|
|
1960
|
+
const docsQAAdapter = new DocsQAAdapter((event) => void eventBus.put(event), docsCitationLookup);
|
|
1961
|
+
// Fan-out: the dispatcher's single `IDashboardStream` slot drives
|
|
1962
|
+
// both the chat and docs-QA adapters. Each adapter no-ops on
|
|
1963
|
+
// unknown channelIds (per F3.2), so only the adapter that owns
|
|
1964
|
+
// the destination channelId actually emits — fan-out is naturally
|
|
1965
|
+
// safe (DOCS_QA_B7_DESIGN.md §9 F3.4 / §11.4).
|
|
1966
|
+
dispatcher.setDashboardStream(new CompositeDashboardStream([dashboardAdapter, docsQAAdapter]));
|
|
1967
|
+
dispatcher.setAttachmentStore(attachmentStore);
|
|
1968
|
+
// Local-Whisper voice transcription for inbound audio attachments.
|
|
1969
|
+
// See docs/design/appendices/voice-transcription.md.
|
|
1970
|
+
//
|
|
1971
|
+
// `enabled` is driven by the `voiceTranscriptionEnabled` runtime setting
|
|
1972
|
+
// (default `false`). The flag is opt-in via the dashboard's Voice Mode
|
|
1973
|
+
// card, which goes through `POST /api/voice/install` so the model
|
|
1974
|
+
// download and the daemon restart happen atomically — the constructor
|
|
1975
|
+
// here observes the persisted flag on the next boot.
|
|
1976
|
+
//
|
|
1977
|
+
// Env vars stay live for advanced operators who want to override the
|
|
1978
|
+
// model id, language, or duration cap without a settings round-trip.
|
|
1979
|
+
// `PA_VOICE_TRANSCRIPTION_ENABLED` is honoured as a fallback when the
|
|
1980
|
+
// setting is unset (legacy behaviour for installs predating the flag).
|
|
1981
|
+
const voiceTranscriberMaxDuration = Number(process.env.PA_VOICE_TRANSCRIPTION_MAX_DURATION_SEC ?? "600");
|
|
1982
|
+
const voiceEnvOverride = process.env.PA_VOICE_TRANSCRIPTION_ENABLED;
|
|
1983
|
+
// When `PA_VOICE_TRANSCRIPTION_ENABLED` is set, env wins (operator
|
|
1984
|
+
// override). Otherwise read live from `config` so the dashboard's
|
|
1985
|
+
// voice install flow — which mutates `config.voiceTranscriptionEnabled`
|
|
1986
|
+
// and persists the setting in the same boot — takes effect on the
|
|
1987
|
+
// next inbound audio attachment without waiting for a daemon restart.
|
|
1988
|
+
const voiceTranscriberEnabled = voiceEnvOverride !== undefined
|
|
1989
|
+
? voiceEnvOverride.toLowerCase() !== "false"
|
|
1990
|
+
: () => config.voiceTranscriptionEnabled;
|
|
1991
|
+
dispatcher.setVoiceTranscriber(new VoiceTranscriber({
|
|
1992
|
+
db,
|
|
1993
|
+
modelDir: join(config.dataDir, "models", "whisper"),
|
|
1994
|
+
enabled: voiceTranscriberEnabled,
|
|
1995
|
+
model: process.env.PA_VOICE_TRANSCRIPTION_MODEL,
|
|
1996
|
+
language: process.env.PA_VOICE_TRANSCRIPTION_LANGUAGE ?? null,
|
|
1997
|
+
maxDurationSec: Number.isFinite(voiceTranscriberMaxDuration)
|
|
1998
|
+
? voiceTranscriberMaxDuration
|
|
1999
|
+
: 600,
|
|
2000
|
+
}));
|
|
2001
|
+
dispatcher.setAuthRecovery(authRecovery);
|
|
2002
|
+
dispatcher.setAuthHealthMonitor(authHealthMonitor);
|
|
2003
|
+
// Wire the delegated-sync refresh callback. The thunk reads the live
|
|
2004
|
+
// `delegatedSyncWorker` reference each call so the dispatcher tracks
|
|
2005
|
+
// re-registration when an integration mode flips. When no delegated
|
|
2006
|
+
// integration is present, the worker is null and the call is a no-op
|
|
2007
|
+
// — the hourly check proceeds without extra latency.
|
|
2008
|
+
// See `docs/design/appendices/delegated-sync-opt-in.md` and the
|
|
2009
|
+
// worker's `runDisabledCadencesForHourlyCheck` doc-comment.
|
|
2010
|
+
dispatcher.setDelegatedSyncRefresh(async () => {
|
|
2011
|
+
await delegatedSyncWorker?.runDisabledCadencesForHourlyCheck();
|
|
2012
|
+
});
|
|
2013
|
+
// Messaging bang-commands (`!stop`/`!start`/`!cost`/`!report`) — owner DM
|
|
2014
|
+
// chokepoint that runs ahead of every other interceptor in handleMessage.
|
|
2015
|
+
// See docs/design/backlog/messaging-bang-commands.md.
|
|
2016
|
+
dispatcher.setBangCommandRegistry(createDefaultBangCommandRegistry());
|
|
2017
|
+
// P22 — wire the optimizer-workdir hooks. The `materialize` callback
|
|
2018
|
+
// captures `db`, `dataDir`, `workspaceDir`, `contextDir`, and `secretStore`
|
|
2019
|
+
// so the dispatcher branch can invoke it without importing the workdir
|
|
2020
|
+
// module directly. `teardown` cleans up under PA_DATA_DIR/optimizer-workdir/.
|
|
2021
|
+
{
|
|
2022
|
+
const { materializeOptimizerWorkdir, teardownOptimizerWorkdir } = await import("./core/skill-curation/workdir.js");
|
|
2023
|
+
dispatcher.setSkillCurationHooks({
|
|
2024
|
+
materialize: (opts) => materializeOptimizerWorkdir({
|
|
2025
|
+
db,
|
|
2026
|
+
dataDir: config.dataDir,
|
|
2027
|
+
workspaceDir: config.workspaceDir,
|
|
2028
|
+
contextDir: getContextDir(config),
|
|
2029
|
+
secretStore: secretBroker,
|
|
2030
|
+
cadence: readSkillCurationCadence(db),
|
|
2031
|
+
...(opts?.manual ? { manual: true } : {}),
|
|
2032
|
+
...(opts?.targetSkillsOverride ? { targetSkillsOverride: opts.targetSkillsOverride } : {}),
|
|
2033
|
+
}),
|
|
2034
|
+
teardown: teardownOptimizerWorkdir,
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
if (isUserPaused(db)) {
|
|
2038
|
+
logger.info("Restored user-paused state, autonomous work remains paused");
|
|
2039
|
+
}
|
|
2040
|
+
emitRoadmapRefreshSink = (source) => dispatcher.emitRoadmapRefresh(source);
|
|
2041
|
+
agentCore.setReadTokenManager?.(readTokenManager);
|
|
2042
|
+
geminiCore.setReadTokenManager?.(readTokenManager);
|
|
2043
|
+
notificationManager.setSignalDetector(signalDetector);
|
|
2044
|
+
let startupComplete = false;
|
|
2045
|
+
let pendingGoogleServicesReady = false;
|
|
2046
|
+
const handleGoogleServicesReady = () => {
|
|
2047
|
+
// Start CalendarPoller if calendar was just hot-loaded AND the
|
|
2048
|
+
// google_calendar integration is in `direct` mode. When the integration
|
|
2049
|
+
// is `delegated`/`disabled` we keep the poller dormant — OAuth is wired
|
|
2050
|
+
// up but the user has explicitly chosen not to poll.
|
|
2051
|
+
if (services.calendar
|
|
2052
|
+
&& !observerManager.has("calendar")
|
|
2053
|
+
&& shouldStartObserversFor(db, "google_calendar")) {
|
|
2054
|
+
const poller = buildCalendarPoller();
|
|
2055
|
+
if (poller) {
|
|
2056
|
+
observerManager.register(poller);
|
|
2057
|
+
void poller.start();
|
|
2058
|
+
logger.info("CalendarPoller started via hot-reload");
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
// Trigger morning_routine catchup if today.md is stale or missing
|
|
2062
|
+
// (same logic as runCatchup, ensures schedule generation after first auth)
|
|
2063
|
+
const contextDir = getContextDir(config);
|
|
2064
|
+
const todayMdPath = join(contextDir, "today.md");
|
|
2065
|
+
let needsMorning = false;
|
|
2066
|
+
if (existsSync(todayMdPath)) {
|
|
2067
|
+
const firstLine = readFileSync(todayMdPath, "utf-8").split("\n")[0];
|
|
2068
|
+
const today = getAgentDayDateStr(config.timezone || undefined, config.dayBoundaryHour);
|
|
2069
|
+
if (!firstLine.includes(today)) {
|
|
2070
|
+
needsMorning = true;
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
else {
|
|
2074
|
+
needsMorning = true;
|
|
2075
|
+
}
|
|
2076
|
+
if (needsMorning) {
|
|
2077
|
+
// Morning routine's post-completion hook will also check roadmap staleness.
|
|
2078
|
+
logger.info("Google services ready — today.md stale, queueing morning_routine wake");
|
|
2079
|
+
scheduler.queueMorningRoutineWake("google_auth_ready");
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
// Only refresh roadmap independently when today.md is already current.
|
|
2083
|
+
// If morning_routine is needed, its post-completion hook will handle stale
|
|
2084
|
+
// roadmap regeneration after the day context has been rebuilt.
|
|
2085
|
+
if (isRoadmapStale(contextDir)) {
|
|
2086
|
+
logger.info("Google services ready — roadmap stale, emitting roadmap_refresh");
|
|
2087
|
+
dispatcher.emitRoadmapRefresh("google_auth_ready");
|
|
2088
|
+
}
|
|
2089
|
+
};
|
|
2090
|
+
const handleSecretChange = async (scope) => {
|
|
2091
|
+
switch (scope) {
|
|
2092
|
+
case "slack":
|
|
2093
|
+
await reloadSlackAdapter(true);
|
|
2094
|
+
return;
|
|
2095
|
+
case "telegram":
|
|
2096
|
+
await reloadTelegramAdapter(true);
|
|
2097
|
+
return;
|
|
2098
|
+
case "discord":
|
|
2099
|
+
await reloadDiscordAdapter(true);
|
|
2100
|
+
return;
|
|
2101
|
+
case "notion":
|
|
2102
|
+
await reloadNotionService();
|
|
2103
|
+
// Hot-reload: if the user just added the Notion API key while
|
|
2104
|
+
// the integration is already in `direct` mode, register the
|
|
2105
|
+
// poller now so they don't have to restart the daemon.
|
|
2106
|
+
// Mirrors the calendar-side `handleGoogleServicesReady` path —
|
|
2107
|
+
// the mode-flip route covers `delegated→direct`, and this
|
|
2108
|
+
// covers the orthogonal "key arrives after direct was set"
|
|
2109
|
+
// case. Idempotent via observerManager.has().
|
|
2110
|
+
if (services.notion
|
|
2111
|
+
&& !observerManager.has("notion-poller")
|
|
2112
|
+
&& Object.keys(config.notionDatabaseIds).length > 0
|
|
2113
|
+
&& shouldStartObserversFor(db, "notion")) {
|
|
2114
|
+
const poller = buildNotionPoller();
|
|
2115
|
+
if (poller) {
|
|
2116
|
+
observerManager.register(poller);
|
|
2117
|
+
void poller.start();
|
|
2118
|
+
logger.info("NotionPoller started via hot-reload");
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
return;
|
|
2122
|
+
case "github":
|
|
2123
|
+
await reloadGitHubService();
|
|
2124
|
+
if (gitWatcher && secretState.githubWebhookConfigured) {
|
|
2125
|
+
gitWatcher.enableWebhookMode();
|
|
2126
|
+
}
|
|
2127
|
+
return;
|
|
2128
|
+
case "google":
|
|
2129
|
+
await reloadGoogleServices();
|
|
2130
|
+
return;
|
|
2131
|
+
case "apple_calendar":
|
|
2132
|
+
await reloadAppleCalendarService();
|
|
2133
|
+
return;
|
|
2134
|
+
case "apiToken":
|
|
2135
|
+
default:
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
};
|
|
2139
|
+
// ── 11. Hono HTTP Server ──
|
|
2140
|
+
// Enable webhook fallback: when GitHub webhook is configured, reduce GitWatcher frequency
|
|
2141
|
+
const startupGitWatcher = gitWatcher;
|
|
2142
|
+
if (startupGitWatcher && secretState.githubWebhookConfigured) {
|
|
2143
|
+
startupGitWatcher.enableWebhookMode();
|
|
2144
|
+
}
|
|
2145
|
+
const handlePromptContextChanged = (path, reason, tier, metadata) => {
|
|
2146
|
+
const setupMode = dispatcher.getCurrentSetupMode();
|
|
2147
|
+
const decision = applyPromptContextStaleness({ path, reason, tier, metadata }, {
|
|
2148
|
+
dmStalenessStrict: config.dmStalenessStrict,
|
|
2149
|
+
setupInProgress: setupMode !== null,
|
|
2150
|
+
markContextChanged: () => markContextChanged(db),
|
|
2151
|
+
markActiveDmSessionsStale: (staleReason) => sessionManager.markActiveDmSessionsStale(staleReason),
|
|
2152
|
+
});
|
|
2153
|
+
logger.debug({
|
|
2154
|
+
path,
|
|
2155
|
+
reason,
|
|
2156
|
+
tier_decided: decision.effectiveTier,
|
|
2157
|
+
tier_requested: decision.requestedTier,
|
|
2158
|
+
tier_reason: metadata?.tierReason,
|
|
2159
|
+
dmStalenessStrict: config.dmStalenessStrict,
|
|
2160
|
+
mode: setupMode,
|
|
2161
|
+
invalidatesDmSessions: decision.invalidatesDmSessions,
|
|
2162
|
+
skippedForSetup: decision.skippedForSetup,
|
|
2163
|
+
}, "Prompt context staleness classified");
|
|
2164
|
+
if (decision.skippedForSetup) {
|
|
2165
|
+
logger.info({ path, reason, mode: setupMode }, "Skipping DM session stale flag - setup in progress");
|
|
2166
|
+
}
|
|
2167
|
+
};
|
|
2168
|
+
const app = createApp({
|
|
2169
|
+
db,
|
|
2170
|
+
config,
|
|
2171
|
+
secretBroker,
|
|
2172
|
+
readTokenValidator: (token) => readTokenManager.isValid(token),
|
|
2173
|
+
enforceReadToken: config.enforceReadToken,
|
|
2174
|
+
agentBackends: [agentCore, codexCore, geminiCore],
|
|
2175
|
+
authHealthMonitor,
|
|
2176
|
+
authRecovery,
|
|
2177
|
+
authTelemetry,
|
|
2178
|
+
eventBus,
|
|
2179
|
+
morningRoutineLock,
|
|
2180
|
+
roadmapWriteLock,
|
|
2181
|
+
migrationLock,
|
|
2182
|
+
contextWriteGate,
|
|
2183
|
+
observerManager,
|
|
2184
|
+
delegatedInvoker: delegatedBackendInvoker,
|
|
2185
|
+
gitAccountRegistry,
|
|
2186
|
+
onPrimaryVaultPathChange: (newPath) => primaryVaultWatcher.setVaultPath(newPath),
|
|
2187
|
+
onGitReposChanged: () => queueGitProjectInitsForCurrentConfig("config-patch"),
|
|
2188
|
+
getInFlightExecutions: () => dispatcher.getInFlightExecutions(),
|
|
2189
|
+
getHealthData: () => {
|
|
2190
|
+
const status = healthMonitor.getStatus();
|
|
2191
|
+
return {
|
|
2192
|
+
uptime: status.daemonUptime,
|
|
2193
|
+
eventBusSize: status.eventBusSize,
|
|
2194
|
+
activeSessions: status.activeSessions,
|
|
2195
|
+
connectedPlatforms: status.connectedPlatforms,
|
|
2196
|
+
registeredObservers: status.registeredObservers,
|
|
2197
|
+
missingContextFiles: status.missingContextFiles,
|
|
2198
|
+
contextFilesOk: status.contextFilesOk,
|
|
2199
|
+
};
|
|
2200
|
+
},
|
|
2201
|
+
getLastTickAt: () => heartbeat.getLastTickAt(),
|
|
2202
|
+
services,
|
|
2203
|
+
isStartupComplete: () => startupComplete,
|
|
2204
|
+
getIntegrationStatus,
|
|
2205
|
+
getMessagingStatus,
|
|
2206
|
+
// Bang-commands `/api/health` surface — messaging-bang-commands.md §6.1.
|
|
2207
|
+
// Returns `"ok"` for the unblocked case so dashboards never have to
|
|
2208
|
+
// null-check, while preserving the original gate enum for the blocked
|
|
2209
|
+
// states.
|
|
2210
|
+
getAutonomousState: () => dispatcher.isAutonomousAllowed() ?? "ok",
|
|
2211
|
+
getNotificationDestinations: () => ({
|
|
2212
|
+
defaultPlatforms: config.defaultNotificationPlatforms,
|
|
2213
|
+
effectiveFallbackPlatforms: messageHub.getEffectiveFallbackPlatforms(),
|
|
2214
|
+
}),
|
|
2215
|
+
getIntegrationDriftSyncStatus: () => delegatedSyncWorker?.getStatus() ?? {
|
|
2216
|
+
workerRunning: false,
|
|
2217
|
+
lastSuccessAt: null,
|
|
2218
|
+
circuitState: "ok",
|
|
2219
|
+
activeHours: { startHour: 4, endHour: 24 },
|
|
2220
|
+
withinActiveHours: false,
|
|
2221
|
+
cadences: {},
|
|
2222
|
+
unrecognizedIntervalKeys: [],
|
|
2223
|
+
ttlContractViolations: [],
|
|
2224
|
+
},
|
|
2225
|
+
// delegated-sync opt-in routes consume the live worker reference for
|
|
2226
|
+
// cadence Run Now + status snapshot. When no integration is in
|
|
2227
|
+
// delegated mode the worker is null and the routes report a
|
|
2228
|
+
// worker_unavailable / empty-status response — see
|
|
2229
|
+
// `docs/design/appendices/delegated-sync-opt-in.md`.
|
|
2230
|
+
get delegatedSyncWorker() {
|
|
2231
|
+
return delegatedSyncWorker ?? undefined;
|
|
2232
|
+
},
|
|
2233
|
+
sendNotification: async ({ message, platforms, priority, notificationType, originSessionId, }) => {
|
|
2234
|
+
const dispatchId = randomBytes(16).toString("hex");
|
|
2235
|
+
const deliveries = await messageHub.sendToUser(message, platforms, {
|
|
2236
|
+
dispatchId,
|
|
2237
|
+
notificationType: notificationType ?? "agent",
|
|
2238
|
+
priority: priority ?? "normal",
|
|
2239
|
+
contentSummary: message.slice(0, 200),
|
|
2240
|
+
});
|
|
2241
|
+
if (deliveries.length > 0) {
|
|
2242
|
+
const insert = db.prepare(`INSERT INTO notification_log (
|
|
2243
|
+
dispatch_id,
|
|
2244
|
+
notification_type,
|
|
2245
|
+
priority,
|
|
2246
|
+
platform,
|
|
2247
|
+
delivery_channel,
|
|
2248
|
+
delivery_message_id,
|
|
2249
|
+
content_summary,
|
|
2250
|
+
status,
|
|
2251
|
+
delivered_at
|
|
2252
|
+
)
|
|
2253
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'delivered', CURRENT_TIMESTAMP)`);
|
|
2254
|
+
for (const delivery of deliveries) {
|
|
2255
|
+
insert.run(dispatchId, notificationType ?? "agent", priority ?? "normal", delivery.platform, delivery.channel, delivery.messageId ?? null, message.slice(0, 200));
|
|
2256
|
+
}
|
|
2257
|
+
recordProactiveForwardDeliveries({
|
|
2258
|
+
db,
|
|
2259
|
+
config,
|
|
2260
|
+
deliveries,
|
|
2261
|
+
content: message,
|
|
2262
|
+
dispatchId,
|
|
2263
|
+
dispatchIds: [dispatchId],
|
|
2264
|
+
originSessionIds: originSessionId !== undefined ? [originSessionId] : [],
|
|
2265
|
+
notificationType: "proactive_forward",
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
return { dispatchId, deliveries };
|
|
2269
|
+
},
|
|
2270
|
+
markEventNotified: (correlationId) => {
|
|
2271
|
+
dispatcher.markEventNotified(correlationId);
|
|
2272
|
+
},
|
|
2273
|
+
onGoogleServicesReady: () => {
|
|
2274
|
+
if (!startupComplete) {
|
|
2275
|
+
pendingGoogleServicesReady = true;
|
|
2276
|
+
logger.info("Google services ready during bootstrap — deferring startup-sensitive actions");
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
handleGoogleServicesReady();
|
|
2280
|
+
},
|
|
2281
|
+
onScheduleConfigChanged: () => scheduler.reloadCrons(),
|
|
2282
|
+
onIntegrationModeChange: async (key, prev, next) => {
|
|
2283
|
+
const buildObserver = (observerName) => {
|
|
2284
|
+
// Registry-gated direct observers. Mail's multi-provider poller is a
|
|
2285
|
+
// deferred follow-up — gmail.observersTouched is empty, so that
|
|
2286
|
+
// branch remains unreachable today.
|
|
2287
|
+
if (observerName === "calendar")
|
|
2288
|
+
return buildCalendarPoller();
|
|
2289
|
+
if (observerName === "notion-poller")
|
|
2290
|
+
return buildNotionPoller();
|
|
2291
|
+
if (observerName === "git")
|
|
2292
|
+
return buildGitWatcher();
|
|
2293
|
+
if (observerName === "github")
|
|
2294
|
+
return buildGithubPoller();
|
|
2295
|
+
return null;
|
|
2296
|
+
};
|
|
2297
|
+
await applyIntegrationModeChange({
|
|
2298
|
+
db,
|
|
2299
|
+
observerManager,
|
|
2300
|
+
buildObserver,
|
|
2301
|
+
buildDelegatedSyncWorker,
|
|
2302
|
+
buildGitDelegatedCronObserver,
|
|
2303
|
+
// DELEGATED-PROXY-API-DESIGN.md Phase F (§4.8) — every mode
|
|
2304
|
+
// change re-materializes active DM workdirs so the next turn
|
|
2305
|
+
// picks up the new skill body / accounts.md / instruction file
|
|
2306
|
+
// without tearing down the SDK session.
|
|
2307
|
+
rematerializeDmSessions: (reason) => {
|
|
2308
|
+
const result = rematerializeActiveDmWorkdirs(reason);
|
|
2309
|
+
if (!result)
|
|
2310
|
+
return;
|
|
2311
|
+
logger.info({ reason, ...result.summary }, "Integration mode changed — DM session workdirs re-materialized");
|
|
2312
|
+
},
|
|
2313
|
+
}, key, prev, next);
|
|
2314
|
+
if (key === "git" && next.mode !== "direct") {
|
|
2315
|
+
gitWatcher = null;
|
|
2316
|
+
}
|
|
2317
|
+
},
|
|
2318
|
+
onMainBackendChange: (reason) => {
|
|
2319
|
+
// DELEGATED-MODE-V2-DESIGN.md §4.4 — every main-backend flip
|
|
2320
|
+
// re-materializes active DM workdirs so the next turn's skill
|
|
2321
|
+
// variant and instruction file reflect the new same- vs.
|
|
2322
|
+
// cross-backend topology. Mirrors the `onMailScopeChanged`
|
|
2323
|
+
// pattern: helper handles the "no active sessions" no-op + per-
|
|
2324
|
+
// session failure containment, and we broadcast a structured
|
|
2325
|
+
// event so the dashboard can refresh without polling.
|
|
2326
|
+
const result = rematerializeActiveDmWorkdirs(reason);
|
|
2327
|
+
if (!result)
|
|
2328
|
+
return;
|
|
2329
|
+
logger.info({ reason, ...result.summary }, "Main backend changed — DM session workdirs re-materialized");
|
|
2330
|
+
eventBroadcaster.broadcastEvent({
|
|
2331
|
+
kind: "main_backend_changed",
|
|
2332
|
+
reason,
|
|
2333
|
+
...result.summary,
|
|
2334
|
+
});
|
|
2335
|
+
},
|
|
2336
|
+
onSetupStart: (mode) => {
|
|
2337
|
+
dispatcher.beginSetupMode(mode);
|
|
2338
|
+
},
|
|
2339
|
+
onSecretChanged: handleSecretChange,
|
|
2340
|
+
onSetupComplete: () => {
|
|
2341
|
+
dispatcher.clearSetupMode();
|
|
2342
|
+
// Kick off an immediate morning routine so today.md is generated
|
|
2343
|
+
// right away — otherwise the user completes setup mid-day and sits
|
|
2344
|
+
// without a populated today.md until 04:00 next morning. We go
|
|
2345
|
+
// through the scheduler's queueMorningRoutineWake so:
|
|
2346
|
+
// - the dedup check prevents a double-run if startup catchup was
|
|
2347
|
+
// about to kick off the same routine (race on first boot after
|
|
2348
|
+
// setup completes)
|
|
2349
|
+
// - the wake task is durable: if the daemon crashes before the
|
|
2350
|
+
// routine runs, it replays on restart via ScheduleWatcher
|
|
2351
|
+
// - ScheduleWatcher obeys the autonomous-work gate, so in the
|
|
2352
|
+
// unlikely case clearSetupMode fails to persist, the gate
|
|
2353
|
+
// still prevents the routine from running mid-setup.
|
|
2354
|
+
// Don't emit roadmap_refresh here — skeleton has "(Not yet configured)"
|
|
2355
|
+
// which isRoadmapStale() detects. The refresh will be triggered by:
|
|
2356
|
+
// - morning_routine's post-completion hook, or
|
|
2357
|
+
// - onGoogleServicesReady (once calendar auth completes)
|
|
2358
|
+
// This avoids generating a roadmap before calendar data is available.
|
|
2359
|
+
try {
|
|
2360
|
+
scheduler.queueMorningRoutineWake("setup_complete");
|
|
2361
|
+
}
|
|
2362
|
+
catch (err) {
|
|
2363
|
+
logger.error({ err }, "Failed to queue post-setup morning routine — today.md will not be generated until next 04:00");
|
|
2364
|
+
}
|
|
2365
|
+
},
|
|
2366
|
+
onPromptContextChanged: (path, reason, tier, metadata) => {
|
|
2367
|
+
// Layer-3 defense for the Customize Your Rules bug: even if the
|
|
2368
|
+
// setup conversation's own agent writes to today/roadmap/management-
|
|
2369
|
+
// rules mid-flight (not typical, but possible via skills), we must
|
|
2370
|
+
// NOT mark the owner-DM session stale. Marking stale would refresh
|
|
2371
|
+
// the conversation_sessions row on the next turn, drop the stored
|
|
2372
|
+
// Claude SDK session_id, and force a fresh `setup.initial` execute
|
|
2373
|
+
// that loses prior Q&A history. Layers 1 (autonomous-work gate)
|
|
2374
|
+
// and 2 (scope-agnostic currentSetupMode) keep prompt selection
|
|
2375
|
+
// correct, but only layer 3 preserves in-conversation continuity.
|
|
2376
|
+
// For loud-tier changes, persist the change moment FIRST so a crash
|
|
2377
|
+
// between the DB write and stale-flag update still leaves the durable
|
|
2378
|
+
// signal in place — the in-memory flag on SessionManager is lost on
|
|
2379
|
+
// restart, so if it were written before the DB write we could lose
|
|
2380
|
+
// the signal entirely on a process crash mid-hook.
|
|
2381
|
+
//
|
|
2382
|
+
// The persisted `dashboard_context_changed_at` is consulted by both
|
|
2383
|
+
// `continueDashboardSession` (resume path) and `getOrCreateDm`
|
|
2384
|
+
// (every DM turn) to decide whether the stored SDK session has gone
|
|
2385
|
+
// stale and must be discarded before resume.
|
|
2386
|
+
handlePromptContextChanged(path, reason, tier, metadata);
|
|
2387
|
+
},
|
|
2388
|
+
onIndexableContextChange: (_path) => {
|
|
2389
|
+
// API-route hint: any successful PUT/PATCH/DELETE under `/context/*`
|
|
2390
|
+
// queues a reconcile. The observer's own chokidar watcher already
|
|
2391
|
+
// catches manual edits the API bypasses (e.g. the user editing via
|
|
2392
|
+
// Obsidian when contextDir *is* the Obsidian vault) — this hint
|
|
2393
|
+
// shortens reconcile latency for API-origin writes from chokidar
|
|
2394
|
+
// debounce + stabilityThreshold to the observer's 10s debounce. The
|
|
2395
|
+
// reconciler short-circuits when nothing changed, so firing for
|
|
2396
|
+
// non-indexed paths is harmless.
|
|
2397
|
+
contextIndexReconciler.requestReconcile("manual");
|
|
2398
|
+
},
|
|
2399
|
+
onCustomRoutinesChanged: () => {
|
|
2400
|
+
try {
|
|
2401
|
+
customRoutineScheduler.reload();
|
|
2402
|
+
}
|
|
2403
|
+
catch (err) {
|
|
2404
|
+
logger.error({ err }, "Custom routine reload failed");
|
|
2405
|
+
}
|
|
2406
|
+
},
|
|
2407
|
+
triggerHourlyCheck: (source, options) => dispatcher.triggerHourlyCheck(source, options),
|
|
2408
|
+
triggerRoadmapRefresh: (source, options) => dispatcher.emitRoadmapRefresh(source, options),
|
|
2409
|
+
endDashboardSession: (channelId) => endDashboardSessionFromChannel({
|
|
2410
|
+
sessionManager,
|
|
2411
|
+
channelId,
|
|
2412
|
+
}),
|
|
2413
|
+
continueDashboardSession: async (sessionId) => continueDashboardSessionFromHistory({
|
|
2414
|
+
db,
|
|
2415
|
+
dataDir: config.dataDir,
|
|
2416
|
+
sessionManager,
|
|
2417
|
+
sessionId,
|
|
2418
|
+
}),
|
|
2419
|
+
writeTracker,
|
|
2420
|
+
blobStore,
|
|
2421
|
+
dashboardAdapter,
|
|
2422
|
+
eventBroadcaster,
|
|
2423
|
+
attachmentStore,
|
|
2424
|
+
auditLogger,
|
|
2425
|
+
validateAttachmentTurnToken: (token) => dispatcher.validateAttachmentTurnToken(token),
|
|
2426
|
+
whatsappControls: {
|
|
2427
|
+
isInitialized: () => whatsappAdapter !== null,
|
|
2428
|
+
enable: async () => {
|
|
2429
|
+
const adapter = buildWhatsAppAdapter();
|
|
2430
|
+
if (adapter.getStatus() === "disabled") {
|
|
2431
|
+
await adapter.start();
|
|
2432
|
+
}
|
|
2433
|
+
},
|
|
2434
|
+
disable: async () => {
|
|
2435
|
+
await teardownWhatsAppAdapter();
|
|
2436
|
+
},
|
|
2437
|
+
requestQr: async () => {
|
|
2438
|
+
if (!whatsappAdapter) {
|
|
2439
|
+
await whatsappControls_enable();
|
|
2440
|
+
}
|
|
2441
|
+
await whatsappAdapter.requestQR();
|
|
2442
|
+
},
|
|
2443
|
+
waitForQr: async (timeoutMs = 10_000) => {
|
|
2444
|
+
if (!whatsappAdapter) {
|
|
2445
|
+
await whatsappControls_enable();
|
|
2446
|
+
}
|
|
2447
|
+
const snapshot = await whatsappAdapter.waitForQr(timeoutMs);
|
|
2448
|
+
return whatsappQrResponseFromAdapter(whatsappAdapter, snapshot);
|
|
2449
|
+
},
|
|
2450
|
+
getQrResponse: () => whatsappQrResponseFromAdapter(whatsappAdapter),
|
|
2451
|
+
},
|
|
2452
|
+
messagingControls: {
|
|
2453
|
+
telegram: buildTelegramControls(),
|
|
2454
|
+
slack: buildSlackControls(),
|
|
2455
|
+
discord: buildDiscordControls(),
|
|
2456
|
+
},
|
|
2457
|
+
});
|
|
2458
|
+
// Local helper avoiding circular reference inside the object literal above.
|
|
2459
|
+
async function whatsappControls_enable() {
|
|
2460
|
+
const adapter = buildWhatsAppAdapter();
|
|
2461
|
+
if (adapter.getStatus() === "disabled") {
|
|
2462
|
+
await adapter.start();
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
// Mount /api/docs/* (DOCS_QA_DESIGN.md §10.4 + DOCS_QA_B7_DESIGN.md
|
|
2466
|
+
// §S5–S6) after createApp so the indexer handle and the QA SSE
|
|
2467
|
+
// adapter can be threaded in without extending ApiDependencies. The
|
|
2468
|
+
// read endpoints don't need messaging/dispatcher deps; the QA
|
|
2469
|
+
// POST/SSE pair leans on `docsQAAdapter` to register clients and
|
|
2470
|
+
// enqueue docs_qa events.
|
|
2471
|
+
app.route("/api", createDocsRoutes({
|
|
2472
|
+
db,
|
|
2473
|
+
...(docsIndexer ? { indexer: docsIndexer } : {}),
|
|
2474
|
+
docsQAAdapter,
|
|
2475
|
+
}));
|
|
2476
|
+
const server = serve({
|
|
2477
|
+
fetch: app.fetch,
|
|
2478
|
+
hostname: "127.0.0.1",
|
|
2479
|
+
port: config.apiPort,
|
|
2480
|
+
});
|
|
2481
|
+
logger.info({ port: config.apiPort }, "API server listening");
|
|
2482
|
+
void dispatcher.run(); // Start consuming dashboard events as soon as the API is live
|
|
2483
|
+
// Notifications Center heartbeat (docs/design/20-notifications-center.md
|
|
2484
|
+
// §"Daemon heartbeat"). MUST start immediately after the API server is
|
|
2485
|
+
// listening — i.e. before any awaited startup work below (catchup,
|
|
2486
|
+
// observers, etc.). `/api/health.lastTickAt` is exposed the moment the
|
|
2487
|
+
// server accepts requests, so any window in which the heartbeat is
|
|
2488
|
+
// constructed but not yet ticking shows the dashboard a stale timestamp
|
|
2489
|
+
// and trips the client-side `system.daemon_frozen` alert (90s threshold).
|
|
2490
|
+
// Startup catchup that runs a morning_routine inline can take several
|
|
2491
|
+
// minutes, so this ordering is load-bearing, not cosmetic.
|
|
2492
|
+
heartbeat.start();
|
|
2493
|
+
// ── 12. Catchup (recover missed actions after restart) ──
|
|
2494
|
+
// Register day boundary callback: summarize DM sessions at 4 AM before morning routine
|
|
2495
|
+
scheduler.setDayBoundaryCallback(async () => {
|
|
2496
|
+
await dispatcher.summarizeDmSessions();
|
|
2497
|
+
});
|
|
2498
|
+
// Register direct DM callback: sends scheduled messages without running an agent
|
|
2499
|
+
scheduler.setSendDmCallback(async (message, platforms) => {
|
|
2500
|
+
if (platforms && platforms.length > 0) {
|
|
2501
|
+
const deliveries = [];
|
|
2502
|
+
for (const platform of platforms) {
|
|
2503
|
+
deliveries.push(await messageHub.sendToPlatform(platform, "user", message));
|
|
2504
|
+
}
|
|
2505
|
+
return deliveries;
|
|
2506
|
+
}
|
|
2507
|
+
return messageHub.sendToUser(message);
|
|
2508
|
+
});
|
|
2509
|
+
scheduler.setHourlyCheckCallback((source) => dispatcher.triggerHourlyCheck(source));
|
|
2510
|
+
// B-004 Phase 2a — nightly context-index reconciler. The design doc
|
|
2511
|
+
// (§4.1, §5.3) originally proposed an `agent_schedule` row with
|
|
2512
|
+
// `task_type: "internal.reconcile_context_index"`, but the dispatcher's
|
|
2513
|
+
// non-"dm" scheduled-task path runs a model-backed task flow. A direct
|
|
2514
|
+
// scheduler-owned cron callback keeps all scheduled work visible in
|
|
2515
|
+
// `AgentScheduler` — the intent of the design — without dispatching a
|
|
2516
|
+
// backend for an internal daemon job.
|
|
2517
|
+
scheduler.setContextIndexReconcilerCallback(() => contextIndexReconciler.requestReconcile("cron"));
|
|
2518
|
+
// Reconciler writes go through the same prompt-cache invalidation path as
|
|
2519
|
+
// API-origin context writes. Installing the sink here (post-dispatcher)
|
|
2520
|
+
// means a reconcile during setup.initial does not destroy in-flight
|
|
2521
|
+
// setup session state — see the matching `onPromptContextChanged`
|
|
2522
|
+
// handler in the createApp dependencies above for the layered guards.
|
|
2523
|
+
contextIndexReconcilerPromptSink = handlePromptContextChanged;
|
|
2524
|
+
// Phase 4 auth probe — runs BEFORE the hourly check on each cron
|
|
2525
|
+
// tick so auth health detection happens independently of the
|
|
2526
|
+
// observation-threshold gate. checkAll() owns its own kill switch
|
|
2527
|
+
// (authProbeDisabled), morning-routine skip, and in-flight dedupe.
|
|
2528
|
+
scheduler.setAuthProbeCallback(() => authHealthMonitor.checkAll());
|
|
2529
|
+
// Wire the autonomous-work gate: when rules/management.md is missing
|
|
2530
|
+
// or a setup conversation is active, the scheduler pauses cron routines
|
|
2531
|
+
// and ScheduleWatcher claims. This prevents any autonomous turn from
|
|
2532
|
+
// racing with the dashboard setup flow and triggering the stale-session
|
|
2533
|
+
// bug that killed setup mode mid-conversation.
|
|
2534
|
+
scheduler.setAutonomousGate(() => dispatcher.isAutonomousAllowed());
|
|
2535
|
+
const startupCatchup = await runCatchup(db, dispatcher, config);
|
|
2536
|
+
// ── 13. Start all components ──
|
|
2537
|
+
await messageHub.startAll();
|
|
2538
|
+
scheduler.start();
|
|
2539
|
+
customRoutineScheduler.start();
|
|
2540
|
+
signalDetector.start();
|
|
2541
|
+
const registeredPlatforms = messageHub.getPlatforms();
|
|
2542
|
+
if (registeredPlatforms.length > 0 && !messageHub.getAdapter(config.primaryPlatform)) {
|
|
2543
|
+
const fallbackPlatform = messageHub.getEffectiveFallbackPlatforms()[0]
|
|
2544
|
+
?? registeredPlatforms[0];
|
|
2545
|
+
logger.error({
|
|
2546
|
+
requestedPrimary: config.primaryPlatform,
|
|
2547
|
+
fallbackPrimary: fallbackPlatform,
|
|
2548
|
+
}, "Primary platform is not registered, falling back");
|
|
2549
|
+
messageHub.setPrimaryPlatform(fallbackPlatform);
|
|
2550
|
+
config.primaryPlatform = fallbackPlatform;
|
|
2551
|
+
}
|
|
2552
|
+
await observerManager.startAll();
|
|
2553
|
+
// Start the integrations.md fs-watcher after the observer manager is up so
|
|
2554
|
+
// a chokidar initialization error surfaces alongside the main observer
|
|
2555
|
+
// lifecycle instead of during boot critical path.
|
|
2556
|
+
try {
|
|
2557
|
+
managementMdWatcher = startManagementMdWatcher(config.dataDir, db, {
|
|
2558
|
+
workspaceDir: config.workspaceDir,
|
|
2559
|
+
// SETUP-FLOW-REDESIGN-PLAN §6.2 — supply live external-vault state
|
|
2560
|
+
// each reconcile so the Note Sources section tracks
|
|
2561
|
+
// `PATCH /api/config` edits that don't touch integrations.md
|
|
2562
|
+
// directly.
|
|
2563
|
+
getNoteSources: () => ({
|
|
2564
|
+
externalObsidianVaultPath: config.externalObsidianVaultPath,
|
|
2565
|
+
externalObsidianWatch: config.externalObsidianWatch,
|
|
2566
|
+
}),
|
|
2567
|
+
sendNotification: async (params) => {
|
|
2568
|
+
await notificationManager.send(params.message, {
|
|
2569
|
+
type: params.notificationType ?? "integration.variant_missing",
|
|
2570
|
+
source: "management-md-watcher",
|
|
2571
|
+
priority: EventPriority.NORMAL,
|
|
2572
|
+
timestamp: new Date(),
|
|
2573
|
+
data: {},
|
|
2574
|
+
correlationId: randomBytes(8).toString("hex"),
|
|
2575
|
+
}, {
|
|
2576
|
+
priority: params.priority ?? "normal",
|
|
2577
|
+
destinationMode: "configured_only",
|
|
2578
|
+
});
|
|
2579
|
+
},
|
|
2580
|
+
});
|
|
2581
|
+
}
|
|
2582
|
+
catch (err) {
|
|
2583
|
+
logger.error({ err }, "integrations.md watcher failed to start");
|
|
2584
|
+
}
|
|
2585
|
+
// Start the rules/management.md fs-watcher alongside its integrations.md
|
|
2586
|
+
// sibling so hand-edits to A-section bindings flow back into the DB. The
|
|
2587
|
+
// watcher is a no-op when the post-setup branch above did not run
|
|
2588
|
+
// (contextDir absent), since chokidar against a missing path is silent.
|
|
2589
|
+
if (isSetupCompleted(db) && !readDegradedMode(db)) {
|
|
2590
|
+
try {
|
|
2591
|
+
managementRegistryWatcher = startManagementRegistryWatcher(getContextDir(config), db);
|
|
2592
|
+
}
|
|
2593
|
+
catch (err) {
|
|
2594
|
+
logger.error({ err }, "rules/management.md watcher failed to start");
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
healthMonitor.start();
|
|
2598
|
+
// heartbeat.start() ran earlier — see the comment near the API listen
|
|
2599
|
+
// call. Keeping it there is required so the dashboard's frozen-alert
|
|
2600
|
+
// does not fire during a long startup catchup.
|
|
2601
|
+
// ── Management Mode health probe (plan §5.4) ──
|
|
2602
|
+
// Poll every 30s. Probe is read-only (no mkdir) so a user deleting their
|
|
2603
|
+
// vault directory flips us to degraded instead of silently re-creating.
|
|
2604
|
+
// Timer runs in all modes so `vaultMode: plain ↔ obsidian` flips via
|
|
2605
|
+
// PATCH /api/config are picked up without restart.
|
|
2606
|
+
const vaultHealthTimer = setInterval(() => {
|
|
2607
|
+
try {
|
|
2608
|
+
const probe = runVaultHealthProbe(config, db);
|
|
2609
|
+
if (probe.action === "entered") {
|
|
2610
|
+
logger.warn({ reason: probe.reason }, "Vault health probe entered degraded mode");
|
|
2611
|
+
}
|
|
2612
|
+
else if (probe.action === "lifted") {
|
|
2613
|
+
logger.info("Vault health probe lifted degraded mode");
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
catch (err) {
|
|
2617
|
+
logger.error({ err }, "vault health probe failed");
|
|
2618
|
+
}
|
|
2619
|
+
}, 30_000);
|
|
2620
|
+
vaultHealthTimer.unref();
|
|
2621
|
+
// Management Mode Phase 2 — daily backup retention sweep. Runs once
|
|
2622
|
+
// per day to remove completed `migration-backups/*` directories whose
|
|
2623
|
+
// 7-day retention has expired. An initial tick fires shortly after
|
|
2624
|
+
// startup so a daemon that runs for less than a full day still sweeps
|
|
2625
|
+
// old backups left from a previous session.
|
|
2626
|
+
const migrationBackupSweepInitial = setTimeout(() => {
|
|
2627
|
+
try {
|
|
2628
|
+
sweepExpiredMigrationBackups(db);
|
|
2629
|
+
}
|
|
2630
|
+
catch (err) {
|
|
2631
|
+
logger.error({ err }, "Initial migration-backup sweep failed");
|
|
2632
|
+
}
|
|
2633
|
+
}, 60_000);
|
|
2634
|
+
migrationBackupSweepInitial.unref();
|
|
2635
|
+
const migrationBackupSweepTimer = setInterval(() => {
|
|
2636
|
+
try {
|
|
2637
|
+
sweepExpiredMigrationBackups(db);
|
|
2638
|
+
}
|
|
2639
|
+
catch (err) {
|
|
2640
|
+
logger.error({ err }, "Daily migration-backup sweep failed");
|
|
2641
|
+
}
|
|
2642
|
+
}, 24 * 60 * 60 * 1000);
|
|
2643
|
+
migrationBackupSweepTimer.unref();
|
|
2644
|
+
startupComplete = true;
|
|
2645
|
+
if (pendingGoogleServicesReady) {
|
|
2646
|
+
pendingGoogleServicesReady = false;
|
|
2647
|
+
handleGoogleServicesReady();
|
|
2648
|
+
}
|
|
2649
|
+
logger.info("All components started");
|
|
2650
|
+
void runPostMessagingCatchup(dispatcher, startupCatchup).catch((err) => {
|
|
2651
|
+
logger.error({ err }, "Post-messaging catchup failed");
|
|
2652
|
+
});
|
|
2653
|
+
// ── 15. Graceful shutdown ──
|
|
2654
|
+
const shutdown = async (signal) => {
|
|
2655
|
+
logger.info({ signal }, "Shutdown signal received");
|
|
2656
|
+
dispatcher.stop(); // Signals dispatcher to exit run() loop
|
|
2657
|
+
scheduler.stop();
|
|
2658
|
+
customRoutineScheduler.stop();
|
|
2659
|
+
healthMonitor.stop();
|
|
2660
|
+
heartbeat.stop();
|
|
2661
|
+
signalDetector.stop();
|
|
2662
|
+
notificationManager.stop(); // Clear pending batch-flush timer
|
|
2663
|
+
authRecovery.shutdown(); // Kill any active recovery subprocesses
|
|
2664
|
+
clearInterval(vaultHealthTimer);
|
|
2665
|
+
clearTimeout(migrationBackupSweepInitial);
|
|
2666
|
+
clearInterval(migrationBackupSweepTimer);
|
|
2667
|
+
if (managementMdWatcher) {
|
|
2668
|
+
await managementMdWatcher.stop().catch((err) => {
|
|
2669
|
+
logger.warn({ err }, "integrations.md watcher stop failed");
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2672
|
+
if (managementRegistryWatcher) {
|
|
2673
|
+
await managementRegistryWatcher.stop().catch((err) => {
|
|
2674
|
+
logger.warn({ err }, "rules/management.md watcher stop failed");
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
if (docsIndexer) {
|
|
2678
|
+
await docsIndexer.stop().catch((err) => {
|
|
2679
|
+
logger.warn({ err }, "docs indexer stop failed");
|
|
2680
|
+
});
|
|
2681
|
+
}
|
|
2682
|
+
await observerManager.stopAll();
|
|
2683
|
+
await messageHub.stopAll();
|
|
2684
|
+
eventBus.close();
|
|
2685
|
+
// Close HTTP server
|
|
2686
|
+
if ("close" in server) {
|
|
2687
|
+
server.close();
|
|
2688
|
+
}
|
|
2689
|
+
db.close();
|
|
2690
|
+
logger.info("Daemon stopped");
|
|
2691
|
+
process.exit(0);
|
|
2692
|
+
};
|
|
2693
|
+
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
2694
|
+
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
2695
|
+
if (process.platform === "win32") {
|
|
2696
|
+
process.on("SIGBREAK", () => void shutdown("SIGBREAK"));
|
|
2697
|
+
}
|
|
2698
|
+
logger.info(`${APP_NAME} Daemon ready`);
|
|
2699
|
+
}
|
|
2700
|
+
async function runCatchup(db, dispatcher, config) {
|
|
2701
|
+
// Setup gate — on first boot (before rules/management.md exists) we must
|
|
2702
|
+
// NOT run the morning routine. Without user/profile.md / rules/management.md
|
|
2703
|
+
// the generated today.md is meaningless AND it fires
|
|
2704
|
+
// onPromptContextChanged → markActiveDmSessionsStale, which destroys the
|
|
2705
|
+
// dashboard setup conversation on the user's next turn. Autonomous
|
|
2706
|
+
// catchup will run on the first normal boot *after* setup completes.
|
|
2707
|
+
const gateReason = dispatcher.isAutonomousAllowed();
|
|
2708
|
+
if (gateReason !== null) {
|
|
2709
|
+
logger.info({ reason: gateReason }, "Skipping startup catchup — autonomous work paused for setup");
|
|
2710
|
+
return {
|
|
2711
|
+
postMessagingRoadmapRefresh: false,
|
|
2712
|
+
postMessagingRoutines: [],
|
|
2713
|
+
postMessagingHourlyCheck: false,
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2716
|
+
const now = new Date();
|
|
2717
|
+
const tz = config.timezone || undefined;
|
|
2718
|
+
const contextDir = getContextDir(config);
|
|
2719
|
+
const todayMdPath = join(contextDir, "today.md");
|
|
2720
|
+
const { start: agentDayStartUtc, end: agentDayEndUtc } = getAgentDayBoundsUtc(tz, config.dayBoundaryHour, now);
|
|
2721
|
+
const skippedPending = discardStalePendingSchedules(db, agentDayStartUtc);
|
|
2722
|
+
if (skippedPending > 0) {
|
|
2723
|
+
logger.warn({ count: skippedPending }, "Discarded stale pending schedules at startup");
|
|
2724
|
+
}
|
|
2725
|
+
const runningRecovery = recoverOrphanedRunningSchedules(db, agentDayStartUtc);
|
|
2726
|
+
if (runningRecovery.skipped > 0 || runningRecovery.failed > 0) {
|
|
2727
|
+
logger.warn(runningRecovery, "Recovered orphaned running schedules without replay");
|
|
2728
|
+
}
|
|
2729
|
+
// Check if morning routine is needed
|
|
2730
|
+
let needsMorning = false;
|
|
2731
|
+
if (existsSync(todayMdPath)) {
|
|
2732
|
+
const firstLine = readFileSync(todayMdPath, "utf-8").split("\n")[0];
|
|
2733
|
+
const today = getAgentDayDateStr(tz, config.dayBoundaryHour, now);
|
|
2734
|
+
if (!firstLine.includes(today)) {
|
|
2735
|
+
needsMorning = true;
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
else {
|
|
2739
|
+
needsMorning = true;
|
|
2740
|
+
}
|
|
2741
|
+
const dueCatchupRoutines = getDueCatchupRoutines(db, config, agentDayStartUtc, agentDayEndUtc, now);
|
|
2742
|
+
const needsHourlyCheckCatchup = shouldCatchUpHourlyCheck(db, config, now);
|
|
2743
|
+
let ranMorningCatchup = false;
|
|
2744
|
+
if (needsMorning) {
|
|
2745
|
+
try {
|
|
2746
|
+
await dispatcher.summarizeDmSessions();
|
|
2747
|
+
}
|
|
2748
|
+
catch (err) {
|
|
2749
|
+
logger.error({ err }, "DM summarization catchup failed before morning routine");
|
|
2750
|
+
}
|
|
2751
|
+
logger.info("Stale today.md detected, running morning_routine catchup inline");
|
|
2752
|
+
await dispatcher.processInline({
|
|
2753
|
+
...createEvent({
|
|
2754
|
+
type: "routine.morning_routine",
|
|
2755
|
+
source: "catchup",
|
|
2756
|
+
priority: EventPriority.HIGH,
|
|
2757
|
+
data: {
|
|
2758
|
+
postCatchupRoutines: dueCatchupRoutines,
|
|
2759
|
+
postCatchupHourlyCheck: needsHourlyCheckCatchup,
|
|
2760
|
+
deferPostMorningCatchupsUntilStartupReady: true,
|
|
2761
|
+
},
|
|
2762
|
+
}),
|
|
2763
|
+
routine: "morning_routine",
|
|
2764
|
+
});
|
|
2765
|
+
ranMorningCatchup = true;
|
|
2766
|
+
if (!hasFreshAgentDayTodayMd(todayMdPath, tz, config.dayBoundaryHour)) {
|
|
2767
|
+
logger.warn("Startup morning catchup did not produce a fresh today.md — deferring remaining catchup work");
|
|
2768
|
+
return {
|
|
2769
|
+
postMessagingRoadmapRefresh: false,
|
|
2770
|
+
postMessagingRoutines: [],
|
|
2771
|
+
postMessagingHourlyCheck: false,
|
|
2772
|
+
};
|
|
2773
|
+
}
|
|
2774
|
+
return {
|
|
2775
|
+
postMessagingRoadmapRefresh: isRoadmapStale(contextDir),
|
|
2776
|
+
postMessagingRoutines: dueCatchupRoutines,
|
|
2777
|
+
postMessagingHourlyCheck: needsHourlyCheckCatchup,
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
if (!ranMorningCatchup && isRoadmapStale(contextDir)) {
|
|
2781
|
+
logger.info("Roadmap stale at startup, running roadmap_refresh catchup inline");
|
|
2782
|
+
await processRoutineCatchup(dispatcher, "roadmap_refresh");
|
|
2783
|
+
}
|
|
2784
|
+
return {
|
|
2785
|
+
postMessagingRoadmapRefresh: false,
|
|
2786
|
+
postMessagingRoutines: dueCatchupRoutines,
|
|
2787
|
+
postMessagingHourlyCheck: needsHourlyCheckCatchup,
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
2790
|
+
async function runPostMessagingCatchup(dispatcher, catchup) {
|
|
2791
|
+
if (catchup.postMessagingRoadmapRefresh) {
|
|
2792
|
+
logger.info("Running roadmap_refresh catchup after messaging startup");
|
|
2793
|
+
await processRoutineCatchup(dispatcher, "roadmap_refresh");
|
|
2794
|
+
}
|
|
2795
|
+
for (const routine of catchup.postMessagingRoutines) {
|
|
2796
|
+
logger.info({ routine }, "Running same-day routine catchup after messaging startup");
|
|
2797
|
+
await processRoutineCatchup(dispatcher, routine);
|
|
2798
|
+
}
|
|
2799
|
+
if (catchup.postMessagingHourlyCheck) {
|
|
2800
|
+
logger.info("Triggering hourly_check catchup after messaging startup");
|
|
2801
|
+
await dispatcher.triggerHourlyCheck("catchup_startup", { force: false });
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
async function processRoutineCatchup(dispatcher, routine) {
|
|
2805
|
+
await dispatcher.processInline({
|
|
2806
|
+
...createEvent({
|
|
2807
|
+
type: `routine.${routine}`,
|
|
2808
|
+
source: "catchup",
|
|
2809
|
+
priority: routine === "hourly_check" ? EventPriority.NORMAL : EventPriority.HIGH,
|
|
2810
|
+
}),
|
|
2811
|
+
routine,
|
|
2812
|
+
});
|
|
2813
|
+
}
|
|
2814
|
+
function getDueCatchupRoutines(db, config, agentDayStartUtc, agentDayEndUtc, now) {
|
|
2815
|
+
const tz = config.timezone || undefined;
|
|
2816
|
+
const progressMinutes = getAgentDayProgressMinutes(tz, config.dayBoundaryHour, now);
|
|
2817
|
+
const dueAt18 = getProgressMinutesForHour(18, config.dayBoundaryHour);
|
|
2818
|
+
if (progressMinutes < dueAt18) {
|
|
2819
|
+
return [];
|
|
2820
|
+
}
|
|
2821
|
+
const routines = [];
|
|
2822
|
+
const agentDayStartMs = parseSqliteUtcMs(agentDayStartUtc);
|
|
2823
|
+
const agentDayLocal = nowInTimezone(tz, new Date(agentDayStartMs));
|
|
2824
|
+
const tomorrowLocal = nowInTimezone(tz, new Date(agentDayStartMs + 24 * 60 * 60 * 1000));
|
|
2825
|
+
if (!hasActionInWindow(db, "routine.evening_review", agentDayStartUtc, agentDayEndUtc)) {
|
|
2826
|
+
routines.push("evening_review");
|
|
2827
|
+
}
|
|
2828
|
+
if (agentDayLocal.dayOfWeek === 5 &&
|
|
2829
|
+
!hasActionInWindow(db, "routine.weekly_review", agentDayStartUtc, agentDayEndUtc)) {
|
|
2830
|
+
routines.push("weekly_review");
|
|
2831
|
+
}
|
|
2832
|
+
if (tomorrowLocal.day === 1 &&
|
|
2833
|
+
!hasActionInWindow(db, "routine.monthly_review", agentDayStartUtc, agentDayEndUtc)) {
|
|
2834
|
+
routines.push("monthly_review");
|
|
2835
|
+
}
|
|
2836
|
+
return routines;
|
|
2837
|
+
}
|
|
2838
|
+
function shouldCatchUpHourlyCheck(db, config, now) {
|
|
2839
|
+
if (!config.hourlyCheckEnabled) {
|
|
2840
|
+
return false;
|
|
2841
|
+
}
|
|
2842
|
+
const tz = config.timezone || undefined;
|
|
2843
|
+
const local = nowInTimezone(tz, now);
|
|
2844
|
+
if (local.hours < config.hourlyCheckActiveStartHour ||
|
|
2845
|
+
local.hours >= config.hourlyCheckActiveEndHour ||
|
|
2846
|
+
local.hours === config.dayBoundaryHour) {
|
|
2847
|
+
return false;
|
|
2848
|
+
}
|
|
2849
|
+
// Slot anchors to `activeStartHour`, mirroring shouldFireHourlyTickAt
|
|
2850
|
+
// in scheduler.ts so the catch-up function picks the same slot the
|
|
2851
|
+
// cron callback would have fired at. The earlier branch already
|
|
2852
|
+
// returned false when local.hours < activeStartHour, so the offset is
|
|
2853
|
+
// always non-negative here.
|
|
2854
|
+
const anchorMinutes = config.hourlyCheckActiveStartHour * 60;
|
|
2855
|
+
const offsetFromAnchor = local.hours * 60 + local.minutes - anchorMinutes;
|
|
2856
|
+
const slotOffsetFromAnchor = Math.floor(offsetFromAnchor / config.hourlyCheckIntervalMinutes) *
|
|
2857
|
+
config.hourlyCheckIntervalMinutes;
|
|
2858
|
+
const slotMinutesSinceMidnight = anchorMinutes + slotOffsetFromAnchor;
|
|
2859
|
+
const dayStartUtc = getAgentDayBoundsUtc(tz, 0, now).start;
|
|
2860
|
+
const slotStartMs = parseSqliteUtcMs(dayStartUtc) + slotMinutesSinceMidnight * 60 * 1000;
|
|
2861
|
+
const slotStartUtc = formatSqliteDatetime(new Date(slotStartMs));
|
|
2862
|
+
return !hasActionInWindow(db, "routine.hourly_check", slotStartUtc, formatSqliteDatetime(now));
|
|
2863
|
+
}
|
|
2864
|
+
function getProgressMinutesForHour(hour, dayBoundaryHour) {
|
|
2865
|
+
const scheduledMinutes = hour * 60;
|
|
2866
|
+
const boundaryMinutes = dayBoundaryHour * 60;
|
|
2867
|
+
return scheduledMinutes >= boundaryMinutes
|
|
2868
|
+
? scheduledMinutes - boundaryMinutes
|
|
2869
|
+
: 24 * 60 - boundaryMinutes + scheduledMinutes;
|
|
2870
|
+
}
|
|
2871
|
+
function hasFreshAgentDayTodayMd(todayMdPath, timezone, dayBoundaryHour) {
|
|
2872
|
+
if (!existsSync(todayMdPath)) {
|
|
2873
|
+
return false;
|
|
2874
|
+
}
|
|
2875
|
+
const firstLine = readFileSync(todayMdPath, "utf-8").split("\n")[0] ?? "";
|
|
2876
|
+
const today = getAgentDayDateStr(timezone, dayBoundaryHour);
|
|
2877
|
+
return firstLine.includes(today);
|
|
2878
|
+
}
|
|
2879
|
+
// P22 — read the operator's chosen cadence for skill curation runs.
|
|
2880
|
+
// Mirrors the helper in `core/scheduler.ts` so the dispatcher hook here can
|
|
2881
|
+
// resolve cadence at runtime without crossing module boundaries.
|
|
2882
|
+
function readSkillCurationCadence(db) {
|
|
2883
|
+
const row = db
|
|
2884
|
+
.prepare(`SELECT value_json FROM runtime_state WHERE key = 'skill_curation.config'`)
|
|
2885
|
+
.get();
|
|
2886
|
+
if (!row)
|
|
2887
|
+
return "weekly";
|
|
2888
|
+
try {
|
|
2889
|
+
const v = JSON.parse(row.value_json);
|
|
2890
|
+
return v.cadence ?? "weekly";
|
|
2891
|
+
}
|
|
2892
|
+
catch {
|
|
2893
|
+
return "weekly";
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
// ── Global safety net ──
|
|
2897
|
+
// Catch unhandled rejections from fire-and-forget patterns (void async calls)
|
|
2898
|
+
// so they are logged before Node.js 22+ terminates the process.
|
|
2899
|
+
process.on("unhandledRejection", (reason) => {
|
|
2900
|
+
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
2901
|
+
logger.fatal({ error: err.message, stack: err.stack }, "Unhandled promise rejection — this is a bug");
|
|
2902
|
+
});
|
|
2903
|
+
process.on("uncaughtException", (err) => {
|
|
2904
|
+
logger.fatal({ error: err.message, stack: err.stack }, "Uncaught exception — process will exit");
|
|
2905
|
+
process.exit(1);
|
|
2906
|
+
});
|
|
2907
|
+
// ── Entry point ──
|
|
2908
|
+
startup().catch((err) => {
|
|
2909
|
+
const e = err;
|
|
2910
|
+
logger.fatal({ error: e?.message, code: e?.code, stack: e?.stack }, "Daemon startup failed");
|
|
2911
|
+
process.exit(1);
|
|
2912
|
+
});
|
|
2913
|
+
//# sourceMappingURL=index.js.map
|