@ouro.bot/cli 0.1.0-alpha.44 → 0.1.0-alpha.46

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.
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getBlueBubblesInboundLogPath = getBlueBubblesInboundLogPath;
37
+ exports.hasRecordedBlueBubblesInbound = hasRecordedBlueBubblesInbound;
38
+ exports.recordBlueBubblesInbound = recordBlueBubblesInbound;
39
+ const fs = __importStar(require("node:fs"));
40
+ const path = __importStar(require("node:path"));
41
+ const config_1 = require("../heart/config");
42
+ const identity_1 = require("../heart/identity");
43
+ const runtime_1 = require("../nerves/runtime");
44
+ function getBlueBubblesInboundLogPath(agentName, sessionKey) {
45
+ return path.join((0, identity_1.getAgentRoot)(agentName), "state", "senses", "bluebubbles", "inbound", `${(0, config_1.sanitizeKey)(sessionKey)}.ndjson`);
46
+ }
47
+ function readEntries(filePath) {
48
+ try {
49
+ const raw = fs.readFileSync(filePath, "utf-8");
50
+ return raw
51
+ .split("\n")
52
+ .map((line) => line.trim())
53
+ .filter(Boolean)
54
+ .map((line) => JSON.parse(line))
55
+ .filter((entry) => typeof entry.messageGuid === "string" && typeof entry.sessionKey === "string");
56
+ }
57
+ catch {
58
+ return [];
59
+ }
60
+ }
61
+ function hasRecordedBlueBubblesInbound(agentName, sessionKey, messageGuid) {
62
+ if (!messageGuid.trim())
63
+ return false;
64
+ const filePath = getBlueBubblesInboundLogPath(agentName, sessionKey);
65
+ return readEntries(filePath).some((entry) => entry.messageGuid === messageGuid);
66
+ }
67
+ function recordBlueBubblesInbound(agentName, event, source) {
68
+ const filePath = getBlueBubblesInboundLogPath(agentName, event.chat.sessionKey);
69
+ try {
70
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
71
+ fs.appendFileSync(filePath, JSON.stringify({
72
+ recordedAt: new Date(event.timestamp).toISOString(),
73
+ messageGuid: event.messageGuid,
74
+ chatGuid: event.chat.chatGuid ?? null,
75
+ chatIdentifier: event.chat.chatIdentifier ?? null,
76
+ sessionKey: event.chat.sessionKey,
77
+ textForAgent: event.textForAgent,
78
+ source,
79
+ }) + "\n", "utf-8");
80
+ }
81
+ catch (error) {
82
+ (0, runtime_1.emitNervesEvent)({
83
+ level: "warn",
84
+ component: "senses",
85
+ event: "senses.bluebubbles_inbound_log_error",
86
+ message: "failed to record bluebubbles inbound sidecar log",
87
+ meta: {
88
+ agentName,
89
+ messageGuid: event.messageGuid,
90
+ sessionKey: event.chat.sessionKey,
91
+ reason: error instanceof Error ? error.message : String(error),
92
+ },
93
+ });
94
+ return filePath;
95
+ }
96
+ (0, runtime_1.emitNervesEvent)({
97
+ component: "senses",
98
+ event: "senses.bluebubbles_inbound_logged",
99
+ message: "recorded bluebubbles inbound message to sidecar log",
100
+ meta: {
101
+ agentName,
102
+ messageGuid: event.messageGuid,
103
+ sessionKey: event.chat.sessionKey,
104
+ source,
105
+ path: filePath,
106
+ },
107
+ });
108
+ return filePath;
109
+ }
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.getBlueBubblesMutationLogPath = getBlueBubblesMutationLogPath;
37
37
  exports.recordBlueBubblesMutation = recordBlueBubblesMutation;
38
+ exports.listBlueBubblesRecoveryCandidates = listBlueBubblesRecoveryCandidates;
38
39
  const fs = __importStar(require("node:fs"));
39
40
  const path = __importStar(require("node:path"));
40
41
  const runtime_1 = require("../nerves/runtime");
@@ -72,3 +73,44 @@ function recordBlueBubblesMutation(agentName, event) {
72
73
  });
73
74
  return filePath;
74
75
  }
76
+ function listBlueBubblesRecoveryCandidates(agentName) {
77
+ const rootDir = path.join((0, identity_1.getAgentRoot)(agentName), "state", "senses", "bluebubbles", "mutations");
78
+ let files;
79
+ try {
80
+ files = fs.readdirSync(rootDir);
81
+ }
82
+ catch {
83
+ return [];
84
+ }
85
+ const deduped = new Map();
86
+ for (const file of files.filter((entry) => entry.endsWith(".ndjson")).sort()) {
87
+ const filePath = path.join(rootDir, file);
88
+ let raw = "";
89
+ try {
90
+ raw = fs.readFileSync(filePath, "utf-8");
91
+ }
92
+ catch {
93
+ continue;
94
+ }
95
+ for (const line of raw.split("\n")) {
96
+ const trimmed = line.trim();
97
+ if (!trimmed)
98
+ continue;
99
+ try {
100
+ const entry = JSON.parse(trimmed);
101
+ if (typeof entry.messageGuid !== "string"
102
+ || !entry.messageGuid.trim()
103
+ || entry.fromMe
104
+ || entry.shouldNotifyAgent
105
+ || (entry.mutationType !== "read" && entry.mutationType !== "delivery")) {
106
+ continue;
107
+ }
108
+ deduped.set(entry.messageGuid, entry);
109
+ }
110
+ catch {
111
+ // ignore malformed recovery candidates
112
+ }
113
+ }
114
+ }
115
+ return [...deduped.values()].sort((left, right) => left.recordedAt.localeCompare(right.recordedAt));
116
+ }
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getBlueBubblesRuntimeStatePath = getBlueBubblesRuntimeStatePath;
37
+ exports.readBlueBubblesRuntimeState = readBlueBubblesRuntimeState;
38
+ exports.writeBlueBubblesRuntimeState = writeBlueBubblesRuntimeState;
39
+ const fs = __importStar(require("node:fs"));
40
+ const path = __importStar(require("node:path"));
41
+ const identity_1 = require("../heart/identity");
42
+ const runtime_1 = require("../nerves/runtime");
43
+ const DEFAULT_RUNTIME_STATE = {
44
+ upstreamStatus: "unknown",
45
+ detail: "startup health probe pending",
46
+ pendingRecoveryCount: 0,
47
+ };
48
+ function getBlueBubblesRuntimeStatePath(agentName, agentRoot = (0, identity_1.getAgentRoot)(agentName)) {
49
+ return path.join(agentRoot, "state", "senses", "bluebubbles", "runtime.json");
50
+ }
51
+ function readBlueBubblesRuntimeState(agentName, agentRoot) {
52
+ const filePath = getBlueBubblesRuntimeStatePath(agentName, agentRoot);
53
+ try {
54
+ const raw = fs.readFileSync(filePath, "utf-8");
55
+ const parsed = JSON.parse(raw);
56
+ return {
57
+ upstreamStatus: parsed.upstreamStatus === "ok" || parsed.upstreamStatus === "error"
58
+ ? parsed.upstreamStatus
59
+ : "unknown",
60
+ detail: typeof parsed.detail === "string" && parsed.detail.trim()
61
+ ? parsed.detail
62
+ : DEFAULT_RUNTIME_STATE.detail,
63
+ lastCheckedAt: typeof parsed.lastCheckedAt === "string" ? parsed.lastCheckedAt : undefined,
64
+ pendingRecoveryCount: typeof parsed.pendingRecoveryCount === "number" && Number.isFinite(parsed.pendingRecoveryCount)
65
+ ? parsed.pendingRecoveryCount
66
+ : 0,
67
+ lastRecoveredAt: typeof parsed.lastRecoveredAt === "string" ? parsed.lastRecoveredAt : undefined,
68
+ lastRecoveredMessageGuid: typeof parsed.lastRecoveredMessageGuid === "string"
69
+ ? parsed.lastRecoveredMessageGuid
70
+ : undefined,
71
+ };
72
+ }
73
+ catch {
74
+ return { ...DEFAULT_RUNTIME_STATE };
75
+ }
76
+ }
77
+ function writeBlueBubblesRuntimeState(agentName, state, agentRoot) {
78
+ const filePath = getBlueBubblesRuntimeStatePath(agentName, agentRoot);
79
+ try {
80
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
81
+ fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
82
+ }
83
+ catch (error) {
84
+ (0, runtime_1.emitNervesEvent)({
85
+ level: "warn",
86
+ component: "senses",
87
+ event: "senses.bluebubbles_runtime_state_error",
88
+ message: "failed to write bluebubbles runtime state",
89
+ meta: {
90
+ agentName,
91
+ upstreamStatus: state.upstreamStatus,
92
+ reason: error instanceof Error ? error.message : String(error),
93
+ },
94
+ });
95
+ return filePath;
96
+ }
97
+ (0, runtime_1.emitNervesEvent)({
98
+ component: "senses",
99
+ event: "senses.bluebubbles_runtime_state_written",
100
+ message: "wrote bluebubbles runtime state",
101
+ meta: {
102
+ agentName,
103
+ upstreamStatus: state.upstreamStatus,
104
+ pendingRecoveryCount: state.pendingRecoveryCount,
105
+ path: filePath,
106
+ },
107
+ });
108
+ return filePath;
109
+ }
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.handleBlueBubblesEvent = handleBlueBubblesEvent;
37
+ exports.recoverMissedBlueBubblesMessages = recoverMissedBlueBubblesMessages;
37
38
  exports.createBlueBubblesWebhookHandler = createBlueBubblesWebhookHandler;
38
39
  exports.drainAndSendPendingBlueBubbles = drainAndSendPendingBlueBubbles;
39
40
  exports.startBlueBubblesApp = startBlueBubblesApp;
@@ -55,7 +56,9 @@ const phrases_1 = require("../mind/phrases");
55
56
  const runtime_1 = require("../nerves/runtime");
56
57
  const bluebubbles_model_1 = require("./bluebubbles-model");
57
58
  const bluebubbles_client_1 = require("./bluebubbles-client");
59
+ const bluebubbles_inbound_log_1 = require("./bluebubbles-inbound-log");
58
60
  const bluebubbles_mutation_log_1 = require("./bluebubbles-mutation-log");
61
+ const bluebubbles_runtime_state_1 = require("./bluebubbles-runtime-state");
59
62
  const bluebubbles_session_cleanup_1 = require("./bluebubbles-session-cleanup");
60
63
  const debug_activity_1 = require("./debug-activity");
61
64
  const trust_gate_1 = require("./trust-gate");
@@ -74,6 +77,7 @@ const defaultDeps = {
74
77
  createFriendResolver: (store, params) => new resolver_1.FriendResolver(store, params),
75
78
  createServer: http.createServer,
76
79
  };
80
+ const BLUEBUBBLES_RUNTIME_SYNC_INTERVAL_MS = 30_000;
77
81
  function resolveFriendParams(event) {
78
82
  if (event.chat.isGroup) {
79
83
  const groupKey = event.chat.chatGuid ?? event.chat.chatIdentifier ?? event.sender.externalId;
@@ -234,6 +238,47 @@ function buildInboundContent(event, existingMessages) {
234
238
  ...event.inputPartsForAgent,
235
239
  ];
236
240
  }
241
+ function sessionLikelyContainsMessage(event, existingMessages) {
242
+ const fragment = event.textForAgent.trim();
243
+ if (!fragment)
244
+ return false;
245
+ return existingMessages.some((message) => {
246
+ if (message.role !== "user")
247
+ return false;
248
+ return extractMessageText(message.content).includes(fragment);
249
+ });
250
+ }
251
+ function mutationEntryToEvent(entry) {
252
+ return {
253
+ kind: "mutation",
254
+ eventType: entry.eventType,
255
+ mutationType: entry.mutationType,
256
+ messageGuid: entry.messageGuid,
257
+ targetMessageGuid: entry.targetMessageGuid ?? undefined,
258
+ timestamp: Date.parse(entry.recordedAt) || Date.now(),
259
+ fromMe: entry.fromMe,
260
+ sender: {
261
+ provider: "imessage-handle",
262
+ externalId: entry.chatIdentifier ?? entry.chatGuid ?? "unknown",
263
+ rawId: entry.chatIdentifier ?? entry.chatGuid ?? "unknown",
264
+ displayName: entry.chatIdentifier ?? entry.chatGuid ?? "Unknown",
265
+ },
266
+ chat: {
267
+ chatGuid: entry.chatGuid ?? undefined,
268
+ chatIdentifier: entry.chatIdentifier ?? undefined,
269
+ displayName: undefined,
270
+ isGroup: Boolean(entry.chatGuid?.includes(";+;")),
271
+ sessionKey: entry.sessionKey,
272
+ sendTarget: entry.chatGuid
273
+ ? { kind: "chat_guid", value: entry.chatGuid }
274
+ : { kind: "chat_identifier", value: entry.chatIdentifier ?? "unknown" },
275
+ participantHandles: [],
276
+ },
277
+ shouldNotifyAgent: entry.shouldNotifyAgent,
278
+ textForAgent: entry.textForAgent,
279
+ requiresRepair: true,
280
+ };
281
+ }
237
282
  function getBlueBubblesContinuityIngressTexts(event) {
238
283
  if (event.kind !== "message")
239
284
  return [];
@@ -435,10 +480,8 @@ function isWebhookPasswordValid(url, expectedPassword) {
435
480
  const provided = url.searchParams.get("password");
436
481
  return !provided || provided === expectedPassword;
437
482
  }
438
- async function handleBlueBubblesEvent(payload, deps = {}) {
439
- const resolvedDeps = { ...defaultDeps, ...deps };
483
+ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source) {
440
484
  const client = resolvedDeps.createClient();
441
- const event = await client.repairEvent((0, bluebubbles_model_1.normalizeBlueBubblesEvent)(payload));
442
485
  if (event.fromMe) {
443
486
  (0, runtime_1.emitNervesEvent)({
444
487
  component: "senses",
@@ -509,6 +552,36 @@ async function handleBlueBubblesEvent(payload, deps = {}) {
509
552
  const sessionMessages = existing?.messages && existing.messages.length > 0
510
553
  ? existing.messages
511
554
  : [{ role: "system", content: await resolvedDeps.buildSystem("bluebubbles", undefined, context) }];
555
+ if (event.kind === "message") {
556
+ const agentName = resolvedDeps.getAgentName();
557
+ if ((0, bluebubbles_inbound_log_1.hasRecordedBlueBubblesInbound)(agentName, event.chat.sessionKey, event.messageGuid)) {
558
+ (0, runtime_1.emitNervesEvent)({
559
+ component: "senses",
560
+ event: "senses.bluebubbles_recovery_skip",
561
+ message: "skipped bluebubbles message already recorded as handled",
562
+ meta: {
563
+ messageGuid: event.messageGuid,
564
+ sessionKey: event.chat.sessionKey,
565
+ source,
566
+ },
567
+ });
568
+ return { handled: true, notifiedAgent: false, kind: event.kind, reason: "already_processed" };
569
+ }
570
+ if (source !== "webhook" && sessionLikelyContainsMessage(event, existing?.messages ?? sessionMessages)) {
571
+ (0, bluebubbles_inbound_log_1.recordBlueBubblesInbound)(agentName, event, "recovery-bootstrap");
572
+ (0, runtime_1.emitNervesEvent)({
573
+ component: "senses",
574
+ event: "senses.bluebubbles_recovery_skip",
575
+ message: "skipped bluebubbles recovery because the session already contains the message text",
576
+ meta: {
577
+ messageGuid: event.messageGuid,
578
+ sessionKey: event.chat.sessionKey,
579
+ source,
580
+ },
581
+ });
582
+ return { handled: true, notifiedAgent: false, kind: event.kind, reason: "already_processed" };
583
+ }
584
+ }
512
585
  // Build inbound user message (adapter concern: BB-specific content formatting)
513
586
  const userMessage = {
514
587
  role: "user",
@@ -578,6 +651,9 @@ async function handleBlueBubblesEvent(payload, deps = {}) {
578
651
  text: result.gateResult.autoReply,
579
652
  });
580
653
  }
654
+ if (event.kind === "message") {
655
+ (0, bluebubbles_inbound_log_1.recordBlueBubblesInbound)(resolvedDeps.getAgentName(), event, source);
656
+ }
581
657
  return {
582
658
  handled: true,
583
659
  notifiedAgent: false,
@@ -586,6 +662,9 @@ async function handleBlueBubblesEvent(payload, deps = {}) {
586
662
  }
587
663
  // Gate allowed — flush the agent's reply
588
664
  await callbacks.flush();
665
+ if (event.kind === "message") {
666
+ (0, bluebubbles_inbound_log_1.recordBlueBubblesInbound)(resolvedDeps.getAgentName(), event, source);
667
+ }
589
668
  (0, runtime_1.emitNervesEvent)({
590
669
  component: "senses",
591
670
  event: "senses.bluebubbles_turn_end",
@@ -606,6 +685,94 @@ async function handleBlueBubblesEvent(payload, deps = {}) {
606
685
  await callbacks.finish();
607
686
  }
608
687
  }
688
+ async function handleBlueBubblesEvent(payload, deps = {}) {
689
+ const resolvedDeps = { ...defaultDeps, ...deps };
690
+ const client = resolvedDeps.createClient();
691
+ const event = await client.repairEvent((0, bluebubbles_model_1.normalizeBlueBubblesEvent)(payload));
692
+ return handleBlueBubblesNormalizedEvent(event, resolvedDeps, "webhook");
693
+ }
694
+ function countPendingRecoveryCandidates(agentName) {
695
+ return (0, bluebubbles_mutation_log_1.listBlueBubblesRecoveryCandidates)(agentName)
696
+ .filter((entry) => !(0, bluebubbles_inbound_log_1.hasRecordedBlueBubblesInbound)(agentName, entry.sessionKey, entry.messageGuid))
697
+ .length;
698
+ }
699
+ async function syncBlueBubblesRuntime(deps = {}) {
700
+ const resolvedDeps = { ...defaultDeps, ...deps };
701
+ const agentName = resolvedDeps.getAgentName();
702
+ const client = resolvedDeps.createClient();
703
+ const checkedAt = new Date().toISOString();
704
+ try {
705
+ await client.checkHealth();
706
+ const recovery = await recoverMissedBlueBubblesMessages(resolvedDeps);
707
+ (0, bluebubbles_runtime_state_1.writeBlueBubblesRuntimeState)(agentName, {
708
+ upstreamStatus: recovery.pending > 0 || recovery.failed > 0 ? "error" : "ok",
709
+ detail: recovery.failed > 0
710
+ ? `recovery failures: ${recovery.failed}`
711
+ : recovery.pending > 0
712
+ ? `pending recovery: ${recovery.pending}`
713
+ : "upstream reachable",
714
+ lastCheckedAt: checkedAt,
715
+ pendingRecoveryCount: recovery.pending,
716
+ lastRecoveredAt: recovery.recovered > 0 ? checkedAt : undefined,
717
+ });
718
+ }
719
+ catch (error) {
720
+ (0, bluebubbles_runtime_state_1.writeBlueBubblesRuntimeState)(agentName, {
721
+ upstreamStatus: "error",
722
+ detail: error instanceof Error ? error.message : String(error),
723
+ lastCheckedAt: checkedAt,
724
+ pendingRecoveryCount: countPendingRecoveryCandidates(agentName),
725
+ });
726
+ }
727
+ }
728
+ async function recoverMissedBlueBubblesMessages(deps = {}) {
729
+ const resolvedDeps = { ...defaultDeps, ...deps };
730
+ const agentName = resolvedDeps.getAgentName();
731
+ const client = resolvedDeps.createClient();
732
+ const result = { recovered: 0, skipped: 0, pending: 0, failed: 0 };
733
+ for (const candidate of (0, bluebubbles_mutation_log_1.listBlueBubblesRecoveryCandidates)(agentName)) {
734
+ if ((0, bluebubbles_inbound_log_1.hasRecordedBlueBubblesInbound)(agentName, candidate.sessionKey, candidate.messageGuid)) {
735
+ result.skipped++;
736
+ continue;
737
+ }
738
+ try {
739
+ const repaired = await client.repairEvent(mutationEntryToEvent(candidate));
740
+ if (repaired.kind !== "message") {
741
+ result.pending++;
742
+ continue;
743
+ }
744
+ const handled = await handleBlueBubblesNormalizedEvent(repaired, resolvedDeps, "mutation-recovery");
745
+ if (handled.reason === "already_processed") {
746
+ result.skipped++;
747
+ }
748
+ else {
749
+ result.recovered++;
750
+ }
751
+ }
752
+ catch (error) {
753
+ result.failed++;
754
+ (0, runtime_1.emitNervesEvent)({
755
+ level: "warn",
756
+ component: "senses",
757
+ event: "senses.bluebubbles_recovery_error",
758
+ message: "bluebubbles backlog recovery failed",
759
+ meta: {
760
+ messageGuid: candidate.messageGuid,
761
+ reason: error instanceof Error ? error.message : String(error),
762
+ },
763
+ });
764
+ }
765
+ }
766
+ if (result.recovered > 0 || result.skipped > 0 || result.pending > 0 || result.failed > 0) {
767
+ (0, runtime_1.emitNervesEvent)({
768
+ component: "senses",
769
+ event: "senses.bluebubbles_recovery_complete",
770
+ message: "bluebubbles backlog recovery pass completed",
771
+ meta: { ...result },
772
+ });
773
+ }
774
+ return result;
775
+ }
609
776
  function createBlueBubblesWebhookHandler(deps = {}) {
610
777
  return async (req, res) => {
611
778
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
@@ -842,6 +1009,12 @@ function startBlueBubblesApp(deps = {}) {
842
1009
  resolvedDeps.createClient();
843
1010
  const channelConfig = (0, config_1.getBlueBubblesChannelConfig)();
844
1011
  const server = resolvedDeps.createServer(createBlueBubblesWebhookHandler(deps));
1012
+ const runtimeTimer = setInterval(() => {
1013
+ void syncBlueBubblesRuntime(resolvedDeps);
1014
+ }, BLUEBUBBLES_RUNTIME_SYNC_INTERVAL_MS);
1015
+ server.on?.("close", () => {
1016
+ clearInterval(runtimeTimer);
1017
+ });
845
1018
  server.listen(channelConfig.port, () => {
846
1019
  (0, runtime_1.emitNervesEvent)({
847
1020
  component: "channels",
@@ -850,5 +1023,6 @@ function startBlueBubblesApp(deps = {}) {
850
1023
  meta: { port: channelConfig.port, webhookPath: channelConfig.webhookPath },
851
1024
  });
852
1025
  });
1026
+ void syncBlueBubblesRuntime(resolvedDeps);
853
1027
  return server;
854
1028
  }
@@ -6,12 +6,12 @@ const inner_dialog_1 = require("./inner-dialog");
6
6
  const runtime_1 = require("../nerves/runtime");
7
7
  function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runInnerDialogTurn)(options)) {
8
8
  let running = false;
9
- async function run(reason) {
9
+ async function run(reason, taskId) {
10
10
  if (running)
11
11
  return;
12
12
  running = true;
13
13
  try {
14
- await runTurn({ reason });
14
+ await runTurn({ reason, taskId });
15
15
  }
16
16
  catch (error) {
17
17
  (0, runtime_1.emitNervesEvent)({
@@ -37,8 +37,11 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
37
37
  await run("heartbeat");
38
38
  return;
39
39
  }
40
- if (maybeMessage.type === "poke" ||
41
- maybeMessage.type === "chat" ||
40
+ if (maybeMessage.type === "poke") {
41
+ await run("instinct", maybeMessage.taskId);
42
+ return;
43
+ }
44
+ if (maybeMessage.type === "chat" ||
42
45
  maybeMessage.type === "message") {
43
46
  await run("instinct");
44
47
  return;