@aitne/daemon 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/whatsapp-adapter.d.ts.map +1 -1
- package/dist/adapters/whatsapp-adapter.js +0 -1
- package/dist/adapters/whatsapp-adapter.js.map +1 -1
- package/dist/api/integration-route-gate.d.ts +15 -11
- package/dist/api/integration-route-gate.d.ts.map +1 -1
- package/dist/api/integration-route-gate.js +60 -23
- package/dist/api/integration-route-gate.js.map +1 -1
- package/dist/api/json-body.d.ts +22 -7
- package/dist/api/json-body.d.ts.map +1 -1
- package/dist/api/json-body.js +27 -8
- package/dist/api/json-body.js.map +1 -1
- package/dist/api/routes/agent.d.ts.map +1 -1
- package/dist/api/routes/agent.js +18 -0
- package/dist/api/routes/agent.js.map +1 -1
- package/dist/api/routes/backends.d.ts.map +1 -1
- package/dist/api/routes/backends.js +96 -1
- package/dist/api/routes/backends.js.map +1 -1
- package/dist/api/routes/books.js +1 -1
- package/dist/api/routes/books.js.map +1 -1
- package/dist/api/routes/context.d.ts.map +1 -1
- package/dist/api/routes/context.js +13 -1
- package/dist/api/routes/context.js.map +1 -1
- package/dist/api/routes/dashboard.d.ts.map +1 -1
- package/dist/api/routes/dashboard.js +75 -5
- package/dist/api/routes/dashboard.js.map +1 -1
- package/dist/api/routes/github.d.ts.map +1 -1
- package/dist/api/routes/github.js +38 -5
- package/dist/api/routes/github.js.map +1 -1
- package/dist/api/routes/integrations.d.ts +35 -6
- package/dist/api/routes/integrations.d.ts.map +1 -1
- package/dist/api/routes/integrations.js +191 -16
- package/dist/api/routes/integrations.js.map +1 -1
- package/dist/api/routes/mail.d.ts.map +1 -1
- package/dist/api/routes/mail.js +112 -46
- package/dist/api/routes/mail.js.map +1 -1
- package/dist/api/routes/observations.d.ts.map +1 -1
- package/dist/api/routes/observations.js +161 -8
- package/dist/api/routes/observations.js.map +1 -1
- package/dist/api/routes/setup-migrate.d.ts +9 -1
- package/dist/api/routes/setup-migrate.d.ts.map +1 -1
- package/dist/api/routes/setup-migrate.js +4 -2
- package/dist/api/routes/setup-migrate.js.map +1 -1
- package/dist/api/routes/skills.d.ts.map +1 -1
- package/dist/api/routes/skills.js +39 -1
- package/dist/api/routes/skills.js.map +1 -1
- package/dist/api/routes/voice.d.ts.map +1 -1
- package/dist/api/routes/voice.js +62 -4
- package/dist/api/routes/voice.js.map +1 -1
- package/dist/bootstrap/adapters.d.ts +109 -0
- package/dist/bootstrap/adapters.d.ts.map +1 -0
- package/dist/bootstrap/adapters.js +237 -0
- package/dist/bootstrap/adapters.js.map +1 -0
- package/dist/bootstrap/catchup.d.ts +23 -0
- package/dist/bootstrap/catchup.d.ts.map +1 -0
- package/dist/bootstrap/catchup.js +124 -0
- package/dist/bootstrap/catchup.js.map +1 -0
- package/dist/bootstrap/schedule-helpers.d.ts +18 -0
- package/dist/bootstrap/schedule-helpers.d.ts.map +1 -0
- package/dist/bootstrap/schedule-helpers.js +96 -0
- package/dist/bootstrap/schedule-helpers.js.map +1 -0
- package/dist/bootstrap/services.d.ts +60 -0
- package/dist/bootstrap/services.d.ts.map +1 -0
- package/dist/bootstrap/services.js +209 -0
- package/dist/bootstrap/services.js.map +1 -0
- package/dist/core/backends/backend-router.d.ts +23 -0
- package/dist/core/backends/backend-router.d.ts.map +1 -1
- package/dist/core/backends/backend-router.js +48 -3
- package/dist/core/backends/backend-router.js.map +1 -1
- package/dist/core/backends/claude-auth.d.ts +70 -0
- package/dist/core/backends/claude-auth.d.ts.map +1 -0
- package/dist/core/backends/claude-auth.js +198 -0
- package/dist/core/backends/claude-auth.js.map +1 -0
- package/dist/core/backends/claude-code-core.d.ts +47 -119
- package/dist/core/backends/claude-code-core.d.ts.map +1 -1
- package/dist/core/backends/claude-code-core.js +112 -1565
- package/dist/core/backends/claude-code-core.js.map +1 -1
- package/dist/core/backends/claude-delegated.d.ts +86 -0
- package/dist/core/backends/claude-delegated.d.ts.map +1 -0
- package/dist/core/backends/claude-delegated.js +801 -0
- package/dist/core/backends/claude-delegated.js.map +1 -0
- package/dist/core/backends/claude-errors.d.ts +39 -0
- package/dist/core/backends/claude-errors.d.ts.map +1 -0
- package/dist/core/backends/claude-errors.js +71 -0
- package/dist/core/backends/claude-errors.js.map +1 -0
- package/dist/core/backends/claude-probe.d.ts +103 -0
- package/dist/core/backends/claude-probe.d.ts.map +1 -0
- package/dist/core/backends/claude-probe.js +336 -0
- package/dist/core/backends/claude-probe.js.map +1 -0
- package/dist/core/backends/claude-tool-collection.d.ts +135 -0
- package/dist/core/backends/claude-tool-collection.d.ts.map +1 -0
- package/dist/core/backends/claude-tool-collection.js +831 -0
- package/dist/core/backends/claude-tool-collection.js.map +1 -0
- package/dist/core/backends/gemini-cli-core.d.ts +21 -0
- package/dist/core/backends/gemini-cli-core.d.ts.map +1 -1
- package/dist/core/backends/gemini-cli-core.js +84 -6
- package/dist/core/backends/gemini-cli-core.js.map +1 -1
- package/dist/core/backends/prompt-utils.d.ts +1 -0
- package/dist/core/backends/prompt-utils.d.ts.map +1 -1
- package/dist/core/backends/prompt-utils.js +60 -3
- package/dist/core/backends/prompt-utils.js.map +1 -1
- package/dist/core/context-builder.d.ts +36 -12
- package/dist/core/context-builder.d.ts.map +1 -1
- package/dist/core/context-builder.js +179 -89
- package/dist/core/context-builder.js.map +1 -1
- package/dist/core/dispatcher-date-utils.d.ts +49 -0
- package/dist/core/dispatcher-date-utils.d.ts.map +1 -0
- package/dist/core/dispatcher-date-utils.js +132 -0
- package/dist/core/dispatcher-date-utils.js.map +1 -0
- package/dist/core/dispatcher-error-handling.d.ts +159 -0
- package/dist/core/dispatcher-error-handling.d.ts.map +1 -0
- package/dist/core/dispatcher-error-handling.js +393 -0
- package/dist/core/dispatcher-error-handling.js.map +1 -0
- package/dist/core/dispatcher-hourly-check.d.ts +150 -0
- package/dist/core/dispatcher-hourly-check.d.ts.map +1 -0
- package/dist/core/dispatcher-hourly-check.js +665 -0
- package/dist/core/dispatcher-hourly-check.js.map +1 -0
- package/dist/core/dispatcher-message-handler.d.ts +170 -0
- package/dist/core/dispatcher-message-handler.d.ts.map +1 -0
- package/dist/core/dispatcher-message-handler.js +1054 -0
- package/dist/core/dispatcher-message-handler.js.map +1 -0
- package/dist/core/dispatcher-morning-routine.d.ts +169 -0
- package/dist/core/dispatcher-morning-routine.d.ts.map +1 -0
- package/dist/core/dispatcher-morning-routine.js +434 -0
- package/dist/core/dispatcher-morning-routine.js.map +1 -0
- package/dist/core/dispatcher-prompt.d.ts +107 -0
- package/dist/core/dispatcher-prompt.d.ts.map +1 -0
- package/dist/core/dispatcher-prompt.js +227 -0
- package/dist/core/dispatcher-prompt.js.map +1 -0
- package/dist/core/dispatcher-repository-helpers.d.ts +39 -0
- package/dist/core/dispatcher-repository-helpers.d.ts.map +1 -0
- package/dist/core/dispatcher-repository-helpers.js +86 -0
- package/dist/core/dispatcher-repository-helpers.js.map +1 -0
- package/dist/core/dispatcher-result-processor.d.ts +145 -0
- package/dist/core/dispatcher-result-processor.d.ts.map +1 -0
- package/dist/core/dispatcher-result-processor.js +414 -0
- package/dist/core/dispatcher-result-processor.js.map +1 -0
- package/dist/core/dispatcher-scheduled-tasks.d.ts +406 -0
- package/dist/core/dispatcher-scheduled-tasks.d.ts.map +1 -0
- package/dist/core/dispatcher-scheduled-tasks.js +998 -0
- package/dist/core/dispatcher-scheduled-tasks.js.map +1 -0
- package/dist/core/dispatcher-types.d.ts +296 -0
- package/dist/core/dispatcher-types.d.ts.map +1 -0
- package/dist/core/dispatcher-types.js +106 -0
- package/dist/core/dispatcher-types.js.map +1 -0
- package/dist/core/dispatcher.d.ts +86 -610
- package/dist/core/dispatcher.d.ts.map +1 -1
- package/dist/core/dispatcher.js +293 -3542
- package/dist/core/dispatcher.js.map +1 -1
- package/dist/core/integration-health.d.ts +18 -10
- package/dist/core/integration-health.d.ts.map +1 -1
- package/dist/core/integration-health.js +31 -1
- package/dist/core/integration-health.js.map +1 -1
- package/dist/core/integration-lifecycle.d.ts +65 -0
- package/dist/core/integration-lifecycle.d.ts.map +1 -1
- package/dist/core/integration-lifecycle.js +167 -16
- package/dist/core/integration-lifecycle.js.map +1 -1
- package/dist/core/integration-main-backend.d.ts +40 -0
- package/dist/core/integration-main-backend.d.ts.map +1 -1
- package/dist/core/integration-main-backend.js +89 -2
- package/dist/core/integration-main-backend.js.map +1 -1
- package/dist/core/management-md.d.ts +51 -17
- package/dist/core/management-md.d.ts.map +1 -1
- package/dist/core/management-md.js +233 -56
- package/dist/core/management-md.js.map +1 -1
- package/dist/core/output-language-policy.d.ts +74 -0
- package/dist/core/output-language-policy.d.ts.map +1 -0
- package/dist/core/output-language-policy.js +194 -0
- package/dist/core/output-language-policy.js.map +1 -0
- package/dist/core/prompts.d.ts +1 -0
- package/dist/core/prompts.d.ts.map +1 -1
- package/dist/core/prompts.js +121 -3
- package/dist/core/prompts.js.map +1 -1
- package/dist/core/repository-management-docs.d.ts +24 -0
- package/dist/core/repository-management-docs.d.ts.map +1 -1
- package/dist/core/repository-management-docs.js +210 -26
- package/dist/core/repository-management-docs.js.map +1 -1
- package/dist/core/routine-acquisition-plan.d.ts +131 -0
- package/dist/core/routine-acquisition-plan.d.ts.map +1 -0
- package/dist/core/routine-acquisition-plan.js +268 -0
- package/dist/core/routine-acquisition-plan.js.map +1 -0
- package/dist/core/routine-fetch-window-runner.d.ts +201 -0
- package/dist/core/routine-fetch-window-runner.d.ts.map +1 -0
- package/dist/core/routine-fetch-window-runner.js +661 -0
- package/dist/core/routine-fetch-window-runner.js.map +1 -0
- package/dist/core/routine-windows.d.ts +156 -0
- package/dist/core/routine-windows.d.ts.map +1 -0
- package/dist/core/routine-windows.js +330 -0
- package/dist/core/routine-windows.js.map +1 -0
- package/dist/core/skills-compiler.d.ts +11 -0
- package/dist/core/skills-compiler.d.ts.map +1 -1
- package/dist/core/skills-compiler.js +102 -13
- package/dist/core/skills-compiler.js.map +1 -1
- package/dist/core/skills-manifest.d.ts.map +1 -1
- package/dist/core/skills-manifest.js +26 -0
- package/dist/core/skills-manifest.js.map +1 -1
- package/dist/core/system-reset.d.ts.map +1 -1
- package/dist/core/system-reset.js +25 -2
- package/dist/core/system-reset.js.map +1 -1
- package/dist/db/observations.d.ts +45 -2
- package/dist/db/observations.d.ts.map +1 -1
- package/dist/db/observations.js +112 -14
- package/dist/db/observations.js.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +13 -25
- package/dist/db/schema.js.map +1 -1
- package/dist/index.js +83 -610
- package/dist/index.js.map +1 -1
- package/dist/observers/delegated-sync-worker.d.ts +45 -2
- package/dist/observers/delegated-sync-worker.d.ts.map +1 -1
- package/dist/observers/delegated-sync-worker.js +71 -21
- package/dist/observers/delegated-sync-worker.js.map +1 -1
- package/dist/observers/mail-poller.d.ts +12 -5
- package/dist/observers/mail-poller.d.ts.map +1 -1
- package/dist/observers/mail-poller.js +36 -14
- package/dist/observers/mail-poller.js.map +1 -1
- package/dist/observers/manager.d.ts +37 -5
- package/dist/observers/manager.d.ts.map +1 -1
- package/dist/observers/manager.js +28 -10
- package/dist/observers/manager.js.map +1 -1
- package/dist/services/delegated-backend-invoker.d.ts +1 -51
- package/dist/services/delegated-backend-invoker.d.ts.map +1 -1
- package/dist/services/delegated-backend-invoker.js +41 -480
- package/dist/services/delegated-backend-invoker.js.map +1 -1
- package/dist/services/delegated-invoker-audit.d.ts +94 -0
- package/dist/services/delegated-invoker-audit.d.ts.map +1 -0
- package/dist/services/delegated-invoker-audit.js +238 -0
- package/dist/services/delegated-invoker-audit.js.map +1 -0
- package/dist/services/delegated-invoker-cache-hits.d.ts +34 -0
- package/dist/services/delegated-invoker-cache-hits.d.ts.map +1 -0
- package/dist/services/delegated-invoker-cache-hits.js +104 -0
- package/dist/services/delegated-invoker-cache-hits.js.map +1 -0
- package/dist/services/delegated-invoker-janitors.d.ts +28 -0
- package/dist/services/delegated-invoker-janitors.d.ts.map +1 -0
- package/dist/services/delegated-invoker-janitors.js +104 -0
- package/dist/services/delegated-invoker-janitors.js.map +1 -0
- package/dist/services/delegated-invoker-utils.d.ts +42 -0
- package/dist/services/delegated-invoker-utils.d.ts.map +1 -0
- package/dist/services/delegated-invoker-utils.js +100 -0
- package/dist/services/delegated-invoker-utils.js.map +1 -0
- package/dist/services/delegated-task-runtime.d.ts +1 -1
- package/dist/services/delegated-task-runtime.js +1 -1
- package/dist/services/integrations/snapshot-partitions.d.ts +5 -0
- package/dist/services/integrations/snapshot-partitions.d.ts.map +1 -1
- package/dist/services/integrations/snapshot-partitions.js +12 -0
- package/dist/services/integrations/snapshot-partitions.js.map +1 -1
- package/dist/services/voice/transcriber-impl.d.ts.map +1 -1
- package/dist/services/voice/transcriber-impl.js +7 -8
- package/dist/services/voice/transcriber-impl.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { serve } from "@hono/node-server";
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
3
2
|
import { join, resolve } from "node:path";
|
|
4
3
|
import { randomBytes } from "node:crypto";
|
|
5
4
|
import { loadConfig, getContextDir, isRoadmapStale, mergeRuntimeSettingsFromDb, runVaultHealthProbe, validateExternalObsidianVaultPath, } from "./config.js";
|
|
@@ -30,21 +29,14 @@ import { RepositoryManagementCron } from "./observers/repository-management-cron
|
|
|
30
29
|
import { ObservationSummarizerWorker, AnthropicSummarizerClient, UnsupportedSummarizerClient, } from "./observers/observation-summarizer/index.js";
|
|
31
30
|
import { CalendarPoller } from "./observers/calendar-poller.js";
|
|
32
31
|
import { ImminentEventScheduler } from "./observers/imminent-event-scheduler.js";
|
|
33
|
-
import { DelegatedSyncWorker,
|
|
32
|
+
import { DelegatedSyncWorker, hasActiveCadenceSyncIntegration, } from "./observers/delegated-sync-worker.js";
|
|
34
33
|
import { applyIntegrationModeChange, shouldStartObserversFor, } from "./core/integration-lifecycle.js";
|
|
35
34
|
import { NotionPoller } from "./observers/notion-poller.js";
|
|
36
35
|
import { DiscordAdapter } from "./adapters/discord.js";
|
|
37
36
|
import { SlackAdapter } from "./adapters/slack-adapter.js";
|
|
38
37
|
import { TelegramAdapter } from "./adapters/telegram-adapter.js";
|
|
39
38
|
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
39
|
import { ObsidianService } from "./services/obsidian.js";
|
|
46
|
-
import { NotionService } from "./services/notion.js";
|
|
47
|
-
import { GitHubService } from "./services/github.js";
|
|
48
40
|
import { createServiceRegistry } from "./services/service-registry.js";
|
|
49
41
|
import { SignalDetector } from "./core/signal-detector.js";
|
|
50
42
|
import { EventDispatcher } from "./core/dispatcher.js";
|
|
@@ -69,7 +61,7 @@ import { NotificationManager } from "./adapters/notification-manager.js";
|
|
|
69
61
|
import { recordProactiveForwardDeliveries } from "./core/channel-timeline.js";
|
|
70
62
|
import { continueDashboardSession as continueDashboardSessionFromHistory, endDashboardSession as endDashboardSessionFromChannel, markContextChanged, } from "./core/dashboard-session-controls.js";
|
|
71
63
|
import { AuditLogger } from "./safety/audit.js";
|
|
72
|
-
import { bootstrapManagementMd,
|
|
64
|
+
import { bootstrapManagementMd, startManagementMdWatcher, } from "./core/management-md.js";
|
|
73
65
|
import { bootstrapManagementRegistry, startManagementRegistryWatcher, } from "./core/management-registry.js";
|
|
74
66
|
import { bootstrapManagedTaskSeq } from "./db/managed-tasks-store.js";
|
|
75
67
|
import { startDocsIndexer, } from "./core/docs/indexer.js";
|
|
@@ -79,11 +71,10 @@ import { DocsQAAdapter } from "./adapters/docs-qa-adapter.js";
|
|
|
79
71
|
import { CompositeDashboardStream } from "./adapters/composite-dashboard-stream.js";
|
|
80
72
|
import { createApp } from "./api/server.js";
|
|
81
73
|
import { EventBroadcaster } from "./api/routes/sse.js";
|
|
82
|
-
import { APP_NAME,
|
|
74
|
+
import { APP_NAME, EventPriority, getBackendIds, } from "@aitne/shared";
|
|
83
75
|
import { getOwnerChannel } from "./messaging/owner-channels.js";
|
|
84
76
|
import { SUPPORTED_MESSAGING_PLATFORMS, } from "./messaging/constants.js";
|
|
85
77
|
import { AgentWriteTracker } from "./safety/agent-write-tracker.js";
|
|
86
|
-
import { discardStalePendingSchedules, hasActionInWindow, recoverOrphanedRunningSchedules, } from "./core/schedule-maintenance.js";
|
|
87
78
|
import { ContextWriteGate, InMemoryTodayWriteLockManager, MigrationLock, getTodayWriteLockTimeoutMs, } from "./core/today-write-lock.js";
|
|
88
79
|
import { applyPromptContextStaleness, } from "./core/context-staleness.js";
|
|
89
80
|
import { InMemoryRoadmapWriteLockManager, getRoadmapWriteLockTimeoutMs, } from "./core/roadmap-write-lock.js";
|
|
@@ -103,13 +94,15 @@ import { parseImapAccountSecret } from "./services/mail/imap/app-password.js";
|
|
|
103
94
|
import { ICloudImapProvider } from "./services/mail/imap/icloud-provider.js";
|
|
104
95
|
import { YahooImapProvider } from "./services/mail/imap/yahoo-provider.js";
|
|
105
96
|
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
97
|
import { MailPoller } from "./observers/mail-poller.js";
|
|
109
98
|
import { MailReconciliationJob } from "./observers/mail-reconciliation.js";
|
|
110
99
|
import { SecretBroker } from "./secrets/secret-broker.js";
|
|
111
100
|
import { captureOriginalShellEnv, syncBackendApiKeyToEnv, } from "./secrets/backend-api-key-env.js";
|
|
112
101
|
import { createLogger, toSafeErrorMessage } from "./logging.js";
|
|
102
|
+
import { runCatchup, runPostMessagingCatchup, } from "./bootstrap/catchup.js";
|
|
103
|
+
import { hasFreshAgentDayTodayMd, readSkillCurationCadence, } from "./bootstrap/schedule-helpers.js";
|
|
104
|
+
import { createAdapterReloaders, whatsappQrResponseFromAdapter, } from "./bootstrap/adapters.js";
|
|
105
|
+
import { createInitialSecretState, createServiceReloaders, } from "./bootstrap/services.js";
|
|
113
106
|
const logger = createLogger("daemon", {
|
|
114
107
|
transport: {
|
|
115
108
|
target: "pino-pretty",
|
|
@@ -227,7 +220,6 @@ async function startup() {
|
|
|
227
220
|
let managementMdWatcher = null;
|
|
228
221
|
let managementRegistryWatcher = null;
|
|
229
222
|
try {
|
|
230
|
-
migrateLegacyManagementMd(config.dataDir);
|
|
231
223
|
await bootstrapManagementMd(config.dataDir, db, config.workspaceDir, {
|
|
232
224
|
externalObsidianVaultPath: config.externalObsidianVaultPath,
|
|
233
225
|
externalObsidianWatch: config.externalObsidianWatch,
|
|
@@ -407,185 +399,34 @@ async function startup() {
|
|
|
407
399
|
logger.warn({ err, platform }, "Failed to deliver welcome DM after auto-pairing");
|
|
408
400
|
}
|
|
409
401
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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 });
|
|
402
|
+
const adapterState = {
|
|
403
|
+
discord: null,
|
|
404
|
+
slack: null,
|
|
405
|
+
telegram: null,
|
|
406
|
+
whatsapp: null,
|
|
407
|
+
};
|
|
408
|
+
const { reloadDiscordAdapter, reloadSlackAdapter, reloadTelegramAdapter, buildWhatsAppAdapter, teardownWhatsAppAdapter, enableWhatsAppAdapter, } = createAdapterReloaders({
|
|
409
|
+
config,
|
|
410
|
+
secretBroker,
|
|
411
|
+
messageHub,
|
|
412
|
+
eventBus,
|
|
413
|
+
attachmentStore,
|
|
414
|
+
recordDetectedOwner,
|
|
415
|
+
onWhatsAppLoggedOut: async () => {
|
|
525
416
|
try {
|
|
526
|
-
await
|
|
527
|
-
messageHub.setPlatformRuntimeStatus("telegram", { runtimeState: "ok", error: null });
|
|
417
|
+
await messageHub.sendToUser("WhatsApp session logged out — re-run foreground pairing");
|
|
528
418
|
}
|
|
529
419
|
catch (err) {
|
|
530
|
-
|
|
531
|
-
messageHub.setPlatformRuntimeStatus("telegram", { runtimeState: "error", error: message });
|
|
532
|
-
logger.error({ err }, "Failed to start Telegram adapter during reload");
|
|
420
|
+
logger.error({ err }, "Failed to deliver WhatsApp logout notification via fallback channel");
|
|
533
421
|
}
|
|
534
|
-
}
|
|
535
|
-
|
|
422
|
+
},
|
|
423
|
+
state: adapterState,
|
|
424
|
+
});
|
|
536
425
|
await Promise.all([
|
|
537
426
|
reloadDiscordAdapter(false),
|
|
538
427
|
reloadSlackAdapter(false),
|
|
539
428
|
reloadTelegramAdapter(false),
|
|
540
429
|
]);
|
|
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
430
|
if (config.whatsappEnabled) {
|
|
590
431
|
if (!config.whatsappOwnerPhone) {
|
|
591
432
|
throw new Error("PA_WHATSAPP_ENABLED=true but PA_WHATSAPP_OWNER_PHONE is not set");
|
|
@@ -637,7 +478,7 @@ async function startup() {
|
|
|
637
478
|
},
|
|
638
479
|
startPairing: async (ttlMs = 5 * 60_000) => {
|
|
639
480
|
assertAdapterReady("telegram");
|
|
640
|
-
const adapter =
|
|
481
|
+
const adapter = adapterState.telegram;
|
|
641
482
|
if (!adapter) {
|
|
642
483
|
throw new Error("Telegram adapter is not initialized. Save the token and retry.");
|
|
643
484
|
}
|
|
@@ -697,12 +538,12 @@ async function startup() {
|
|
|
697
538
|
};
|
|
698
539
|
},
|
|
699
540
|
getPairingStatus: () => ({
|
|
700
|
-
paired:
|
|
701
|
-
ownerChatId:
|
|
702
|
-
pairingActive:
|
|
541
|
+
paired: adapterState.telegram?.getOwnerChatId() !== null,
|
|
542
|
+
ownerChatId: adapterState.telegram?.getOwnerChatId() ?? null,
|
|
543
|
+
pairingActive: adapterState.telegram?.isPairingActive() ?? false,
|
|
703
544
|
}),
|
|
704
545
|
cancelPairing: () => {
|
|
705
|
-
|
|
546
|
+
adapterState.telegram?.cancelPairing();
|
|
706
547
|
},
|
|
707
548
|
};
|
|
708
549
|
}
|
|
@@ -724,7 +565,7 @@ async function startup() {
|
|
|
724
565
|
},
|
|
725
566
|
startPairing: async (ttlMs = 5 * 60_000) => {
|
|
726
567
|
assertAdapterReady("slack");
|
|
727
|
-
const adapter =
|
|
568
|
+
const adapter = adapterState.slack;
|
|
728
569
|
if (!adapter) {
|
|
729
570
|
throw new Error("Slack adapter is not initialized. Save the tokens and retry.");
|
|
730
571
|
}
|
|
@@ -743,12 +584,12 @@ async function startup() {
|
|
|
743
584
|
return { phrase, expiresAt };
|
|
744
585
|
},
|
|
745
586
|
cancelPairing: () => {
|
|
746
|
-
|
|
587
|
+
adapterState.slack?.cancelPairing();
|
|
747
588
|
},
|
|
748
589
|
getPairingStatus: () => ({
|
|
749
|
-
paired:
|
|
750
|
-
ownerUserId:
|
|
751
|
-
pairingActive:
|
|
590
|
+
paired: adapterState.slack?.getOwnerUserId() !== null,
|
|
591
|
+
ownerUserId: adapterState.slack?.getOwnerUserId() ?? null,
|
|
592
|
+
pairingActive: adapterState.slack?.isPairingActive() ?? false,
|
|
752
593
|
}),
|
|
753
594
|
};
|
|
754
595
|
}
|
|
@@ -770,7 +611,7 @@ async function startup() {
|
|
|
770
611
|
},
|
|
771
612
|
startPairing: async (ttlMs = 5 * 60_000) => {
|
|
772
613
|
assertAdapterReady("discord");
|
|
773
|
-
const adapter =
|
|
614
|
+
const adapter = adapterState.discord;
|
|
774
615
|
if (!adapter) {
|
|
775
616
|
throw new Error("Discord adapter is not initialized. Save the token and retry.");
|
|
776
617
|
}
|
|
@@ -784,38 +625,15 @@ async function startup() {
|
|
|
784
625
|
return { phrase, expiresAt };
|
|
785
626
|
},
|
|
786
627
|
cancelPairing: () => {
|
|
787
|
-
|
|
628
|
+
adapterState.discord?.cancelPairing();
|
|
788
629
|
},
|
|
789
630
|
getPairingStatus: () => ({
|
|
790
|
-
paired:
|
|
791
|
-
ownerUserId:
|
|
792
|
-
pairingActive:
|
|
631
|
+
paired: adapterState.discord?.getOwnerUserId() !== null,
|
|
632
|
+
ownerUserId: adapterState.discord?.getOwnerUserId() ?? null,
|
|
633
|
+
pairingActive: adapterState.discord?.isPairingActive() ?? false,
|
|
793
634
|
}),
|
|
794
635
|
};
|
|
795
636
|
}
|
|
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
637
|
// Dashboard adapter — always registered (activates on SSE connect)
|
|
820
638
|
const dashboardAdapter = new DashboardAdapter((event) => void eventBus.put(event));
|
|
821
639
|
messageHub.register(dashboardAdapter);
|
|
@@ -882,96 +700,14 @@ async function startup() {
|
|
|
882
700
|
},
|
|
883
701
|
},
|
|
884
702
|
});
|
|
885
|
-
const secretState =
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
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
|
-
}
|
|
703
|
+
const secretState = createInitialSecretState();
|
|
704
|
+
const { reloadGoogleServices, reloadAppleCalendarService, reloadNotionService, reloadGitHubService, } = createServiceReloaders({
|
|
705
|
+
db,
|
|
706
|
+
config,
|
|
707
|
+
secretBroker,
|
|
708
|
+
services,
|
|
709
|
+
secretState,
|
|
710
|
+
});
|
|
975
711
|
// Google Maps (F-08: commute optimization)
|
|
976
712
|
{
|
|
977
713
|
const { GoogleMapsService } = await import("./services/google-maps.js");
|
|
@@ -1007,78 +743,6 @@ async function startup() {
|
|
|
1007
743
|
else if (config.externalObsidianVaultPath && !config.externalObsidianVaultName) {
|
|
1008
744
|
services.errors.obsidian = "externalObsidianVaultName is required for the Obsidian CLI service (externalObsidianVaultPath alone enables file watching only)";
|
|
1009
745
|
}
|
|
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
746
|
await Promise.all([
|
|
1083
747
|
reloadGoogleServices(),
|
|
1084
748
|
reloadAppleCalendarService(),
|
|
@@ -1088,7 +752,7 @@ async function startup() {
|
|
|
1088
752
|
/** Build integration status snapshot for /api/health */
|
|
1089
753
|
const getIntegrationStatus = () => {
|
|
1090
754
|
const whatsappState = config.whatsappEnabled
|
|
1091
|
-
? (
|
|
755
|
+
? (adapterState.whatsapp?.getStatus() ?? "disabled")
|
|
1092
756
|
: "not_configured";
|
|
1093
757
|
return {
|
|
1094
758
|
google: {
|
|
@@ -1720,7 +1384,7 @@ async function startup() {
|
|
|
1720
1384
|
}
|
|
1721
1385
|
return delegatedSyncWorker;
|
|
1722
1386
|
};
|
|
1723
|
-
if (
|
|
1387
|
+
if (hasActiveCadenceSyncIntegration(db)) {
|
|
1724
1388
|
observerManager.register(buildDelegatedSyncWorker());
|
|
1725
1389
|
}
|
|
1726
1390
|
// ── Delegated probe observer (DELEGATED-MODE-V2 §7.1) ──
|
|
@@ -2071,17 +1735,7 @@ async function startup() {
|
|
|
2071
1735
|
// (same logic as runCatchup, ensures schedule generation after first auth)
|
|
2072
1736
|
const contextDir = getContextDir(config);
|
|
2073
1737
|
const todayMdPath = join(contextDir, "today.md");
|
|
2074
|
-
|
|
2075
|
-
if (existsSync(todayMdPath)) {
|
|
2076
|
-
const firstLine = readFileSync(todayMdPath, "utf-8").split("\n")[0];
|
|
2077
|
-
const today = getAgentDayDateStr(config.timezone || undefined, config.dayBoundaryHour);
|
|
2078
|
-
if (!firstLine.includes(today)) {
|
|
2079
|
-
needsMorning = true;
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2082
|
-
else {
|
|
2083
|
-
needsMorning = true;
|
|
2084
|
-
}
|
|
1738
|
+
const needsMorning = !hasFreshAgentDayTodayMd(todayMdPath, config.timezone || undefined, config.dayBoundaryHour);
|
|
2085
1739
|
if (needsMorning) {
|
|
2086
1740
|
// Morning routine's post-completion hook will also check roadmap staleness.
|
|
2087
1741
|
logger.info("Google services ready — today.md stale, queueing morning_routine wake");
|
|
@@ -2433,7 +2087,7 @@ async function startup() {
|
|
|
2433
2087
|
auditLogger,
|
|
2434
2088
|
validateAttachmentTurnToken: (token) => dispatcher.validateAttachmentTurnToken(token),
|
|
2435
2089
|
whatsappControls: {
|
|
2436
|
-
isInitialized: () =>
|
|
2090
|
+
isInitialized: () => adapterState.whatsapp !== null,
|
|
2437
2091
|
enable: async () => {
|
|
2438
2092
|
const adapter = buildWhatsAppAdapter();
|
|
2439
2093
|
if (adapter.getStatus() === "disabled") {
|
|
@@ -2444,19 +2098,19 @@ async function startup() {
|
|
|
2444
2098
|
await teardownWhatsAppAdapter();
|
|
2445
2099
|
},
|
|
2446
2100
|
requestQr: async () => {
|
|
2447
|
-
if (!
|
|
2448
|
-
await
|
|
2101
|
+
if (!adapterState.whatsapp) {
|
|
2102
|
+
await enableWhatsAppAdapter();
|
|
2449
2103
|
}
|
|
2450
|
-
await
|
|
2104
|
+
await adapterState.whatsapp.requestQR();
|
|
2451
2105
|
},
|
|
2452
2106
|
waitForQr: async (timeoutMs = 10_000) => {
|
|
2453
|
-
if (!
|
|
2454
|
-
await
|
|
2107
|
+
if (!adapterState.whatsapp) {
|
|
2108
|
+
await enableWhatsAppAdapter();
|
|
2455
2109
|
}
|
|
2456
|
-
const snapshot = await
|
|
2457
|
-
return whatsappQrResponseFromAdapter(
|
|
2110
|
+
const snapshot = await adapterState.whatsapp.waitForQr(timeoutMs);
|
|
2111
|
+
return whatsappQrResponseFromAdapter(adapterState.whatsapp, snapshot);
|
|
2458
2112
|
},
|
|
2459
|
-
getQrResponse: () => whatsappQrResponseFromAdapter(
|
|
2113
|
+
getQrResponse: () => whatsappQrResponseFromAdapter(adapterState.whatsapp),
|
|
2460
2114
|
},
|
|
2461
2115
|
messagingControls: {
|
|
2462
2116
|
telegram: buildTelegramControls(),
|
|
@@ -2464,13 +2118,6 @@ async function startup() {
|
|
|
2464
2118
|
discord: buildDiscordControls(),
|
|
2465
2119
|
},
|
|
2466
2120
|
});
|
|
2467
|
-
// Local helper avoiding circular reference inside the object literal above.
|
|
2468
|
-
async function whatsappControls_enable() {
|
|
2469
|
-
const adapter = buildWhatsAppAdapter();
|
|
2470
|
-
if (adapter.getStatus() === "disabled") {
|
|
2471
|
-
await adapter.start();
|
|
2472
|
-
}
|
|
2473
|
-
}
|
|
2474
2121
|
// Mount /api/docs/* (DOCS_QA_DESIGN.md §10.4 + DOCS_QA_B7_DESIGN.md
|
|
2475
2122
|
// §S5–S6) after createApp so the indexer handle and the QA SSE
|
|
2476
2123
|
// adapter can be threaded in without extending ApiDependencies. The
|
|
@@ -2486,6 +2133,22 @@ async function startup() {
|
|
|
2486
2133
|
fetch: app.fetch,
|
|
2487
2134
|
hostname: "127.0.0.1",
|
|
2488
2135
|
port: config.apiPort,
|
|
2136
|
+
// @hono/node-server's getRequestListener defaults to replacing
|
|
2137
|
+
// `globalThis.Request` / `globalThis.Response` with its own lazy
|
|
2138
|
+
// wrapper classes (named `_Request` / `_Response`) for response-
|
|
2139
|
+
// body materialization performance. The wrappers are prototype-
|
|
2140
|
+
// chained to the native classes — fine for Hono's own response
|
|
2141
|
+
// path — but they break `instanceof Response` checks for objects
|
|
2142
|
+
// returned by native `fetch()`, because a native Response's
|
|
2143
|
+
// prototype chain doesn't include `_Response.prototype`. This
|
|
2144
|
+
// makes `@huggingface/transformers`'s `toCacheResponse` check
|
|
2145
|
+
// (`response instanceof Response && response.status === 200`)
|
|
2146
|
+
// evaluate to false on fresh fetches, which silently skips
|
|
2147
|
+
// `cache.put` and then throws "Unable to get model file path or
|
|
2148
|
+
// buffer." Disabling the override keeps the native globals
|
|
2149
|
+
// intact and costs us nothing — we don't construct Hono's
|
|
2150
|
+
// `Response` instances directly anywhere in the daemon.
|
|
2151
|
+
overrideGlobalObjects: false,
|
|
2489
2152
|
});
|
|
2490
2153
|
logger.info({ port: config.apiPort }, "API server listening");
|
|
2491
2154
|
void dispatcher.run(); // Start consuming dashboard events as soon as the API is live
|
|
@@ -2706,202 +2369,12 @@ async function startup() {
|
|
|
2706
2369
|
}
|
|
2707
2370
|
logger.info(`${APP_NAME} Daemon ready`);
|
|
2708
2371
|
}
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
// catchup will run on the first normal boot *after* setup completes.
|
|
2716
|
-
const gateReason = dispatcher.isAutonomousAllowed();
|
|
2717
|
-
if (gateReason !== null) {
|
|
2718
|
-
logger.info({ reason: gateReason }, "Skipping startup catchup — autonomous work paused for setup");
|
|
2719
|
-
return {
|
|
2720
|
-
postMessagingRoadmapRefresh: false,
|
|
2721
|
-
postMessagingRoutines: [],
|
|
2722
|
-
postMessagingHourlyCheck: false,
|
|
2723
|
-
};
|
|
2724
|
-
}
|
|
2725
|
-
const now = new Date();
|
|
2726
|
-
const tz = config.timezone || undefined;
|
|
2727
|
-
const contextDir = getContextDir(config);
|
|
2728
|
-
const todayMdPath = join(contextDir, "today.md");
|
|
2729
|
-
const { start: agentDayStartUtc, end: agentDayEndUtc } = getAgentDayBoundsUtc(tz, config.dayBoundaryHour, now);
|
|
2730
|
-
const skippedPending = discardStalePendingSchedules(db, agentDayStartUtc);
|
|
2731
|
-
if (skippedPending > 0) {
|
|
2732
|
-
logger.warn({ count: skippedPending }, "Discarded stale pending schedules at startup");
|
|
2733
|
-
}
|
|
2734
|
-
const runningRecovery = recoverOrphanedRunningSchedules(db, agentDayStartUtc);
|
|
2735
|
-
if (runningRecovery.skipped > 0 || runningRecovery.failed > 0) {
|
|
2736
|
-
logger.warn(runningRecovery, "Recovered orphaned running schedules without replay");
|
|
2737
|
-
}
|
|
2738
|
-
// Check if morning routine is needed
|
|
2739
|
-
let needsMorning = false;
|
|
2740
|
-
if (existsSync(todayMdPath)) {
|
|
2741
|
-
const firstLine = readFileSync(todayMdPath, "utf-8").split("\n")[0];
|
|
2742
|
-
const today = getAgentDayDateStr(tz, config.dayBoundaryHour, now);
|
|
2743
|
-
if (!firstLine.includes(today)) {
|
|
2744
|
-
needsMorning = true;
|
|
2745
|
-
}
|
|
2746
|
-
}
|
|
2747
|
-
else {
|
|
2748
|
-
needsMorning = true;
|
|
2749
|
-
}
|
|
2750
|
-
const dueCatchupRoutines = getDueCatchupRoutines(db, config, agentDayStartUtc, agentDayEndUtc, now);
|
|
2751
|
-
const needsHourlyCheckCatchup = shouldCatchUpHourlyCheck(db, config, now);
|
|
2752
|
-
let ranMorningCatchup = false;
|
|
2753
|
-
if (needsMorning) {
|
|
2754
|
-
try {
|
|
2755
|
-
await dispatcher.summarizeDmSessions();
|
|
2756
|
-
}
|
|
2757
|
-
catch (err) {
|
|
2758
|
-
logger.error({ err }, "DM summarization catchup failed before morning routine");
|
|
2759
|
-
}
|
|
2760
|
-
logger.info("Stale today.md detected, running morning_routine catchup inline");
|
|
2761
|
-
await dispatcher.processInline({
|
|
2762
|
-
...createEvent({
|
|
2763
|
-
type: "routine.morning_routine",
|
|
2764
|
-
source: "catchup",
|
|
2765
|
-
priority: EventPriority.HIGH,
|
|
2766
|
-
data: {
|
|
2767
|
-
postCatchupRoutines: dueCatchupRoutines,
|
|
2768
|
-
postCatchupHourlyCheck: needsHourlyCheckCatchup,
|
|
2769
|
-
deferPostMorningCatchupsUntilStartupReady: true,
|
|
2770
|
-
},
|
|
2771
|
-
}),
|
|
2772
|
-
routine: "morning_routine",
|
|
2773
|
-
});
|
|
2774
|
-
ranMorningCatchup = true;
|
|
2775
|
-
if (!hasFreshAgentDayTodayMd(todayMdPath, tz, config.dayBoundaryHour)) {
|
|
2776
|
-
logger.warn("Startup morning catchup did not produce a fresh today.md — deferring remaining catchup work");
|
|
2777
|
-
return {
|
|
2778
|
-
postMessagingRoadmapRefresh: false,
|
|
2779
|
-
postMessagingRoutines: [],
|
|
2780
|
-
postMessagingHourlyCheck: false,
|
|
2781
|
-
};
|
|
2782
|
-
}
|
|
2783
|
-
return {
|
|
2784
|
-
postMessagingRoadmapRefresh: isRoadmapStale(contextDir),
|
|
2785
|
-
postMessagingRoutines: dueCatchupRoutines,
|
|
2786
|
-
postMessagingHourlyCheck: needsHourlyCheckCatchup,
|
|
2787
|
-
};
|
|
2788
|
-
}
|
|
2789
|
-
if (!ranMorningCatchup && isRoadmapStale(contextDir)) {
|
|
2790
|
-
logger.info("Roadmap stale at startup, running roadmap_refresh catchup inline");
|
|
2791
|
-
await processRoutineCatchup(dispatcher, "roadmap_refresh");
|
|
2792
|
-
}
|
|
2793
|
-
return {
|
|
2794
|
-
postMessagingRoadmapRefresh: false,
|
|
2795
|
-
postMessagingRoutines: dueCatchupRoutines,
|
|
2796
|
-
postMessagingHourlyCheck: needsHourlyCheckCatchup,
|
|
2797
|
-
};
|
|
2798
|
-
}
|
|
2799
|
-
async function runPostMessagingCatchup(dispatcher, catchup) {
|
|
2800
|
-
if (catchup.postMessagingRoadmapRefresh) {
|
|
2801
|
-
logger.info("Running roadmap_refresh catchup after messaging startup");
|
|
2802
|
-
await processRoutineCatchup(dispatcher, "roadmap_refresh");
|
|
2803
|
-
}
|
|
2804
|
-
for (const routine of catchup.postMessagingRoutines) {
|
|
2805
|
-
logger.info({ routine }, "Running same-day routine catchup after messaging startup");
|
|
2806
|
-
await processRoutineCatchup(dispatcher, routine);
|
|
2807
|
-
}
|
|
2808
|
-
if (catchup.postMessagingHourlyCheck) {
|
|
2809
|
-
logger.info("Triggering hourly_check catchup after messaging startup");
|
|
2810
|
-
await dispatcher.triggerHourlyCheck("catchup_startup", { force: false });
|
|
2811
|
-
}
|
|
2812
|
-
}
|
|
2813
|
-
async function processRoutineCatchup(dispatcher, routine) {
|
|
2814
|
-
await dispatcher.processInline({
|
|
2815
|
-
...createEvent({
|
|
2816
|
-
type: `routine.${routine}`,
|
|
2817
|
-
source: "catchup",
|
|
2818
|
-
priority: routine === "hourly_check" ? EventPriority.NORMAL : EventPriority.HIGH,
|
|
2819
|
-
}),
|
|
2820
|
-
routine,
|
|
2821
|
-
});
|
|
2822
|
-
}
|
|
2823
|
-
function getDueCatchupRoutines(db, config, agentDayStartUtc, agentDayEndUtc, now) {
|
|
2824
|
-
const tz = config.timezone || undefined;
|
|
2825
|
-
const progressMinutes = getAgentDayProgressMinutes(tz, config.dayBoundaryHour, now);
|
|
2826
|
-
const dueAt18 = getProgressMinutesForHour(18, config.dayBoundaryHour);
|
|
2827
|
-
if (progressMinutes < dueAt18) {
|
|
2828
|
-
return [];
|
|
2829
|
-
}
|
|
2830
|
-
const routines = [];
|
|
2831
|
-
const agentDayStartMs = parseSqliteUtcMs(agentDayStartUtc);
|
|
2832
|
-
const agentDayLocal = nowInTimezone(tz, new Date(agentDayStartMs));
|
|
2833
|
-
const tomorrowLocal = nowInTimezone(tz, new Date(agentDayStartMs + 24 * 60 * 60 * 1000));
|
|
2834
|
-
if (!hasActionInWindow(db, "routine.evening_review", agentDayStartUtc, agentDayEndUtc)) {
|
|
2835
|
-
routines.push("evening_review");
|
|
2836
|
-
}
|
|
2837
|
-
if (agentDayLocal.dayOfWeek === 5 &&
|
|
2838
|
-
!hasActionInWindow(db, "routine.weekly_review", agentDayStartUtc, agentDayEndUtc)) {
|
|
2839
|
-
routines.push("weekly_review");
|
|
2840
|
-
}
|
|
2841
|
-
if (tomorrowLocal.day === 1 &&
|
|
2842
|
-
!hasActionInWindow(db, "routine.monthly_review", agentDayStartUtc, agentDayEndUtc)) {
|
|
2843
|
-
routines.push("monthly_review");
|
|
2844
|
-
}
|
|
2845
|
-
return routines;
|
|
2846
|
-
}
|
|
2847
|
-
function shouldCatchUpHourlyCheck(db, config, now) {
|
|
2848
|
-
if (!config.hourlyCheckEnabled) {
|
|
2849
|
-
return false;
|
|
2850
|
-
}
|
|
2851
|
-
const tz = config.timezone || undefined;
|
|
2852
|
-
const local = nowInTimezone(tz, now);
|
|
2853
|
-
if (local.hours < config.hourlyCheckActiveStartHour ||
|
|
2854
|
-
local.hours >= config.hourlyCheckActiveEndHour ||
|
|
2855
|
-
local.hours === config.dayBoundaryHour) {
|
|
2856
|
-
return false;
|
|
2857
|
-
}
|
|
2858
|
-
// Slot anchors to `activeStartHour`, mirroring shouldFireHourlyTickAt
|
|
2859
|
-
// in scheduler.ts so the catch-up function picks the same slot the
|
|
2860
|
-
// cron callback would have fired at. The earlier branch already
|
|
2861
|
-
// returned false when local.hours < activeStartHour, so the offset is
|
|
2862
|
-
// always non-negative here.
|
|
2863
|
-
const anchorMinutes = config.hourlyCheckActiveStartHour * 60;
|
|
2864
|
-
const offsetFromAnchor = local.hours * 60 + local.minutes - anchorMinutes;
|
|
2865
|
-
const slotOffsetFromAnchor = Math.floor(offsetFromAnchor / config.hourlyCheckIntervalMinutes) *
|
|
2866
|
-
config.hourlyCheckIntervalMinutes;
|
|
2867
|
-
const slotMinutesSinceMidnight = anchorMinutes + slotOffsetFromAnchor;
|
|
2868
|
-
const dayStartUtc = getAgentDayBoundsUtc(tz, 0, now).start;
|
|
2869
|
-
const slotStartMs = parseSqliteUtcMs(dayStartUtc) + slotMinutesSinceMidnight * 60 * 1000;
|
|
2870
|
-
const slotStartUtc = formatSqliteDatetime(new Date(slotStartMs));
|
|
2871
|
-
return !hasActionInWindow(db, "routine.hourly_check", slotStartUtc, formatSqliteDatetime(now));
|
|
2872
|
-
}
|
|
2873
|
-
function getProgressMinutesForHour(hour, dayBoundaryHour) {
|
|
2874
|
-
const scheduledMinutes = hour * 60;
|
|
2875
|
-
const boundaryMinutes = dayBoundaryHour * 60;
|
|
2876
|
-
return scheduledMinutes >= boundaryMinutes
|
|
2877
|
-
? scheduledMinutes - boundaryMinutes
|
|
2878
|
-
: 24 * 60 - boundaryMinutes + scheduledMinutes;
|
|
2879
|
-
}
|
|
2880
|
-
function hasFreshAgentDayTodayMd(todayMdPath, timezone, dayBoundaryHour) {
|
|
2881
|
-
if (!existsSync(todayMdPath)) {
|
|
2882
|
-
return false;
|
|
2883
|
-
}
|
|
2884
|
-
const firstLine = readFileSync(todayMdPath, "utf-8").split("\n")[0] ?? "";
|
|
2885
|
-
const today = getAgentDayDateStr(timezone, dayBoundaryHour);
|
|
2886
|
-
return firstLine.includes(today);
|
|
2887
|
-
}
|
|
2888
|
-
// P22 — read the operator's chosen cadence for skill curation runs.
|
|
2889
|
-
// Mirrors the helper in `core/scheduler.ts` so the dispatcher hook here can
|
|
2890
|
-
// resolve cadence at runtime without crossing module boundaries.
|
|
2891
|
-
function readSkillCurationCadence(db) {
|
|
2892
|
-
const row = db
|
|
2893
|
-
.prepare(`SELECT value_json FROM runtime_state WHERE key = 'skill_curation.config'`)
|
|
2894
|
-
.get();
|
|
2895
|
-
if (!row)
|
|
2896
|
-
return "weekly";
|
|
2897
|
-
try {
|
|
2898
|
-
const v = JSON.parse(row.value_json);
|
|
2899
|
-
return v.cadence ?? "weekly";
|
|
2900
|
-
}
|
|
2901
|
-
catch {
|
|
2902
|
-
return "weekly";
|
|
2903
|
-
}
|
|
2904
|
-
}
|
|
2372
|
+
// Catchup (`runCatchup` / `runPostMessagingCatchup`) and the pure schedule
|
|
2373
|
+
// predicates (`getDueCatchupRoutines`, `shouldCatchUpHourlyCheck`,
|
|
2374
|
+
// `getProgressMinutesForHour`, `hasFreshAgentDayTodayMd`,
|
|
2375
|
+
// `readSkillCurationCadence`) live in `./bootstrap/` — see
|
|
2376
|
+
// `docs/design/appendices/file-split-plan.md` §10. Imports are at the top
|
|
2377
|
+
// of this file.
|
|
2905
2378
|
// ── Global safety net ──
|
|
2906
2379
|
// Catch unhandled rejections from fire-and-forget patterns (void async calls)
|
|
2907
2380
|
// so they are logged before Node.js 22+ terminates the process.
|