@ouro.bot/cli 0.1.0-alpha.2 → 0.1.0-alpha.20
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/AdoptionSpecialist.ouro/agent.json +70 -9
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/assets/ouroboros.png +0 -0
- package/dist/heart/config.js +66 -4
- package/dist/heart/core.js +75 -2
- package/dist/heart/daemon/daemon-cli.js +507 -29
- package/dist/heart/daemon/daemon-entry.js +13 -5
- package/dist/heart/daemon/daemon.js +42 -9
- package/dist/heart/daemon/hatch-animation.js +35 -0
- package/dist/heart/daemon/hatch-flow.js +2 -11
- package/dist/heart/daemon/hatch-specialist.js +6 -1
- package/dist/heart/daemon/ouro-bot-wrapper.js +4 -3
- package/dist/heart/daemon/ouro-path-installer.js +177 -0
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/process-manager.js +1 -1
- package/dist/heart/daemon/runtime-logging.js +9 -5
- package/dist/heart/daemon/runtime-metadata.js +118 -0
- package/dist/heart/daemon/sense-manager.js +266 -0
- package/dist/heart/daemon/specialist-orchestrator.js +129 -0
- package/dist/heart/daemon/specialist-prompt.js +98 -0
- package/dist/heart/daemon/specialist-tools.js +237 -0
- package/dist/heart/daemon/subagent-installer.js +10 -1
- package/dist/heart/identity.js +77 -1
- package/dist/heart/providers/anthropic.js +19 -2
- package/dist/heart/sense-truth.js +61 -0
- package/dist/heart/streaming.js +99 -21
- package/dist/mind/bundle-manifest.js +58 -0
- package/dist/mind/friends/channel.js +8 -0
- package/dist/mind/friends/types.js +1 -1
- package/dist/mind/prompt.js +77 -3
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/feedback.js +134 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +61 -2
- package/dist/repertoire/coding/spawner.js +3 -3
- package/dist/repertoire/coding/tools.js +41 -2
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/tools-base.js +69 -5
- package/dist/repertoire/tools-teams.js +57 -4
- package/dist/repertoire/tools.js +44 -11
- package/dist/senses/bluebubbles-client.js +433 -0
- package/dist/senses/bluebubbles-entry.js +11 -0
- package/dist/senses/bluebubbles-media.js +244 -0
- package/dist/senses/bluebubbles-model.js +253 -0
- package/dist/senses/bluebubbles-mutation-log.js +76 -0
- package/dist/senses/bluebubbles.js +421 -0
- package/dist/senses/cli.js +293 -133
- package/dist/senses/debug-activity.js +107 -0
- package/dist/senses/teams.js +173 -54
- package/package.json +11 -4
- package/subagents/work-doer.md +26 -24
- package/subagents/work-merger.md +24 -30
- package/subagents/work-planner.md +34 -25
- package/dist/inner-worker-entry.js +0 -4
|
@@ -0,0 +1,421 @@
|
|
|
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.handleBlueBubblesEvent = handleBlueBubblesEvent;
|
|
37
|
+
exports.createBlueBubblesWebhookHandler = createBlueBubblesWebhookHandler;
|
|
38
|
+
exports.startBlueBubblesApp = startBlueBubblesApp;
|
|
39
|
+
const http = __importStar(require("node:http"));
|
|
40
|
+
const path = __importStar(require("node:path"));
|
|
41
|
+
const core_1 = require("../heart/core");
|
|
42
|
+
const config_1 = require("../heart/config");
|
|
43
|
+
const identity_1 = require("../heart/identity");
|
|
44
|
+
const context_1 = require("../mind/context");
|
|
45
|
+
const tokens_1 = require("../mind/friends/tokens");
|
|
46
|
+
const resolver_1 = require("../mind/friends/resolver");
|
|
47
|
+
const store_file_1 = require("../mind/friends/store-file");
|
|
48
|
+
const prompt_1 = require("../mind/prompt");
|
|
49
|
+
const phrases_1 = require("../mind/phrases");
|
|
50
|
+
const runtime_1 = require("../nerves/runtime");
|
|
51
|
+
const bluebubbles_model_1 = require("./bluebubbles-model");
|
|
52
|
+
const bluebubbles_client_1 = require("./bluebubbles-client");
|
|
53
|
+
const bluebubbles_mutation_log_1 = require("./bluebubbles-mutation-log");
|
|
54
|
+
const debug_activity_1 = require("./debug-activity");
|
|
55
|
+
const defaultDeps = {
|
|
56
|
+
getAgentName: identity_1.getAgentName,
|
|
57
|
+
buildSystem: prompt_1.buildSystem,
|
|
58
|
+
runAgent: core_1.runAgent,
|
|
59
|
+
loadSession: context_1.loadSession,
|
|
60
|
+
postTurn: context_1.postTurn,
|
|
61
|
+
sessionPath: config_1.sessionPath,
|
|
62
|
+
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
63
|
+
createClient: () => (0, bluebubbles_client_1.createBlueBubblesClient)(),
|
|
64
|
+
recordMutation: bluebubbles_mutation_log_1.recordBlueBubblesMutation,
|
|
65
|
+
createFriendStore: () => new store_file_1.FileFriendStore(path.join((0, identity_1.getAgentRoot)(), "friends")),
|
|
66
|
+
createFriendResolver: (store, params) => new resolver_1.FriendResolver(store, params),
|
|
67
|
+
createServer: http.createServer,
|
|
68
|
+
};
|
|
69
|
+
function resolveFriendParams(event) {
|
|
70
|
+
if (event.chat.isGroup) {
|
|
71
|
+
const groupKey = event.chat.chatGuid ?? event.chat.chatIdentifier ?? event.sender.externalId;
|
|
72
|
+
return {
|
|
73
|
+
provider: "imessage-handle",
|
|
74
|
+
externalId: `group:${groupKey}`,
|
|
75
|
+
displayName: event.chat.displayName ?? "Unknown Group",
|
|
76
|
+
channel: "bluebubbles",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
provider: "imessage-handle",
|
|
81
|
+
externalId: event.sender.externalId || event.sender.rawId,
|
|
82
|
+
displayName: event.sender.displayName || "Unknown",
|
|
83
|
+
channel: "bluebubbles",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function buildInboundText(event) {
|
|
87
|
+
const baseText = event.repairNotice?.trim()
|
|
88
|
+
? `${event.textForAgent}\n[${event.repairNotice.trim()}]`
|
|
89
|
+
: event.textForAgent;
|
|
90
|
+
if (!event.chat.isGroup)
|
|
91
|
+
return baseText;
|
|
92
|
+
if (event.kind === "mutation") {
|
|
93
|
+
return `${event.sender.displayName} ${baseText}`;
|
|
94
|
+
}
|
|
95
|
+
return `${event.sender.displayName}: ${baseText}`;
|
|
96
|
+
}
|
|
97
|
+
function buildInboundContent(event) {
|
|
98
|
+
const text = buildInboundText(event);
|
|
99
|
+
if (event.kind !== "message" || !event.inputPartsForAgent || event.inputPartsForAgent.length === 0) {
|
|
100
|
+
return text;
|
|
101
|
+
}
|
|
102
|
+
return [
|
|
103
|
+
{ type: "text", text },
|
|
104
|
+
...event.inputPartsForAgent,
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
function createBlueBubblesCallbacks(client, chat, replyToMessageGuid) {
|
|
108
|
+
let textBuffer = "";
|
|
109
|
+
const phrases = (0, phrases_1.getPhrases)();
|
|
110
|
+
const activity = (0, debug_activity_1.createDebugActivityController)({
|
|
111
|
+
thinkingPhrases: phrases.thinking,
|
|
112
|
+
followupPhrases: phrases.followup,
|
|
113
|
+
transport: {
|
|
114
|
+
sendStatus: async (text) => {
|
|
115
|
+
const sent = await client.sendText({
|
|
116
|
+
chat,
|
|
117
|
+
text,
|
|
118
|
+
replyToMessageGuid,
|
|
119
|
+
});
|
|
120
|
+
return sent.messageGuid;
|
|
121
|
+
},
|
|
122
|
+
editStatus: async (_messageGuid, text) => {
|
|
123
|
+
await client.sendText({
|
|
124
|
+
chat,
|
|
125
|
+
text,
|
|
126
|
+
replyToMessageGuid,
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
setTyping: async (active) => {
|
|
130
|
+
await client.setTyping(chat, active);
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
onTransportError: (operation, error) => {
|
|
134
|
+
(0, runtime_1.emitNervesEvent)({
|
|
135
|
+
level: "warn",
|
|
136
|
+
component: "senses",
|
|
137
|
+
event: "senses.bluebubbles_activity_error",
|
|
138
|
+
message: "bluebubbles activity transport failed",
|
|
139
|
+
meta: {
|
|
140
|
+
operation,
|
|
141
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
return {
|
|
147
|
+
onModelStart() {
|
|
148
|
+
activity.onModelStart();
|
|
149
|
+
(0, runtime_1.emitNervesEvent)({
|
|
150
|
+
component: "senses",
|
|
151
|
+
event: "senses.bluebubbles_turn_start",
|
|
152
|
+
message: "bluebubbles turn started",
|
|
153
|
+
meta: { chatGuid: chat.chatGuid ?? null },
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
onModelStreamStart() {
|
|
157
|
+
(0, runtime_1.emitNervesEvent)({
|
|
158
|
+
component: "senses",
|
|
159
|
+
event: "senses.bluebubbles_stream_start",
|
|
160
|
+
message: "bluebubbles non-streaming response started",
|
|
161
|
+
meta: {},
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
onTextChunk(text) {
|
|
165
|
+
activity.onTextChunk(text);
|
|
166
|
+
textBuffer += text;
|
|
167
|
+
},
|
|
168
|
+
onReasoningChunk(_text) { },
|
|
169
|
+
onToolStart(name, _args) {
|
|
170
|
+
activity.onToolStart(name, _args);
|
|
171
|
+
(0, runtime_1.emitNervesEvent)({
|
|
172
|
+
component: "senses",
|
|
173
|
+
event: "senses.bluebubbles_tool_start",
|
|
174
|
+
message: "bluebubbles tool execution started",
|
|
175
|
+
meta: { name },
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
onToolEnd(name, summary, success) {
|
|
179
|
+
activity.onToolEnd(name, summary, success);
|
|
180
|
+
(0, runtime_1.emitNervesEvent)({
|
|
181
|
+
component: "senses",
|
|
182
|
+
event: "senses.bluebubbles_tool_end",
|
|
183
|
+
message: "bluebubbles tool execution completed",
|
|
184
|
+
meta: { name, success, summary },
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
onError(error, severity) {
|
|
188
|
+
activity.onError(error);
|
|
189
|
+
(0, runtime_1.emitNervesEvent)({
|
|
190
|
+
level: severity === "terminal" ? "error" : "warn",
|
|
191
|
+
component: "senses",
|
|
192
|
+
event: "senses.bluebubbles_turn_error",
|
|
193
|
+
message: "bluebubbles turn callback error",
|
|
194
|
+
meta: { severity, reason: error.message },
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
onClearText() {
|
|
198
|
+
textBuffer = "";
|
|
199
|
+
},
|
|
200
|
+
async flush() {
|
|
201
|
+
await activity.drain();
|
|
202
|
+
const trimmed = textBuffer.trim();
|
|
203
|
+
if (!trimmed) {
|
|
204
|
+
await activity.finish();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
textBuffer = "";
|
|
208
|
+
await client.sendText({
|
|
209
|
+
chat,
|
|
210
|
+
text: trimmed,
|
|
211
|
+
replyToMessageGuid,
|
|
212
|
+
});
|
|
213
|
+
await activity.finish();
|
|
214
|
+
},
|
|
215
|
+
async finish() {
|
|
216
|
+
await activity.finish();
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
async function readRequestBody(req) {
|
|
221
|
+
let body = "";
|
|
222
|
+
for await (const chunk of req) {
|
|
223
|
+
body += chunk.toString();
|
|
224
|
+
}
|
|
225
|
+
return body;
|
|
226
|
+
}
|
|
227
|
+
function writeJson(res, statusCode, payload) {
|
|
228
|
+
res.statusCode = statusCode;
|
|
229
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
230
|
+
res.end(JSON.stringify(payload));
|
|
231
|
+
}
|
|
232
|
+
function isWebhookPasswordValid(url, expectedPassword) {
|
|
233
|
+
const provided = url.searchParams.get("password");
|
|
234
|
+
return !provided || provided === expectedPassword;
|
|
235
|
+
}
|
|
236
|
+
async function handleBlueBubblesEvent(payload, deps = {}) {
|
|
237
|
+
const resolvedDeps = { ...defaultDeps, ...deps };
|
|
238
|
+
const client = resolvedDeps.createClient();
|
|
239
|
+
const event = await client.repairEvent((0, bluebubbles_model_1.normalizeBlueBubblesEvent)(payload));
|
|
240
|
+
if (event.fromMe) {
|
|
241
|
+
(0, runtime_1.emitNervesEvent)({
|
|
242
|
+
component: "senses",
|
|
243
|
+
event: "senses.bluebubbles_from_me_ignored",
|
|
244
|
+
message: "ignored from-me bluebubbles event",
|
|
245
|
+
meta: {
|
|
246
|
+
messageGuid: event.messageGuid,
|
|
247
|
+
kind: event.kind,
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
return { handled: true, notifiedAgent: false, kind: event.kind, reason: "from_me" };
|
|
251
|
+
}
|
|
252
|
+
if (event.kind === "mutation") {
|
|
253
|
+
try {
|
|
254
|
+
resolvedDeps.recordMutation(resolvedDeps.getAgentName(), event);
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
(0, runtime_1.emitNervesEvent)({
|
|
258
|
+
level: "error",
|
|
259
|
+
component: "senses",
|
|
260
|
+
event: "senses.bluebubbles_mutation_log_error",
|
|
261
|
+
message: "failed recording bluebubbles mutation sidecar",
|
|
262
|
+
meta: {
|
|
263
|
+
messageGuid: event.messageGuid,
|
|
264
|
+
mutationType: event.mutationType,
|
|
265
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (event.kind === "mutation" && !event.shouldNotifyAgent) {
|
|
271
|
+
(0, runtime_1.emitNervesEvent)({
|
|
272
|
+
component: "senses",
|
|
273
|
+
event: "senses.bluebubbles_state_mutation_recorded",
|
|
274
|
+
message: "recorded non-notify bluebubbles mutation",
|
|
275
|
+
meta: {
|
|
276
|
+
messageGuid: event.messageGuid,
|
|
277
|
+
mutationType: event.mutationType,
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
return { handled: true, notifiedAgent: false, kind: event.kind, reason: "mutation_state_only" };
|
|
281
|
+
}
|
|
282
|
+
const store = resolvedDeps.createFriendStore();
|
|
283
|
+
const resolver = resolvedDeps.createFriendResolver(store, resolveFriendParams(event));
|
|
284
|
+
const context = await resolver.resolve();
|
|
285
|
+
const toolContext = {
|
|
286
|
+
signin: async () => undefined,
|
|
287
|
+
friendStore: store,
|
|
288
|
+
summarize: (0, core_1.createSummarize)(),
|
|
289
|
+
context,
|
|
290
|
+
codingFeedback: {
|
|
291
|
+
send: async (message) => {
|
|
292
|
+
await client.sendText({
|
|
293
|
+
chat: event.chat,
|
|
294
|
+
text: message,
|
|
295
|
+
replyToMessageGuid: event.kind === "message" ? event.messageGuid : undefined,
|
|
296
|
+
});
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
const friendId = context.friend.id;
|
|
301
|
+
const sessPath = resolvedDeps.sessionPath(friendId, "bluebubbles", event.chat.sessionKey);
|
|
302
|
+
const existing = resolvedDeps.loadSession(sessPath);
|
|
303
|
+
const messages = existing?.messages && existing.messages.length > 0
|
|
304
|
+
? existing.messages
|
|
305
|
+
: [{ role: "system", content: await resolvedDeps.buildSystem("bluebubbles", undefined, context) }];
|
|
306
|
+
messages.push({ role: "user", content: buildInboundContent(event) });
|
|
307
|
+
const callbacks = createBlueBubblesCallbacks(client, event.chat, event.kind === "message" ? event.messageGuid : undefined);
|
|
308
|
+
const controller = new AbortController();
|
|
309
|
+
const agentOptions = {
|
|
310
|
+
toolContext,
|
|
311
|
+
};
|
|
312
|
+
try {
|
|
313
|
+
const result = await resolvedDeps.runAgent(messages, callbacks, "bluebubbles", controller.signal, agentOptions);
|
|
314
|
+
await callbacks.flush();
|
|
315
|
+
resolvedDeps.postTurn(messages, sessPath, result.usage);
|
|
316
|
+
await resolvedDeps.accumulateFriendTokens(store, friendId, result.usage);
|
|
317
|
+
try {
|
|
318
|
+
await client.markChatRead(event.chat);
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
(0, runtime_1.emitNervesEvent)({
|
|
322
|
+
level: "warn",
|
|
323
|
+
component: "senses",
|
|
324
|
+
event: "senses.bluebubbles_mark_read_error",
|
|
325
|
+
message: "failed to mark bluebubbles chat as read",
|
|
326
|
+
meta: {
|
|
327
|
+
chatGuid: event.chat.chatGuid ?? null,
|
|
328
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
(0, runtime_1.emitNervesEvent)({
|
|
333
|
+
component: "senses",
|
|
334
|
+
event: "senses.bluebubbles_turn_end",
|
|
335
|
+
message: "bluebubbles event handled",
|
|
336
|
+
meta: {
|
|
337
|
+
messageGuid: event.messageGuid,
|
|
338
|
+
kind: event.kind,
|
|
339
|
+
sessionKey: event.chat.sessionKey,
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
return {
|
|
343
|
+
handled: true,
|
|
344
|
+
notifiedAgent: true,
|
|
345
|
+
kind: event.kind,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
finally {
|
|
349
|
+
await callbacks.finish();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function createBlueBubblesWebhookHandler(deps = {}) {
|
|
353
|
+
return async (req, res) => {
|
|
354
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
355
|
+
const channelConfig = (0, config_1.getBlueBubblesChannelConfig)();
|
|
356
|
+
const runtimeConfig = (0, config_1.getBlueBubblesConfig)();
|
|
357
|
+
if (url.pathname !== channelConfig.webhookPath) {
|
|
358
|
+
writeJson(res, 404, { error: "Not found" });
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (req.method !== "POST") {
|
|
362
|
+
writeJson(res, 405, { error: "Method not allowed" });
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (!isWebhookPasswordValid(url, runtimeConfig.password)) {
|
|
366
|
+
writeJson(res, 401, { error: "Unauthorized" });
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
let payload;
|
|
370
|
+
try {
|
|
371
|
+
const rawBody = await readRequestBody(req);
|
|
372
|
+
payload = JSON.parse(rawBody);
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
(0, runtime_1.emitNervesEvent)({
|
|
376
|
+
level: "warn",
|
|
377
|
+
component: "senses",
|
|
378
|
+
event: "senses.bluebubbles_webhook_bad_json",
|
|
379
|
+
message: "failed to parse bluebubbles webhook body",
|
|
380
|
+
meta: {
|
|
381
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
writeJson(res, 400, { error: "Invalid JSON body" });
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
try {
|
|
388
|
+
const result = await handleBlueBubblesEvent(payload, deps);
|
|
389
|
+
writeJson(res, 200, result);
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
(0, runtime_1.emitNervesEvent)({
|
|
393
|
+
level: "error",
|
|
394
|
+
component: "senses",
|
|
395
|
+
event: "senses.bluebubbles_webhook_error",
|
|
396
|
+
message: "bluebubbles webhook handling failed",
|
|
397
|
+
meta: {
|
|
398
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
writeJson(res, 500, {
|
|
402
|
+
error: error instanceof Error ? error.message : String(error),
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function startBlueBubblesApp(deps = {}) {
|
|
408
|
+
const resolvedDeps = { ...defaultDeps, ...deps };
|
|
409
|
+
resolvedDeps.createClient();
|
|
410
|
+
const channelConfig = (0, config_1.getBlueBubblesChannelConfig)();
|
|
411
|
+
const server = resolvedDeps.createServer(createBlueBubblesWebhookHandler(deps));
|
|
412
|
+
server.listen(channelConfig.port, () => {
|
|
413
|
+
(0, runtime_1.emitNervesEvent)({
|
|
414
|
+
component: "channels",
|
|
415
|
+
event: "channel.app_started",
|
|
416
|
+
message: "BlueBubbles sense started",
|
|
417
|
+
meta: { port: channelConfig.port, webhookPath: channelConfig.webhookPath },
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
return server;
|
|
421
|
+
}
|