@runcore-sh/runcore 0.4.0 → 0.5.1
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/dictionary.json +2 -2
- package/dist/activity/log.js +2 -2
- package/dist/activity/log.js.map +1 -1
- package/dist/agents/governed-spawn.d.ts.map +1 -1
- package/dist/cli.js +101 -11
- package/dist/cli.js.map +1 -1
- package/dist/extensions/cache.d.ts +57 -0
- package/dist/extensions/cache.d.ts.map +1 -0
- package/dist/extensions/cache.js +173 -0
- package/dist/extensions/cache.js.map +1 -0
- package/dist/extensions/client.d.ts +55 -0
- package/dist/extensions/client.d.ts.map +1 -0
- package/dist/extensions/client.js +120 -0
- package/dist/extensions/client.js.map +1 -0
- package/dist/extensions/index.d.ts +13 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/index.js +12 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/extensions/loader.d.ts +50 -0
- package/dist/extensions/loader.d.ts.map +1 -0
- package/dist/extensions/loader.js +166 -0
- package/dist/extensions/loader.js.map +1 -0
- package/dist/extensions/manifest.d.ts +38 -0
- package/dist/extensions/manifest.d.ts.map +1 -0
- package/dist/extensions/manifest.js +17 -0
- package/dist/extensions/manifest.js.map +1 -0
- package/dist/extensions/stubs.d.ts +27 -0
- package/dist/extensions/stubs.d.ts.map +1 -0
- package/dist/extensions/stubs.js +45 -0
- package/dist/extensions/stubs.js.map +1 -0
- package/dist/lib/audit.js +2 -2
- package/dist/lib/audit.js.map +1 -1
- package/dist/lib/brain-migrate.d.ts +21 -0
- package/dist/lib/brain-migrate.d.ts.map +1 -0
- package/dist/lib/brain-migrate.js +137 -0
- package/dist/lib/brain-migrate.js.map +1 -0
- package/dist/lib/paths.d.ts +27 -0
- package/dist/lib/paths.d.ts.map +1 -1
- package/dist/lib/paths.js +65 -0
- package/dist/lib/paths.js.map +1 -1
- package/dist/llm/call-log.d.ts +40 -0
- package/dist/llm/call-log.d.ts.map +1 -0
- package/dist/llm/call-log.js +35 -0
- package/dist/llm/call-log.js.map +1 -0
- package/dist/llm/complete.d.ts +6 -0
- package/dist/llm/complete.d.ts.map +1 -1
- package/dist/llm/complete.js +27 -0
- package/dist/llm/complete.js.map +1 -1
- package/dist/mcp-server.js +118 -2
- package/dist/mcp-server.js.map +1 -1
- package/dist/memory/file-backed.d.ts +4 -0
- package/dist/memory/file-backed.d.ts.map +1 -1
- package/dist/memory/file-backed.js +4 -0
- package/dist/memory/file-backed.js.map +1 -1
- package/dist/memory/vector-index.d.ts +4 -12
- package/dist/memory/vector-index.d.ts.map +1 -1
- package/dist/memory/vector-index.js +11 -93
- package/dist/memory/vector-index.js.map +1 -1
- package/dist/search/brain-docs.d.ts +17 -7
- package/dist/search/brain-docs.d.ts.map +1 -1
- package/dist/search/brain-docs.js +170 -52
- package/dist/search/brain-docs.js.map +1 -1
- package/dist/search/brain-rag.d.ts +45 -0
- package/dist/search/brain-rag.d.ts.map +1 -0
- package/dist/search/brain-rag.js +275 -0
- package/dist/search/brain-rag.js.map +1 -0
- package/dist/search/chunker.d.ts +24 -0
- package/dist/search/chunker.d.ts.map +1 -0
- package/dist/search/chunker.js +95 -0
- package/dist/search/chunker.js.map +1 -0
- package/dist/search/embedder.d.ts +16 -0
- package/dist/search/embedder.d.ts.map +1 -0
- package/dist/search/embedder.js +108 -0
- package/dist/search/embedder.js.map +1 -0
- package/dist/search/file-watcher.d.ts +11 -0
- package/dist/search/file-watcher.d.ts.map +1 -0
- package/dist/search/file-watcher.js +86 -0
- package/dist/search/file-watcher.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +814 -472
- package/dist/server.js.map +1 -1
- package/dist/sessions/store.d.ts +9 -0
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js.map +1 -1
- package/dist/settings.d.ts +26 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +78 -2
- package/dist/settings.js.map +1 -1
- package/dist/tracing/init.d.ts +1 -1
- package/dist/tracing/init.d.ts.map +1 -1
- package/dist/utils/logger.js +2 -2
- package/dist/utils/logger.js.map +1 -1
- package/module-tiers.json +164 -0
- package/package.json +9 -13
- package/public/avatar/cache/1184385ec5522b57.mp4 +0 -0
- package/public/avatar/cache/1f15f6a1ebd7e439.mp4 +0 -0
- package/public/avatar/cache/2c7e47ff0bdeb8d1.mp4 +0 -0
- package/public/avatar/cache/5f308566f7abb8f2.mp4 +0 -0
- package/public/avatar/cache/62f9cfba848d724e.mp4 +0 -0
- package/public/avatar/cache/6d64e657e6bf2aab.mp4 +0 -0
- package/public/avatar/cache/763ad0349e0b6f26.mp4 +0 -0
- package/public/avatar/cache/81a516cfd461b2b9.mp4 +0 -0
- package/public/avatar/cache/9366de15fd6910ca.mp4 +0 -0
- package/public/avatar/cache/ade41a846b283895.mp4 +0 -0
- package/public/avatar/cache/b6066e5c65383eec.mp4 +0 -0
- package/public/avatar/cache/edadb75d37891fc7.mp4 +0 -0
- package/public/avatar/cache/f0ae159640621dd9.mp4 +0 -0
- package/public/avatar/cache/fc2e5419adf29d96.mp4 +0 -0
- package/public/index.html +379 -59
- package/dist/agents/autonomous.js +0 -749
- package/dist/agents/autonomous.js.map +0 -1
- package/dist/agents/commit.js +0 -113
- package/dist/agents/commit.js.map +0 -1
- package/dist/agents/continue.js +0 -158
- package/dist/agents/continue.js.map +0 -1
- package/dist/agents/cooldown.js +0 -397
- package/dist/agents/cooldown.js.map +0 -1
- package/dist/agents/dedup-guard.js +0 -131
- package/dist/agents/dedup-guard.js.map +0 -1
- package/dist/agents/feed.js +0 -176
- package/dist/agents/feed.js.map +0 -1
- package/dist/agents/governance.js +0 -292
- package/dist/agents/governance.js.map +0 -1
- package/dist/agents/governed-spawn.js +0 -192
- package/dist/agents/governed-spawn.js.map +0 -1
- package/dist/agents/heartbeat.js +0 -324
- package/dist/agents/heartbeat.js.map +0 -1
- package/dist/agents/instance-manager.js +0 -850
- package/dist/agents/instance-manager.js.map +0 -1
- package/dist/agents/issue-reporter.js +0 -123
- package/dist/agents/issue-reporter.js.map +0 -1
- package/dist/agents/issues.js +0 -141
- package/dist/agents/issues.js.map +0 -1
- package/dist/agents/locks.js +0 -234
- package/dist/agents/locks.js.map +0 -1
- package/dist/agents/memory.js +0 -93
- package/dist/agents/memory.js.map +0 -1
- package/dist/agents/monitor.js +0 -235
- package/dist/agents/monitor.js.map +0 -1
- package/dist/agents/orchestration.js +0 -715
- package/dist/agents/orchestration.js.map +0 -1
- package/dist/agents/recover.js +0 -166
- package/dist/agents/recover.js.map +0 -1
- package/dist/agents/reflection.js +0 -199
- package/dist/agents/reflection.js.map +0 -1
- package/dist/agents/runtime/bus.js +0 -174
- package/dist/agents/runtime/bus.js.map +0 -1
- package/dist/agents/runtime/config.js +0 -101
- package/dist/agents/runtime/config.js.map +0 -1
- package/dist/agents/runtime/driver.js +0 -214
- package/dist/agents/runtime/driver.js.map +0 -1
- package/dist/agents/runtime/errors.js +0 -40
- package/dist/agents/runtime/errors.js.map +0 -1
- package/dist/agents/runtime/index.js +0 -54
- package/dist/agents/runtime/index.js.map +0 -1
- package/dist/agents/runtime/lifecycle.js +0 -116
- package/dist/agents/runtime/lifecycle.js.map +0 -1
- package/dist/agents/runtime/manager.js +0 -948
- package/dist/agents/runtime/manager.js.map +0 -1
- package/dist/agents/runtime/registry.js +0 -195
- package/dist/agents/runtime/registry.js.map +0 -1
- package/dist/agents/runtime/resources.js +0 -146
- package/dist/agents/runtime/resources.js.map +0 -1
- package/dist/agents/runtime/types.js +0 -24
- package/dist/agents/runtime/types.js.map +0 -1
- package/dist/agents/spawn-policy.js +0 -202
- package/dist/agents/spawn-policy.js.map +0 -1
- package/dist/agents/spawn.js +0 -970
- package/dist/agents/spawn.js.map +0 -1
- package/dist/agents/triage.js +0 -81
- package/dist/agents/triage.js.map +0 -1
- package/dist/agents/workflow.js +0 -543
- package/dist/agents/workflow.js.map +0 -1
- package/dist/avatar/client.js +0 -172
- package/dist/avatar/client.js.map +0 -1
- package/dist/avatar/sidecar.js +0 -125
- package/dist/avatar/sidecar.js.map +0 -1
- package/dist/browser/sessions.js +0 -122
- package/dist/browser/sessions.js.map +0 -1
- package/dist/capabilities/definitions/browser.js +0 -242
- package/dist/capabilities/definitions/browser.js.map +0 -1
- package/dist/channels/whatsapp.js +0 -200
- package/dist/channels/whatsapp.js.map +0 -1
- package/dist/credentials/store.js +0 -189
- package/dist/credentials/store.js.map +0 -1
- package/dist/files/deep-index.js +0 -337
- package/dist/files/deep-index.js.map +0 -1
- package/dist/files/extract.js +0 -33
- package/dist/files/extract.js.map +0 -1
- package/dist/files/gdrive.js +0 -246
- package/dist/files/gdrive.js.map +0 -1
- package/dist/github/client.js +0 -408
- package/dist/github/client.js.map +0 -1
- package/dist/github/commit-analysis.js +0 -276
- package/dist/github/commit-analysis.js.map +0 -1
- package/dist/github/contributor-stats.js +0 -119
- package/dist/github/contributor-stats.js.map +0 -1
- package/dist/github/issue-sla.js +0 -220
- package/dist/github/issue-sla.js.map +0 -1
- package/dist/github/issue-triage.js +0 -286
- package/dist/github/issue-triage.js.map +0 -1
- package/dist/github/pr-readiness.js +0 -197
- package/dist/github/pr-readiness.js.map +0 -1
- package/dist/github/pr-review.js +0 -410
- package/dist/github/pr-review.js.map +0 -1
- package/dist/github/release-notes.js +0 -227
- package/dist/github/release-notes.js.map +0 -1
- package/dist/github/repo-health.js +0 -303
- package/dist/github/repo-health.js.map +0 -1
- package/dist/github/retry.js +0 -117
- package/dist/github/retry.js.map +0 -1
- package/dist/github/types.js +0 -8
- package/dist/github/types.js.map +0 -1
- package/dist/github/webhooks.js +0 -153
- package/dist/github/webhooks.js.map +0 -1
- package/dist/google/auth.js +0 -325
- package/dist/google/auth.js.map +0 -1
- package/dist/google/calendar-timer.js +0 -91
- package/dist/google/calendar-timer.js.map +0 -1
- package/dist/google/calendar.js +0 -270
- package/dist/google/calendar.js.map +0 -1
- package/dist/google/docs.js +0 -309
- package/dist/google/docs.js.map +0 -1
- package/dist/google/gmail-send.js +0 -219
- package/dist/google/gmail-send.js.map +0 -1
- package/dist/google/gmail-timer.js +0 -223
- package/dist/google/gmail-timer.js.map +0 -1
- package/dist/google/gmail.js +0 -470
- package/dist/google/gmail.js.map +0 -1
- package/dist/google/plugin.js +0 -169
- package/dist/google/plugin.js.map +0 -1
- package/dist/google/tasks-timer.js +0 -107
- package/dist/google/tasks-timer.js.map +0 -1
- package/dist/google/tasks.js +0 -331
- package/dist/google/tasks.js.map +0 -1
- package/dist/google/temporal.js +0 -176
- package/dist/google/temporal.js.map +0 -1
- package/dist/integrations/gate.js +0 -100
- package/dist/integrations/gate.js.map +0 -1
- package/dist/integrations/github.js +0 -331
- package/dist/integrations/github.js.map +0 -1
- package/dist/integrations/google-tasks.js +0 -432
- package/dist/integrations/google-tasks.js.map +0 -1
- package/dist/mdns.js +0 -110
- package/dist/mdns.js.map +0 -1
- package/dist/notifications/channel.js +0 -83
- package/dist/notifications/channel.js.map +0 -1
- package/dist/notifications/channels/adapter.js +0 -55
- package/dist/notifications/channels/adapter.js.map +0 -1
- package/dist/notifications/channels/index.js +0 -6
- package/dist/notifications/channels/index.js.map +0 -1
- package/dist/notifications/channels/log.js +0 -29
- package/dist/notifications/channels/log.js.map +0 -1
- package/dist/notifications/email.js +0 -72
- package/dist/notifications/email.js.map +0 -1
- package/dist/notifications/engine.js +0 -198
- package/dist/notifications/engine.js.map +0 -1
- package/dist/notifications/index.js +0 -24
- package/dist/notifications/index.js.map +0 -1
- package/dist/notifications/phone.js +0 -48
- package/dist/notifications/phone.js.map +0 -1
- package/dist/notifications/sms.js +0 -65
- package/dist/notifications/sms.js.map +0 -1
- package/dist/notifications/types.js +0 -14
- package/dist/notifications/types.js.map +0 -1
- package/dist/notifications/webhook.js +0 -65
- package/dist/notifications/webhook.js.map +0 -1
- package/dist/resend/inbox.js +0 -199
- package/dist/resend/inbox.js.map +0 -1
- package/dist/resend/webhooks.js +0 -244
- package/dist/resend/webhooks.js.map +0 -1
- package/dist/search/browse.js +0 -225
- package/dist/search/browse.js.map +0 -1
- package/dist/search/perplexity.js +0 -41
- package/dist/search/perplexity.js.map +0 -1
- package/dist/slack/channels.js +0 -277
- package/dist/slack/channels.js.map +0 -1
- package/dist/slack/client.js +0 -468
- package/dist/slack/client.js.map +0 -1
- package/dist/slack/retry.js +0 -100
- package/dist/slack/retry.js.map +0 -1
- package/dist/slack/types.js +0 -52
- package/dist/slack/types.js.map +0 -1
- package/dist/slack/webhooks.js +0 -285
- package/dist/slack/webhooks.js.map +0 -1
- package/dist/stt/client.js +0 -66
- package/dist/stt/client.js.map +0 -1
- package/dist/stt/sidecar.js +0 -115
- package/dist/stt/sidecar.js.map +0 -1
- package/dist/tracing/bridge.js +0 -70
- package/dist/tracing/bridge.js.map +0 -1
- package/dist/tracing/correlation.js +0 -49
- package/dist/tracing/correlation.js.map +0 -1
- package/dist/tracing/index.js +0 -18
- package/dist/tracing/index.js.map +0 -1
- package/dist/tracing/init.js +0 -81
- package/dist/tracing/init.js.map +0 -1
- package/dist/tracing/instrument.js +0 -145
- package/dist/tracing/instrument.js.map +0 -1
- package/dist/tracing/middleware.js +0 -69
- package/dist/tracing/middleware.js.map +0 -1
- package/dist/tracing/tracer.js +0 -327
- package/dist/tracing/tracer.js.map +0 -1
- package/dist/tts/client.js +0 -48
- package/dist/tts/client.js.map +0 -1
- package/dist/tts/sidecar.js +0 -148
- package/dist/tts/sidecar.js.map +0 -1
- package/dist/twilio/call.js +0 -79
- package/dist/twilio/call.js.map +0 -1
- package/dist/vault/matcher.js +0 -197
- package/dist/vault/matcher.js.map +0 -1
- package/dist/vault/personal.js +0 -163
- package/dist/vault/personal.js.map +0 -1
- package/dist/vault/policy.js +0 -159
- package/dist/vault/policy.js.map +0 -1
- package/dist/vault/store.js +0 -122
- package/dist/vault/store.js.map +0 -1
- package/dist/vault/transfer.js +0 -188
- package/dist/vault/transfer.js.map +0 -1
- package/dist/volumes/index.js +0 -2
- package/dist/volumes/index.js.map +0 -1
- package/dist/volumes/manager.js +0 -462
- package/dist/volumes/manager.js.map +0 -1
- package/dist/volumes/types.js +0 -8
- package/dist/volumes/types.js.map +0 -1
- package/dist/webhooks/config.js +0 -214
- package/dist/webhooks/config.js.map +0 -1
- package/dist/webhooks/event-log.js +0 -132
- package/dist/webhooks/event-log.js.map +0 -1
- package/dist/webhooks/handler.js +0 -103
- package/dist/webhooks/handler.js.map +0 -1
- package/dist/webhooks/handlers.js +0 -231
- package/dist/webhooks/handlers.js.map +0 -1
- package/dist/webhooks/index.js +0 -33
- package/dist/webhooks/index.js.map +0 -1
- package/dist/webhooks/mount.js +0 -400
- package/dist/webhooks/mount.js.map +0 -1
- package/dist/webhooks/registry.js +0 -143
- package/dist/webhooks/registry.js.map +0 -1
- package/dist/webhooks/relay.js +0 -53
- package/dist/webhooks/relay.js.map +0 -1
- package/dist/webhooks/retry.js +0 -270
- package/dist/webhooks/retry.js.map +0 -1
- package/dist/webhooks/router.js +0 -290
- package/dist/webhooks/router.js.map +0 -1
- package/dist/webhooks/twilio.js +0 -129
- package/dist/webhooks/twilio.js.map +0 -1
- package/dist/webhooks/types.js +0 -8
- package/dist/webhooks/types.js.map +0 -1
- package/dist/webhooks/verify.js +0 -154
- package/dist/webhooks/verify.js.map +0 -1
package/dist/server.js
CHANGED
|
@@ -37,38 +37,47 @@ import { installFetchGuard } from "./llm/fetch-guard.js";
|
|
|
37
37
|
import { SensitiveRegistry } from "./llm/sensitive-registry.js";
|
|
38
38
|
import { PrivacyMembrane } from "./llm/membrane.js";
|
|
39
39
|
import { setActiveMembrane, getActiveMembrane, rehydrateResponse } from "./llm/redact.js";
|
|
40
|
-
import {
|
|
40
|
+
import { logLlmCall } from "./llm/call-log.js";
|
|
41
|
+
// VolumeManager — dynamically imported in start() (EXT-BYOK)
|
|
42
|
+
let _volumes = null;
|
|
41
43
|
import { loadSettings, getSettings, updateSettings, resolveProvider, resolveChatModel, resolveUtilityModel, getPulseSettings, getMeshConfig, } from "./settings.js";
|
|
42
|
-
|
|
44
|
+
// mdns — dynamically imported in start() (EXT-BYOK)
|
|
45
|
+
let _mdns = null;
|
|
43
46
|
import { ingestDirectory } from "./files/ingest.js";
|
|
44
47
|
import { processIngestFolder } from "./files/ingest-folder.js";
|
|
45
48
|
import { saveSession, loadSession } from "./sessions/store.js";
|
|
46
49
|
import { extractAndLearn } from "./learning/extractor.js";
|
|
47
50
|
import { startSidecar, stopSidecar, isSidecarAvailable } from "./search/sidecar.js";
|
|
48
51
|
import { classifySearchNeed } from "./search/classify.js";
|
|
49
|
-
import { findBrainDocument } from "./search/brain-docs.js";
|
|
52
|
+
import { findBrainDocument, setBrainRAG } from "./search/brain-docs.js";
|
|
53
|
+
import { BrainRAG } from "./search/brain-rag.js";
|
|
54
|
+
import { watchBrain } from "./search/file-watcher.js";
|
|
55
|
+
let stopFileWatcher = () => { };
|
|
50
56
|
import { isSearchAvailable, search } from "./search/client.js";
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
57
|
+
// browse, tts, stt, avatar, vault, integrations, twilio — dynamically imported in start() (EXT-BYOK)
|
|
58
|
+
let _browse = null;
|
|
59
|
+
let _ttsSidecar = null;
|
|
60
|
+
let _ttsClient = null;
|
|
61
|
+
let _sttSidecar = null;
|
|
62
|
+
let _sttClient = null;
|
|
63
|
+
let _avatarSidecar = null;
|
|
64
|
+
let _avatarClient = null;
|
|
65
|
+
let _settingsVoice = null;
|
|
66
|
+
let _vaultStore = null;
|
|
67
|
+
let _vaultTransfer = null;
|
|
68
|
+
let _integrationsGate = null;
|
|
69
|
+
let _twilioCall = null;
|
|
70
|
+
// Google workspace — dynamically imported in start() (EXT-BYOK)
|
|
71
|
+
let _googleAuth = null;
|
|
72
|
+
let _googleCalendar = null;
|
|
73
|
+
let _googleTemporal = null;
|
|
74
|
+
let _googleCalendarTimer = null;
|
|
75
|
+
let _googleGmail = null;
|
|
76
|
+
let _googleGmailTimer = null;
|
|
77
|
+
let _googleTasksTimer = null;
|
|
78
|
+
let _googleGmailSend = null;
|
|
79
|
+
let _googleDocs = null;
|
|
80
|
+
let _googleTasks = null;
|
|
72
81
|
import { runGoalCheck } from "./goals/loop.js";
|
|
73
82
|
import { startGoalTimer, stopGoalTimer } from "./goals/timer.js";
|
|
74
83
|
import { drainNotifications, pushNotification, initNotifications } from "./goals/notifications.js";
|
|
@@ -76,9 +85,10 @@ import { logActivity, getActivities, getActivitiesByIds, generateTraceId } from
|
|
|
76
85
|
import { compactHistory } from "./context/compaction.js";
|
|
77
86
|
import { rateLimit } from "./rate-limit.js";
|
|
78
87
|
import { initAgents, recoverAndStartMonitor, shutdownAgents, submitTask, getTask, listTasks as listAgentTasks, cancelTask, getTaskOutput, setOnBatchComplete, setAgentPool } from "./agents/index.js";
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
// Agent memory, locks, autonomous — dynamically imported in start() (EXT-SPAWN)
|
|
89
|
+
let _agentMemory = null;
|
|
90
|
+
let _agentLocks = null;
|
|
91
|
+
let _agentAutonomous = null;
|
|
82
92
|
import { initPressureIntegrator, getPressureIntegrator } from "./pulse/pressure.js";
|
|
83
93
|
import { startBacklogReviewTimer, stopBacklogReviewTimer, triggerBacklogReview, getLastBacklogReview, isBacklogReviewRunning } from "./services/backlogReview.js";
|
|
84
94
|
import { startBriefingTimer, stopBriefingTimer, triggerBriefing, getLastBriefing, getLastDeliveryResult, updateBriefingConfig } from "./services/morningBriefing.js";
|
|
@@ -86,43 +96,49 @@ import { initTraining, getTrainingProgress } from "./services/training.js";
|
|
|
86
96
|
import { startInsightsTimer, stopInsightsTimer, getInsights, getLastInsightRun, triggerInsightAnalysis } from "./services/traceInsights.js";
|
|
87
97
|
import { startCreditMonitor, stopCreditMonitor, getCreditStatus, triggerCreditCheck } from "./services/credit-monitor.js";
|
|
88
98
|
import { loadLoops, loadLoopsByState, loadTriads, transitionLoop, startOpenLoopScanner, stopOpenLoopScanner, getResonances, getLastScanRun, triggerOpenLoopScan, triggerResolutionScan, getResolutions, getLastResolutionScanRun, foldBack, } from "./openloop/index.js";
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
// Agent runtime, instance manager, pool, workflow — dynamically imported in start() (EXT-SPAWN)
|
|
100
|
+
let _agentRuntime = null;
|
|
101
|
+
let _agentInstanceManager = null;
|
|
102
|
+
let _agentPoolMod = null;
|
|
103
|
+
let _agentWorkflow = null;
|
|
93
104
|
import { getBoardProvider, setBoardProvider, isBoardAvailable } from "./board/provider.js";
|
|
94
105
|
import { QueueBoardProvider } from "./queue/provider.js";
|
|
95
106
|
import { QUEUE_STATES } from "./queue/types.js";
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
107
|
+
// Tracing — dynamically imported in start() (EXT-HOSTED)
|
|
108
|
+
let _tracingTracer = null;
|
|
109
|
+
let _tracingInit = null;
|
|
110
|
+
let _tracingMiddleware = null;
|
|
111
|
+
let _tracingBridge = null;
|
|
100
112
|
import { HealthChecker, memoryCheck, eventLoopCheck, availabilityCheck, cpuCheck, diskUsageCheck, diskCheck, queueStoreCheck, agentCapacityCheck, agentHealthCheck, boardCheck, RecoveryManager, sidecarRecovery, AlertManager, defaultAlertConfig, } from "./health/index.js";
|
|
101
|
-
|
|
113
|
+
// Notifications — dynamically imported in start() (EXT-BYOK)
|
|
114
|
+
let _notifications = null;
|
|
102
115
|
import { skillRegistry as _skillRegistry } from "./skills/index.js";
|
|
103
116
|
import { createModuleRegistry, getModuleRegistry } from "./modules/index.js";
|
|
104
|
-
import { createCapabilityRegistry, getCapabilityRegistry, calendarCapability, emailCapability, docsCapability, boardCapability,
|
|
117
|
+
import { createCapabilityRegistry, getCapabilityRegistry, calendarCapability, emailCapability, docsCapability, boardCapability, taskDoneCapability, calendarContextProvider, emailContextProvider, createWebSearchContextProvider, vaultContextProvider } from "./capabilities/index.js";
|
|
118
|
+
// browserCapability, closeBrowser — dynamically imported in start() (EXT-HOSTED)
|
|
119
|
+
let _browser = null;
|
|
105
120
|
import { MetricsStore, startCollector, stopCollector, registerDefaultThresholds, evaluateAlerts, buildDashboard, metricsMiddleware, collectPrometheus, generatePeriodStats, generateComparisonReport, } from "./metrics/index.js";
|
|
106
121
|
import { startGroomingTimer, stopGroomingTimer } from "./queue/grooming.js";
|
|
107
122
|
import { createSchedulingStore, getSchedulingStore } from "./scheduling/store.js";
|
|
108
123
|
import { startSchedulingTimer, stopSchedulingTimer } from "./scheduling/timer.js";
|
|
109
124
|
import { createContactStore, getContactStore } from "./contacts/store.js";
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
125
|
+
// Credentials, GitHub, Slack, WhatsApp, Webhooks — dynamically imported in start() (EXT-BYOK)
|
|
126
|
+
let _credentialStore = null;
|
|
127
|
+
let _githubWebhooks = null;
|
|
128
|
+
let _integrationsGithub = null;
|
|
129
|
+
let _slackClient = null;
|
|
130
|
+
let _slackWebhooks = null;
|
|
131
|
+
let _slackChannels = null;
|
|
132
|
+
let _channelsWhatsapp = null;
|
|
133
|
+
let _webhooksTwilio = null;
|
|
134
|
+
let _resendWebhooks = null;
|
|
135
|
+
let _servicesWhatsapp = null;
|
|
121
136
|
import { initLLMCache, shutdownLLMCache, getCacheDiagnostics } from "./cache/llm-cache.js";
|
|
122
|
-
import {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
import { createWebhookRoute, verifyWebhookRequest } from "./webhooks/mount.js";
|
|
138
|
+
let _webhooksMount = null;
|
|
139
|
+
let _webhooksConfig = null;
|
|
140
|
+
let _webhooksRegistry = null;
|
|
141
|
+
let _webhooksRelay = null;
|
|
126
142
|
import { setEncryptionKey } from "./lib/key-store.js";
|
|
127
143
|
import { FileManager } from "./files/manager.js";
|
|
128
144
|
import { createLibraryStore } from "./library/store.js";
|
|
@@ -159,15 +175,14 @@ health.register("cpu", cpuCheck());
|
|
|
159
175
|
health.register("disk", diskCheck(BRAIN_DIR));
|
|
160
176
|
health.register("disk_usage", diskUsageCheck(BRAIN_DIR));
|
|
161
177
|
const recovery = new RecoveryManager(health);
|
|
162
|
-
// --- Alerting ---
|
|
163
|
-
|
|
164
|
-
|
|
178
|
+
// --- Alerting (initialized in start() after dynamic import) ---
|
|
179
|
+
let alertDispatcher = null;
|
|
180
|
+
let alertManager = null;
|
|
165
181
|
// --- Metrics ---
|
|
166
182
|
const metricsStore = new MetricsStore(BRAIN_DIR);
|
|
167
183
|
registerDefaultThresholds();
|
|
168
|
-
// --- Volume manager ---
|
|
169
|
-
|
|
170
|
-
volumeManager.init().catch((err) => log.warn("Volume manager init failed — single-volume mode", { error: String(err) }));
|
|
184
|
+
// --- Volume manager (initialized in start() after dynamic import) ---
|
|
185
|
+
let volumeManager = null;
|
|
171
186
|
const chatSessions = new Map();
|
|
172
187
|
const sessionKeys = new Map();
|
|
173
188
|
let goalTimerStarted = false;
|
|
@@ -184,6 +199,41 @@ function getThreadsForSession(sessionId) {
|
|
|
184
199
|
function generateThreadId() {
|
|
185
200
|
return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
|
|
186
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Switch cs to point at the given thread's history (or back to main).
|
|
204
|
+
* Saves/restores main history transparently.
|
|
205
|
+
*/
|
|
206
|
+
function switchSessionThread(cs, threadId, sessionId) {
|
|
207
|
+
const currentThread = cs.activeThreadId;
|
|
208
|
+
if (currentThread === threadId)
|
|
209
|
+
return; // already there
|
|
210
|
+
// Save current history back to wherever it belongs
|
|
211
|
+
if (currentThread) {
|
|
212
|
+
// Currently on a thread — save back to that thread
|
|
213
|
+
const threads = getThreadsForSession(sessionId);
|
|
214
|
+
const t = threads.get(currentThread);
|
|
215
|
+
if (t) {
|
|
216
|
+
t.history = cs.history;
|
|
217
|
+
t.historySummary = cs.historySummary;
|
|
218
|
+
}
|
|
219
|
+
// Restore main
|
|
220
|
+
cs.history = cs.mainHistory;
|
|
221
|
+
cs.historySummary = cs.mainHistorySummary;
|
|
222
|
+
}
|
|
223
|
+
if (threadId) {
|
|
224
|
+
// Switching to a thread — save main, load thread
|
|
225
|
+
const threads = getThreadsForSession(sessionId);
|
|
226
|
+
const t = threads.get(threadId);
|
|
227
|
+
if (t) {
|
|
228
|
+
cs.mainHistory = cs.history;
|
|
229
|
+
cs.mainHistorySummary = cs.historySummary;
|
|
230
|
+
cs.history = t.history;
|
|
231
|
+
cs.historySummary = t.historySummary;
|
|
232
|
+
t.updatedAt = new Date().toISOString();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
cs.activeThreadId = threadId;
|
|
236
|
+
}
|
|
187
237
|
/** One-time startup token for zero-friction local auth. */
|
|
188
238
|
let startupToken = null;
|
|
189
239
|
export function getStartupToken() {
|
|
@@ -191,7 +241,7 @@ export function getStartupToken() {
|
|
|
191
241
|
}
|
|
192
242
|
/** Autonomous timer started flag. */
|
|
193
243
|
let autonomousStarted = false;
|
|
194
|
-
|
|
244
|
+
let tracer = null;
|
|
195
245
|
let instanceManager = null;
|
|
196
246
|
let agentPool = null;
|
|
197
247
|
let workflowEngine = null;
|
|
@@ -314,20 +364,20 @@ async function getOrCreateChatSession(sessionId, name) {
|
|
|
314
364
|
: `You do NOT have web search capability. If ${name} asks you to search or asks about current events, be honest that you can't look things up right now.`,
|
|
315
365
|
``,
|
|
316
366
|
// Google Workspace status (gated on auth, instructions come from registry)
|
|
317
|
-
...(isGoogleAuthenticated()
|
|
367
|
+
...((_googleAuth?.isGoogleAuthenticated() ?? false)
|
|
318
368
|
? [
|
|
319
369
|
`You have Google Workspace integration. Your available actions are listed below.`,
|
|
320
370
|
`If Google data appears in your context, use it. It is real, live data from ${name}'s account.`,
|
|
321
371
|
`You do NOT need to build or implement Google integration — it is already working.`,
|
|
322
372
|
``,
|
|
323
373
|
]
|
|
324
|
-
: isGoogleConfigured()
|
|
374
|
+
: (_googleAuth?.isGoogleConfigured() ?? false)
|
|
325
375
|
? [`Google Workspace credentials are configured but not yet authorized. Tell ${name} to click "Connect Google" in settings to complete the setup.`, ``]
|
|
326
376
|
: [`Google Workspace is not connected. ${name} can add GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in vault settings to enable Calendar, Gmail, and Drive access.`, ``]),
|
|
327
377
|
// Non-Google capability instructions (always injected)
|
|
328
378
|
...(getCapabilityRegistry()?.getPromptInstructions({ origin: "chat", name, exclude: ["calendar", "email", "docs"] }) ?? "").split("\n"),
|
|
329
379
|
// Google capability instructions (only when authenticated)
|
|
330
|
-
...(isGoogleAuthenticated()
|
|
380
|
+
...((_googleAuth?.isGoogleAuthenticated() ?? false)
|
|
331
381
|
? (getCapabilityRegistry()?.getPromptInstructions({ origin: "chat", name, filter: ["calendar", "email", "docs"] }) ?? "").split("\n")
|
|
332
382
|
: []),
|
|
333
383
|
``,
|
|
@@ -377,7 +427,7 @@ async function getOrCreateChatSession(sessionId, name) {
|
|
|
377
427
|
] : []), // end spawning gate
|
|
378
428
|
// Inject instance-readable vault values (CORE_*/DASH_* prefixed only — never secrets)
|
|
379
429
|
...(() => {
|
|
380
|
-
const readable = getDashReadableVault();
|
|
430
|
+
const readable = (_vaultStore ? _vaultStore.getDashReadableVault() : []);
|
|
381
431
|
if (readable.length === 0)
|
|
382
432
|
return [];
|
|
383
433
|
const lines = readable.map((r) => `- ${r.name}: ${r.value}`);
|
|
@@ -430,12 +480,26 @@ async function getOrCreateChatSession(sessionId, name) {
|
|
|
430
480
|
if (key) {
|
|
431
481
|
const restored = await loadSession(sessionId, key);
|
|
432
482
|
if (restored) {
|
|
433
|
-
cs = { history: restored.history, historySummary: restored.historySummary ?? "", brain, fileContext: restored.fileContext, learnedPaths: restored.learnedPaths, ingestedContext, turnCount: 0, lastExtractionTurn: 0, foldedBack: false };
|
|
483
|
+
cs = { history: restored.history, historySummary: restored.historySummary ?? "", brain, fileContext: restored.fileContext, learnedPaths: restored.learnedPaths, ingestedContext, turnCount: 0, lastExtractionTurn: 0, foldedBack: false, activeThreadId: null, mainHistory: [], mainHistorySummary: "" };
|
|
434
484
|
chatSessions.set(sessionId, cs);
|
|
485
|
+
// Restore threads
|
|
486
|
+
if (restored.threads && restored.threads.length > 0) {
|
|
487
|
+
const threads = getThreadsForSession(sessionId);
|
|
488
|
+
for (const t of restored.threads) {
|
|
489
|
+
threads.set(t.id, {
|
|
490
|
+
id: t.id,
|
|
491
|
+
title: t.title,
|
|
492
|
+
history: t.history || [],
|
|
493
|
+
historySummary: t.historySummary || "",
|
|
494
|
+
createdAt: t.createdAt,
|
|
495
|
+
updatedAt: t.updatedAt,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
435
499
|
return cs;
|
|
436
500
|
}
|
|
437
501
|
}
|
|
438
|
-
cs = { history: [], historySummary: "", brain, fileContext: "", learnedPaths: [], ingestedContext, turnCount: 0, lastExtractionTurn: 0, foldedBack: false };
|
|
502
|
+
cs = { history: [], historySummary: "", brain, fileContext: "", learnedPaths: [], ingestedContext, turnCount: 0, lastExtractionTurn: 0, foldedBack: false, activeThreadId: null, mainHistory: [], mainHistorySummary: "" };
|
|
439
503
|
chatSessions.set(sessionId, cs);
|
|
440
504
|
return cs;
|
|
441
505
|
}
|
|
@@ -496,8 +560,13 @@ app.use("/api/*", async (c, next) => {
|
|
|
496
560
|
const caller = `http:${c.req.method} ${c.req.path}`;
|
|
497
561
|
return runWithAuditContext({ caller, channel: "http" }, () => next());
|
|
498
562
|
});
|
|
499
|
-
// --- Tracing middleware ---
|
|
500
|
-
app.use("/api/*",
|
|
563
|
+
// --- Tracing middleware (lazy — only active when hosted tier loads tracing) ---
|
|
564
|
+
app.use("/api/*", async (c, next) => {
|
|
565
|
+
if (_tracingMiddleware) {
|
|
566
|
+
return _tracingMiddleware.tracingMiddleware()(c, next);
|
|
567
|
+
}
|
|
568
|
+
return next();
|
|
569
|
+
});
|
|
501
570
|
// --- Metrics middleware ---
|
|
502
571
|
app.use("/api/*", metricsMiddleware());
|
|
503
572
|
// --- Rate limiting ---
|
|
@@ -526,35 +595,48 @@ app.use("/api/*", postureTracker());
|
|
|
526
595
|
app.use("/api/*", postureHeader());
|
|
527
596
|
// --- Webhook initialization (batch registration + config + admin routes) ---
|
|
528
597
|
const webhookInitStart = performance.now();
|
|
529
|
-
// Phase 1:
|
|
530
|
-
//
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
598
|
+
// Phase 1-3: Webhook provider registration is deferred to start() where modules are loaded.
|
|
599
|
+
// initWebhookProviders() is called in start() after dynamic imports complete.
|
|
600
|
+
function initWebhookProviders() {
|
|
601
|
+
if (!_webhooksRegistry || !_webhooksConfig || !_webhooksMount || !_githubWebhooks || !_slackWebhooks || !_webhooksTwilio || !_resendWebhooks) {
|
|
602
|
+
log.debug("Webhook init skipped — BYOK modules not loaded");
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
const registerStart = performance.now();
|
|
606
|
+
_webhooksRegistry.registerProviders([_githubWebhooks.githubProvider, _slackWebhooks.slackEventsProvider, _slackWebhooks.slackCommandsProvider, _slackWebhooks.slackInteractionsProvider, _webhooksTwilio.twilioProvider, _resendWebhooks.resendProvider]);
|
|
607
|
+
const registerMs = performance.now() - registerStart;
|
|
608
|
+
const configStart = performance.now();
|
|
609
|
+
_webhooksConfig.setProviderConfigs([
|
|
610
|
+
{ name: "slack-events", secret: "SLACK_SIGNING_SECRET", signatureHeader: "x-slack-signature", algorithm: "slack-v0", path: "/api/slack/events" },
|
|
611
|
+
{ name: "slack-commands", secret: "SLACK_SIGNING_SECRET", signatureHeader: "x-slack-signature", algorithm: "slack-v0", path: "/api/slack/commands" },
|
|
612
|
+
{ name: "slack-interactions", secret: "SLACK_SIGNING_SECRET", signatureHeader: "x-slack-signature", algorithm: "slack-v0", path: "/api/slack/interactions" },
|
|
613
|
+
{ name: "twilio", secret: "TWILIO_AUTH_TOKEN", signatureHeader: "x-twilio-signature", algorithm: "twilio", path: "/api/twilio/whatsapp" },
|
|
614
|
+
{ name: "github", secret: "GITHUB_WEBHOOK_SECRET", signatureHeader: "x-hub-signature-256", algorithm: "hmac-sha256-hex", path: "/api/github/webhooks" },
|
|
615
|
+
{ name: "resend", secret: "RESEND_WEBHOOK_SECRET", signatureHeader: "svix-signature", algorithm: "custom", path: "/api/resend/webhooks" },
|
|
616
|
+
]);
|
|
617
|
+
const configMs = performance.now() - configStart;
|
|
618
|
+
const mountStart = performance.now();
|
|
619
|
+
_webhooksMount.mountWebhookAdmin(app);
|
|
620
|
+
const mountMs = performance.now() - mountStart;
|
|
621
|
+
const webhookInitMs = performance.now() - webhookInitStart;
|
|
622
|
+
log.debug(`Webhook init complete: ${webhookInitMs.toFixed(1)}ms — register:${registerMs.toFixed(1)}ms, config:${configMs.toFixed(1)}ms, mount:${mountMs.toFixed(1)}ms`, { durationMs: Math.round(webhookInitMs), registerMs: Math.round(registerMs), configMs: Math.round(configMs), mountMs: Math.round(mountMs) });
|
|
623
|
+
if (webhookInitMs > 100) {
|
|
624
|
+
logActivity({
|
|
625
|
+
source: "system",
|
|
626
|
+
summary: `[perf] Webhook init slow: ${webhookInitMs.toFixed(1)}ms — register:${registerMs.toFixed(1)}ms, config:${configMs.toFixed(1)}ms, mount:${mountMs.toFixed(1)}ms`,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
556
629
|
}
|
|
557
630
|
// --- API routes ---
|
|
631
|
+
// UI version check (stub — UI polls this to detect hot-reload)
|
|
632
|
+
app.get("/api/ui-version", (c) => c.json({ version: "0.4.0" }));
|
|
633
|
+
// Return current seal values so client can blur on page load
|
|
634
|
+
app.get("/api/sensitive/seals", (c) => {
|
|
635
|
+
const membrane = getActiveMembrane();
|
|
636
|
+
return c.json({ values: membrane ? membrane.knownValues.map(v => v.value) : [] });
|
|
637
|
+
});
|
|
638
|
+
// Pending questions (stub — proactive question chips)
|
|
639
|
+
app.get("/api/pending-questions", (c) => c.json({ questions: [] }));
|
|
558
640
|
// Status: what screen should the UI show?
|
|
559
641
|
app.get("/api/status", async (c) => {
|
|
560
642
|
const status = await getStatus();
|
|
@@ -566,9 +648,9 @@ app.get("/api/status", async (c) => {
|
|
|
566
648
|
privateMode: settings.privateMode,
|
|
567
649
|
authMode: settings.safeWordMode,
|
|
568
650
|
search: isSearchAvailable(),
|
|
569
|
-
tts: isTtsAvailable(),
|
|
570
|
-
stt: isSttAvailable(),
|
|
571
|
-
avatar: isAvatarAvailable(),
|
|
651
|
+
tts: (_ttsClient?.isTtsAvailable() ?? false),
|
|
652
|
+
stt: (_sttClient?.isSttAvailable() ?? false),
|
|
653
|
+
avatar: (_avatarSidecar?.isAvatarAvailable() ?? false),
|
|
572
654
|
agentName: getInstanceName(),
|
|
573
655
|
});
|
|
574
656
|
});
|
|
@@ -601,7 +683,8 @@ app.post("/api/pair", async (c) => {
|
|
|
601
683
|
sessionKeys.set(result.session.id, result.sessionKey);
|
|
602
684
|
setEncryptionKey(result.sessionKey);
|
|
603
685
|
cacheSessionKey(result.sessionKey);
|
|
604
|
-
|
|
686
|
+
if (_vaultStore)
|
|
687
|
+
await _vaultStore.loadVault(result.sessionKey);
|
|
605
688
|
// Save agent name to settings + update in-memory name
|
|
606
689
|
if (agentName) {
|
|
607
690
|
setInstanceName(agentName);
|
|
@@ -633,7 +716,8 @@ app.post("/api/auth", async (c) => {
|
|
|
633
716
|
sessionKeys.set(result.session.id, result.sessionKey);
|
|
634
717
|
setEncryptionKey(result.sessionKey);
|
|
635
718
|
cacheSessionKey(result.sessionKey);
|
|
636
|
-
|
|
719
|
+
if (_vaultStore)
|
|
720
|
+
await _vaultStore.loadVault(result.sessionKey);
|
|
637
721
|
return c.json({ sessionId: result.session.id, name: result.name });
|
|
638
722
|
});
|
|
639
723
|
// Validate an existing session (for "restart" auth mode)
|
|
@@ -692,7 +776,8 @@ app.post("/api/recover", async (c) => {
|
|
|
692
776
|
sessionKeys.set(result.session.id, result.sessionKey);
|
|
693
777
|
setEncryptionKey(result.sessionKey);
|
|
694
778
|
cacheSessionKey(result.sessionKey);
|
|
695
|
-
|
|
779
|
+
if (_vaultStore)
|
|
780
|
+
await _vaultStore.loadVault(result.sessionKey);
|
|
696
781
|
return c.json({ sessionId: result.session.id, name: result.name });
|
|
697
782
|
});
|
|
698
783
|
const deviceVouchers = new Map();
|
|
@@ -1076,7 +1161,7 @@ app.get("/api/vault", async (c) => {
|
|
|
1076
1161
|
const session = validateSession(sessionId);
|
|
1077
1162
|
if (!session)
|
|
1078
1163
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1079
|
-
return c.json({ keys: listVaultKeys() });
|
|
1164
|
+
return c.json({ keys: (_vaultStore ? _vaultStore.listVaultKeys() : []) });
|
|
1080
1165
|
});
|
|
1081
1166
|
// Add or update a vault key
|
|
1082
1167
|
app.put("/api/vault/:name", async (c) => {
|
|
@@ -1094,7 +1179,7 @@ app.put("/api/vault/:name", async (c) => {
|
|
|
1094
1179
|
const { value, label } = body;
|
|
1095
1180
|
if (!value)
|
|
1096
1181
|
return c.json({ error: "value required" }, 400);
|
|
1097
|
-
await setVaultKey(name, value, key, label);
|
|
1182
|
+
await _vaultStore.setVaultKey(name, value, key, label);
|
|
1098
1183
|
return c.json({ ok: true });
|
|
1099
1184
|
});
|
|
1100
1185
|
// Delete a vault key
|
|
@@ -1109,7 +1194,7 @@ app.delete("/api/vault/:name", async (c) => {
|
|
|
1109
1194
|
if (!key)
|
|
1110
1195
|
return c.json({ error: "Session key not found" }, 401);
|
|
1111
1196
|
const name = c.req.param("name");
|
|
1112
|
-
await deleteVaultKey(name, key);
|
|
1197
|
+
await _vaultStore.deleteVaultKey(name, key);
|
|
1113
1198
|
return c.json({ ok: true });
|
|
1114
1199
|
});
|
|
1115
1200
|
// Export vault to portable passphrase-encrypted file
|
|
@@ -1125,7 +1210,7 @@ app.post("/api/vault/export", async (c) => {
|
|
|
1125
1210
|
return c.json({ error: "Passphrase required (min 8 characters)" }, 400);
|
|
1126
1211
|
}
|
|
1127
1212
|
try {
|
|
1128
|
-
const result = await exportVault(body.passphrase);
|
|
1213
|
+
const result = await _vaultTransfer.exportVault(body.passphrase);
|
|
1129
1214
|
return c.json({ ok: true, filePath: result.filePath, stats: result.stats });
|
|
1130
1215
|
}
|
|
1131
1216
|
catch (e) {
|
|
@@ -1149,7 +1234,7 @@ app.post("/api/vault/import", async (c) => {
|
|
|
1149
1234
|
}
|
|
1150
1235
|
const strategy = body.strategy ?? "skip";
|
|
1151
1236
|
try {
|
|
1152
|
-
const result = await importVault(body.filePath, body.passphrase, strategy, key);
|
|
1237
|
+
const result = await _vaultTransfer.importVault(body.filePath, body.passphrase, strategy, key);
|
|
1153
1238
|
return c.json({ ok: true, stats: result.stats });
|
|
1154
1239
|
}
|
|
1155
1240
|
catch (e) {
|
|
@@ -1169,7 +1254,7 @@ app.post("/api/vault/verify-export", async (c) => {
|
|
|
1169
1254
|
return c.json({ error: "filePath and passphrase required" }, 400);
|
|
1170
1255
|
}
|
|
1171
1256
|
try {
|
|
1172
|
-
const result = await verifyExport(body.filePath, body.passphrase);
|
|
1257
|
+
const result = await _vaultTransfer.verifyExport(body.filePath, body.passphrase);
|
|
1173
1258
|
return c.json({ ok: true, message: result.message, stats: result.stats });
|
|
1174
1259
|
}
|
|
1175
1260
|
catch (e) {
|
|
@@ -1181,7 +1266,7 @@ app.post("/api/vault/verify-export", async (c) => {
|
|
|
1181
1266
|
// No session required: this just redirects to Google, no sensitive data returned
|
|
1182
1267
|
app.get("/api/google/auth", async (c) => {
|
|
1183
1268
|
const redirectUri = `http://localhost:${PORT}/api/google/callback`;
|
|
1184
|
-
const result = getAuthUrl(redirectUri);
|
|
1269
|
+
const result = _googleAuth.getAuthUrl(redirectUri);
|
|
1185
1270
|
if (!result.ok) {
|
|
1186
1271
|
return c.html(`<html><body><h2>Google OAuth not configured</h2><p>${result.message}</p></body></html>`);
|
|
1187
1272
|
}
|
|
@@ -1198,7 +1283,7 @@ app.get("/api/google/callback", async (c) => {
|
|
|
1198
1283
|
return c.html(`<html><body><h2>Missing authorization code</h2><p>No code received from Google.</p></body></html>`);
|
|
1199
1284
|
}
|
|
1200
1285
|
const redirectUri = `http://localhost:${PORT}/api/google/callback`;
|
|
1201
|
-
const result = await exchangeCode(code, redirectUri);
|
|
1286
|
+
const result = await _googleAuth.exchangeCode(code, redirectUri);
|
|
1202
1287
|
if (!result.ok) {
|
|
1203
1288
|
return c.html(`<html><body><h2>Token exchange failed</h2><p>${result.message}</p></body></html>`);
|
|
1204
1289
|
}
|
|
@@ -1214,26 +1299,27 @@ app.get("/api/google/callback", async (c) => {
|
|
|
1214
1299
|
vaultKey = restored.sessionKey;
|
|
1215
1300
|
sessionKeys.set(restored.session.id, restored.sessionKey);
|
|
1216
1301
|
setEncryptionKey(restored.sessionKey);
|
|
1217
|
-
|
|
1302
|
+
if (_vaultStore)
|
|
1303
|
+
await _vaultStore.loadVault(restored.sessionKey);
|
|
1218
1304
|
}
|
|
1219
1305
|
}
|
|
1220
1306
|
if (!vaultKey) {
|
|
1221
1307
|
return c.html(`<html><body><h2>Session not found</h2><p>Could not find a session key to store credentials. Please log in first, then try connecting Google again.</p></body></html>`);
|
|
1222
1308
|
}
|
|
1223
|
-
await setVaultKey("GOOGLE_REFRESH_TOKEN", result.refreshToken, vaultKey, "Google OAuth refresh token");
|
|
1309
|
+
await _vaultStore.setVaultKey("GOOGLE_REFRESH_TOKEN", result.refreshToken, vaultKey, "Google OAuth refresh token");
|
|
1224
1310
|
logActivity({ source: "google", summary: "Google OAuth connected — refresh token stored in vault", actionLabel: "PROMPTED", reason: "user connected Google OAuth" });
|
|
1225
1311
|
// Start Google polling timers now that Google is connected
|
|
1226
|
-
startCalendarTimer();
|
|
1227
|
-
startGmailTimer();
|
|
1228
|
-
startTasksTimer();
|
|
1312
|
+
_googleCalendarTimer?.startCalendarTimer();
|
|
1313
|
+
_googleGmailTimer?.startGmailTimer();
|
|
1314
|
+
_googleTasksTimer?.startTasksTimer();
|
|
1229
1315
|
return c.html(`<html><body><h2>Google connected!</h2><p>${getInstanceName()} now has access to Calendar, Gmail, and Drive.</p><p>This tab will close automatically.</p><script>if(window.opener){window.opener.postMessage("google-connected","*")}setTimeout(()=>window.close(),1500)</script></body></html>`);
|
|
1230
1316
|
});
|
|
1231
1317
|
// Check Google auth state (public — only returns booleans, no sensitive data)
|
|
1232
1318
|
app.get("/api/google/status", async (c) => {
|
|
1233
1319
|
return c.json({
|
|
1234
|
-
configured: isGoogleConfigured(),
|
|
1235
|
-
authenticated: isGoogleAuthenticated(),
|
|
1236
|
-
scopes: isGoogleAuthenticated() ? ["gmail.modify", "calendar.events", "drive.file", "tasks"] : [],
|
|
1320
|
+
configured: (_googleAuth?.isGoogleConfigured() ?? false),
|
|
1321
|
+
authenticated: (_googleAuth?.isGoogleAuthenticated() ?? false),
|
|
1322
|
+
scopes: (_googleAuth?.isGoogleAuthenticated() ?? false) ? ["gmail.modify", "calendar.events", "drive.file", "tasks"] : [],
|
|
1237
1323
|
});
|
|
1238
1324
|
});
|
|
1239
1325
|
// Send email via Gmail
|
|
@@ -1244,14 +1330,14 @@ app.post("/api/google/send-email", async (c) => {
|
|
|
1244
1330
|
const session = validateSession(sessionId);
|
|
1245
1331
|
if (!session)
|
|
1246
1332
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1247
|
-
if (!isGoogleAuthenticated()) {
|
|
1333
|
+
if (!(_googleAuth?.isGoogleAuthenticated() ?? false)) {
|
|
1248
1334
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1249
1335
|
}
|
|
1250
1336
|
const body = await c.req.json();
|
|
1251
1337
|
if (!body.to || !body.subject || !body.body) {
|
|
1252
1338
|
return c.json({ error: "to, subject, and body are required" }, 400);
|
|
1253
1339
|
}
|
|
1254
|
-
const result = await sendEmail(body);
|
|
1340
|
+
const result = await _googleGmailSend.sendEmail(body);
|
|
1255
1341
|
if (!result.ok)
|
|
1256
1342
|
return c.json({ error: result.message }, 500);
|
|
1257
1343
|
logActivity({ source: "gmail", summary: `Email sent to ${Array.isArray(body.to) ? body.to.join(", ") : body.to}: "${body.subject}"`, actionLabel: "PROMPTED", reason: "user sent email" });
|
|
@@ -1266,10 +1352,10 @@ app.get("/api/google/gmail/inbox-summary", async (c) => {
|
|
|
1266
1352
|
const session = validateSession(sessionId);
|
|
1267
1353
|
if (!session)
|
|
1268
1354
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1269
|
-
if (!isGmailAvailable())
|
|
1355
|
+
if (!(_googleGmail?.isGmailAvailable() ?? false))
|
|
1270
1356
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1271
1357
|
const hours = parseInt(c.req.query("hours") ?? "24", 10);
|
|
1272
|
-
const result = await getInboxSummary(hours);
|
|
1358
|
+
const result = await _googleGmail.getInboxSummary(hours);
|
|
1273
1359
|
if (!result.ok)
|
|
1274
1360
|
return c.json({ error: result.message }, 500);
|
|
1275
1361
|
return c.json(result);
|
|
@@ -1282,10 +1368,10 @@ app.get("/api/google/gmail/categorize", async (c) => {
|
|
|
1282
1368
|
const session = validateSession(sessionId);
|
|
1283
1369
|
if (!session)
|
|
1284
1370
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1285
|
-
if (!isGmailAvailable())
|
|
1371
|
+
if (!(_googleGmail?.isGmailAvailable() ?? false))
|
|
1286
1372
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1287
1373
|
const hours = parseInt(c.req.query("hours") ?? "24", 10);
|
|
1288
|
-
const result = await categorizeMessages(hours);
|
|
1374
|
+
const result = await _googleGmail.categorizeMessages(hours);
|
|
1289
1375
|
if (!result.ok)
|
|
1290
1376
|
return c.json({ error: result.message }, 500);
|
|
1291
1377
|
return c.json(result);
|
|
@@ -1298,10 +1384,10 @@ app.get("/api/google/gmail/prioritize", async (c) => {
|
|
|
1298
1384
|
const session = validateSession(sessionId);
|
|
1299
1385
|
if (!session)
|
|
1300
1386
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1301
|
-
if (!isGmailAvailable())
|
|
1387
|
+
if (!(_googleGmail?.isGmailAvailable() ?? false))
|
|
1302
1388
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1303
1389
|
const hours = parseInt(c.req.query("hours") ?? "24", 10);
|
|
1304
|
-
const result = await prioritizeInbox(hours);
|
|
1390
|
+
const result = await _googleGmail.prioritizeInbox(hours);
|
|
1305
1391
|
if (!result.ok)
|
|
1306
1392
|
return c.json({ error: result.message }, 500);
|
|
1307
1393
|
return c.json(result);
|
|
@@ -1314,18 +1400,18 @@ app.post("/api/google/gmail/mark-read", async (c) => {
|
|
|
1314
1400
|
const session = validateSession(sessionId);
|
|
1315
1401
|
if (!session)
|
|
1316
1402
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1317
|
-
if (!isGmailAvailable())
|
|
1403
|
+
if (!(_googleGmail?.isGmailAvailable() ?? false))
|
|
1318
1404
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1319
1405
|
const body = await c.req.json();
|
|
1320
1406
|
if (body.messageIds && body.messageIds.length > 0) {
|
|
1321
|
-
const result = await batchMarkAsRead(body.messageIds);
|
|
1407
|
+
const result = await _googleGmail.batchMarkAsRead(body.messageIds);
|
|
1322
1408
|
if (!result.ok)
|
|
1323
1409
|
return c.json({ error: result.message }, 500);
|
|
1324
1410
|
return c.json(result);
|
|
1325
1411
|
}
|
|
1326
1412
|
if (!body.messageId)
|
|
1327
1413
|
return c.json({ error: "messageId or messageIds required" }, 400);
|
|
1328
|
-
const result = await markAsRead(body.messageId);
|
|
1414
|
+
const result = await _googleGmail.markAsRead(body.messageId);
|
|
1329
1415
|
if (!result.ok)
|
|
1330
1416
|
return c.json({ error: result.message }, 500);
|
|
1331
1417
|
return c.json(result);
|
|
@@ -1338,18 +1424,18 @@ app.post("/api/google/gmail/mark-unread", async (c) => {
|
|
|
1338
1424
|
const session = validateSession(sessionId);
|
|
1339
1425
|
if (!session)
|
|
1340
1426
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1341
|
-
if (!isGmailAvailable())
|
|
1427
|
+
if (!(_googleGmail?.isGmailAvailable() ?? false))
|
|
1342
1428
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1343
1429
|
const body = await c.req.json();
|
|
1344
1430
|
if (body.messageIds && body.messageIds.length > 0) {
|
|
1345
|
-
const result = await batchMarkAsUnread(body.messageIds);
|
|
1431
|
+
const result = await _googleGmail.batchMarkAsUnread(body.messageIds);
|
|
1346
1432
|
if (!result.ok)
|
|
1347
1433
|
return c.json({ error: result.message }, 500);
|
|
1348
1434
|
return c.json(result);
|
|
1349
1435
|
}
|
|
1350
1436
|
if (!body.messageId)
|
|
1351
1437
|
return c.json({ error: "messageId or messageIds required" }, 400);
|
|
1352
|
-
const result = await markAsUnread(body.messageId);
|
|
1438
|
+
const result = await _googleGmail.markAsUnread(body.messageId);
|
|
1353
1439
|
if (!result.ok)
|
|
1354
1440
|
return c.json({ error: result.message }, 500);
|
|
1355
1441
|
return c.json(result);
|
|
@@ -1357,53 +1443,53 @@ app.post("/api/google/gmail/mark-unread", async (c) => {
|
|
|
1357
1443
|
// --- Google Calendar routes ---
|
|
1358
1444
|
// Get today's schedule
|
|
1359
1445
|
app.get("/api/google/calendar/today", async (c) => {
|
|
1360
|
-
if (!isCalendarAvailable())
|
|
1446
|
+
if (!(_googleCalendar?.isCalendarAvailable() ?? false))
|
|
1361
1447
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1362
|
-
const result = await getTodaySchedule();
|
|
1448
|
+
const result = await _googleCalendar.getTodaySchedule();
|
|
1363
1449
|
if (!result.ok)
|
|
1364
1450
|
return c.json({ error: result.message }, 500);
|
|
1365
1451
|
return c.json(result);
|
|
1366
1452
|
});
|
|
1367
1453
|
// Get upcoming events
|
|
1368
1454
|
app.get("/api/google/calendar/upcoming", async (c) => {
|
|
1369
|
-
if (!isCalendarAvailable())
|
|
1455
|
+
if (!(_googleCalendar?.isCalendarAvailable() ?? false))
|
|
1370
1456
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1371
1457
|
const hours = parseInt(c.req.query("hours") ?? "4", 10);
|
|
1372
|
-
const result = await getUpcomingEvents(hours);
|
|
1458
|
+
const result = await _googleCalendar.getUpcomingEvents(hours);
|
|
1373
1459
|
if (!result.ok)
|
|
1374
1460
|
return c.json({ error: result.message }, 500);
|
|
1375
1461
|
return c.json(result);
|
|
1376
1462
|
});
|
|
1377
1463
|
// Get free/busy
|
|
1378
1464
|
app.post("/api/google/calendar/freebusy", async (c) => {
|
|
1379
|
-
if (!isCalendarAvailable())
|
|
1465
|
+
if (!(_googleCalendar?.isCalendarAvailable() ?? false))
|
|
1380
1466
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1381
1467
|
const body = await c.req.json();
|
|
1382
1468
|
if (!body.start || !body.end)
|
|
1383
1469
|
return c.json({ error: "start and end are required" }, 400);
|
|
1384
|
-
const result = await getFreeBusy(body.start, body.end);
|
|
1470
|
+
const result = await _googleCalendar.getFreeBusy(body.start, body.end);
|
|
1385
1471
|
if (!result.ok)
|
|
1386
1472
|
return c.json({ error: result.message }, 500);
|
|
1387
1473
|
return c.json(result);
|
|
1388
1474
|
});
|
|
1389
1475
|
// Create calendar event
|
|
1390
1476
|
app.post("/api/google/calendar/events", async (c) => {
|
|
1391
|
-
if (!isCalendarAvailable())
|
|
1477
|
+
if (!(_googleCalendar?.isCalendarAvailable() ?? false))
|
|
1392
1478
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1393
1479
|
const body = await c.req.json();
|
|
1394
1480
|
if (!body.title || !body.start || !body.end) {
|
|
1395
1481
|
return c.json({ error: "title, start, and end are required" }, 400);
|
|
1396
1482
|
}
|
|
1397
1483
|
// Temporal validation: catch day-of-week mismatches before creating events (ts_temporal_mismatch_01)
|
|
1398
|
-
const temporalCheck = validateCalendarEntry(body.start, body.expectedDayOfWeek);
|
|
1484
|
+
const temporalCheck = _googleTemporal.validateCalendarEntry(body.start, body.expectedDayOfWeek);
|
|
1399
1485
|
if (!temporalCheck.ok) {
|
|
1400
1486
|
return c.json({
|
|
1401
1487
|
error: temporalCheck.message,
|
|
1402
1488
|
suggestion: temporalCheck.suggestion,
|
|
1403
|
-
actualDayOfWeek: getDayOfWeek(body.start),
|
|
1489
|
+
actualDayOfWeek: _googleTemporal.getDayOfWeek(body.start),
|
|
1404
1490
|
}, 400);
|
|
1405
1491
|
}
|
|
1406
|
-
const result = await createEvent(body.title, body.start, body.end, {
|
|
1492
|
+
const result = await _googleCalendar.createEvent(body.title, body.start, body.end, {
|
|
1407
1493
|
description: body.description,
|
|
1408
1494
|
location: body.location,
|
|
1409
1495
|
attendees: body.attendees,
|
|
@@ -1413,13 +1499,13 @@ app.post("/api/google/calendar/events", async (c) => {
|
|
|
1413
1499
|
if (!result.ok)
|
|
1414
1500
|
return c.json({ error: result.message }, 500);
|
|
1415
1501
|
logActivity({ source: "calendar", summary: `Event created: ${body.title}`, actionLabel: "PROMPTED", reason: "user created calendar event" });
|
|
1416
|
-
return c.json({ ...result, actualDayOfWeek: getDayOfWeek(body.start) });
|
|
1502
|
+
return c.json({ ...result, actualDayOfWeek: _googleTemporal.getDayOfWeek(body.start) });
|
|
1417
1503
|
});
|
|
1418
1504
|
// List events with flexible filtering
|
|
1419
1505
|
app.get("/api/google/calendar/events", async (c) => {
|
|
1420
|
-
if (!isCalendarAvailable())
|
|
1506
|
+
if (!(_googleCalendar?.isCalendarAvailable() ?? false))
|
|
1421
1507
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1422
|
-
const result = await listEvents({
|
|
1508
|
+
const result = await _googleCalendar.listEvents({
|
|
1423
1509
|
timeMin: c.req.query("timeMin"),
|
|
1424
1510
|
timeMax: c.req.query("timeMax"),
|
|
1425
1511
|
query: c.req.query("q"),
|
|
@@ -1432,34 +1518,34 @@ app.get("/api/google/calendar/events", async (c) => {
|
|
|
1432
1518
|
});
|
|
1433
1519
|
// Update calendar event
|
|
1434
1520
|
app.patch("/api/google/calendar/events/:eventId", async (c) => {
|
|
1435
|
-
if (!isCalendarAvailable())
|
|
1521
|
+
if (!(_googleCalendar?.isCalendarAvailable() ?? false))
|
|
1436
1522
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1437
1523
|
const eventId = c.req.param("eventId");
|
|
1438
1524
|
const body = await c.req.json();
|
|
1439
1525
|
// Temporal validation on updated start date (ts_temporal_mismatch_01)
|
|
1440
1526
|
if (body.start) {
|
|
1441
|
-
const temporalCheck = validateCalendarEntry(body.start, body.expectedDayOfWeek);
|
|
1527
|
+
const temporalCheck = _googleTemporal.validateCalendarEntry(body.start, body.expectedDayOfWeek);
|
|
1442
1528
|
if (!temporalCheck.ok) {
|
|
1443
1529
|
return c.json({
|
|
1444
1530
|
error: temporalCheck.message,
|
|
1445
1531
|
suggestion: temporalCheck.suggestion,
|
|
1446
|
-
actualDayOfWeek: getDayOfWeek(body.start),
|
|
1532
|
+
actualDayOfWeek: _googleTemporal.getDayOfWeek(body.start),
|
|
1447
1533
|
}, 400);
|
|
1448
1534
|
}
|
|
1449
1535
|
}
|
|
1450
|
-
const result = await updateEvent(eventId, body);
|
|
1536
|
+
const result = await _googleCalendar.updateEvent(eventId, body);
|
|
1451
1537
|
if (!result.ok)
|
|
1452
1538
|
return c.json({ error: result.message }, 500);
|
|
1453
1539
|
logActivity({ source: "calendar", summary: `Event updated: ${eventId}`, actionLabel: "PROMPTED", reason: "user updated calendar event" });
|
|
1454
|
-
return c.json(body.start ? { ...result, actualDayOfWeek: getDayOfWeek(body.start) } : result);
|
|
1540
|
+
return c.json(body.start ? { ...result, actualDayOfWeek: _googleTemporal.getDayOfWeek(body.start) } : result);
|
|
1455
1541
|
});
|
|
1456
1542
|
// Delete calendar event
|
|
1457
1543
|
app.delete("/api/google/calendar/events/:eventId", async (c) => {
|
|
1458
|
-
if (!isCalendarAvailable())
|
|
1544
|
+
if (!(_googleCalendar?.isCalendarAvailable() ?? false))
|
|
1459
1545
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1460
1546
|
const eventId = c.req.param("eventId");
|
|
1461
1547
|
const sendUpdates = c.req.query("sendUpdates");
|
|
1462
|
-
const result = await deleteEvent(eventId, { sendUpdates });
|
|
1548
|
+
const result = await _googleCalendar.deleteEvent(eventId, { sendUpdates });
|
|
1463
1549
|
if (!result.ok)
|
|
1464
1550
|
return c.json({ error: result.message }, 500);
|
|
1465
1551
|
logActivity({ source: "calendar", summary: `Event deleted: ${eventId}`, actionLabel: "PROMPTED", reason: "user deleted calendar event" });
|
|
@@ -1467,12 +1553,12 @@ app.delete("/api/google/calendar/events/:eventId", async (c) => {
|
|
|
1467
1553
|
});
|
|
1468
1554
|
// Search calendar events by text
|
|
1469
1555
|
app.get("/api/google/calendar/search", async (c) => {
|
|
1470
|
-
if (!isCalendarAvailable())
|
|
1556
|
+
if (!(_googleCalendar?.isCalendarAvailable() ?? false))
|
|
1471
1557
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1472
1558
|
const query = c.req.query("q");
|
|
1473
1559
|
if (!query)
|
|
1474
1560
|
return c.json({ error: "q (search query) is required" }, 400);
|
|
1475
|
-
const result = await searchEvents(query, {
|
|
1561
|
+
const result = await _googleCalendar.searchEvents(query, {
|
|
1476
1562
|
timeMin: c.req.query("timeMin"),
|
|
1477
1563
|
timeMax: c.req.query("timeMax"),
|
|
1478
1564
|
maxResults: c.req.query("maxResults") ? parseInt(c.req.query("maxResults"), 10) : undefined,
|
|
@@ -1490,9 +1576,9 @@ app.get("/api/google/tasks/lists", async (c) => {
|
|
|
1490
1576
|
const session = validateSession(sessionId);
|
|
1491
1577
|
if (!session)
|
|
1492
1578
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1493
|
-
if (!isTasksAvailable())
|
|
1579
|
+
if (!(_googleTasks?.isTasksAvailable() ?? false))
|
|
1494
1580
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1495
|
-
const result = await listTaskLists();
|
|
1581
|
+
const result = await _googleTasks.listTaskLists();
|
|
1496
1582
|
if (!result.ok)
|
|
1497
1583
|
return c.json({ error: result.message }, 500);
|
|
1498
1584
|
return c.json(result);
|
|
@@ -1505,12 +1591,12 @@ app.post("/api/google/tasks/lists", async (c) => {
|
|
|
1505
1591
|
const session = validateSession(sessionId);
|
|
1506
1592
|
if (!session)
|
|
1507
1593
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1508
|
-
if (!isTasksAvailable())
|
|
1594
|
+
if (!(_googleTasks?.isTasksAvailable() ?? false))
|
|
1509
1595
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1510
1596
|
const body = await c.req.json();
|
|
1511
1597
|
if (!body.title)
|
|
1512
1598
|
return c.json({ error: "title is required" }, 400);
|
|
1513
|
-
const result = await createTaskList(body.title);
|
|
1599
|
+
const result = await _googleTasks.createTaskList(body.title);
|
|
1514
1600
|
if (!result.ok)
|
|
1515
1601
|
return c.json({ error: result.message }, 500);
|
|
1516
1602
|
logActivity({ source: "tasks", summary: `Task list created: ${body.title}`, actionLabel: "PROMPTED", reason: "user created task list" });
|
|
@@ -1524,13 +1610,13 @@ app.patch("/api/google/tasks/lists/:listId", async (c) => {
|
|
|
1524
1610
|
const session = validateSession(sessionId);
|
|
1525
1611
|
if (!session)
|
|
1526
1612
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1527
|
-
if (!isTasksAvailable())
|
|
1613
|
+
if (!(_googleTasks?.isTasksAvailable() ?? false))
|
|
1528
1614
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1529
1615
|
const listId = c.req.param("listId");
|
|
1530
1616
|
const body = await c.req.json();
|
|
1531
1617
|
if (!body.title)
|
|
1532
1618
|
return c.json({ error: "title is required" }, 400);
|
|
1533
|
-
const result = await updateTaskList(listId, body.title);
|
|
1619
|
+
const result = await _googleTasks.updateTaskList(listId, body.title);
|
|
1534
1620
|
if (!result.ok)
|
|
1535
1621
|
return c.json({ error: result.message }, 500);
|
|
1536
1622
|
return c.json(result);
|
|
@@ -1543,10 +1629,10 @@ app.delete("/api/google/tasks/lists/:listId", async (c) => {
|
|
|
1543
1629
|
const session = validateSession(sessionId);
|
|
1544
1630
|
if (!session)
|
|
1545
1631
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1546
|
-
if (!isTasksAvailable())
|
|
1632
|
+
if (!(_googleTasks?.isTasksAvailable() ?? false))
|
|
1547
1633
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1548
1634
|
const listId = c.req.param("listId");
|
|
1549
|
-
const result = await deleteTaskList(listId);
|
|
1635
|
+
const result = await _googleTasks.deleteTaskList(listId);
|
|
1550
1636
|
if (!result.ok)
|
|
1551
1637
|
return c.json({ error: result.message }, 500);
|
|
1552
1638
|
logActivity({ source: "tasks", summary: `Task list deleted: ${listId}`, actionLabel: "PROMPTED", reason: "user deleted task list" });
|
|
@@ -1560,7 +1646,7 @@ app.post("/api/google/tasks/recurring", async (c) => {
|
|
|
1560
1646
|
const session = validateSession(sessionId);
|
|
1561
1647
|
if (!session)
|
|
1562
1648
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1563
|
-
if (!isTasksAvailable())
|
|
1649
|
+
if (!(_googleTasks?.isTasksAvailable() ?? false))
|
|
1564
1650
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1565
1651
|
const body = await c.req.json();
|
|
1566
1652
|
if (!body.title)
|
|
@@ -1584,7 +1670,7 @@ app.post("/api/google/tasks/recurring", async (c) => {
|
|
|
1584
1670
|
}, 400);
|
|
1585
1671
|
}
|
|
1586
1672
|
}
|
|
1587
|
-
const result = await createRecurringWeeklyTasks(body);
|
|
1673
|
+
const result = await _googleTasks.createRecurringWeeklyTasks(body);
|
|
1588
1674
|
if (!result.ok)
|
|
1589
1675
|
return c.json({ error: result.message }, 500);
|
|
1590
1676
|
logActivity({ source: "tasks", summary: result.message, actionLabel: "PROMPTED", reason: "user created recurring tasks" });
|
|
@@ -1598,13 +1684,13 @@ app.get("/api/google/tasks/:listId", async (c) => {
|
|
|
1598
1684
|
const session = validateSession(sessionId);
|
|
1599
1685
|
if (!session)
|
|
1600
1686
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1601
|
-
if (!isTasksAvailable())
|
|
1687
|
+
if (!(_googleTasks?.isTasksAvailable() ?? false))
|
|
1602
1688
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1603
1689
|
const listId = c.req.param("listId");
|
|
1604
1690
|
const showCompleted = c.req.query("showCompleted") === "true";
|
|
1605
1691
|
const dueMin = c.req.query("dueMin");
|
|
1606
1692
|
const dueMax = c.req.query("dueMax");
|
|
1607
|
-
const result = await listTasks(listId, {
|
|
1693
|
+
const result = await _googleTasks.listTasks(listId, {
|
|
1608
1694
|
showCompleted,
|
|
1609
1695
|
dueMin: dueMin || undefined,
|
|
1610
1696
|
dueMax: dueMax || undefined,
|
|
@@ -1621,11 +1707,11 @@ app.get("/api/google/tasks/:listId/:taskId", async (c) => {
|
|
|
1621
1707
|
const session = validateSession(sessionId);
|
|
1622
1708
|
if (!session)
|
|
1623
1709
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1624
|
-
if (!isTasksAvailable())
|
|
1710
|
+
if (!(_googleTasks?.isTasksAvailable() ?? false))
|
|
1625
1711
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1626
1712
|
const listId = c.req.param("listId");
|
|
1627
1713
|
const taskId = c.req.param("taskId");
|
|
1628
|
-
const result = await
|
|
1714
|
+
const result = await _googleTasks.getTask(listId, taskId);
|
|
1629
1715
|
if (!result.ok)
|
|
1630
1716
|
return c.json({ error: result.message }, 500);
|
|
1631
1717
|
return c.json(result);
|
|
@@ -1638,13 +1724,13 @@ app.post("/api/google/tasks/:listId", async (c) => {
|
|
|
1638
1724
|
const session = validateSession(sessionId);
|
|
1639
1725
|
if (!session)
|
|
1640
1726
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1641
|
-
if (!isTasksAvailable())
|
|
1727
|
+
if (!(_googleTasks?.isTasksAvailable() ?? false))
|
|
1642
1728
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1643
1729
|
const listId = c.req.param("listId");
|
|
1644
1730
|
const body = await c.req.json();
|
|
1645
1731
|
if (!body.title)
|
|
1646
1732
|
return c.json({ error: "title is required" }, 400);
|
|
1647
|
-
const result = await createTask(listId, body);
|
|
1733
|
+
const result = await _googleTasks.createTask(listId, body);
|
|
1648
1734
|
if (!result.ok)
|
|
1649
1735
|
return c.json({ error: result.message }, 500);
|
|
1650
1736
|
logActivity({ source: "tasks", summary: `Task created: ${body.title}`, actionLabel: "PROMPTED", reason: "user created task" });
|
|
@@ -1658,12 +1744,12 @@ app.patch("/api/google/tasks/:listId/:taskId", async (c) => {
|
|
|
1658
1744
|
const session = validateSession(sessionId);
|
|
1659
1745
|
if (!session)
|
|
1660
1746
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1661
|
-
if (!isTasksAvailable())
|
|
1747
|
+
if (!(_googleTasks?.isTasksAvailable() ?? false))
|
|
1662
1748
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1663
1749
|
const listId = c.req.param("listId");
|
|
1664
1750
|
const taskId = c.req.param("taskId");
|
|
1665
1751
|
const body = await c.req.json();
|
|
1666
|
-
const result = await updateTask(listId, taskId, body);
|
|
1752
|
+
const result = await _googleTasks.updateTask(listId, taskId, body);
|
|
1667
1753
|
if (!result.ok)
|
|
1668
1754
|
return c.json({ error: result.message }, 500);
|
|
1669
1755
|
logActivity({ source: "tasks", summary: `Task updated: ${taskId}`, actionLabel: "PROMPTED", reason: "user updated task" });
|
|
@@ -1677,11 +1763,11 @@ app.post("/api/google/tasks/:listId/:taskId/complete", async (c) => {
|
|
|
1677
1763
|
const session = validateSession(sessionId);
|
|
1678
1764
|
if (!session)
|
|
1679
1765
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1680
|
-
if (!isTasksAvailable())
|
|
1766
|
+
if (!(_googleTasks?.isTasksAvailable() ?? false))
|
|
1681
1767
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1682
1768
|
const listId = c.req.param("listId");
|
|
1683
1769
|
const taskId = c.req.param("taskId");
|
|
1684
|
-
const result = await completeTask(listId, taskId);
|
|
1770
|
+
const result = await _googleTasks.completeTask(listId, taskId);
|
|
1685
1771
|
if (!result.ok)
|
|
1686
1772
|
return c.json({ error: result.message }, 500);
|
|
1687
1773
|
logActivity({ source: "tasks", summary: `Task completed: ${taskId}`, actionLabel: "PROMPTED", reason: "user completed task" });
|
|
@@ -1695,11 +1781,11 @@ app.post("/api/google/tasks/:listId/:taskId/uncomplete", async (c) => {
|
|
|
1695
1781
|
const session = validateSession(sessionId);
|
|
1696
1782
|
if (!session)
|
|
1697
1783
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1698
|
-
if (!isTasksAvailable())
|
|
1784
|
+
if (!(_googleTasks?.isTasksAvailable() ?? false))
|
|
1699
1785
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1700
1786
|
const listId = c.req.param("listId");
|
|
1701
1787
|
const taskId = c.req.param("taskId");
|
|
1702
|
-
const result = await uncompleteTask(listId, taskId);
|
|
1788
|
+
const result = await _googleTasks.uncompleteTask(listId, taskId);
|
|
1703
1789
|
if (!result.ok)
|
|
1704
1790
|
return c.json({ error: result.message }, 500);
|
|
1705
1791
|
return c.json(result);
|
|
@@ -1712,11 +1798,11 @@ app.delete("/api/google/tasks/:listId/:taskId", async (c) => {
|
|
|
1712
1798
|
const session = validateSession(sessionId);
|
|
1713
1799
|
if (!session)
|
|
1714
1800
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1715
|
-
if (!isTasksAvailable())
|
|
1801
|
+
if (!(_googleTasks?.isTasksAvailable() ?? false))
|
|
1716
1802
|
return c.json({ error: "Google not authenticated" }, 400);
|
|
1717
1803
|
const listId = c.req.param("listId");
|
|
1718
1804
|
const taskId = c.req.param("taskId");
|
|
1719
|
-
const result = await deleteTask(listId, taskId);
|
|
1805
|
+
const result = await _googleTasks.deleteTask(listId, taskId);
|
|
1720
1806
|
if (!result.ok)
|
|
1721
1807
|
return c.json({ error: result.message }, 500);
|
|
1722
1808
|
logActivity({ source: "tasks", summary: `Task deleted: ${taskId}`, actionLabel: "PROMPTED", reason: "user deleted task" });
|
|
@@ -1857,7 +1943,7 @@ app.put("/api/settings", async (c) => {
|
|
|
1857
1943
|
app.get("/api/admin/integrations", async (c) => {
|
|
1858
1944
|
const settings = getSettings();
|
|
1859
1945
|
const integrations = settings.integrations ?? { enabled: true };
|
|
1860
|
-
const status = getIntegrationStatus();
|
|
1946
|
+
const status = (_integrationsGate ? _integrationsGate.getIntegrationStatus() : []);
|
|
1861
1947
|
return c.json({
|
|
1862
1948
|
enabled: integrations.enabled ?? true,
|
|
1863
1949
|
services: status,
|
|
@@ -1876,9 +1962,9 @@ app.post("/api/admin/integrations", async (c) => {
|
|
|
1876
1962
|
}
|
|
1877
1963
|
const updated = await updateSettings(patch);
|
|
1878
1964
|
// Re-hydrate env with new gates — clear integration vars first, then re-hydrate
|
|
1879
|
-
const status = getIntegrationStatus();
|
|
1880
|
-
|
|
1881
|
-
const credStore = getCredentialStore();
|
|
1965
|
+
const status = (_integrationsGate ? _integrationsGate.getIntegrationStatus() : []);
|
|
1966
|
+
_vaultStore?.hydrateEnv();
|
|
1967
|
+
const credStore = (_credentialStore ? _credentialStore.getCredentialStore() : null);
|
|
1882
1968
|
if (credStore)
|
|
1883
1969
|
await credStore.hydrate();
|
|
1884
1970
|
return c.json({
|
|
@@ -1886,23 +1972,23 @@ app.post("/api/admin/integrations", async (c) => {
|
|
|
1886
1972
|
services: status.map((s) => ({
|
|
1887
1973
|
...s,
|
|
1888
1974
|
// Re-check after settings update
|
|
1889
|
-
enabled: isIntegrationEnabled(s.service),
|
|
1975
|
+
enabled: (_integrationsGate?.isIntegrationEnabled(s.service) ?? false),
|
|
1890
1976
|
})),
|
|
1891
1977
|
});
|
|
1892
1978
|
});
|
|
1893
1979
|
// --- Voice routes ---
|
|
1894
1980
|
// Voice status: which voice features are available?
|
|
1895
1981
|
app.get("/api/voice-status", async (c) => {
|
|
1896
|
-
return c.json({ tts: isTtsAvailable(), stt: isSttAvailable() });
|
|
1982
|
+
return c.json({ tts: (_ttsClient?.isTtsAvailable() ?? false), stt: (_sttClient?.isSttAvailable() ?? false) });
|
|
1897
1983
|
});
|
|
1898
1984
|
// TTS: synthesize text to WAV audio via Piper
|
|
1899
1985
|
app.get("/api/tts", async (c) => {
|
|
1900
1986
|
const text = c.req.query("text");
|
|
1901
1987
|
if (!text)
|
|
1902
1988
|
return c.json({ error: "text query param required" }, 400);
|
|
1903
|
-
if (!isTtsAvailable())
|
|
1989
|
+
if (!(_ttsClient?.isTtsAvailable() ?? false))
|
|
1904
1990
|
return c.json({ error: "TTS not available" }, 503);
|
|
1905
|
-
const wav = await synthesize(text);
|
|
1991
|
+
const wav = await _ttsClient.synthesize(text);
|
|
1906
1992
|
if (!wav)
|
|
1907
1993
|
return c.json({ error: "Synthesis failed" }, 502);
|
|
1908
1994
|
return new Response(wav, {
|
|
@@ -1914,12 +2000,12 @@ app.get("/api/tts", async (c) => {
|
|
|
1914
2000
|
});
|
|
1915
2001
|
// STT: transcribe audio via whisper-server
|
|
1916
2002
|
app.post("/api/stt", async (c) => {
|
|
1917
|
-
if (!isSttAvailable())
|
|
2003
|
+
if (!(_sttClient?.isSttAvailable() ?? false))
|
|
1918
2004
|
return c.json({ error: "STT not available" }, 503);
|
|
1919
2005
|
const body = await c.req.arrayBuffer();
|
|
1920
2006
|
if (!body || body.byteLength === 0)
|
|
1921
2007
|
return c.json({ error: "Audio body required" }, 400);
|
|
1922
|
-
const text = await transcribe(Buffer.from(body));
|
|
2008
|
+
const text = await _sttClient.transcribe(Buffer.from(body));
|
|
1923
2009
|
if (!text)
|
|
1924
2010
|
return c.json({ error: "Transcription failed" }, 502);
|
|
1925
2011
|
return c.json({ text });
|
|
@@ -1934,7 +2020,7 @@ function pushPendingVideo(filename) {
|
|
|
1934
2020
|
}
|
|
1935
2021
|
// Avatar status: is MuseTalk sidecar running?
|
|
1936
2022
|
app.get("/api/avatar/status", async (c) => {
|
|
1937
|
-
return c.json({ available: isAvatarAvailable() });
|
|
2023
|
+
return c.json({ available: (_avatarSidecar?.isAvatarAvailable() ?? false) });
|
|
1938
2024
|
});
|
|
1939
2025
|
// Poll for new avatar videos since a given timestamp
|
|
1940
2026
|
app.get("/api/avatar/latest", async (c) => {
|
|
@@ -1976,18 +2062,18 @@ app.post("/api/avatar/photo", async (c) => {
|
|
|
1976
2062
|
const session = validateSession(sessionId);
|
|
1977
2063
|
if (!session)
|
|
1978
2064
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1979
|
-
if (!isAvatarAvailable())
|
|
2065
|
+
if (!(_avatarSidecar?.isAvatarAvailable() ?? false))
|
|
1980
2066
|
return c.json({ error: "Avatar not available" }, 503);
|
|
1981
2067
|
const body = await c.req.arrayBuffer();
|
|
1982
2068
|
if (!body || body.byteLength === 0)
|
|
1983
2069
|
return c.json({ error: "Photo body required" }, 400);
|
|
1984
|
-
const avatarConfig = getAvatarConfig();
|
|
2070
|
+
const avatarConfig = _settingsVoice ? _settingsVoice.getAvatarConfig() : { photoPath: "public/avatar/photo.png", port: 0, enabled: false };
|
|
1985
2071
|
const photoPath = join(process.cwd(), avatarConfig.photoPath);
|
|
1986
2072
|
await mkdir(join(UI_DIR, "avatar"), { recursive: true });
|
|
1987
2073
|
await writeFile(photoPath, Buffer.from(body));
|
|
1988
|
-
const ok = await preparePhoto(photoPath);
|
|
2074
|
+
const ok = await _avatarClient.preparePhoto(photoPath);
|
|
1989
2075
|
if (ok) {
|
|
1990
|
-
await clearVideoCache();
|
|
2076
|
+
await _avatarClient.clearVideoCache();
|
|
1991
2077
|
latestAvatarVideo = null;
|
|
1992
2078
|
}
|
|
1993
2079
|
return c.json({ ok });
|
|
@@ -2036,8 +2122,9 @@ app.get("/api/history", async (c) => {
|
|
|
2036
2122
|
if (!session)
|
|
2037
2123
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
2038
2124
|
const cs = await getOrCreateChatSession(sessionId, session.name);
|
|
2039
|
-
//
|
|
2040
|
-
const
|
|
2125
|
+
// Always return main history (not active thread's)
|
|
2126
|
+
const historySource = cs.activeThreadId ? cs.mainHistory : cs.history;
|
|
2127
|
+
const messages = historySource
|
|
2041
2128
|
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
2042
2129
|
.map((m) => ({ role: m.role, content: m.content }));
|
|
2043
2130
|
return c.json({ messages });
|
|
@@ -2084,20 +2171,25 @@ app.post("/api/threads", async (c) => {
|
|
|
2084
2171
|
const now = new Date().toISOString();
|
|
2085
2172
|
const thread = {
|
|
2086
2173
|
id: generateThreadId(),
|
|
2087
|
-
title: body.title || "New
|
|
2174
|
+
title: body.title || "New chat",
|
|
2088
2175
|
history: [],
|
|
2089
2176
|
historySummary: "",
|
|
2090
2177
|
createdAt: now,
|
|
2091
2178
|
updatedAt: now,
|
|
2092
2179
|
};
|
|
2093
2180
|
threads.set(thread.id, thread);
|
|
2094
|
-
//
|
|
2181
|
+
// New thread starts blank. Main chat keeps its history.
|
|
2182
|
+
// If currently on a thread, save it back first.
|
|
2095
2183
|
const cs = chatSessions.get(sessionId);
|
|
2096
|
-
if (cs) {
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2184
|
+
if (cs && cs.activeThreadId) {
|
|
2185
|
+
const prevThread = threads.get(cs.activeThreadId);
|
|
2186
|
+
if (prevThread) {
|
|
2187
|
+
prevThread.history = cs.history;
|
|
2188
|
+
prevThread.historySummary = cs.historySummary;
|
|
2189
|
+
}
|
|
2190
|
+
cs.history = cs.mainHistory;
|
|
2191
|
+
cs.historySummary = cs.mainHistorySummary;
|
|
2192
|
+
cs.activeThreadId = null;
|
|
2101
2193
|
}
|
|
2102
2194
|
return c.json({ thread: { id: thread.id, title: thread.title, createdAt: thread.createdAt, updatedAt: thread.updatedAt } });
|
|
2103
2195
|
});
|
|
@@ -2108,11 +2200,15 @@ app.get("/api/threads/:id/history", async (c) => {
|
|
|
2108
2200
|
const session = validateSession(sessionId);
|
|
2109
2201
|
if (!session)
|
|
2110
2202
|
return c.json({ error: "Invalid session" }, 401);
|
|
2203
|
+
const threadId = c.req.param("id");
|
|
2111
2204
|
const threads = getThreadsForSession(sessionId);
|
|
2112
|
-
const thread = threads.get(
|
|
2205
|
+
const thread = threads.get(threadId);
|
|
2113
2206
|
if (!thread)
|
|
2114
2207
|
return c.json({ error: "Thread not found" }, 404);
|
|
2115
|
-
|
|
2208
|
+
// If this thread is currently active on cs, its live history is in cs.history
|
|
2209
|
+
const cs = chatSessions.get(sessionId);
|
|
2210
|
+
const historySource = (cs && cs.activeThreadId === threadId) ? cs.history : thread.history;
|
|
2211
|
+
const messages = historySource
|
|
2116
2212
|
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
2117
2213
|
.map((m) => ({ role: m.role, content: m.content }));
|
|
2118
2214
|
return c.json({ messages });
|
|
@@ -2141,8 +2237,14 @@ app.delete("/api/threads/:id", async (c) => {
|
|
|
2141
2237
|
const session = validateSession(sessionId);
|
|
2142
2238
|
if (!session)
|
|
2143
2239
|
return c.json({ error: "Invalid session" }, 401);
|
|
2240
|
+
const threadId = c.req.param("id");
|
|
2144
2241
|
const threads = getThreadsForSession(sessionId);
|
|
2145
|
-
|
|
2242
|
+
// If deleting the active thread, switch back to main first
|
|
2243
|
+
const cs = chatSessions.get(sessionId);
|
|
2244
|
+
if (cs && cs.activeThreadId === threadId) {
|
|
2245
|
+
switchSessionThread(cs, null, sessionId);
|
|
2246
|
+
}
|
|
2247
|
+
threads.delete(threadId);
|
|
2146
2248
|
return c.json({ ok: true });
|
|
2147
2249
|
});
|
|
2148
2250
|
// Activity log: poll for background actions
|
|
@@ -2273,13 +2375,17 @@ app.post("/api/branch", async (c) => {
|
|
|
2273
2375
|
reqSignal?.addEventListener("abort", onAbort, { once: true });
|
|
2274
2376
|
// Token buffer for split-placeholder rehydration
|
|
2275
2377
|
let tokenBuf = "";
|
|
2378
|
+
let totalOutputChars = 0;
|
|
2379
|
+
const streamStartMs = performance.now();
|
|
2276
2380
|
const flushBuf = () => {
|
|
2277
2381
|
if (!tokenBuf)
|
|
2278
2382
|
return;
|
|
2279
2383
|
const rehydrated = rehydrateResponse(tokenBuf);
|
|
2384
|
+
totalOutputChars += rehydrated.length;
|
|
2280
2385
|
tokenBuf = "";
|
|
2281
2386
|
stream.writeSSE({ data: JSON.stringify({ token: rehydrated }) }).catch(() => { });
|
|
2282
2387
|
};
|
|
2388
|
+
const streamModel = activeChatModel ?? (activeProvider === "ollama" ? "llama3.2:3b" : "claude-sonnet-4");
|
|
2283
2389
|
stream_fn({
|
|
2284
2390
|
messages: redactedBranchMessages,
|
|
2285
2391
|
model: activeChatModel,
|
|
@@ -2296,6 +2402,12 @@ app.post("/api/branch", async (c) => {
|
|
|
2296
2402
|
flushBuf(); // flush remainder
|
|
2297
2403
|
reqSignal?.removeEventListener("abort", onAbort);
|
|
2298
2404
|
stream.writeSSE({ data: JSON.stringify({ done: true }) }).catch(() => { });
|
|
2405
|
+
const durationMs = Math.round(performance.now() - streamStartMs);
|
|
2406
|
+
logLlmCall({
|
|
2407
|
+
ts: new Date().toISOString(), mode: "stream",
|
|
2408
|
+
provider: activeProvider, model: streamModel,
|
|
2409
|
+
durationMs, outputTokens: Math.ceil(totalOutputChars / 4), ok: true,
|
|
2410
|
+
});
|
|
2299
2411
|
resolve();
|
|
2300
2412
|
},
|
|
2301
2413
|
onError: async (err) => {
|
|
@@ -2307,6 +2419,12 @@ app.post("/api/branch", async (c) => {
|
|
|
2307
2419
|
if (!health.ok)
|
|
2308
2420
|
errorMsg += " — Check that Ollama is running. " + health.message;
|
|
2309
2421
|
}
|
|
2422
|
+
const durationMs = Math.round(performance.now() - streamStartMs);
|
|
2423
|
+
logLlmCall({
|
|
2424
|
+
ts: new Date().toISOString(), mode: "stream",
|
|
2425
|
+
provider: activeProvider, model: streamModel,
|
|
2426
|
+
durationMs, ok: false, error: errorMsg,
|
|
2427
|
+
});
|
|
2310
2428
|
stream.writeSSE({ data: JSON.stringify({ error: errorMsg }) }).catch(() => { });
|
|
2311
2429
|
resolve();
|
|
2312
2430
|
},
|
|
@@ -2357,7 +2475,7 @@ app.post("/api/agents/tasks/:id/cancel", async (c) => {
|
|
|
2357
2475
|
});
|
|
2358
2476
|
// --- File lock routes ---
|
|
2359
2477
|
app.get("/api/agents/locks", async (c) => {
|
|
2360
|
-
const locks = await listLocks();
|
|
2478
|
+
const locks = await _agentLocks.listLocks();
|
|
2361
2479
|
return c.json({ locks });
|
|
2362
2480
|
});
|
|
2363
2481
|
app.post("/api/agents/locks/acquire", async (c) => {
|
|
@@ -2366,7 +2484,7 @@ app.post("/api/agents/locks/acquire", async (c) => {
|
|
|
2366
2484
|
if (!agentId || !filePaths || !Array.isArray(filePaths)) {
|
|
2367
2485
|
return c.json({ error: "agentId and filePaths[] required" }, 400);
|
|
2368
2486
|
}
|
|
2369
|
-
const result = await acquireLocks(agentId, agentLabel || agentId, filePaths, timeoutMs);
|
|
2487
|
+
const result = await _agentLocks.acquireLocks(agentId, agentLabel || agentId, filePaths, timeoutMs);
|
|
2370
2488
|
return c.json(result, result.acquired ? 200 : 409);
|
|
2371
2489
|
});
|
|
2372
2490
|
app.post("/api/agents/locks/release", async (c) => {
|
|
@@ -2375,10 +2493,10 @@ app.post("/api/agents/locks/release", async (c) => {
|
|
|
2375
2493
|
if (!agentId)
|
|
2376
2494
|
return c.json({ error: "agentId required" }, 400);
|
|
2377
2495
|
if (filePath) {
|
|
2378
|
-
const ok = await releaseFileLock(agentId, filePath);
|
|
2496
|
+
const ok = await _agentLocks.releaseFileLock(agentId, filePath);
|
|
2379
2497
|
return c.json({ released: ok ? 1 : 0 });
|
|
2380
2498
|
}
|
|
2381
|
-
const count = await releaseLocks(agentId);
|
|
2499
|
+
const count = await _agentLocks.releaseLocks(agentId);
|
|
2382
2500
|
return c.json({ released: count });
|
|
2383
2501
|
});
|
|
2384
2502
|
app.post("/api/agents/locks/force-release", async (c) => {
|
|
@@ -2386,7 +2504,7 @@ app.post("/api/agents/locks/force-release", async (c) => {
|
|
|
2386
2504
|
const { filePath } = body;
|
|
2387
2505
|
if (!filePath)
|
|
2388
2506
|
return c.json({ error: "filePath required" }, 400);
|
|
2389
|
-
const ok = await forceReleaseLock(filePath);
|
|
2507
|
+
const ok = await _agentLocks.forceReleaseLock(filePath);
|
|
2390
2508
|
return c.json({ released: ok });
|
|
2391
2509
|
});
|
|
2392
2510
|
app.post("/api/agents/locks/check", async (c) => {
|
|
@@ -2395,11 +2513,11 @@ app.post("/api/agents/locks/check", async (c) => {
|
|
|
2395
2513
|
if (!filePaths || !Array.isArray(filePaths)) {
|
|
2396
2514
|
return c.json({ error: "filePaths[] required" }, 400);
|
|
2397
2515
|
}
|
|
2398
|
-
const conflicts = await checkLocks(filePaths);
|
|
2516
|
+
const conflicts = await _agentLocks.checkLocks(filePaths);
|
|
2399
2517
|
return c.json({ locked: conflicts.length > 0, conflicts });
|
|
2400
2518
|
});
|
|
2401
2519
|
app.post("/api/agents/locks/prune", async (_c) => {
|
|
2402
|
-
const pruned = await pruneAllStaleLocks();
|
|
2520
|
+
const pruned = await _agentLocks.pruneAllStaleLocks();
|
|
2403
2521
|
return _c.json({ pruned });
|
|
2404
2522
|
});
|
|
2405
2523
|
// --- Self-reported issues (autonomous agent findings) ---
|
|
@@ -2410,7 +2528,7 @@ app.get("/api/agents/issues", async (c) => {
|
|
|
2410
2528
|
});
|
|
2411
2529
|
// --- Agent runtime routes ---
|
|
2412
2530
|
app.get("/api/runtime/status", async (c) => {
|
|
2413
|
-
const rt = getRuntime();
|
|
2531
|
+
const rt = (_agentRuntime?.getRuntime() ?? null);
|
|
2414
2532
|
if (!rt)
|
|
2415
2533
|
return c.json({ available: false });
|
|
2416
2534
|
return c.json({
|
|
@@ -2420,14 +2538,14 @@ app.get("/api/runtime/status", async (c) => {
|
|
|
2420
2538
|
});
|
|
2421
2539
|
});
|
|
2422
2540
|
app.get("/api/runtime/instances", async (c) => {
|
|
2423
|
-
const rt = getRuntime();
|
|
2541
|
+
const rt = (_agentRuntime?.getRuntime() ?? null);
|
|
2424
2542
|
if (!rt)
|
|
2425
2543
|
return c.json({ instances: [] });
|
|
2426
2544
|
const states = c.req.query("states")?.split(",");
|
|
2427
2545
|
return c.json({ instances: rt.listInstances(states ? { states } : undefined) });
|
|
2428
2546
|
});
|
|
2429
2547
|
app.get("/api/runtime/instances/:id", async (c) => {
|
|
2430
|
-
const rt = getRuntime();
|
|
2548
|
+
const rt = (_agentRuntime?.getRuntime() ?? null);
|
|
2431
2549
|
if (!rt)
|
|
2432
2550
|
return c.json({ error: "Runtime not initialized" }, 503);
|
|
2433
2551
|
const inst = rt.getInstance(c.req.param("id"));
|
|
@@ -2436,7 +2554,7 @@ app.get("/api/runtime/instances/:id", async (c) => {
|
|
|
2436
2554
|
return c.json(inst);
|
|
2437
2555
|
});
|
|
2438
2556
|
app.post("/api/runtime/spawn", async (c) => {
|
|
2439
|
-
const rt = getRuntime();
|
|
2557
|
+
const rt = (_agentRuntime?.getRuntime() ?? null);
|
|
2440
2558
|
if (!rt)
|
|
2441
2559
|
return c.json({ error: "Runtime not initialized" }, 503);
|
|
2442
2560
|
const body = await c.req.json();
|
|
@@ -2481,7 +2599,7 @@ app.post("/api/runtime/spawn", async (c) => {
|
|
|
2481
2599
|
}
|
|
2482
2600
|
});
|
|
2483
2601
|
app.post("/api/runtime/instances/:id/pause", async (c) => {
|
|
2484
|
-
const rt = getRuntime();
|
|
2602
|
+
const rt = (_agentRuntime?.getRuntime() ?? null);
|
|
2485
2603
|
if (!rt)
|
|
2486
2604
|
return c.json({ error: "Runtime not initialized" }, 503);
|
|
2487
2605
|
try {
|
|
@@ -2493,7 +2611,7 @@ app.post("/api/runtime/instances/:id/pause", async (c) => {
|
|
|
2493
2611
|
}
|
|
2494
2612
|
});
|
|
2495
2613
|
app.post("/api/runtime/instances/:id/resume", async (c) => {
|
|
2496
|
-
const rt = getRuntime();
|
|
2614
|
+
const rt = (_agentRuntime?.getRuntime() ?? null);
|
|
2497
2615
|
if (!rt)
|
|
2498
2616
|
return c.json({ error: "Runtime not initialized" }, 503);
|
|
2499
2617
|
try {
|
|
@@ -2505,7 +2623,7 @@ app.post("/api/runtime/instances/:id/resume", async (c) => {
|
|
|
2505
2623
|
}
|
|
2506
2624
|
});
|
|
2507
2625
|
app.post("/api/runtime/instances/:id/terminate", async (c) => {
|
|
2508
|
-
const rt = getRuntime();
|
|
2626
|
+
const rt = (_agentRuntime?.getRuntime() ?? null);
|
|
2509
2627
|
if (!rt)
|
|
2510
2628
|
return c.json({ error: "Runtime not initialized" }, 503);
|
|
2511
2629
|
try {
|
|
@@ -2517,7 +2635,7 @@ app.post("/api/runtime/instances/:id/terminate", async (c) => {
|
|
|
2517
2635
|
}
|
|
2518
2636
|
});
|
|
2519
2637
|
app.post("/api/runtime/instances/:id/message", async (c) => {
|
|
2520
|
-
const rt = getRuntime();
|
|
2638
|
+
const rt = (_agentRuntime?.getRuntime() ?? null);
|
|
2521
2639
|
if (!rt)
|
|
2522
2640
|
return c.json({ error: "Runtime not initialized" }, 503);
|
|
2523
2641
|
const body = await c.req.json();
|
|
@@ -2666,7 +2784,7 @@ app.post("/api/github/webhooks", async (c) => {
|
|
|
2666
2784
|
const rawBody = await c.req.text();
|
|
2667
2785
|
const secret = process.env.GITHUB_WEBHOOK_SECRET;
|
|
2668
2786
|
if (secret && signature) {
|
|
2669
|
-
if (!verifyWebhookSignature(rawBody, signature, secret)) {
|
|
2787
|
+
if (!_githubWebhooks.verifyWebhookSignature(rawBody, signature, secret)) {
|
|
2670
2788
|
return c.json({ error: "Invalid signature" }, 401);
|
|
2671
2789
|
}
|
|
2672
2790
|
}
|
|
@@ -2677,12 +2795,12 @@ app.post("/api/github/webhooks", async (c) => {
|
|
|
2677
2795
|
catch {
|
|
2678
2796
|
return c.json({ error: "Invalid JSON" }, 400);
|
|
2679
2797
|
}
|
|
2680
|
-
const result = await
|
|
2798
|
+
const result = await _integrationsGithub.processWebhook(eventType, payload);
|
|
2681
2799
|
return c.json(result);
|
|
2682
2800
|
});
|
|
2683
2801
|
// GitHub status
|
|
2684
2802
|
app.get("/api/github/status", async (c) => {
|
|
2685
|
-
const status = await getGitHubStatus();
|
|
2803
|
+
const status = await _integrationsGithub.getGitHubStatus();
|
|
2686
2804
|
return c.json(status);
|
|
2687
2805
|
});
|
|
2688
2806
|
// GitHub PR review
|
|
@@ -2698,8 +2816,8 @@ app.post("/api/github/pr/review", async (c) => {
|
|
|
2698
2816
|
if (!prNumber)
|
|
2699
2817
|
return c.json({ error: "prNumber required" }, 400);
|
|
2700
2818
|
const result = postComment
|
|
2701
|
-
? await reviewAndCommentPR(prNumber, repo)
|
|
2702
|
-
: await reviewPullRequest(prNumber, repo);
|
|
2819
|
+
? await _integrationsGithub.reviewAndCommentPR(prNumber, repo)
|
|
2820
|
+
: await _integrationsGithub.reviewPullRequest(prNumber, repo);
|
|
2703
2821
|
if (!result)
|
|
2704
2822
|
return c.json({ error: "Failed to review PR" }, 502);
|
|
2705
2823
|
return c.json(result);
|
|
@@ -2717,8 +2835,8 @@ app.post("/api/github/issues/triage", async (c) => {
|
|
|
2717
2835
|
if (!issueNumber)
|
|
2718
2836
|
return c.json({ error: "issueNumber required" }, 400);
|
|
2719
2837
|
const result = apply
|
|
2720
|
-
? await triageAndLabelIssue(issueNumber, repo)
|
|
2721
|
-
: await triageGitHubIssue(issueNumber, repo);
|
|
2838
|
+
? await _integrationsGithub.triageAndLabelIssue(issueNumber, repo)
|
|
2839
|
+
: await _integrationsGithub.triageGitHubIssue(issueNumber, repo);
|
|
2722
2840
|
if (!result)
|
|
2723
2841
|
return c.json({ error: "Failed to triage issue" }, 502);
|
|
2724
2842
|
return c.json(result);
|
|
@@ -2733,7 +2851,7 @@ app.post("/api/github/issues/triage/batch", async (c) => {
|
|
|
2733
2851
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
2734
2852
|
const body = await c.req.json();
|
|
2735
2853
|
const { repo, apply } = body;
|
|
2736
|
-
const results = await batchTriageIssues(repo, { apply });
|
|
2854
|
+
const results = await _integrationsGithub.batchTriageIssues(repo, { apply });
|
|
2737
2855
|
return c.json({ count: results.length, results });
|
|
2738
2856
|
});
|
|
2739
2857
|
// GitHub commit analysis
|
|
@@ -2747,34 +2865,34 @@ app.post("/api/github/commits/analyze", async (c) => {
|
|
|
2747
2865
|
const body = await c.req.json();
|
|
2748
2866
|
const { sha, repo, count, since } = body;
|
|
2749
2867
|
if (sha) {
|
|
2750
|
-
const result = await analyzeGitHubCommit(sha, repo);
|
|
2868
|
+
const result = await _integrationsGithub.analyzeGitHubCommit(sha, repo);
|
|
2751
2869
|
if (!result)
|
|
2752
2870
|
return c.json({ error: "Failed to analyze commit" }, 502);
|
|
2753
2871
|
return c.json(result);
|
|
2754
2872
|
}
|
|
2755
|
-
const results = await analyzeRecentGitHubCommits(repo, { count, since });
|
|
2873
|
+
const results = await _integrationsGithub.analyzeRecentGitHubCommits(repo, { count, since });
|
|
2756
2874
|
return c.json({ count: results.length, results });
|
|
2757
2875
|
});
|
|
2758
2876
|
// GitHub repo health
|
|
2759
2877
|
app.get("/api/github/health/:owner/:repo", async (c) => {
|
|
2760
2878
|
const { owner, repo } = c.req.param();
|
|
2761
|
-
const report = await getGitHubRepoHealth(`${owner}/${repo}`);
|
|
2879
|
+
const report = await _integrationsGithub.getGitHubRepoHealth(`${owner}/${repo}`);
|
|
2762
2880
|
if (!report)
|
|
2763
2881
|
return c.json({ error: "Failed to generate health report" }, 502);
|
|
2764
2882
|
return c.json(report);
|
|
2765
2883
|
});
|
|
2766
2884
|
app.get("/api/github/health/:owner/:repo/markdown", async (c) => {
|
|
2767
2885
|
const { owner, repo } = c.req.param();
|
|
2768
|
-
const report = await getGitHubRepoHealth(`${owner}/${repo}`);
|
|
2886
|
+
const report = await _integrationsGithub.getGitHubRepoHealth(`${owner}/${repo}`);
|
|
2769
2887
|
if (!report)
|
|
2770
2888
|
return c.json({ error: "Failed to generate health report" }, 502);
|
|
2771
|
-
return c.text(formatHealthReport(report));
|
|
2889
|
+
return c.text(_integrationsGithub.formatHealthReport(report));
|
|
2772
2890
|
});
|
|
2773
2891
|
// --- Slack routes ---
|
|
2774
2892
|
// Slack OAuth: initiate "Add to Slack" flow
|
|
2775
2893
|
app.get("/api/slack/auth", async (c) => {
|
|
2776
2894
|
const redirectUri = `http://localhost:${PORT}/api/slack/callback`;
|
|
2777
|
-
const result =
|
|
2895
|
+
const result = _slackClient.getOAuthUrl(redirectUri);
|
|
2778
2896
|
if (!result.ok) {
|
|
2779
2897
|
return c.html(`<html><body><h2>Slack not configured</h2><p>${result.message}</p></body></html>`);
|
|
2780
2898
|
}
|
|
@@ -2791,7 +2909,7 @@ app.get("/api/slack/callback", async (c) => {
|
|
|
2791
2909
|
return c.html(`<html><body><h2>Missing authorization code</h2></body></html>`);
|
|
2792
2910
|
}
|
|
2793
2911
|
const redirectUri = `http://localhost:${PORT}/api/slack/callback`;
|
|
2794
|
-
const result = await
|
|
2912
|
+
const result = await _slackClient.exchangeOAuthCode(code, redirectUri);
|
|
2795
2913
|
if (!result.ok) {
|
|
2796
2914
|
return c.html(`<html><body><h2>Token exchange failed</h2><p>${result.message}</p></body></html>`);
|
|
2797
2915
|
}
|
|
@@ -2803,32 +2921,33 @@ app.get("/api/slack/callback", async (c) => {
|
|
|
2803
2921
|
vaultKey = restored.sessionKey;
|
|
2804
2922
|
sessionKeys.set(restored.session.id, restored.sessionKey);
|
|
2805
2923
|
setEncryptionKey(restored.sessionKey);
|
|
2806
|
-
|
|
2924
|
+
if (_vaultStore)
|
|
2925
|
+
await _vaultStore.loadVault(restored.sessionKey);
|
|
2807
2926
|
}
|
|
2808
2927
|
}
|
|
2809
2928
|
if (!vaultKey) {
|
|
2810
2929
|
return c.html(`<html><body><h2>Session not found</h2><p>Log in first, then try connecting Slack again.</p></body></html>`);
|
|
2811
2930
|
}
|
|
2812
|
-
await setVaultKey("SLACK_BOT_TOKEN", result.botToken, vaultKey, "Slack Bot Token");
|
|
2931
|
+
await _vaultStore.setVaultKey("SLACK_BOT_TOKEN", result.botToken, vaultKey, "Slack Bot Token");
|
|
2813
2932
|
if (result.teamId) {
|
|
2814
|
-
await setVaultKey("SLACK_TEAM_ID", result.teamId, vaultKey, "Slack Team ID");
|
|
2933
|
+
await _vaultStore.setVaultKey("SLACK_TEAM_ID", result.teamId, vaultKey, "Slack Team ID");
|
|
2815
2934
|
}
|
|
2816
2935
|
logActivity({ source: "slack", summary: `Slack OAuth connected — team: ${result.teamName ?? result.teamId}`, actionLabel: "PROMPTED", reason: "user connected Slack OAuth" });
|
|
2817
2936
|
return c.html(`<html><body><h2>Slack connected!</h2><p>Team: ${result.teamName ?? "connected"}</p><p>This tab will close automatically.</p><script>if(window.opener){window.opener.postMessage("slack-connected","*")}setTimeout(()=>window.close(),1500)</script></body></html>`);
|
|
2818
2937
|
});
|
|
2819
2938
|
// Slack status: check connection state
|
|
2820
2939
|
app.get("/api/slack/status", async (c) => {
|
|
2821
|
-
const client =
|
|
2940
|
+
const client = (_slackClient?.getClient() ?? null);
|
|
2822
2941
|
if (!client) {
|
|
2823
2942
|
return c.json({
|
|
2824
|
-
configured: isSlackConfigured(),
|
|
2943
|
+
configured: (_slackClient?.isSlackConfigured() ?? false),
|
|
2825
2944
|
authenticated: false,
|
|
2826
2945
|
message: "SLACK_BOT_TOKEN not set",
|
|
2827
2946
|
});
|
|
2828
2947
|
}
|
|
2829
2948
|
const auth = await client.testAuth();
|
|
2830
2949
|
return c.json({
|
|
2831
|
-
configured: isSlackConfigured(),
|
|
2950
|
+
configured: (_slackClient?.isSlackConfigured() ?? false),
|
|
2832
2951
|
authenticated: auth.ok,
|
|
2833
2952
|
userId: auth.userId,
|
|
2834
2953
|
teamId: auth.teamId,
|
|
@@ -2844,7 +2963,7 @@ app.post("/api/slack/send", async (c) => {
|
|
|
2844
2963
|
const session = validateSession(sessionId);
|
|
2845
2964
|
if (!session)
|
|
2846
2965
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
2847
|
-
const client =
|
|
2966
|
+
const client = (_slackClient?.getClient() ?? null);
|
|
2848
2967
|
if (!client)
|
|
2849
2968
|
return c.json({ error: "Slack not available" }, 503);
|
|
2850
2969
|
const body = await c.req.json();
|
|
@@ -2867,7 +2986,7 @@ app.post("/api/slack/dm", async (c) => {
|
|
|
2867
2986
|
const session = validateSession(sessionId);
|
|
2868
2987
|
if (!session)
|
|
2869
2988
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
2870
|
-
const client =
|
|
2989
|
+
const client = (_slackClient?.getClient() ?? null);
|
|
2871
2990
|
if (!client)
|
|
2872
2991
|
return c.json({ error: "Slack not available" }, 503);
|
|
2873
2992
|
const body = await c.req.json();
|
|
@@ -2888,7 +3007,7 @@ app.get("/api/slack/channels", async (c) => {
|
|
|
2888
3007
|
if (!session)
|
|
2889
3008
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
2890
3009
|
const types = c.req.query("types") || undefined;
|
|
2891
|
-
const result = await listChannels({ types });
|
|
3010
|
+
const result = await _slackChannels.listChannels({ types });
|
|
2892
3011
|
if (!result.ok)
|
|
2893
3012
|
return c.json({ error: result.message }, 502);
|
|
2894
3013
|
return c.json({ channels: result.channels });
|
|
@@ -2901,7 +3020,7 @@ app.get("/api/slack/channels/:id", async (c) => {
|
|
|
2901
3020
|
const session = validateSession(sessionId);
|
|
2902
3021
|
if (!session)
|
|
2903
3022
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
2904
|
-
const result = await getChannelInfo(c.req.param("id"));
|
|
3023
|
+
const result = await _slackChannels.getChannelInfo(c.req.param("id"));
|
|
2905
3024
|
if (!result.ok)
|
|
2906
3025
|
return c.json({ error: result.message }, 502);
|
|
2907
3026
|
return c.json(result.channel);
|
|
@@ -2914,7 +3033,7 @@ app.post("/api/slack/channels/:id/join", async (c) => {
|
|
|
2914
3033
|
const session = validateSession(sessionId);
|
|
2915
3034
|
if (!session)
|
|
2916
3035
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
2917
|
-
const result = await joinChannel(c.req.param("id"));
|
|
3036
|
+
const result = await _slackChannels.joinChannel(c.req.param("id"));
|
|
2918
3037
|
if (!result.ok)
|
|
2919
3038
|
return c.json({ error: result.message }, 502);
|
|
2920
3039
|
return c.json({ ok: true, message: result.message });
|
|
@@ -2928,7 +3047,7 @@ app.get("/api/slack/channels/:id/history", async (c) => {
|
|
|
2928
3047
|
if (!session)
|
|
2929
3048
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
2930
3049
|
const limit = c.req.query("limit") ? parseInt(c.req.query("limit"), 10) : undefined;
|
|
2931
|
-
const result = await getChannelHistory(c.req.param("id"), { limit });
|
|
3050
|
+
const result = await _slackChannels.getChannelHistory(c.req.param("id"), { limit });
|
|
2932
3051
|
if (!result.ok)
|
|
2933
3052
|
return c.json({ error: result.message }, 502);
|
|
2934
3053
|
return c.json({ messages: result.messages });
|
|
@@ -2941,7 +3060,7 @@ app.get("/api/slack/users/:id", async (c) => {
|
|
|
2941
3060
|
const session = validateSession(sessionId);
|
|
2942
3061
|
if (!session)
|
|
2943
3062
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
2944
|
-
const client =
|
|
3063
|
+
const client = (_slackClient?.getClient() ?? null);
|
|
2945
3064
|
if (!client)
|
|
2946
3065
|
return c.json({ error: "Slack not available" }, 503);
|
|
2947
3066
|
const result = await client.getUser(c.req.param("id"));
|
|
@@ -2986,14 +3105,14 @@ app.post("/api/resend/check-inbox", async (c) => {
|
|
|
2986
3105
|
// --- WhatsApp routes (Twilio-backed) ---
|
|
2987
3106
|
// WhatsApp status: check if configured
|
|
2988
3107
|
app.get("/api/whatsapp/status", (c) => {
|
|
2989
|
-
return c.json({ available: isWhatsAppConfigured() });
|
|
3108
|
+
return c.json({ available: (_channelsWhatsapp?.isWhatsAppConfigured() ?? false) });
|
|
2990
3109
|
});
|
|
2991
3110
|
// WhatsApp webhook: receive incoming messages from Twilio
|
|
2992
3111
|
// Uses centralized verification via verifyWebhookRequest from the webhook registry.
|
|
2993
3112
|
// Custom TwiML response handling prevents use of generic createWebhookRoute.
|
|
2994
3113
|
app.post("/api/twilio/whatsapp", async (c) => {
|
|
2995
3114
|
const rawBody = await c.req.text();
|
|
2996
|
-
const params = parseFormBody(rawBody);
|
|
3115
|
+
const params = _webhooksTwilio.parseFormBody(rawBody);
|
|
2997
3116
|
// Verify via centralized webhook system (handles secret resolution from config)
|
|
2998
3117
|
const headers = {};
|
|
2999
3118
|
c.req.raw.headers.forEach((value, key) => {
|
|
@@ -3008,24 +3127,24 @@ app.post("/api/twilio/whatsapp", async (c) => {
|
|
|
3008
3127
|
}
|
|
3009
3128
|
const payload = params;
|
|
3010
3129
|
// Store the inbound message in history + update contacts
|
|
3011
|
-
await processIncomingMessage(payload);
|
|
3130
|
+
await _webhooksTwilio.processIncomingMessage(payload);
|
|
3012
3131
|
// Extract sender info
|
|
3013
3132
|
const from = payload.From?.replace(/^whatsapp:/, "") ?? "";
|
|
3014
3133
|
const body = payload.Body?.trim() ?? "";
|
|
3015
3134
|
if (!body) {
|
|
3016
3135
|
c.header("Content-Type", "text/xml");
|
|
3017
|
-
return c.body(emptyTwimlResponse());
|
|
3136
|
+
return c.body(_webhooksTwilio.emptyTwimlResponse());
|
|
3018
3137
|
}
|
|
3019
3138
|
// Process through chat pipeline (async — Twilio allows up to 15s for response).
|
|
3020
3139
|
// The service sends the reply via Twilio API, so we return empty TwiML to avoid
|
|
3021
3140
|
// duplicate messages. If chat processing fails, we reply inline via TwiML as fallback.
|
|
3022
|
-
const result = await handleWhatsAppMessage(from, body, payload.ProfileName);
|
|
3141
|
+
const result = await _servicesWhatsapp.handleWhatsAppMessage(from, body, payload.ProfileName);
|
|
3023
3142
|
c.header("Content-Type", "text/xml");
|
|
3024
3143
|
if (!result.ok && !result.reply) {
|
|
3025
3144
|
// Chat failed and no reply was sent — respond inline so user isn't left hanging
|
|
3026
|
-
return c.body(replyTwiml("Sorry, I couldn't process that right now. Please try again."));
|
|
3145
|
+
return c.body(_webhooksTwilio.replyTwiml("Sorry, I couldn't process that right now. Please try again."));
|
|
3027
3146
|
}
|
|
3028
|
-
return c.body(emptyTwimlResponse());
|
|
3147
|
+
return c.body(_webhooksTwilio.emptyTwimlResponse());
|
|
3029
3148
|
});
|
|
3030
3149
|
// WhatsApp relay: receive pre-verified messages from the Cloudflare Worker.
|
|
3031
3150
|
// The Worker has already verified Twilio's signature — this endpoint verifies
|
|
@@ -3037,21 +3156,21 @@ app.post("/api/relay/whatsapp", async (c) => {
|
|
|
3037
3156
|
headers[key.toLowerCase()] = value;
|
|
3038
3157
|
});
|
|
3039
3158
|
const relaySecret = process.env.RELAY_SECRET ?? "";
|
|
3040
|
-
const verification = verifyRelaySignature(rawBody, headers, relaySecret);
|
|
3159
|
+
const verification = _webhooksRelay.verifyRelaySignature(rawBody, headers, relaySecret);
|
|
3041
3160
|
if (!verification.valid) {
|
|
3042
3161
|
return c.json({ error: verification.error ?? "Invalid relay signature" }, 401);
|
|
3043
3162
|
}
|
|
3044
|
-
const params = parseFormBody(rawBody);
|
|
3163
|
+
const params = _webhooksTwilio.parseFormBody(rawBody);
|
|
3045
3164
|
const payload = params;
|
|
3046
3165
|
// Store inbound message + update contacts (same as direct webhook path)
|
|
3047
|
-
await processIncomingMessage(payload);
|
|
3166
|
+
await _webhooksTwilio.processIncomingMessage(payload);
|
|
3048
3167
|
const from = payload.From?.replace(/^whatsapp:/, "") ?? "";
|
|
3049
3168
|
const body = payload.Body?.trim() ?? "";
|
|
3050
3169
|
if (!body) {
|
|
3051
3170
|
return c.json({ ok: true, message: "Empty message, skipped" });
|
|
3052
3171
|
}
|
|
3053
3172
|
// Process through chat pipeline — reply sent via Twilio API
|
|
3054
|
-
const result = await handleWhatsAppMessage(from, body, payload.ProfileName);
|
|
3173
|
+
const result = await _servicesWhatsapp.handleWhatsAppMessage(from, body, payload.ProfileName);
|
|
3055
3174
|
return c.json({ ok: result.ok, reply: result.reply, error: result.error });
|
|
3056
3175
|
});
|
|
3057
3176
|
// Resend relay: receive pre-verified inbound emails from the Cloudflare Worker.
|
|
@@ -3064,7 +3183,7 @@ app.post("/api/relay/resend", async (c) => {
|
|
|
3064
3183
|
headers[key.toLowerCase()] = value;
|
|
3065
3184
|
});
|
|
3066
3185
|
const relaySecret = process.env.RELAY_SECRET ?? "";
|
|
3067
|
-
const verification = verifyRelaySignature(rawBody, headers, relaySecret);
|
|
3186
|
+
const verification = _webhooksRelay.verifyRelaySignature(rawBody, headers, relaySecret);
|
|
3068
3187
|
if (!verification.valid) {
|
|
3069
3188
|
return c.json({ error: verification.error ?? "Invalid relay signature" }, 401);
|
|
3070
3189
|
}
|
|
@@ -3115,7 +3234,7 @@ app.post("/api/whatsapp/send", async (c) => {
|
|
|
3115
3234
|
const session = validateSession(sessionId);
|
|
3116
3235
|
if (!session)
|
|
3117
3236
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
3118
|
-
const client =
|
|
3237
|
+
const client = (_channelsWhatsapp?.getClient() ?? null);
|
|
3119
3238
|
if (!client)
|
|
3120
3239
|
return c.json({ error: "WhatsApp not configured. Add TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_PHONE_NUMBER to vault." }, 503);
|
|
3121
3240
|
const body = await c.req.json();
|
|
@@ -3134,7 +3253,7 @@ app.get("/api/whatsapp/contacts", async (c) => {
|
|
|
3134
3253
|
const session = validateSession(sessionId);
|
|
3135
3254
|
if (!session)
|
|
3136
3255
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
3137
|
-
const client =
|
|
3256
|
+
const client = (_channelsWhatsapp?.getClient() ?? null);
|
|
3138
3257
|
if (!client)
|
|
3139
3258
|
return c.json({ error: "WhatsApp not configured" }, 503);
|
|
3140
3259
|
const contacts = await client.listContacts();
|
|
@@ -3148,7 +3267,7 @@ app.get("/api/whatsapp/history", async (c) => {
|
|
|
3148
3267
|
const session = validateSession(sessionId);
|
|
3149
3268
|
if (!session)
|
|
3150
3269
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
3151
|
-
const client =
|
|
3270
|
+
const client = (_channelsWhatsapp?.getClient() ?? null);
|
|
3152
3271
|
if (!client)
|
|
3153
3272
|
return c.json({ error: "WhatsApp not configured" }, 503);
|
|
3154
3273
|
const phone = c.req.query("phone") || undefined;
|
|
@@ -3802,18 +3921,18 @@ app.get("/api/contacts/graph/:id", async (c) => {
|
|
|
3802
3921
|
// --- Credentials endpoints ---
|
|
3803
3922
|
// List credentials (values masked).
|
|
3804
3923
|
app.get("/api/credentials", async (c) => {
|
|
3805
|
-
const store = getCredentialStore();
|
|
3924
|
+
const store = (_credentialStore ? _credentialStore.getCredentialStore() : null);
|
|
3806
3925
|
if (!store)
|
|
3807
3926
|
return c.json({ error: "Credentials not initialized" }, 503);
|
|
3808
3927
|
const type = (c.req.query("type") || undefined);
|
|
3809
3928
|
const search = c.req.query("search") || undefined;
|
|
3810
3929
|
const creds = await store.list({ type, search });
|
|
3811
|
-
const masked = creds.map((cr) => ({ ...cr, value: maskValue(cr.value) }));
|
|
3930
|
+
const masked = creds.map((cr) => ({ ...cr, value: _credentialStore.maskValue(cr.value) }));
|
|
3812
3931
|
return c.json({ credentials: masked, count: masked.length });
|
|
3813
3932
|
});
|
|
3814
3933
|
// Get single credential (full value for reveal/copy).
|
|
3815
3934
|
app.get("/api/credentials/:id", async (c) => {
|
|
3816
|
-
const store = getCredentialStore();
|
|
3935
|
+
const store = (_credentialStore ? _credentialStore.getCredentialStore() : null);
|
|
3817
3936
|
if (!store)
|
|
3818
3937
|
return c.json({ error: "Credentials not initialized" }, 503);
|
|
3819
3938
|
const cred = await store.get(c.req.param("id"));
|
|
@@ -3823,7 +3942,7 @@ app.get("/api/credentials/:id", async (c) => {
|
|
|
3823
3942
|
});
|
|
3824
3943
|
// Create credential.
|
|
3825
3944
|
app.post("/api/credentials", async (c) => {
|
|
3826
|
-
const store = getCredentialStore();
|
|
3945
|
+
const store = (_credentialStore ? _credentialStore.getCredentialStore() : null);
|
|
3827
3946
|
if (!store)
|
|
3828
3947
|
return c.json({ error: "Credentials not initialized" }, 503);
|
|
3829
3948
|
const body = await c.req.json();
|
|
@@ -3839,7 +3958,7 @@ app.post("/api/credentials", async (c) => {
|
|
|
3839
3958
|
});
|
|
3840
3959
|
// Update credential.
|
|
3841
3960
|
app.patch("/api/credentials/:id", async (c) => {
|
|
3842
|
-
const store = getCredentialStore();
|
|
3961
|
+
const store = (_credentialStore ? _credentialStore.getCredentialStore() : null);
|
|
3843
3962
|
if (!store)
|
|
3844
3963
|
return c.json({ error: "Credentials not initialized" }, 503);
|
|
3845
3964
|
const id = c.req.param("id");
|
|
@@ -3855,7 +3974,7 @@ app.patch("/api/credentials/:id", async (c) => {
|
|
|
3855
3974
|
});
|
|
3856
3975
|
// Archive credential (soft-delete).
|
|
3857
3976
|
app.delete("/api/credentials/:id", async (c) => {
|
|
3858
|
-
const store = getCredentialStore();
|
|
3977
|
+
const store = (_credentialStore ? _credentialStore.getCredentialStore() : null);
|
|
3859
3978
|
if (!store)
|
|
3860
3979
|
return c.json({ error: "Credentials not initialized" }, 503);
|
|
3861
3980
|
const id = c.req.param("id");
|
|
@@ -3870,10 +3989,10 @@ app.delete("/api/credentials/:id", async (c) => {
|
|
|
3870
3989
|
});
|
|
3871
3990
|
// Migrate vault keys → credential store.
|
|
3872
3991
|
app.post("/api/credentials/migrate-vault", async (c) => {
|
|
3873
|
-
const store = getCredentialStore();
|
|
3992
|
+
const store = (_credentialStore ? _credentialStore.getCredentialStore() : null);
|
|
3874
3993
|
if (!store)
|
|
3875
3994
|
return c.json({ error: "Credentials not initialized" }, 503);
|
|
3876
|
-
const vaultEntries = getVaultEntries();
|
|
3995
|
+
const vaultEntries = (_vaultStore ? _vaultStore.getVaultEntries() : []);
|
|
3877
3996
|
if (vaultEntries.length === 0) {
|
|
3878
3997
|
return c.json({ migrated: 0, message: "No vault keys to migrate" });
|
|
3879
3998
|
}
|
|
@@ -4159,14 +4278,14 @@ app.get("/api/help/context", async (c) => {
|
|
|
4159
4278
|
model: resolveChatModel() ?? "auto",
|
|
4160
4279
|
sidecars: {
|
|
4161
4280
|
search: isSearchAvailable(),
|
|
4162
|
-
tts: isTtsAvailable(),
|
|
4163
|
-
stt: isSttAvailable(),
|
|
4164
|
-
avatar: isAvatarAvailable(),
|
|
4281
|
+
tts: (_ttsClient?.isTtsAvailable() ?? false),
|
|
4282
|
+
stt: (_sttClient?.isSttAvailable() ?? false),
|
|
4283
|
+
avatar: (_avatarSidecar?.isAvatarAvailable() ?? false),
|
|
4165
4284
|
},
|
|
4166
4285
|
integrations: {
|
|
4167
|
-
google: isGoogleAuthenticated(),
|
|
4168
|
-
slack: isSlackConfigured(),
|
|
4169
|
-
whatsapp: isWhatsAppConfigured(),
|
|
4286
|
+
google: (_googleAuth?.isGoogleAuthenticated() ?? false),
|
|
4287
|
+
slack: (_slackClient?.isSlackConfigured() ?? false),
|
|
4288
|
+
whatsapp: (_channelsWhatsapp?.isWhatsAppConfigured() ?? false),
|
|
4170
4289
|
},
|
|
4171
4290
|
recentActivity: last20.map((e) => ({
|
|
4172
4291
|
timestamp: e.timestamp,
|
|
@@ -4303,7 +4422,7 @@ app.get("/api/browse", async (c) => {
|
|
|
4303
4422
|
catch {
|
|
4304
4423
|
return c.json({ error: "Invalid URL" }, 400);
|
|
4305
4424
|
}
|
|
4306
|
-
const result = await browseUrl(url);
|
|
4425
|
+
const result = await _browse.browseUrl(url);
|
|
4307
4426
|
if (!result) {
|
|
4308
4427
|
return c.json({ error: "Failed to fetch URL — timeout, non-HTML content, or network error" }, 502);
|
|
4309
4428
|
}
|
|
@@ -4409,9 +4528,9 @@ app.get("/api/ops/health", async (c) => {
|
|
|
4409
4528
|
models: settings.models,
|
|
4410
4529
|
sidecars: {
|
|
4411
4530
|
search: isSidecarAvailable(),
|
|
4412
|
-
tts: isTtsAvailable(),
|
|
4413
|
-
stt: isSttAvailable(),
|
|
4414
|
-
avatar: isAvatarAvailable(),
|
|
4531
|
+
tts: (_ttsClient?.isTtsAvailable() ?? false),
|
|
4532
|
+
stt: (_sttClient?.isSttAvailable() ?? false),
|
|
4533
|
+
avatar: (_avatarSidecar?.isAvatarAvailable() ?? false),
|
|
4415
4534
|
},
|
|
4416
4535
|
agents: {
|
|
4417
4536
|
total: agents.length,
|
|
@@ -4434,20 +4553,20 @@ app.get("/api/ops/sidecars", async (c) => {
|
|
|
4434
4553
|
port: parseInt(resolveEnv("SEARCH_PORT") ?? "3578", 10),
|
|
4435
4554
|
},
|
|
4436
4555
|
tts: {
|
|
4437
|
-
available: isTtsAvailable(),
|
|
4556
|
+
available: (_ttsClient?.isTtsAvailable() ?? false),
|
|
4438
4557
|
enabled: settings.tts.enabled,
|
|
4439
4558
|
port: settings.tts.port,
|
|
4440
4559
|
voice: settings.tts.voice,
|
|
4441
4560
|
autoPlay: settings.tts.autoPlay,
|
|
4442
4561
|
},
|
|
4443
4562
|
stt: {
|
|
4444
|
-
available: isSttAvailable(),
|
|
4563
|
+
available: (_sttClient?.isSttAvailable() ?? false),
|
|
4445
4564
|
enabled: settings.stt.enabled,
|
|
4446
4565
|
port: settings.stt.port,
|
|
4447
4566
|
model: settings.stt.model,
|
|
4448
4567
|
},
|
|
4449
4568
|
avatar: {
|
|
4450
|
-
available: isAvatarAvailable(),
|
|
4569
|
+
available: (_avatarSidecar?.isAvatarAvailable() ?? false),
|
|
4451
4570
|
enabled: settings.avatar.enabled,
|
|
4452
4571
|
port: settings.avatar.port,
|
|
4453
4572
|
},
|
|
@@ -4463,16 +4582,16 @@ app.post("/api/ops/sidecars/:name/restart", async (c) => {
|
|
|
4463
4582
|
ok = await startSidecar();
|
|
4464
4583
|
break;
|
|
4465
4584
|
case "tts":
|
|
4466
|
-
stopTtsSidecar();
|
|
4467
|
-
ok = await startTtsSidecar();
|
|
4585
|
+
_ttsSidecar?.stopTtsSidecar();
|
|
4586
|
+
ok = await _ttsSidecar?.startTtsSidecar() ?? false;
|
|
4468
4587
|
break;
|
|
4469
4588
|
case "stt":
|
|
4470
|
-
stopSttSidecar();
|
|
4471
|
-
ok = await startSttSidecar();
|
|
4589
|
+
_sttSidecar?.stopSttSidecar();
|
|
4590
|
+
ok = await _sttSidecar?.startSttSidecar() ?? false;
|
|
4472
4591
|
break;
|
|
4473
4592
|
case "avatar":
|
|
4474
|
-
stopAvatarSidecar();
|
|
4475
|
-
ok = await startAvatarSidecar();
|
|
4593
|
+
_avatarSidecar?.stopAvatarSidecar();
|
|
4594
|
+
ok = await _avatarSidecar?.startAvatarSidecar() ?? false;
|
|
4476
4595
|
break;
|
|
4477
4596
|
default:
|
|
4478
4597
|
return c.json({ error: `Unknown sidecar: ${name}` }, 400);
|
|
@@ -4775,8 +4894,11 @@ app.post("/api/chat", async (c) => {
|
|
|
4775
4894
|
return c.json({ error: "Invalid or expired session" }, 401);
|
|
4776
4895
|
}
|
|
4777
4896
|
const cs = await getOrCreateChatSession(sessionId, session.name);
|
|
4897
|
+
// Route to thread history if threadId is provided
|
|
4898
|
+
const requestThreadId = body.threadId;
|
|
4899
|
+
switchSessionThread(cs, requestThreadId || null, sessionId);
|
|
4778
4900
|
// Reset continuation rounds when user sends a real message
|
|
4779
|
-
resetContinuation(sessionId);
|
|
4901
|
+
_agentAutonomous?.resetContinuation(sessionId);
|
|
4780
4902
|
// Inject user-chat tension into the nervous system
|
|
4781
4903
|
getPressureIntegrator()?.addUserChatTension();
|
|
4782
4904
|
// Start goal timer on first chat call
|
|
@@ -4784,25 +4906,40 @@ app.post("/api/chat", async (c) => {
|
|
|
4784
4906
|
goalTimerStarted = true;
|
|
4785
4907
|
startGoalTimer({ brain: cs.brain, humanName: session.name });
|
|
4786
4908
|
// Start Google polling timers if already authenticated
|
|
4787
|
-
if (isCalendarAvailable()) {
|
|
4788
|
-
startCalendarTimer();
|
|
4909
|
+
if ((_googleCalendar?.isCalendarAvailable() ?? false)) {
|
|
4910
|
+
_googleCalendarTimer?.startCalendarTimer();
|
|
4789
4911
|
}
|
|
4790
|
-
if (isGmailAvailable()) {
|
|
4791
|
-
startGmailTimer();
|
|
4912
|
+
if ((_googleGmail?.isGmailAvailable() ?? false)) {
|
|
4913
|
+
_googleGmailTimer?.startGmailTimer();
|
|
4792
4914
|
}
|
|
4793
|
-
if (isTasksAvailable()) {
|
|
4794
|
-
startTasksTimer();
|
|
4915
|
+
if ((_googleTasks?.isTasksAvailable() ?? false)) {
|
|
4916
|
+
_googleTasksTimer?.startTasksTimer();
|
|
4795
4917
|
}
|
|
4796
4918
|
}
|
|
4797
4919
|
// Helper: persist session to disk (fire-and-forget)
|
|
4798
4920
|
const persistSession = () => {
|
|
4799
4921
|
const key = sessionKeys.get(sessionId);
|
|
4800
4922
|
if (key) {
|
|
4923
|
+
// Collect threads for persistence
|
|
4924
|
+
const threads = getThreadsForSession(sessionId);
|
|
4925
|
+
const threadList = [...threads.values()].map((t) => ({
|
|
4926
|
+
id: t.id,
|
|
4927
|
+
title: t.title,
|
|
4928
|
+
// If this thread is active, its live history is in cs.history
|
|
4929
|
+
history: (cs.activeThreadId === t.id) ? cs.history : t.history,
|
|
4930
|
+
historySummary: (cs.activeThreadId === t.id) ? cs.historySummary : t.historySummary,
|
|
4931
|
+
createdAt: t.createdAt,
|
|
4932
|
+
updatedAt: t.updatedAt,
|
|
4933
|
+
}));
|
|
4934
|
+
// Persist main history (from mainHistory if a thread is active, else cs.history)
|
|
4935
|
+
const mainHistory = cs.activeThreadId ? cs.mainHistory : cs.history;
|
|
4936
|
+
const mainSummary = cs.activeThreadId ? cs.mainHistorySummary : cs.historySummary;
|
|
4801
4937
|
saveSession(sessionId, {
|
|
4802
|
-
history:
|
|
4938
|
+
history: mainHistory,
|
|
4803
4939
|
fileContext: cs.fileContext,
|
|
4804
4940
|
learnedPaths: cs.learnedPaths,
|
|
4805
|
-
historySummary:
|
|
4941
|
+
historySummary: mainSummary,
|
|
4942
|
+
threads: threadList.length > 0 ? threadList : undefined,
|
|
4806
4943
|
}, key).catch(() => { });
|
|
4807
4944
|
}
|
|
4808
4945
|
// Auto fold-back: fire once per session when user messages reach threshold
|
|
@@ -4841,14 +4978,14 @@ app.post("/api/chat", async (c) => {
|
|
|
4841
4978
|
if (callMatch) {
|
|
4842
4979
|
const to = callMatch[1] || undefined;
|
|
4843
4980
|
const msg = callMatch[2]?.trim() || undefined;
|
|
4844
|
-
const result = await makeCall({ to, message: msg });
|
|
4981
|
+
const result = await _twilioCall.makeCall({ to, message: msg });
|
|
4845
4982
|
return c.json({ system: true, content: result.message });
|
|
4846
4983
|
}
|
|
4847
4984
|
// --- Handle "email <address> subject: <subject> body: <body>" command ---
|
|
4848
4985
|
const emailMatch = message.match(/^(?:email|send email|mail)\s+(\S+@\S+)\s+subject:\s*(.+?)\s+body:\s*([\s\S]+)$/i);
|
|
4849
4986
|
if (emailMatch) {
|
|
4850
4987
|
const [, to, subject, body] = emailMatch;
|
|
4851
|
-
const result = await sendEmail({ to: to.trim(), subject: subject.trim(), body: body.trim() });
|
|
4988
|
+
const result = await _googleGmailSend.sendEmail({ to: to.trim(), subject: subject.trim(), body: body.trim() });
|
|
4852
4989
|
logActivity({ source: "gmail", summary: `Email sent to ${to.trim()}: "${subject.trim()}"`, actionLabel: "PROMPTED", reason: "user sent email via chat" });
|
|
4853
4990
|
return c.json({ system: true, content: result.message });
|
|
4854
4991
|
}
|
|
@@ -4922,7 +5059,7 @@ app.post("/api/chat", async (c) => {
|
|
|
4922
5059
|
// --- Handle "auto" / "autonomous" command ---
|
|
4923
5060
|
const autoMatch = message.match(/^(?:auto|autonomous)\s*$/i);
|
|
4924
5061
|
if (autoMatch) {
|
|
4925
|
-
const status = getAutonomousStatus();
|
|
5062
|
+
const status = (_agentAutonomous?.getAutonomousStatus() ?? null);
|
|
4926
5063
|
const lines = [
|
|
4927
5064
|
`**Autonomous Work Loop**`,
|
|
4928
5065
|
``,
|
|
@@ -5151,10 +5288,10 @@ app.post("/api/chat", async (c) => {
|
|
|
5151
5288
|
});
|
|
5152
5289
|
}
|
|
5153
5290
|
// --- URL browse injection ---
|
|
5154
|
-
const detectedUrl = detectUrl(chatMessage);
|
|
5291
|
+
const detectedUrl = _browse?.detectUrl(chatMessage);
|
|
5155
5292
|
if (detectedUrl) {
|
|
5156
5293
|
logActivity({ source: "browse", summary: `Auto-browsing URL in message: ${detectedUrl}`, actionLabel: "PROMPTED", reason: "user message contained URL" });
|
|
5157
|
-
const browseResult = await browseUrl(detectedUrl);
|
|
5294
|
+
const browseResult = await _browse.browseUrl(detectedUrl);
|
|
5158
5295
|
if (browseResult) {
|
|
5159
5296
|
const browseMsg = {
|
|
5160
5297
|
role: "system",
|
|
@@ -5175,21 +5312,25 @@ app.post("/api/chat", async (c) => {
|
|
|
5175
5312
|
const doc = await findBrainDocument(chatMessage);
|
|
5176
5313
|
if (doc) {
|
|
5177
5314
|
brainDocFound = true;
|
|
5315
|
+
const siblingNote = doc.siblings && doc.siblings.length > 0
|
|
5316
|
+
? `\nAlso in the same directory: ${doc.siblings.join(", ")}\nYou can ask to see any of these files.`
|
|
5317
|
+
: "";
|
|
5178
5318
|
const docMsg = {
|
|
5179
5319
|
role: "system",
|
|
5180
5320
|
content: [
|
|
5181
5321
|
`--- Brain document: ${doc.filename} ---`,
|
|
5182
5322
|
doc.content,
|
|
5183
5323
|
`--- End ${doc.filename} ---`,
|
|
5184
|
-
`This document was found in your brain files. Use it to answer the user's question
|
|
5324
|
+
`This document was found in your brain files. Use it to answer the user's question.${siblingNote}`,
|
|
5185
5325
|
].join("\n"),
|
|
5186
5326
|
};
|
|
5187
5327
|
ctx.messages.splice(1, 0, docMsg);
|
|
5188
5328
|
logActivity({ source: "system", summary: `Auto-loaded brain document: ${doc.filename}`, actionLabel: "PROMPTED", reason: "user message referenced a brain document" });
|
|
5189
5329
|
}
|
|
5190
5330
|
}
|
|
5191
|
-
catch {
|
|
5331
|
+
catch (err) {
|
|
5192
5332
|
// Non-critical — fall through to web search
|
|
5333
|
+
console.error("[brain-docs] findBrainDocument error:", err instanceof Error ? err.message : String(err));
|
|
5193
5334
|
}
|
|
5194
5335
|
// --- Context provider injection (web search, calendar, email) ---
|
|
5195
5336
|
{
|
|
@@ -5544,6 +5685,8 @@ app.post("/api/chat", async (c) => {
|
|
|
5544
5685
|
reqSignal?.addEventListener("abort", onAbort, { once: true });
|
|
5545
5686
|
// Token buffer for split-placeholder rehydration
|
|
5546
5687
|
let tokenBuf2 = "";
|
|
5688
|
+
const streamStartMs2 = performance.now();
|
|
5689
|
+
const streamModel2 = activeChatModel ?? (activeProvider === "ollama" ? "llama3.2:3b" : "claude-sonnet-4");
|
|
5547
5690
|
const flushBuf2 = () => {
|
|
5548
5691
|
if (!tokenBuf2)
|
|
5549
5692
|
return;
|
|
@@ -5568,6 +5711,12 @@ app.post("/api/chat", async (c) => {
|
|
|
5568
5711
|
onDone: async () => {
|
|
5569
5712
|
flushBuf2(); // flush remainder
|
|
5570
5713
|
reqSignal?.removeEventListener("abort", onAbort);
|
|
5714
|
+
logLlmCall({
|
|
5715
|
+
ts: new Date().toISOString(), mode: "stream",
|
|
5716
|
+
provider: activeProvider, model: streamModel2,
|
|
5717
|
+
durationMs: Math.round(performance.now() - streamStartMs2),
|
|
5718
|
+
outputTokens: Math.ceil(fullResponse.length / 4), ok: true,
|
|
5719
|
+
});
|
|
5571
5720
|
// Process action blocks BEFORE sending done — ensures SSE events reach client before stream closes.
|
|
5572
5721
|
// ALWAYS strip [AGENT_REQUEST] blocks from response, even if they can't be parsed.
|
|
5573
5722
|
// Capture raw block content first for logging/parsing.
|
|
@@ -5740,20 +5889,20 @@ app.post("/api/chat", async (c) => {
|
|
|
5740
5889
|
}
|
|
5741
5890
|
}).catch(() => { });
|
|
5742
5891
|
// Fire-and-forget: generate avatar video (TTS → MuseTalk → MP4)
|
|
5743
|
-
if (isAvatarAvailable() && isTtsAvailable() && fullResponse) {
|
|
5892
|
+
if ((_avatarSidecar?.isAvatarAvailable() ?? false) && (_ttsClient?.isTtsAvailable() ?? false) && fullResponse) {
|
|
5744
5893
|
const trimmedText = fullResponse.slice(0, 2000);
|
|
5745
|
-
synthesize(trimmedText).then(async (wavBuffer) => {
|
|
5894
|
+
_ttsClient.synthesize(trimmedText).then(async (wavBuffer) => {
|
|
5746
5895
|
if (!wavBuffer)
|
|
5747
5896
|
return;
|
|
5748
|
-
const cached = await getCachedVideo(wavBuffer);
|
|
5897
|
+
const cached = await _avatarClient.getCachedVideo(wavBuffer);
|
|
5749
5898
|
if (cached) {
|
|
5750
5899
|
pushPendingVideo(cached);
|
|
5751
5900
|
return;
|
|
5752
5901
|
}
|
|
5753
|
-
const mp4 = await generateVideo(wavBuffer);
|
|
5902
|
+
const mp4 = await _avatarClient.generateVideo(wavBuffer);
|
|
5754
5903
|
if (!mp4)
|
|
5755
5904
|
return;
|
|
5756
|
-
const filename = await cacheVideo(mp4, wavBuffer);
|
|
5905
|
+
const filename = await _avatarClient.cacheVideo(mp4, wavBuffer);
|
|
5757
5906
|
pushPendingVideo(filename);
|
|
5758
5907
|
logActivity({ source: "avatar", summary: "Generated avatar video", actionLabel: "PROMPTED", reason: "user conversation triggered avatar" });
|
|
5759
5908
|
}).catch((err) => {
|
|
@@ -5796,6 +5945,12 @@ app.post("/api/chat", async (c) => {
|
|
|
5796
5945
|
// If this error is from an abort, save partial and exit quietly
|
|
5797
5946
|
if (reqSignal?.aborted) {
|
|
5798
5947
|
savePartial();
|
|
5948
|
+
logLlmCall({
|
|
5949
|
+
ts: new Date().toISOString(), mode: "stream",
|
|
5950
|
+
provider: activeProvider, model: streamModel2,
|
|
5951
|
+
durationMs: Math.round(performance.now() - streamStartMs2),
|
|
5952
|
+
outputTokens: Math.ceil(fullResponse.length / 4), ok: true, error: "client_abort",
|
|
5953
|
+
});
|
|
5799
5954
|
}
|
|
5800
5955
|
else {
|
|
5801
5956
|
let errorMsg = err instanceof LLMError ? err.userMessage : (err.message || "Stream error");
|
|
@@ -5808,6 +5963,12 @@ app.post("/api/chat", async (c) => {
|
|
|
5808
5963
|
if (!health.ok)
|
|
5809
5964
|
errorMsg += " — Check that Ollama is running. " + health.message;
|
|
5810
5965
|
}
|
|
5966
|
+
logLlmCall({
|
|
5967
|
+
ts: new Date().toISOString(), mode: "stream",
|
|
5968
|
+
provider: activeProvider, model: streamModel2,
|
|
5969
|
+
durationMs: Math.round(performance.now() - streamStartMs2),
|
|
5970
|
+
ok: false, error: errorMsg,
|
|
5971
|
+
});
|
|
5811
5972
|
stream.writeSSE({ data: JSON.stringify({ error: errorMsg, errorDetail: rawDetail }) }).catch(() => { });
|
|
5812
5973
|
}
|
|
5813
5974
|
resolve(); // Still resolve so stream closes
|
|
@@ -5924,12 +6085,111 @@ async function start(opts) {
|
|
|
5924
6085
|
const tierGate = await import("./tier/gate.js");
|
|
5925
6086
|
// Initialize instance name before anything else
|
|
5926
6087
|
initInstanceName();
|
|
5927
|
-
//
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
6088
|
+
// --- Dynamic imports: load gated modules based on tier ---
|
|
6089
|
+
// EXT-BYOK: vault, voice, integrations, notifications, etc.
|
|
6090
|
+
if (tierGate.meetsMinimum(tier, "byok")) {
|
|
6091
|
+
[
|
|
6092
|
+
_browse, _ttsSidecar, _ttsClient, _sttSidecar, _sttClient,
|
|
6093
|
+
_avatarSidecar, _avatarClient, _settingsVoice,
|
|
6094
|
+
_vaultStore, _vaultTransfer, _integrationsGate, _twilioCall,
|
|
6095
|
+
_googleAuth, _googleCalendar, _googleTemporal, _googleCalendarTimer,
|
|
6096
|
+
_googleGmail, _googleGmailTimer, _googleTasksTimer, _googleGmailSend,
|
|
6097
|
+
_googleDocs, _googleTasks,
|
|
6098
|
+
_notifications, _credentialStore, _githubWebhooks, _integrationsGithub,
|
|
6099
|
+
_slackClient, _slackWebhooks, _slackChannels,
|
|
6100
|
+
_channelsWhatsapp, _webhooksTwilio, _resendWebhooks, _servicesWhatsapp,
|
|
6101
|
+
_webhooksMount, _webhooksConfig, _webhooksRegistry, _webhooksRelay,
|
|
6102
|
+
_volumes, _mdns,
|
|
6103
|
+
] = await Promise.all([
|
|
6104
|
+
import("./search/browse.js"),
|
|
6105
|
+
import("./tts/sidecar.js"),
|
|
6106
|
+
import("./tts/client.js"),
|
|
6107
|
+
import("./stt/sidecar.js"),
|
|
6108
|
+
import("./stt/client.js"),
|
|
6109
|
+
import("./avatar/sidecar.js"),
|
|
6110
|
+
import("./avatar/client.js"),
|
|
6111
|
+
import("./settings.js"),
|
|
6112
|
+
import("./vault/store.js"),
|
|
6113
|
+
import("./vault/transfer.js"),
|
|
6114
|
+
import("./integrations/gate.js"),
|
|
6115
|
+
import("./twilio/call.js"),
|
|
6116
|
+
import("./google/auth.js"),
|
|
6117
|
+
import("./google/calendar.js"),
|
|
6118
|
+
import("./google/temporal.js"),
|
|
6119
|
+
import("./google/calendar-timer.js"),
|
|
6120
|
+
import("./google/gmail.js"),
|
|
6121
|
+
import("./google/gmail-timer.js"),
|
|
6122
|
+
import("./google/tasks-timer.js"),
|
|
6123
|
+
import("./google/gmail-send.js"),
|
|
6124
|
+
import("./google/docs.js"),
|
|
6125
|
+
import("./google/tasks.js"),
|
|
6126
|
+
import("./notifications/index.js"),
|
|
6127
|
+
import("./credentials/store.js"),
|
|
6128
|
+
import("./github/webhooks.js"),
|
|
6129
|
+
import("./integrations/github.js"),
|
|
6130
|
+
import("./slack/client.js"),
|
|
6131
|
+
import("./slack/webhooks.js"),
|
|
6132
|
+
import("./slack/channels.js"),
|
|
6133
|
+
import("./channels/whatsapp.js"),
|
|
6134
|
+
import("./webhooks/twilio.js"),
|
|
6135
|
+
import("./resend/webhooks.js"),
|
|
6136
|
+
import("./services/whatsapp.js"),
|
|
6137
|
+
import("./webhooks/mount.js"),
|
|
6138
|
+
import("./webhooks/config.js"),
|
|
6139
|
+
import("./webhooks/registry.js"),
|
|
6140
|
+
import("./webhooks/relay.js"),
|
|
6141
|
+
import("./volumes/index.js"),
|
|
6142
|
+
import("./mdns.js"),
|
|
6143
|
+
]);
|
|
6144
|
+
// Initialize alerting (requires notifications module)
|
|
6145
|
+
alertDispatcher = new _notifications.NotificationDispatcher();
|
|
6146
|
+
alertManager = new AlertManager(health, defaultAlertConfig(), alertDispatcher);
|
|
6147
|
+
// Initialize volume manager
|
|
6148
|
+
volumeManager = new _volumes.VolumeManager(BRAIN_DIR);
|
|
6149
|
+
volumeManager.init().catch((err) => log.warn("Volume manager init failed — single-volume mode", { error: String(err) }));
|
|
6150
|
+
// Initialize webhook providers (now that modules are loaded)
|
|
6151
|
+
initWebhookProviders();
|
|
6152
|
+
log.info(`BYOK-tier modules loaded (tier: ${tier})`);
|
|
6153
|
+
}
|
|
6154
|
+
else {
|
|
6155
|
+
// Local tier: alerting with no-op dispatcher
|
|
6156
|
+
alertDispatcher = null;
|
|
6157
|
+
alertManager = new AlertManager(health, defaultAlertConfig(), { dispatch: async () => { } });
|
|
6158
|
+
log.info(`Local-tier — BYOK modules skipped (tier: ${tier})`);
|
|
6159
|
+
}
|
|
6160
|
+
// EXT-SPAWN: agent runtime, instance manager, pool, workflow
|
|
6161
|
+
if (tierGate.canSpawn(tier)) {
|
|
6162
|
+
[_agentMemory, _agentLocks, _agentAutonomous, _agentRuntime, _agentInstanceManager, _agentPoolMod, _agentWorkflow] = await Promise.all([
|
|
6163
|
+
import("./agents/memory.js"),
|
|
6164
|
+
import("./agents/locks.js"),
|
|
6165
|
+
import("./agents/autonomous.js"),
|
|
6166
|
+
import("./agents/runtime/index.js"),
|
|
6167
|
+
import("./agents/instance-manager.js"),
|
|
6168
|
+
import("./agents/runtime.js"),
|
|
6169
|
+
import("./agents/workflow.js"),
|
|
6170
|
+
]);
|
|
6171
|
+
log.info(`Spawn-tier modules loaded (tier: ${tier})`);
|
|
6172
|
+
}
|
|
6173
|
+
// EXT-HOSTED: tracing, browser
|
|
6174
|
+
if (tierGate.meetsMinimum(tier, "hosted")) {
|
|
6175
|
+
[_tracingTracer, _tracingInit, _tracingMiddleware, _tracingBridge, _browser] = await Promise.all([
|
|
6176
|
+
import("./tracing/tracer.js"),
|
|
6177
|
+
import("./tracing/init.js"),
|
|
6178
|
+
import("./tracing/middleware.js"),
|
|
6179
|
+
import("./tracing/bridge.js"),
|
|
6180
|
+
import("./capabilities/definitions/browser.js"),
|
|
6181
|
+
]);
|
|
6182
|
+
tracer = new _tracingTracer.Tracer();
|
|
6183
|
+
log.info(`Hosted-tier modules loaded (tier: ${tier})`);
|
|
6184
|
+
}
|
|
6185
|
+
// Initialize OpenTelemetry tracing (must be early, before instrumented code) — hosted tier only
|
|
6186
|
+
if (_tracingInit) {
|
|
6187
|
+
_tracingInit.initTracing({
|
|
6188
|
+
serviceName: `${getInstanceNameLower()}-brain`,
|
|
6189
|
+
serviceVersion: "0.1.0",
|
|
6190
|
+
consoleExport: process.env.OTEL_CONSOLE === "1",
|
|
6191
|
+
});
|
|
6192
|
+
}
|
|
5933
6193
|
// Load settings (airplane mode, model selection)
|
|
5934
6194
|
const settings = await loadSettings();
|
|
5935
6195
|
// Initialize posture system (UI surface assembly)
|
|
@@ -5953,13 +6213,18 @@ async function start(opts) {
|
|
|
5953
6213
|
if (restored) {
|
|
5954
6214
|
sessionKeys.set(restored.session.id, restored.sessionKey);
|
|
5955
6215
|
setEncryptionKey(restored.sessionKey);
|
|
5956
|
-
|
|
6216
|
+
if (_vaultStore)
|
|
6217
|
+
await _vaultStore.loadVault(restored.sessionKey);
|
|
5957
6218
|
log.info("Session restored (no re-auth needed)");
|
|
5958
6219
|
}
|
|
5959
6220
|
return code;
|
|
5960
6221
|
})(),
|
|
5961
|
-
// Start all sidecars in parallel
|
|
5962
|
-
Promise.all([
|
|
6222
|
+
// Start all sidecars in parallel (search always, tts/stt if BYOK)
|
|
6223
|
+
Promise.all([
|
|
6224
|
+
startSidecar(),
|
|
6225
|
+
_ttsSidecar ? _ttsSidecar.startTtsSidecar() : Promise.resolve(false),
|
|
6226
|
+
_sttSidecar ? _sttSidecar.startSttSidecar() : Promise.resolve(false),
|
|
6227
|
+
]),
|
|
5963
6228
|
]);
|
|
5964
6229
|
const code = pairingCode;
|
|
5965
6230
|
const [searchAvailable, ttsAvailable, sttAvailable] = sidecarResults;
|
|
@@ -5996,15 +6261,29 @@ async function start(opts) {
|
|
|
5996
6261
|
log.info(`Loaded ${notifCount} pending notification(s) from disk`);
|
|
5997
6262
|
// Register sidecar health checks (optional — degraded, not unhealthy)
|
|
5998
6263
|
health.register("search", availabilityCheck(isSidecarAvailable, "search"), { critical: false });
|
|
5999
|
-
|
|
6000
|
-
|
|
6264
|
+
if (_ttsClient)
|
|
6265
|
+
health.register("tts", availabilityCheck(_ttsClient.isTtsAvailable, "tts"), { critical: false });
|
|
6266
|
+
if (_sttClient)
|
|
6267
|
+
health.register("stt", availabilityCheck(_sttClient.isSttAvailable, "stt"), { critical: false });
|
|
6001
6268
|
// Google Workspace health checks (optional — degraded if not connected)
|
|
6002
|
-
|
|
6269
|
+
if (_googleCalendar)
|
|
6270
|
+
health.register("google_calendar", availabilityCheck(_googleCalendar.isCalendarAvailable, "Google Calendar"), { critical: false });
|
|
6003
6271
|
// Initialize FileManager (file registry + storage for uploads, visual memory, etc.)
|
|
6004
6272
|
await FileManager.init(BRAIN_DIR, join(BRAIN_DIR, "files", "storage"));
|
|
6005
6273
|
// Initialize Library store (virtual folders over file registry)
|
|
6006
6274
|
createLibraryStore(BRAIN_DIR);
|
|
6007
6275
|
initBrainShadow(BRAIN_DIR);
|
|
6276
|
+
// Initialize Brain RAG (semantic file search)
|
|
6277
|
+
const rag = new BrainRAG();
|
|
6278
|
+
await rag.load();
|
|
6279
|
+
setBrainRAG(rag);
|
|
6280
|
+
stopFileWatcher = watchBrain(rag);
|
|
6281
|
+
// Background index — never block startup
|
|
6282
|
+
rag.indexAll().then((r) => {
|
|
6283
|
+
log.info(`Brain RAG index: ${r.indexed} indexed, ${r.skipped} skipped, ${r.errors} errors`);
|
|
6284
|
+
}).catch((err) => {
|
|
6285
|
+
log.error("Brain RAG indexAll failed", { error: err instanceof Error ? err.message : String(err) });
|
|
6286
|
+
});
|
|
6008
6287
|
// Register board provider (local queue, always available)
|
|
6009
6288
|
const queueProvider = new QueueBoardProvider(BRAIN_DIR);
|
|
6010
6289
|
queueProvider.setOnProjectlessTask((identifier, title) => {
|
|
@@ -6014,7 +6293,8 @@ async function start(opts) {
|
|
|
6014
6293
|
queueProvider.getStore().setOnStateTransition(async (task, from, to) => {
|
|
6015
6294
|
if (to === "done" || to === "cancelled") {
|
|
6016
6295
|
try {
|
|
6017
|
-
|
|
6296
|
+
if (_agentMemory)
|
|
6297
|
+
await _agentMemory.rememberTaskCompletion(task, from);
|
|
6018
6298
|
}
|
|
6019
6299
|
catch (err) {
|
|
6020
6300
|
log.warn("Failed to record task completion memory", {
|
|
@@ -6029,10 +6309,13 @@ async function start(opts) {
|
|
|
6029
6309
|
startSchedulingTimer(schedulingStore);
|
|
6030
6310
|
createContactStore(BRAIN_DIR);
|
|
6031
6311
|
createCalendarStore(BRAIN_DIR);
|
|
6032
|
-
|
|
6033
|
-
|
|
6312
|
+
if (_credentialStore) {
|
|
6313
|
+
const credStore = _credentialStore.createCredentialStore(BRAIN_DIR);
|
|
6314
|
+
await credStore.hydrate();
|
|
6315
|
+
}
|
|
6034
6316
|
initTraining();
|
|
6035
|
-
|
|
6317
|
+
if (_integrationsGithub)
|
|
6318
|
+
_integrationsGithub.initGitHub();
|
|
6036
6319
|
// Start metrics collector (system, HTTP, agent metrics at 30s interval)
|
|
6037
6320
|
// brainDir enables tiered aggregation (hourly/daily rollups)
|
|
6038
6321
|
startCollector(metricsStore, undefined, BRAIN_DIR);
|
|
@@ -6041,26 +6324,33 @@ async function start(opts) {
|
|
|
6041
6324
|
// of skills/module init, so running them concurrently improves startup time (DASH-60).
|
|
6042
6325
|
// Note: initAgents() only creates directories — recovery runs after the runtime
|
|
6043
6326
|
// is ready so the monitor can skip runtime-managed tasks (DASH-82 fix).
|
|
6044
|
-
const
|
|
6327
|
+
const initPromises = [
|
|
6045
6328
|
_skillRegistry.refresh(),
|
|
6046
6329
|
Promise.resolve(createModuleRegistry(BRAIN_DIR)),
|
|
6047
6330
|
initAgents(),
|
|
6048
|
-
|
|
6049
|
-
|
|
6331
|
+
];
|
|
6332
|
+
if (_agentRuntime)
|
|
6333
|
+
initPromises.push(_agentRuntime.createRuntime());
|
|
6334
|
+
else
|
|
6335
|
+
initPromises.push(Promise.resolve(null));
|
|
6336
|
+
const [, moduleRegistry, , runtime] = await Promise.all(initPromises);
|
|
6050
6337
|
const skillRegistry = _skillRegistry;
|
|
6051
6338
|
// Recover tasks from previous session AFTER runtime is initialized.
|
|
6052
6339
|
// The monitor checks the runtime registry to skip tasks that RuntimeManager
|
|
6053
6340
|
// already handles, preventing the double-recovery race (DASH-82).
|
|
6054
6341
|
await recoverAndStartMonitor();
|
|
6055
|
-
tracer
|
|
6056
|
-
|
|
6342
|
+
if (tracer && runtime?.bus)
|
|
6343
|
+
tracer.attachToBus(runtime.bus);
|
|
6344
|
+
if (_tracingBridge && runtime?.bus)
|
|
6345
|
+
_tracingBridge.attachOTelToBus(runtime.bus);
|
|
6057
6346
|
// Initialize capability registry (action blocks + context providers)
|
|
6058
6347
|
const capRegistry = createCapabilityRegistry();
|
|
6059
6348
|
capRegistry.register(calendarCapability);
|
|
6060
6349
|
capRegistry.register(emailCapability);
|
|
6061
6350
|
capRegistry.register(docsCapability);
|
|
6062
6351
|
capRegistry.register(boardCapability);
|
|
6063
|
-
|
|
6352
|
+
if (_browser)
|
|
6353
|
+
capRegistry.register(_browser.browserCapability);
|
|
6064
6354
|
// Meta capabilities
|
|
6065
6355
|
capRegistry.register(taskDoneCapability);
|
|
6066
6356
|
// Context providers — replace hardcoded injection logic
|
|
@@ -6073,11 +6363,12 @@ async function start(opts) {
|
|
|
6073
6363
|
}));
|
|
6074
6364
|
capRegistry.register(vaultContextProvider);
|
|
6075
6365
|
// Start autonomous work timer (60-min coma failsafe — primary trigger is now tension-based)
|
|
6076
|
-
|
|
6366
|
+
if (_agentAutonomous)
|
|
6367
|
+
_agentAutonomous.startAutonomousTimer();
|
|
6077
6368
|
// Initialize metabolic pulse (tension-based heartbeat)
|
|
6078
6369
|
const pulseSettings = getPulseSettings();
|
|
6079
|
-
if (pulseSettings.mode !== "timer") {
|
|
6080
|
-
const pulse = initPressureIntegrator(triggerPulse, { threshold: pulseSettings.threshold });
|
|
6370
|
+
if (pulseSettings.mode !== "timer" && _agentAutonomous) {
|
|
6371
|
+
const pulse = initPressureIntegrator(_agentAutonomous.triggerPulse, { threshold: pulseSettings.threshold });
|
|
6081
6372
|
log.info(`Metabolic pulse initialized: Θ=${pulseSettings.threshold}mV, mode=${pulseSettings.mode}`);
|
|
6082
6373
|
// Boot scan: if todos already exist, inject tension so Core starts working immediately
|
|
6083
6374
|
const todoCount = (await queueProvider.getStore().list()).filter((t) => t.state === "todo").length;
|
|
@@ -6111,17 +6402,17 @@ async function start(opts) {
|
|
|
6111
6402
|
// Start open loop scanner (ambient resonance matching)
|
|
6112
6403
|
startOpenLoopScanner();
|
|
6113
6404
|
// Start Google polling timers at boot if already authenticated (don't wait for first chat)
|
|
6114
|
-
if (isGmailAvailable())
|
|
6115
|
-
startGmailTimer();
|
|
6116
|
-
if (isCalendarAvailable()) {
|
|
6117
|
-
startCalendarTimer();
|
|
6405
|
+
if (_googleGmail?.isGmailAvailable())
|
|
6406
|
+
_googleGmailTimer.startGmailTimer();
|
|
6407
|
+
if (_googleCalendar?.isCalendarAvailable()) {
|
|
6408
|
+
_googleCalendarTimer.startCalendarTimer();
|
|
6118
6409
|
// Initial sync: pull Google events into local calendar store
|
|
6119
6410
|
getGoogleCalendarAdapter().sync().catch((err) => {
|
|
6120
6411
|
log.warn("Initial Google Calendar sync failed", { error: err instanceof Error ? err.message : String(err) });
|
|
6121
6412
|
});
|
|
6122
6413
|
}
|
|
6123
|
-
if (isTasksAvailable())
|
|
6124
|
-
startTasksTimer();
|
|
6414
|
+
if (_googleTasks?.isTasksAvailable())
|
|
6415
|
+
_googleTasksTimer.startTasksTimer();
|
|
6125
6416
|
// Initialize plugin registry (authenticate + start all registered plugins)
|
|
6126
6417
|
await initPlugins().catch((err) => {
|
|
6127
6418
|
log.warn("Plugin init failed", { error: err instanceof Error ? err.message : String(err) });
|
|
@@ -6130,7 +6421,8 @@ async function start(opts) {
|
|
|
6130
6421
|
setOnBatchComplete(async (sessionId, results) => {
|
|
6131
6422
|
try {
|
|
6132
6423
|
logActivity({ source: "agent", summary: `Batch complete: session=${sessionId}, ${results.length} result(s)` });
|
|
6133
|
-
|
|
6424
|
+
if (_agentAutonomous)
|
|
6425
|
+
await _agentAutonomous.continueAfterBatch(sessionId, results);
|
|
6134
6426
|
}
|
|
6135
6427
|
catch (err) {
|
|
6136
6428
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -6138,15 +6430,15 @@ async function start(opts) {
|
|
|
6138
6430
|
}
|
|
6139
6431
|
});
|
|
6140
6432
|
// Initialize agent spawning — tier >= spawn only
|
|
6141
|
-
if (tierGate.canSpawn(tier)) {
|
|
6433
|
+
if (tierGate.canSpawn(tier) && _agentInstanceManager && _agentPoolMod && _agentWorkflow && runtime) {
|
|
6142
6434
|
// Initialize instance manager (GC, health checks, load balancing)
|
|
6143
|
-
instanceManager = new AgentInstanceManager(runtime);
|
|
6435
|
+
instanceManager = new _agentInstanceManager.AgentInstanceManager(runtime);
|
|
6144
6436
|
await instanceManager.init();
|
|
6145
6437
|
// Initialize agent pool (circuit breakers, isolation, resource management)
|
|
6146
|
-
agentPool = AgentPool.fromExisting(runtime, instanceManager);
|
|
6438
|
+
agentPool = _agentPoolMod.AgentPool.fromExisting(runtime, instanceManager);
|
|
6147
6439
|
setAgentPool(agentPool);
|
|
6148
6440
|
// Initialize workflow engine for multi-agent coordination
|
|
6149
|
-
workflowEngine = new WorkflowEngine(agentPool);
|
|
6441
|
+
workflowEngine = new _agentWorkflow.WorkflowEngine(agentPool);
|
|
6150
6442
|
await workflowEngine.loadAllDefinitions().catch((err) => {
|
|
6151
6443
|
log.warn("Failed to load workflow definitions", { error: err instanceof Error ? err.message : String(err) });
|
|
6152
6444
|
});
|
|
@@ -6162,7 +6454,7 @@ async function start(opts) {
|
|
|
6162
6454
|
health.register("board", boardCheck(() => getBoardProvider()), { critical: false });
|
|
6163
6455
|
// Agent runtime capacity: resource utilization
|
|
6164
6456
|
health.register("agent_capacity", agentCapacityCheck(() => {
|
|
6165
|
-
const rt = getRuntime();
|
|
6457
|
+
const rt = _agentRuntime ? _agentRuntime.getRuntime() : null;
|
|
6166
6458
|
return rt ? rt.getResourceSnapshot() : null;
|
|
6167
6459
|
}), { critical: false });
|
|
6168
6460
|
// Agent instance health: aggregate health scores
|
|
@@ -6172,17 +6464,20 @@ async function start(opts) {
|
|
|
6172
6464
|
// --- Register auto-recovery actions ---
|
|
6173
6465
|
// Sidecar recovery: restart if unavailable for 3+ checks
|
|
6174
6466
|
recovery.register(sidecarRecovery("search", "search", stopSidecar, startSidecar));
|
|
6175
|
-
|
|
6176
|
-
|
|
6467
|
+
if (_ttsSidecar)
|
|
6468
|
+
recovery.register(sidecarRecovery("tts", "tts", _ttsSidecar.stopTtsSidecar, _ttsSidecar.startTtsSidecar));
|
|
6469
|
+
if (_sttSidecar)
|
|
6470
|
+
recovery.register(sidecarRecovery("stt", "stt", _sttSidecar.stopSttSidecar, _sttSidecar.startSttSidecar));
|
|
6177
6471
|
// Start recovery loop (evaluates every 30s)
|
|
6178
6472
|
recovery.start();
|
|
6179
6473
|
// Start alert evaluation loop (evaluates every 30s)
|
|
6180
|
-
alertManager
|
|
6474
|
+
if (alertManager)
|
|
6475
|
+
alertManager.start();
|
|
6181
6476
|
// Wire notification channels — tier >= byok only (requires BYOK API keys)
|
|
6182
|
-
if (tierGate.canAlert(tier)) {
|
|
6477
|
+
if (tierGate.canAlert(tier) && _notifications && alertDispatcher) {
|
|
6183
6478
|
const resendKey = process.env.RESEND_API_KEY;
|
|
6184
6479
|
if (resendKey) {
|
|
6185
|
-
alertDispatcher.add(new EmailChannel({
|
|
6480
|
+
alertDispatcher.add(new _notifications.EmailChannel({
|
|
6186
6481
|
endpoint: "https://api.resend.com/emails",
|
|
6187
6482
|
apiKey: resendKey,
|
|
6188
6483
|
from: `${getInstanceName()} <${getAlertEmailFrom()}>`,
|
|
@@ -6191,7 +6486,7 @@ async function start(opts) {
|
|
|
6191
6486
|
log.info("Alert channel: email (Resend)");
|
|
6192
6487
|
}
|
|
6193
6488
|
if (process.env.TWILIO_ACCOUNT_SID) {
|
|
6194
|
-
alertDispatcher.add(new PhoneChannel());
|
|
6489
|
+
alertDispatcher.add(new _notifications.PhoneChannel());
|
|
6195
6490
|
log.info("Alert channel: phone (Twilio voice)");
|
|
6196
6491
|
}
|
|
6197
6492
|
alertManager.updateNotifications([
|
|
@@ -6202,21 +6497,25 @@ async function start(opts) {
|
|
|
6202
6497
|
// Start credit monitoring (checks every 5 min, configurable via CORE_CREDIT_CHECK_INTERVAL_MS)
|
|
6203
6498
|
startCreditMonitor(health, alertManager);
|
|
6204
6499
|
// Avatar sidecar launches in background — slow (model loading) but non-blocking
|
|
6205
|
-
const avatarConfig = getAvatarConfig();
|
|
6500
|
+
const avatarConfig = _settingsVoice ? _settingsVoice.getAvatarConfig() : { enabled: false, port: 0, photoPath: "" };
|
|
6206
6501
|
let avatarAvailable = false;
|
|
6207
|
-
(
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
if (
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6502
|
+
if (_avatarSidecar && _avatarClient) {
|
|
6503
|
+
const avatarSidecar = _avatarSidecar;
|
|
6504
|
+
const avatarClient = _avatarClient;
|
|
6505
|
+
(async () => {
|
|
6506
|
+
avatarAvailable = await avatarSidecar.startAvatarSidecar();
|
|
6507
|
+
if (avatarAvailable) {
|
|
6508
|
+
const photoPath = join(process.cwd(), avatarConfig.photoPath);
|
|
6509
|
+
const prepared = await avatarClient.preparePhoto(photoPath).catch(() => false);
|
|
6510
|
+
if (!prepared) {
|
|
6511
|
+
log.warn("Photo preparation failed — place a photo at " + avatarConfig.photoPath, { namespace: "avatar" });
|
|
6512
|
+
}
|
|
6513
|
+
else {
|
|
6514
|
+
log.info("Avatar ready — MuseTalk sidecar (port " + avatarConfig.port + ")", { namespace: "avatar" });
|
|
6515
|
+
}
|
|
6217
6516
|
}
|
|
6218
|
-
}
|
|
6219
|
-
}
|
|
6517
|
+
})();
|
|
6518
|
+
}
|
|
6220
6519
|
log.info(`${getInstanceName()} — Local Chat starting`);
|
|
6221
6520
|
// Show LLM provider based on settings
|
|
6222
6521
|
const provider = resolveProvider();
|
|
@@ -6252,8 +6551,8 @@ async function start(opts) {
|
|
|
6252
6551
|
log.info("Search: awaiting auth (Perplexity via vault, or: cd sidecar/search && pip install -r requirements.txt)");
|
|
6253
6552
|
}
|
|
6254
6553
|
// Show voice status
|
|
6255
|
-
const ttsConfig = getTtsConfig();
|
|
6256
|
-
const sttConfig = getSttConfig();
|
|
6554
|
+
const ttsConfig = _settingsVoice ? _settingsVoice.getTtsConfig() : { enabled: false, port: 0 };
|
|
6555
|
+
const sttConfig = _settingsVoice ? _settingsVoice.getSttConfig() : { enabled: false, port: 0 };
|
|
6257
6556
|
if (ttsAvailable) {
|
|
6258
6557
|
log.info(`TTS: Piper sidecar (port ${ttsConfig.port})`);
|
|
6259
6558
|
}
|
|
@@ -6279,16 +6578,16 @@ async function start(opts) {
|
|
|
6279
6578
|
log.info("Avatar: disabled");
|
|
6280
6579
|
}
|
|
6281
6580
|
// Show runtime status
|
|
6282
|
-
const rt = getRuntime();
|
|
6581
|
+
const rt = (_agentRuntime?.getRuntime() ?? null);
|
|
6283
6582
|
if (rt) {
|
|
6284
6583
|
const snap = rt.getResourceSnapshot();
|
|
6285
6584
|
log.info(`Runtime: active (${snap.activeAgents}/${snap.maxAgents} agents, ${snap.totalMemoryMB}/${snap.maxMemoryMB}MB)`);
|
|
6286
6585
|
}
|
|
6287
6586
|
// Show Google integration status
|
|
6288
|
-
if (isGoogleAuthenticated()) {
|
|
6587
|
+
if ((_googleAuth?.isGoogleAuthenticated() ?? false)) {
|
|
6289
6588
|
log.info("Google: connected (Calendar, Gmail, Drive)");
|
|
6290
6589
|
}
|
|
6291
|
-
else if (isGoogleConfigured()) {
|
|
6590
|
+
else if ((_googleAuth?.isGoogleConfigured() ?? false)) {
|
|
6292
6591
|
log.info("Google: configured — visit /api/google/auth to connect");
|
|
6293
6592
|
}
|
|
6294
6593
|
else {
|
|
@@ -6335,84 +6634,86 @@ async function start(opts) {
|
|
|
6335
6634
|
}
|
|
6336
6635
|
}
|
|
6337
6636
|
// Register email handler — emails with instance name in subject are processed as chat
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6637
|
+
if (_googleGmailTimer)
|
|
6638
|
+
_googleGmailTimer.onDashEmail(async (message) => {
|
|
6639
|
+
const human = await readHuman();
|
|
6640
|
+
const name = human?.name ?? "Human";
|
|
6641
|
+
const emailBody = message.body?.trim();
|
|
6642
|
+
if (!emailBody)
|
|
6643
|
+
return null;
|
|
6644
|
+
// Build a lightweight Brain for email context
|
|
6645
|
+
const ltm = new FileSystemLongTermMemory(MEMORY_DIR);
|
|
6646
|
+
await ltm.init();
|
|
6647
|
+
const emailBrain = new Brain({
|
|
6648
|
+
systemPrompt: [
|
|
6649
|
+
`You are ${getInstanceName()}, a personal AI agent paired with ${name}. You run locally on ${name}'s machine.`,
|
|
6650
|
+
`Today is ${new Date().toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" })}.`,
|
|
6651
|
+
`You are responding to an email that was sent to you. This is a working email channel — you received this email and your reply will be sent back automatically via Gmail.`,
|
|
6652
|
+
``,
|
|
6653
|
+
`Your capabilities — USE THEM when the email requests action:`,
|
|
6654
|
+
`- Google Calendar: CREATE, UPDATE, DELETE events using [CALENDAR_ACTION] blocks.`,
|
|
6655
|
+
`- Gmail: SEND emails and REPLY to threads using [EMAIL_ACTION] blocks.`,
|
|
6656
|
+
`- Google Docs & Sheets: CREATE documents using [DOC_ACTION] blocks.`,
|
|
6657
|
+
`- Task board for tracking work.`,
|
|
6658
|
+
`- Long-term memory of past conversations and learned facts.`,
|
|
6659
|
+
``,
|
|
6660
|
+
...(getCapabilityRegistry()?.getPromptInstructions({ origin: "email", name }) ?? "").split("\n"),
|
|
6661
|
+
``,
|
|
6662
|
+
`Rules for email replies:`,
|
|
6663
|
+
`- Write a natural, helpful reply as plain text (no markdown).`,
|
|
6664
|
+
`- Be warm and direct — you have personality, you're not a corporate assistant.`,
|
|
6665
|
+
`- Keep it concise and appropriate for email.`,
|
|
6666
|
+
`- Do not include a subject line. Just write the body of the reply.`,
|
|
6667
|
+
`- NEVER claim you can't do something you clearly just did (you ARE sending this email).`,
|
|
6668
|
+
`- When someone asks you to DO something (schedule, create, send), DO IT with the appropriate action block — don't just acknowledge it.`,
|
|
6669
|
+
`- Action blocks will be stripped from the email reply automatically. The recipient only sees your text.`,
|
|
6670
|
+
`- Sign off as "— ${getInstanceName()}"`,
|
|
6671
|
+
].join("\n"),
|
|
6672
|
+
}, ltm);
|
|
6673
|
+
const senderName = message.from.split("<")[0].trim() || "someone";
|
|
6674
|
+
const ctx = await emailBrain.getContextForTurn({
|
|
6675
|
+
userInput: emailBody,
|
|
6676
|
+
conversationHistory: [],
|
|
6677
|
+
});
|
|
6678
|
+
// Inject email metadata so the agent knows the context
|
|
6679
|
+
ctx.messages.splice(1, 0, {
|
|
6680
|
+
role: "system",
|
|
6681
|
+
content: [
|
|
6682
|
+
`--- Incoming email ---`,
|
|
6683
|
+
`From: ${message.from}`,
|
|
6684
|
+
`Subject: ${message.subject}`,
|
|
6685
|
+
`Date: ${message.date}`,
|
|
6686
|
+
`--- End email metadata ---`,
|
|
6687
|
+
].join("\n"),
|
|
6688
|
+
});
|
|
6689
|
+
// Add the email body as the "user" message
|
|
6690
|
+
ctx.messages.push({ role: "user", content: emailBody });
|
|
6691
|
+
const provider = getProvider(resolveProvider());
|
|
6692
|
+
const model = resolveChatModel();
|
|
6693
|
+
let reply = await provider.completeChat(ctx.messages, model ?? undefined);
|
|
6694
|
+
if (!reply?.trim())
|
|
6695
|
+
return null;
|
|
6696
|
+
// Process action blocks from the AI response via capability registry
|
|
6697
|
+
{
|
|
6698
|
+
const capReg = getCapabilityRegistry();
|
|
6699
|
+
if (capReg) {
|
|
6700
|
+
const { cleaned } = await capReg.processResponse(reply, { origin: "email", name });
|
|
6701
|
+
reply = cleaned;
|
|
6702
|
+
}
|
|
6402
6703
|
}
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6704
|
+
// Guard: if stripping action blocks left only a signature,
|
|
6705
|
+
// the reply has no real content — don't send an empty email.
|
|
6706
|
+
const signaturePattern = new RegExp(`[\\s\\n]*[—\\-]+\\s*${getInstanceName()}[\\s.!]*$`, "i");
|
|
6707
|
+
const withoutSignature = reply.replace(signaturePattern, "").trim();
|
|
6708
|
+
if (!withoutSignature) {
|
|
6709
|
+
log.warn("Email reply was empty after stripping action blocks — not sending", { to: message.from, subject: message.subject });
|
|
6710
|
+
return null;
|
|
6711
|
+
}
|
|
6712
|
+
log.info("Email reply generated", { to: message.from, subject: message.subject, replyLength: reply.length });
|
|
6713
|
+
return reply.trim();
|
|
6714
|
+
});
|
|
6715
|
+
if (_googleGmailTimer)
|
|
6716
|
+
log.info(`${getInstanceName()} email handler registered — emails with '${getInstanceName()}' in subject will be auto-replied`);
|
|
6416
6717
|
await new Promise((resolve) => {
|
|
6417
6718
|
function onListening(server) {
|
|
6418
6719
|
const addr = server.address();
|
|
@@ -6440,7 +6741,8 @@ async function start(opts) {
|
|
|
6440
6741
|
// Announce on LAN if mesh.lanAnnounce is enabled AND tier >= byok
|
|
6441
6742
|
if (tierGate.canMesh(tier) && getMeshConfig().lanAnnounce) {
|
|
6442
6743
|
try {
|
|
6443
|
-
|
|
6744
|
+
if (_mdns)
|
|
6745
|
+
_mdns.startMdns(actualPort);
|
|
6444
6746
|
}
|
|
6445
6747
|
catch (err) {
|
|
6446
6748
|
log.warn("mDNS announcement failed — discovery disabled", {
|
|
@@ -6571,46 +6873,86 @@ function parseRoadmapYaml(raw) {
|
|
|
6571
6873
|
// then sidecars, timers, and monitors.
|
|
6572
6874
|
async function gracefulShutdown(signal) {
|
|
6573
6875
|
// Agent pool handles coordinated shutdown: drain → terminate → cleanup
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6876
|
+
try {
|
|
6877
|
+
if (workflowEngine) {
|
|
6878
|
+
await workflowEngine.shutdown().catch(() => { });
|
|
6879
|
+
workflowEngine = null;
|
|
6880
|
+
}
|
|
6881
|
+
if (agentPool) {
|
|
6882
|
+
await agentPool.shutdown(signal).catch(() => { });
|
|
6883
|
+
setAgentPool(null);
|
|
6884
|
+
agentPool = null;
|
|
6885
|
+
}
|
|
6886
|
+
else if (instanceManager || _agentRuntime) {
|
|
6887
|
+
// Fallback if pool wasn't initialized
|
|
6888
|
+
await instanceManager?.shutdown().catch(() => { });
|
|
6889
|
+
if (_agentRuntime)
|
|
6890
|
+
await _agentRuntime.shutdownRuntime(signal).catch(() => { });
|
|
6891
|
+
}
|
|
6587
6892
|
}
|
|
6893
|
+
catch { }
|
|
6588
6894
|
recovery.stop();
|
|
6589
6895
|
stopCollector();
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6896
|
+
try {
|
|
6897
|
+
_googleGmailTimer?.stopGmailTimer();
|
|
6898
|
+
}
|
|
6899
|
+
catch { }
|
|
6900
|
+
try {
|
|
6901
|
+
_googleCalendarTimer?.stopCalendarTimer();
|
|
6902
|
+
}
|
|
6903
|
+
catch { }
|
|
6904
|
+
try {
|
|
6905
|
+
_googleTasksTimer?.stopTasksTimer();
|
|
6906
|
+
}
|
|
6907
|
+
catch { }
|
|
6593
6908
|
stopGroomingTimer();
|
|
6594
6909
|
stopSchedulingTimer();
|
|
6595
|
-
|
|
6910
|
+
try {
|
|
6911
|
+
_integrationsGithub?.shutdownGitHub();
|
|
6912
|
+
}
|
|
6913
|
+
catch { }
|
|
6596
6914
|
stopGoalTimer();
|
|
6597
|
-
|
|
6915
|
+
try {
|
|
6916
|
+
_agentAutonomous?.stopAutonomousTimer();
|
|
6917
|
+
}
|
|
6918
|
+
catch { }
|
|
6598
6919
|
stopBacklogReviewTimer();
|
|
6599
6920
|
stopBriefingTimer();
|
|
6600
6921
|
stopInsightsTimer();
|
|
6601
6922
|
stopOpenLoopScanner();
|
|
6602
6923
|
stopCreditMonitor();
|
|
6603
6924
|
stopPushMonitor();
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6925
|
+
try {
|
|
6926
|
+
_mdns?.stopMdns();
|
|
6927
|
+
}
|
|
6928
|
+
catch { }
|
|
6929
|
+
stopFileWatcher();
|
|
6930
|
+
try {
|
|
6931
|
+
_avatarSidecar?.stopAvatarSidecar();
|
|
6932
|
+
}
|
|
6933
|
+
catch { }
|
|
6934
|
+
try {
|
|
6935
|
+
_ttsSidecar?.stopTtsSidecar();
|
|
6936
|
+
}
|
|
6937
|
+
catch { }
|
|
6938
|
+
try {
|
|
6939
|
+
_sttSidecar?.stopSttSidecar();
|
|
6940
|
+
}
|
|
6941
|
+
catch { }
|
|
6608
6942
|
stopSidecar();
|
|
6609
|
-
|
|
6943
|
+
try {
|
|
6944
|
+
if (_browser)
|
|
6945
|
+
await _browser.closeBrowser();
|
|
6946
|
+
}
|
|
6947
|
+
catch { }
|
|
6610
6948
|
shutdownAgents();
|
|
6611
6949
|
await shutdownPlugins().catch(() => { });
|
|
6612
6950
|
await shutdownLLMCache();
|
|
6613
|
-
|
|
6951
|
+
try {
|
|
6952
|
+
if (_tracingInit)
|
|
6953
|
+
await _tracingInit.shutdownTracing();
|
|
6954
|
+
}
|
|
6955
|
+
catch { }
|
|
6614
6956
|
releaseLock();
|
|
6615
6957
|
process.exit(0);
|
|
6616
6958
|
}
|