@ouro.bot/cli 0.1.0-alpha.4 → 0.1.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/heart/config.js +34 -0
- package/dist/heart/core.js +9 -0
- package/dist/heart/daemon/daemon-cli.js +86 -1
- package/dist/heart/daemon/hatch-animation.js +28 -0
- package/dist/heart/daemon/hatch-flow.js +1 -0
- package/dist/heart/daemon/specialist-orchestrator.js +160 -0
- package/dist/heart/daemon/specialist-prompt.js +40 -0
- package/dist/heart/daemon/specialist-session.js +142 -0
- package/dist/heart/daemon/specialist-tools.js +128 -0
- package/dist/heart/identity.js +14 -0
- package/dist/mind/friends/channel.js +8 -0
- package/dist/mind/friends/types.js +1 -1
- package/dist/mind/prompt.js +3 -0
- package/dist/repertoire/tools.js +3 -3
- package/dist/senses/bluebubbles-client.js +279 -0
- package/dist/senses/bluebubbles-entry.js +11 -0
- package/dist/senses/bluebubbles-model.js +253 -0
- package/dist/senses/bluebubbles-mutation-log.js +76 -0
- package/dist/senses/bluebubbles.js +332 -0
- package/package.json +2 -1
|
@@ -0,0 +1,128 @@
|
|
|
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.getSpecialistTools = getSpecialistTools;
|
|
37
|
+
exports.execSpecialistTool = execSpecialistTool;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const tools_base_1 = require("../../repertoire/tools-base");
|
|
40
|
+
const hatch_flow_1 = require("./hatch-flow");
|
|
41
|
+
const hatch_animation_1 = require("./hatch-animation");
|
|
42
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
43
|
+
const hatchAgentTool = {
|
|
44
|
+
type: "function",
|
|
45
|
+
function: {
|
|
46
|
+
name: "hatch_agent",
|
|
47
|
+
description: "create a new agent bundle with the given name. call this when you have gathered enough information from the human to hatch their agent.",
|
|
48
|
+
parameters: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
name: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "the name for the new agent (PascalCase, e.g. 'Slugger')",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
required: ["name"],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
const readFileTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "read_file");
|
|
61
|
+
const listDirTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "list_directory");
|
|
62
|
+
/**
|
|
63
|
+
* Returns the specialist's tool schema array.
|
|
64
|
+
*/
|
|
65
|
+
function getSpecialistTools() {
|
|
66
|
+
return [hatchAgentTool, tools_base_1.finalAnswerTool, readFileTool.tool, listDirTool.tool];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Execute a specialist tool call.
|
|
70
|
+
* Returns the tool result string.
|
|
71
|
+
*/
|
|
72
|
+
async function execSpecialistTool(name, args, deps) {
|
|
73
|
+
(0, runtime_1.emitNervesEvent)({
|
|
74
|
+
component: "daemon",
|
|
75
|
+
event: "daemon.specialist_tool_exec",
|
|
76
|
+
message: "executing specialist tool",
|
|
77
|
+
meta: { tool: name },
|
|
78
|
+
});
|
|
79
|
+
if (name === "hatch_agent") {
|
|
80
|
+
const agentName = args.name;
|
|
81
|
+
if (!agentName) {
|
|
82
|
+
return "error: missing required 'name' parameter for hatch_agent";
|
|
83
|
+
}
|
|
84
|
+
const input = {
|
|
85
|
+
agentName,
|
|
86
|
+
humanName: deps.humanName,
|
|
87
|
+
provider: deps.provider,
|
|
88
|
+
credentials: deps.credentials,
|
|
89
|
+
};
|
|
90
|
+
// Pass identity dirs to prevent hatch flow from syncing to ~/AgentBundles/AdoptionSpecialist.ouro/
|
|
91
|
+
// or cwd/AdoptionSpecialist.ouro/. The specialist already picked its identity; the hatch flow
|
|
92
|
+
// just needs a valid source dir to pick from for the hatchling's LORE.md seed.
|
|
93
|
+
const identitiesDir = deps.specialistIdentitiesDir;
|
|
94
|
+
const result = await (0, hatch_flow_1.runHatchFlow)(input, {
|
|
95
|
+
bundlesRoot: deps.bundlesRoot,
|
|
96
|
+
secretsRoot: deps.secretsRoot,
|
|
97
|
+
...(identitiesDir ? { specialistIdentitySourceDir: identitiesDir, specialistIdentityTargetDir: identitiesDir } : {}),
|
|
98
|
+
});
|
|
99
|
+
await (0, hatch_animation_1.playHatchAnimation)(agentName, deps.animationWriter);
|
|
100
|
+
return [
|
|
101
|
+
`hatched ${agentName} successfully.`,
|
|
102
|
+
`bundle path: ${result.bundleRoot}`,
|
|
103
|
+
`identity seed: ${result.selectedIdentity}`,
|
|
104
|
+
`specialist secrets: ${result.specialistSecretsPath}`,
|
|
105
|
+
`hatchling secrets: ${result.hatchlingSecretsPath}`,
|
|
106
|
+
].join("\n");
|
|
107
|
+
}
|
|
108
|
+
if (name === "read_file") {
|
|
109
|
+
try {
|
|
110
|
+
return fs.readFileSync(args.path, "utf-8");
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (name === "list_directory") {
|
|
117
|
+
try {
|
|
118
|
+
return fs
|
|
119
|
+
.readdirSync(args.path, { withFileTypes: true })
|
|
120
|
+
.map((e) => `${e.isDirectory() ? "d" : "-"} ${e.name}`)
|
|
121
|
+
.join("\n");
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return `error: unknown tool '${name}'`;
|
|
128
|
+
}
|
package/dist/heart/identity.js
CHANGED
|
@@ -42,6 +42,7 @@ exports.getAgentRoot = getAgentRoot;
|
|
|
42
42
|
exports.getAgentSecretsPath = getAgentSecretsPath;
|
|
43
43
|
exports.loadAgentConfig = loadAgentConfig;
|
|
44
44
|
exports.setAgentName = setAgentName;
|
|
45
|
+
exports.setAgentConfigOverride = setAgentConfigOverride;
|
|
45
46
|
exports.resetIdentity = resetIdentity;
|
|
46
47
|
const fs = __importStar(require("fs"));
|
|
47
48
|
const os = __importStar(require("os"));
|
|
@@ -71,6 +72,7 @@ function buildDefaultAgentTemplate(_agentName) {
|
|
|
71
72
|
}
|
|
72
73
|
let _cachedAgentName = null;
|
|
73
74
|
let _cachedAgentConfig = null;
|
|
75
|
+
let _agentConfigOverride = null;
|
|
74
76
|
/**
|
|
75
77
|
* Parse `--agent <name>` from process.argv.
|
|
76
78
|
* Caches the result after first parse.
|
|
@@ -131,6 +133,9 @@ function getAgentSecretsPath(agentName = getAgentName()) {
|
|
|
131
133
|
* Throws descriptive error if file is missing or contains invalid JSON.
|
|
132
134
|
*/
|
|
133
135
|
function loadAgentConfig() {
|
|
136
|
+
if (_agentConfigOverride) {
|
|
137
|
+
return _agentConfigOverride;
|
|
138
|
+
}
|
|
134
139
|
if (_cachedAgentConfig) {
|
|
135
140
|
(0, runtime_1.emitNervesEvent)({
|
|
136
141
|
event: "identity.resolve",
|
|
@@ -271,6 +276,14 @@ function loadAgentConfig() {
|
|
|
271
276
|
function setAgentName(name) {
|
|
272
277
|
_cachedAgentName = name;
|
|
273
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Override the agent config returned by loadAgentConfig().
|
|
281
|
+
* When set to a non-null AgentConfig, loadAgentConfig() returns the override
|
|
282
|
+
* instead of reading from disk. When set to null, normal disk-based loading resumes.
|
|
283
|
+
*/
|
|
284
|
+
function setAgentConfigOverride(config) {
|
|
285
|
+
_agentConfigOverride = config;
|
|
286
|
+
}
|
|
274
287
|
/**
|
|
275
288
|
* Clear all cached identity state.
|
|
276
289
|
* Used in tests and when switching agent context.
|
|
@@ -278,4 +291,5 @@ function setAgentName(name) {
|
|
|
278
291
|
function resetIdentity() {
|
|
279
292
|
_cachedAgentName = null;
|
|
280
293
|
_cachedAgentConfig = null;
|
|
294
|
+
_agentConfigOverride = null;
|
|
281
295
|
}
|
|
@@ -21,6 +21,14 @@ const CHANNEL_CAPABILITIES = {
|
|
|
21
21
|
supportsRichCards: true,
|
|
22
22
|
maxMessageLength: Infinity,
|
|
23
23
|
},
|
|
24
|
+
bluebubbles: {
|
|
25
|
+
channel: "bluebubbles",
|
|
26
|
+
availableIntegrations: [],
|
|
27
|
+
supportsMarkdown: false,
|
|
28
|
+
supportsStreaming: false,
|
|
29
|
+
supportsRichCards: false,
|
|
30
|
+
maxMessageLength: Infinity,
|
|
31
|
+
},
|
|
24
32
|
};
|
|
25
33
|
const DEFAULT_CAPABILITIES = {
|
|
26
34
|
channel: "cli",
|
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
5
5
|
exports.isIdentityProvider = isIdentityProvider;
|
|
6
6
|
exports.isIntegration = isIntegration;
|
|
7
7
|
const runtime_1 = require("../../nerves/runtime");
|
|
8
|
-
const IDENTITY_PROVIDERS = new Set(["aad", "local", "teams-conversation"]);
|
|
8
|
+
const IDENTITY_PROVIDERS = new Set(["aad", "local", "teams-conversation", "imessage-handle"]);
|
|
9
9
|
function isIdentityProvider(value) {
|
|
10
10
|
(0, runtime_1.emitNervesEvent)({
|
|
11
11
|
component: "friends",
|
package/dist/mind/prompt.js
CHANGED
|
@@ -198,6 +198,9 @@ function runtimeInfoSection(channel) {
|
|
|
198
198
|
if (channel === "cli") {
|
|
199
199
|
lines.push("i introduce myself on boot with a fun random greeting.");
|
|
200
200
|
}
|
|
201
|
+
else if (channel === "bluebubbles") {
|
|
202
|
+
lines.push("i am responding in iMessage through BlueBubbles. i keep replies short and phone-native. i do not use markdown. i do not introduce myself on boot.");
|
|
203
|
+
}
|
|
201
204
|
else {
|
|
202
205
|
lines.push("i am responding in Microsoft Teams. i keep responses concise. i use markdown formatting. i do not introduce myself on boot.");
|
|
203
206
|
}
|
package/dist/repertoire/tools.js
CHANGED
|
@@ -20,7 +20,7 @@ Object.defineProperty(exports, "teamsTools", { enumerable: true, get: function (
|
|
|
20
20
|
const allDefinitions = [...tools_base_1.baseToolDefinitions, ...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions];
|
|
21
21
|
const REMOTE_BLOCKED_LOCAL_TOOLS = new Set(["shell", "read_file", "write_file", "git_commit", "gh_cli"]);
|
|
22
22
|
function baseToolsForCapabilities(capabilities) {
|
|
23
|
-
const isRemoteChannel = capabilities?.channel === "teams";
|
|
23
|
+
const isRemoteChannel = capabilities?.channel === "teams" || capabilities?.channel === "bluebubbles";
|
|
24
24
|
if (!isRemoteChannel)
|
|
25
25
|
return tools_base_1.tools;
|
|
26
26
|
return tools_base_1.tools.filter((tool) => !REMOTE_BLOCKED_LOCAL_TOOLS.has(tool.function.name));
|
|
@@ -87,7 +87,7 @@ async function execTool(name, args, ctx) {
|
|
|
87
87
|
});
|
|
88
88
|
return `unknown: ${name}`;
|
|
89
89
|
}
|
|
90
|
-
const isRemoteChannel = ctx?.context?.channel?.channel === "teams";
|
|
90
|
+
const isRemoteChannel = ctx?.context?.channel?.channel === "teams" || ctx?.context?.channel?.channel === "bluebubbles";
|
|
91
91
|
if (isRemoteChannel && REMOTE_BLOCKED_LOCAL_TOOLS.has(name)) {
|
|
92
92
|
const message = "I can't do that from here because I'm talking to multiple people in a shared remote channel, and local shell/file/git/gh operations could let conversations interfere with each other. Ask me for a remote-safe alternative (Graph/ADO/web), or run that operation from CLI.";
|
|
93
93
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -95,7 +95,7 @@ async function execTool(name, args, ctx) {
|
|
|
95
95
|
event: "tool.error",
|
|
96
96
|
component: "tools",
|
|
97
97
|
message: "blocked local tool in remote channel",
|
|
98
|
-
meta: { name, channel:
|
|
98
|
+
meta: { name, channel: ctx?.context?.channel?.channel },
|
|
99
99
|
});
|
|
100
100
|
return message;
|
|
101
101
|
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createBlueBubblesClient = createBlueBubblesClient;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const config_1 = require("../heart/config");
|
|
6
|
+
const runtime_1 = require("../nerves/runtime");
|
|
7
|
+
const bluebubbles_model_1 = require("./bluebubbles-model");
|
|
8
|
+
function buildBlueBubblesApiUrl(baseUrl, endpoint, password) {
|
|
9
|
+
const root = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
10
|
+
const url = new URL(endpoint.replace(/^\//, ""), root);
|
|
11
|
+
url.searchParams.set("password", password);
|
|
12
|
+
return url.toString();
|
|
13
|
+
}
|
|
14
|
+
function asRecord(value) {
|
|
15
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
16
|
+
? value
|
|
17
|
+
: null;
|
|
18
|
+
}
|
|
19
|
+
function extractMessageGuid(payload) {
|
|
20
|
+
if (!payload || typeof payload !== "object")
|
|
21
|
+
return undefined;
|
|
22
|
+
const record = payload;
|
|
23
|
+
const data = record.data && typeof record.data === "object" && !Array.isArray(record.data)
|
|
24
|
+
? record.data
|
|
25
|
+
: null;
|
|
26
|
+
const candidates = [
|
|
27
|
+
record.messageGuid,
|
|
28
|
+
record.messageId,
|
|
29
|
+
record.guid,
|
|
30
|
+
data?.messageGuid,
|
|
31
|
+
data?.messageId,
|
|
32
|
+
data?.guid,
|
|
33
|
+
];
|
|
34
|
+
for (const candidate of candidates) {
|
|
35
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
36
|
+
return candidate.trim();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
async function parseJsonBody(response) {
|
|
42
|
+
const raw = await response.text();
|
|
43
|
+
if (!raw.trim())
|
|
44
|
+
return null;
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(raw);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function buildRepairUrl(baseUrl, messageGuid, password) {
|
|
53
|
+
const url = buildBlueBubblesApiUrl(baseUrl, `/api/v1/message/${encodeURIComponent(messageGuid)}`, password);
|
|
54
|
+
const parsed = new URL(url);
|
|
55
|
+
parsed.searchParams.set("with", "attachments,payloadData,chats,messageSummaryInfo");
|
|
56
|
+
return parsed.toString();
|
|
57
|
+
}
|
|
58
|
+
function collectPreviewStrings(value, out, depth = 0) {
|
|
59
|
+
if (depth > 4 || out.length >= 4)
|
|
60
|
+
return;
|
|
61
|
+
if (typeof value === "string") {
|
|
62
|
+
const trimmed = value.trim();
|
|
63
|
+
if (trimmed)
|
|
64
|
+
out.push(trimmed);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (Array.isArray(value)) {
|
|
68
|
+
for (const entry of value)
|
|
69
|
+
collectPreviewStrings(entry, out, depth + 1);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const record = asRecord(value);
|
|
73
|
+
if (!record)
|
|
74
|
+
return;
|
|
75
|
+
const preferredKeys = ["title", "summary", "subtitle", "previewText", "siteName", "host", "url"];
|
|
76
|
+
for (const key of preferredKeys) {
|
|
77
|
+
if (out.length >= 4)
|
|
78
|
+
break;
|
|
79
|
+
collectPreviewStrings(record[key], out, depth + 1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function extractLinkPreviewText(data) {
|
|
83
|
+
const values = [];
|
|
84
|
+
collectPreviewStrings(data.payloadData, values);
|
|
85
|
+
collectPreviewStrings(data.messageSummaryInfo, values);
|
|
86
|
+
const unique = [...new Set(values.map((value) => value.trim()).filter(Boolean))];
|
|
87
|
+
if (unique.length === 0)
|
|
88
|
+
return undefined;
|
|
89
|
+
return unique.slice(0, 2).join(" — ");
|
|
90
|
+
}
|
|
91
|
+
function applyRepairNotice(event, notice) {
|
|
92
|
+
return {
|
|
93
|
+
...event,
|
|
94
|
+
requiresRepair: false,
|
|
95
|
+
repairNotice: notice,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function hydrateTextForAgent(event, rawData) {
|
|
99
|
+
if (event.kind !== "message") {
|
|
100
|
+
return { ...event, requiresRepair: false };
|
|
101
|
+
}
|
|
102
|
+
if (event.balloonBundleId !== "com.apple.messages.URLBalloonProvider") {
|
|
103
|
+
return { ...event, requiresRepair: false };
|
|
104
|
+
}
|
|
105
|
+
const previewText = extractLinkPreviewText(rawData);
|
|
106
|
+
if (!previewText) {
|
|
107
|
+
return { ...event, requiresRepair: false };
|
|
108
|
+
}
|
|
109
|
+
const base = event.text.trim();
|
|
110
|
+
const textForAgent = base
|
|
111
|
+
? `${base}\n[link preview: ${previewText}]`
|
|
112
|
+
: `[link preview: ${previewText}]`;
|
|
113
|
+
return {
|
|
114
|
+
...event,
|
|
115
|
+
textForAgent,
|
|
116
|
+
requiresRepair: false,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function extractRepairData(payload) {
|
|
120
|
+
const record = asRecord(payload);
|
|
121
|
+
return asRecord(record?.data) ?? record;
|
|
122
|
+
}
|
|
123
|
+
function createBlueBubblesClient(config = (0, config_1.getBlueBubblesConfig)(), channelConfig = (0, config_1.getBlueBubblesChannelConfig)()) {
|
|
124
|
+
return {
|
|
125
|
+
async sendText(params) {
|
|
126
|
+
const trimmedText = params.text.trim();
|
|
127
|
+
if (!trimmedText) {
|
|
128
|
+
throw new Error("BlueBubbles send requires non-empty text.");
|
|
129
|
+
}
|
|
130
|
+
if (!params.chat.chatGuid) {
|
|
131
|
+
throw new Error("BlueBubbles send currently requires chat.chatGuid from the inbound event.");
|
|
132
|
+
}
|
|
133
|
+
const url = buildBlueBubblesApiUrl(config.serverUrl, "/api/v1/message/text", config.password);
|
|
134
|
+
const body = {
|
|
135
|
+
chatGuid: params.chat.chatGuid,
|
|
136
|
+
tempGuid: (0, node_crypto_1.randomUUID)(),
|
|
137
|
+
message: trimmedText,
|
|
138
|
+
};
|
|
139
|
+
if (params.replyToMessageGuid?.trim()) {
|
|
140
|
+
body.method = "private-api";
|
|
141
|
+
body.selectedMessageGuid = params.replyToMessageGuid.trim();
|
|
142
|
+
body.partIndex = 0;
|
|
143
|
+
}
|
|
144
|
+
(0, runtime_1.emitNervesEvent)({
|
|
145
|
+
component: "senses",
|
|
146
|
+
event: "senses.bluebubbles_send_start",
|
|
147
|
+
message: "sending bluebubbles message",
|
|
148
|
+
meta: {
|
|
149
|
+
chatGuid: params.chat.chatGuid,
|
|
150
|
+
hasReplyTarget: Boolean(params.replyToMessageGuid?.trim()),
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
const response = await fetch(url, {
|
|
154
|
+
method: "POST",
|
|
155
|
+
headers: { "Content-Type": "application/json" },
|
|
156
|
+
body: JSON.stringify(body),
|
|
157
|
+
signal: AbortSignal.timeout(channelConfig.requestTimeoutMs),
|
|
158
|
+
});
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
const errorText = await response.text().catch(() => "");
|
|
161
|
+
(0, runtime_1.emitNervesEvent)({
|
|
162
|
+
level: "error",
|
|
163
|
+
component: "senses",
|
|
164
|
+
event: "senses.bluebubbles_send_error",
|
|
165
|
+
message: "bluebubbles send failed",
|
|
166
|
+
meta: {
|
|
167
|
+
status: response.status,
|
|
168
|
+
reason: errorText || "unknown",
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
throw new Error(`BlueBubbles send failed (${response.status}): ${errorText || "unknown"}`);
|
|
172
|
+
}
|
|
173
|
+
const payload = await parseJsonBody(response);
|
|
174
|
+
const messageGuid = extractMessageGuid(payload);
|
|
175
|
+
(0, runtime_1.emitNervesEvent)({
|
|
176
|
+
component: "senses",
|
|
177
|
+
event: "senses.bluebubbles_send_end",
|
|
178
|
+
message: "bluebubbles message sent",
|
|
179
|
+
meta: {
|
|
180
|
+
chatGuid: params.chat.chatGuid,
|
|
181
|
+
messageGuid: messageGuid ?? null,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
return { messageGuid };
|
|
185
|
+
},
|
|
186
|
+
async repairEvent(event) {
|
|
187
|
+
if (!event.requiresRepair) {
|
|
188
|
+
(0, runtime_1.emitNervesEvent)({
|
|
189
|
+
component: "senses",
|
|
190
|
+
event: "senses.bluebubbles_repair_skipped",
|
|
191
|
+
message: "bluebubbles event repair skipped",
|
|
192
|
+
meta: {
|
|
193
|
+
kind: event.kind,
|
|
194
|
+
messageGuid: event.messageGuid,
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
return event;
|
|
198
|
+
}
|
|
199
|
+
(0, runtime_1.emitNervesEvent)({
|
|
200
|
+
component: "senses",
|
|
201
|
+
event: "senses.bluebubbles_repair_start",
|
|
202
|
+
message: "repairing bluebubbles event by guid",
|
|
203
|
+
meta: {
|
|
204
|
+
kind: event.kind,
|
|
205
|
+
messageGuid: event.messageGuid,
|
|
206
|
+
eventType: event.eventType,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
const url = buildRepairUrl(config.serverUrl, event.messageGuid, config.password);
|
|
210
|
+
try {
|
|
211
|
+
const response = await fetch(url, {
|
|
212
|
+
method: "GET",
|
|
213
|
+
signal: AbortSignal.timeout(channelConfig.requestTimeoutMs),
|
|
214
|
+
});
|
|
215
|
+
if (!response.ok) {
|
|
216
|
+
const errorText = await response.text().catch(() => "");
|
|
217
|
+
const repaired = applyRepairNotice(event, `BlueBubbles repair failed: ${errorText || `HTTP ${response.status}`}`);
|
|
218
|
+
(0, runtime_1.emitNervesEvent)({
|
|
219
|
+
level: "warn",
|
|
220
|
+
component: "senses",
|
|
221
|
+
event: "senses.bluebubbles_repair_error",
|
|
222
|
+
message: "bluebubbles repair request failed",
|
|
223
|
+
meta: {
|
|
224
|
+
messageGuid: event.messageGuid,
|
|
225
|
+
status: response.status,
|
|
226
|
+
reason: errorText || "unknown",
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
return repaired;
|
|
230
|
+
}
|
|
231
|
+
const payload = await parseJsonBody(response);
|
|
232
|
+
const data = extractRepairData(payload);
|
|
233
|
+
if (!data || typeof data.guid !== "string") {
|
|
234
|
+
const repaired = applyRepairNotice(event, "BlueBubbles repair failed: invalid message payload");
|
|
235
|
+
(0, runtime_1.emitNervesEvent)({
|
|
236
|
+
level: "warn",
|
|
237
|
+
component: "senses",
|
|
238
|
+
event: "senses.bluebubbles_repair_error",
|
|
239
|
+
message: "bluebubbles repair returned unusable payload",
|
|
240
|
+
meta: {
|
|
241
|
+
messageGuid: event.messageGuid,
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
return repaired;
|
|
245
|
+
}
|
|
246
|
+
const normalized = (0, bluebubbles_model_1.normalizeBlueBubblesEvent)({
|
|
247
|
+
type: event.eventType,
|
|
248
|
+
data,
|
|
249
|
+
});
|
|
250
|
+
const hydrated = hydrateTextForAgent(normalized, data);
|
|
251
|
+
(0, runtime_1.emitNervesEvent)({
|
|
252
|
+
component: "senses",
|
|
253
|
+
event: "senses.bluebubbles_repair_end",
|
|
254
|
+
message: "bluebubbles event repaired",
|
|
255
|
+
meta: {
|
|
256
|
+
kind: hydrated.kind,
|
|
257
|
+
messageGuid: hydrated.messageGuid,
|
|
258
|
+
repairedFrom: event.kind,
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
return hydrated;
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
265
|
+
(0, runtime_1.emitNervesEvent)({
|
|
266
|
+
level: "warn",
|
|
267
|
+
component: "senses",
|
|
268
|
+
event: "senses.bluebubbles_repair_error",
|
|
269
|
+
message: "bluebubbles repair threw",
|
|
270
|
+
meta: {
|
|
271
|
+
messageGuid: event.messageGuid,
|
|
272
|
+
reason,
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
return applyRepairNotice(event, `BlueBubbles repair failed: ${reason}`);
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Thin entrypoint for `npm run bluebubbles` / `node dist/senses/bluebubbles-entry.js --agent <name>`.
|
|
3
|
+
// Separated from bluebubbles.ts so the BlueBubbles adapter stays testable.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
if (!process.argv.includes("--agent")) {
|
|
6
|
+
// eslint-disable-next-line no-console -- pre-boot guard: --agent check before imports
|
|
7
|
+
console.error("Missing required --agent <name> argument.\nUsage: node dist/senses/bluebubbles-entry.js --agent ouroboros");
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
const bluebubbles_1 = require("./bluebubbles");
|
|
11
|
+
(0, bluebubbles_1.startBlueBubblesApp)();
|