@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.
- package/changelog.json +21 -0
- package/dist/heart/daemon/daemon-cli.js +106 -0
- package/dist/heart/daemon/launchd.js +21 -4
- package/dist/heart/daemon/sense-manager.js +26 -2
- package/dist/heart/daemon/thoughts.js +225 -0
- package/dist/senses/bluebubbles-client.js +51 -1
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-mutation-log.js +42 -0
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles.js +177 -3
- package/dist/senses/inner-dialog-worker.js +7 -4
- package/dist/senses/inner-dialog.js +88 -14
- package/package.json +1 -1
- package/subagents/work-doer.md +22 -20
- package/subagents/work-planner.md +14 -6
|
@@ -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
|
|
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
|
-
|
|
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;
|