@hasna/bridge 0.1.2 → 0.2.0
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/README.md +79 -3
- package/dist/cli/index.js +786 -26
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +630 -11
- package/dist/lib/agents.d.ts +14 -1
- package/dist/lib/agents.d.ts.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/daemon.d.ts.map +1 -1
- package/dist/lib/doctor.d.ts.map +1 -1
- package/dist/lib/imessage.d.ts +36 -0
- package/dist/lib/imessage.d.ts.map +1 -0
- package/dist/lib/router.d.ts.map +1 -1
- package/dist/lib/sessions.d.ts +48 -0
- package/dist/lib/sessions.d.ts.map +1 -0
- package/dist/lib/state.d.ts +8 -1
- package/dist/lib/state.d.ts.map +1 -1
- package/dist/lib/telegram.d.ts +1 -0
- package/dist/lib/telegram.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +584 -7
- package/dist/types.d.ts +73 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/architecture.md +69 -15
- package/docs/session-bridge-plan.md +204 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -32,6 +32,11 @@ function mergeEnv(profile, agent) {
|
|
|
32
32
|
env["HOME"] = profile.home;
|
|
33
33
|
return Object.keys(env).length ? env : undefined;
|
|
34
34
|
}
|
|
35
|
+
function compatibilityDetail(kind) {
|
|
36
|
+
if (kind === "shell")
|
|
37
|
+
return "shell command session; local bridge state is durable";
|
|
38
|
+
return "compatibility mode: this adapter invokes the current CLI one message at a time until a stable create/send/resume API is wired";
|
|
39
|
+
}
|
|
35
40
|
function resolveAgent(config, agentId) {
|
|
36
41
|
const agent = config.agents[agentId];
|
|
37
42
|
if (!agent)
|
|
@@ -50,7 +55,7 @@ function buildAgentCommand(config, agentId, input) {
|
|
|
50
55
|
const kind = agent.kind;
|
|
51
56
|
const command = agent.command || profile?.command;
|
|
52
57
|
const args = agent.args || profile?.args;
|
|
53
|
-
const cwd = agent.cwd || profile?.cwd;
|
|
58
|
+
const cwd = input.session?.cwd || agent.cwd || profile?.cwd;
|
|
54
59
|
const env = mergeEnv(profile, agent);
|
|
55
60
|
if (command) {
|
|
56
61
|
return { command: [command, ...renderCustomArgs(args, prompt)], cwd, env };
|
|
@@ -72,6 +77,46 @@ function buildAgentCommand(config, agentId, input) {
|
|
|
72
77
|
}
|
|
73
78
|
return { command: ["sh", "-lc", prompt], cwd, env };
|
|
74
79
|
}
|
|
80
|
+
function createAgentSessionRef(config, agentId) {
|
|
81
|
+
const { agent } = resolveAgent(config, agentId);
|
|
82
|
+
const timestamp = new Date().toISOString();
|
|
83
|
+
return {
|
|
84
|
+
kind: agent.kind,
|
|
85
|
+
mode: "compatibility",
|
|
86
|
+
createdAt: timestamp,
|
|
87
|
+
updatedAt: timestamp,
|
|
88
|
+
detail: compatibilityDetail(agent.kind)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function resumeAgentSessionRef(session) {
|
|
92
|
+
return {
|
|
93
|
+
supported: session.agentSession?.mode === "durable",
|
|
94
|
+
ref: session.agentSession,
|
|
95
|
+
detail: session.agentSession?.mode === "durable" ? "durable agent session ref is available" : "compatibility sessions do not expose agent-side resume; bridge binding state is still durable"
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function cancelAgentSession(session) {
|
|
99
|
+
return {
|
|
100
|
+
supported: false,
|
|
101
|
+
ref: session.agentSession,
|
|
102
|
+
detail: `cancel is not implemented for ${session.agentSession?.kind || "unknown"} ${session.agentSession?.mode || "compatibility"} sessions`
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function closeAgentSession(session) {
|
|
106
|
+
return {
|
|
107
|
+
supported: session.agentSession?.mode === "durable",
|
|
108
|
+
ref: session.agentSession,
|
|
109
|
+
detail: session.agentSession?.mode === "durable" ? "durable close is adapter-owned" : "compatibility close only updates bridge session state"
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async function sendAgentSessionMessage(config, session, message, options = {}) {
|
|
113
|
+
const run = options.run || runAgent;
|
|
114
|
+
return run(config, session.agentId, {
|
|
115
|
+
message,
|
|
116
|
+
route: { id: `session:${session.id}`, fromChannel: message.channelId, toAgent: session.agentId },
|
|
117
|
+
session
|
|
118
|
+
});
|
|
119
|
+
}
|
|
75
120
|
async function runAgent(config, agentId, input) {
|
|
76
121
|
const { agent } = resolveAgent(config, agentId);
|
|
77
122
|
const built = buildAgentCommand(config, agentId, input);
|
|
@@ -4130,7 +4175,14 @@ var channelSchema = exports_external.discriminatedUnion("kind", [
|
|
|
4130
4175
|
kind: exports_external.literal("imessage"),
|
|
4131
4176
|
label: exports_external.string().optional(),
|
|
4132
4177
|
enabled: exports_external.boolean().optional(),
|
|
4133
|
-
account: exports_external.string().optional()
|
|
4178
|
+
account: exports_external.string().optional(),
|
|
4179
|
+
serviceName: exports_external.string().optional(),
|
|
4180
|
+
defaultHandle: exports_external.string().optional(),
|
|
4181
|
+
allowedHandles: exports_external.array(exports_external.string()).optional(),
|
|
4182
|
+
allowAllHandles: exports_external.boolean().optional(),
|
|
4183
|
+
receiveMode: exports_external.enum(["disabled", "chat-db"]).optional(),
|
|
4184
|
+
chatDbPath: exports_external.string().optional(),
|
|
4185
|
+
pollLimit: exports_external.number().int().positive().max(500).optional()
|
|
4134
4186
|
})
|
|
4135
4187
|
]);
|
|
4136
4188
|
var envSchema = exports_external.record(exports_external.string(), exports_external.string());
|
|
@@ -4268,19 +4320,35 @@ import { dirname as dirname3, join as join3, resolve } from "path";
|
|
|
4268
4320
|
// src/lib/state.ts
|
|
4269
4321
|
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
4270
4322
|
import { dirname as dirname2, join as join2 } from "path";
|
|
4323
|
+
var STATE_SCHEMA_VERSION = 2;
|
|
4271
4324
|
function defaultStatePath() {
|
|
4272
4325
|
return process.env["BRIDGE_STATE"] || join2(bridgeHome(), "state.json");
|
|
4273
4326
|
}
|
|
4274
4327
|
function emptyState() {
|
|
4275
|
-
return {
|
|
4328
|
+
return {
|
|
4329
|
+
schemaVersion: STATE_SCHEMA_VERSION,
|
|
4330
|
+
telegramOffsets: {},
|
|
4331
|
+
sessions: {},
|
|
4332
|
+
bindings: {},
|
|
4333
|
+
messageLedger: {},
|
|
4334
|
+
cursors: {}
|
|
4335
|
+
};
|
|
4336
|
+
}
|
|
4337
|
+
function normalizeState(value) {
|
|
4338
|
+
return {
|
|
4339
|
+
schemaVersion: STATE_SCHEMA_VERSION,
|
|
4340
|
+
telegramOffsets: value.telegramOffsets && typeof value.telegramOffsets === "object" ? value.telegramOffsets : {},
|
|
4341
|
+
sessions: value.sessions && typeof value.sessions === "object" ? value.sessions : {},
|
|
4342
|
+
bindings: value.bindings && typeof value.bindings === "object" ? value.bindings : {},
|
|
4343
|
+
messageLedger: value.messageLedger && typeof value.messageLedger === "object" ? value.messageLedger : {},
|
|
4344
|
+
cursors: value.cursors && typeof value.cursors === "object" ? value.cursors : {}
|
|
4345
|
+
};
|
|
4276
4346
|
}
|
|
4277
4347
|
async function loadState(statePath = defaultStatePath()) {
|
|
4278
4348
|
try {
|
|
4279
4349
|
const raw = await readFile2(statePath, "utf-8");
|
|
4280
4350
|
const parsed = JSON.parse(raw);
|
|
4281
|
-
return
|
|
4282
|
-
telegramOffsets: parsed.telegramOffsets && typeof parsed.telegramOffsets === "object" ? parsed.telegramOffsets : {}
|
|
4283
|
-
};
|
|
4351
|
+
return normalizeState(parsed);
|
|
4284
4352
|
} catch (err) {
|
|
4285
4353
|
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
|
|
4286
4354
|
return emptyState();
|
|
@@ -4289,8 +4357,9 @@ async function loadState(statePath = defaultStatePath()) {
|
|
|
4289
4357
|
}
|
|
4290
4358
|
}
|
|
4291
4359
|
async function saveState(state, statePath = defaultStatePath()) {
|
|
4360
|
+
const normalized = normalizeState(state);
|
|
4292
4361
|
await mkdir2(dirname2(statePath), { recursive: true, mode: 448 });
|
|
4293
|
-
await writeFile2(statePath, `${JSON.stringify(
|
|
4362
|
+
await writeFile2(statePath, `${JSON.stringify(normalized, null, 2)}
|
|
4294
4363
|
`, { encoding: "utf-8", mode: 384 });
|
|
4295
4364
|
await chmod2(statePath, 384);
|
|
4296
4365
|
}
|
|
@@ -4377,6 +4446,7 @@ function telegramUpdateToMessage(channelId, update) {
|
|
|
4377
4446
|
channelId,
|
|
4378
4447
|
text,
|
|
4379
4448
|
chatId: String(chatId),
|
|
4449
|
+
threadId: update.message?.message_thread_id !== undefined ? String(update.message.message_thread_id) : undefined,
|
|
4380
4450
|
from: update.message?.from?.username || (update.message?.from?.id !== undefined ? String(update.message.from.id) : undefined),
|
|
4381
4451
|
receivedAt: update.message?.date ? new Date(update.message.date * 1000).toISOString() : new Date().toISOString(),
|
|
4382
4452
|
raw: update
|
|
@@ -4555,14 +4625,17 @@ function startCommand(options) {
|
|
|
4555
4625
|
function telegramChannels(config) {
|
|
4556
4626
|
return Object.values(config.channels).filter((channel) => channel.kind === "telegram" && channel.enabled !== false);
|
|
4557
4627
|
}
|
|
4628
|
+
function imessagePollChannels(config) {
|
|
4629
|
+
return Object.values(config.channels).filter((channel) => channel.kind === "imessage" && channel.enabled !== false && channel.receiveMode === "chat-db");
|
|
4630
|
+
}
|
|
4558
4631
|
function requiredTelegramEnvVars(config) {
|
|
4559
4632
|
return [...new Set(telegramChannels(config).map((channel) => channel.botTokenEnv || "TELEGRAM_BOT_TOKEN"))];
|
|
4560
4633
|
}
|
|
4561
4634
|
async function validateStartConfig(configPath) {
|
|
4562
4635
|
const config = await loadConfig(configPath);
|
|
4563
|
-
const channels = telegramChannels(config);
|
|
4636
|
+
const channels = [...telegramChannels(config), ...imessagePollChannels(config)];
|
|
4564
4637
|
if (!channels.length)
|
|
4565
|
-
throw new Error("No enabled
|
|
4638
|
+
throw new Error("No enabled pollable channels configured; add Telegram or iMessage receive before starting the daemon");
|
|
4566
4639
|
for (const envName of requiredTelegramEnvVars(config)) {
|
|
4567
4640
|
if (!process.env[envName])
|
|
4568
4641
|
throw new Error(`Missing Telegram bot token env var for daemon start: ${envName}`);
|
|
@@ -4964,6 +5037,167 @@ async function daemonLogs(options = {}) {
|
|
|
4964
5037
|
}
|
|
4965
5038
|
// src/lib/doctor.ts
|
|
4966
5039
|
import { stat as stat2 } from "fs/promises";
|
|
5040
|
+
|
|
5041
|
+
// src/lib/imessage.ts
|
|
5042
|
+
import { access } from "fs/promises";
|
|
5043
|
+
import { join as join4 } from "path";
|
|
5044
|
+
import { Database } from "bun:sqlite";
|
|
5045
|
+
function defaultMessagesDbPath() {
|
|
5046
|
+
return join4(homeDir(), "Library", "Messages", "chat.db");
|
|
5047
|
+
}
|
|
5048
|
+
function imessageHandleAllowed(channel, handle) {
|
|
5049
|
+
if (channel.allowAllHandles)
|
|
5050
|
+
return true;
|
|
5051
|
+
if (!channel.allowedHandles?.length)
|
|
5052
|
+
return false;
|
|
5053
|
+
return Boolean(handle && channel.allowedHandles.includes(handle));
|
|
5054
|
+
}
|
|
5055
|
+
function appleScriptString(value) {
|
|
5056
|
+
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', "\\\"")}"`;
|
|
5057
|
+
}
|
|
5058
|
+
function renderSendIMessageScript(channel, handle, text) {
|
|
5059
|
+
const service = channel.serviceName || "iMessage";
|
|
5060
|
+
const serviceSelector = channel.account ? `1st service whose name = ${appleScriptString(service)} and account = ${appleScriptString(channel.account)}` : `1st service whose name = ${appleScriptString(service)}`;
|
|
5061
|
+
const targetLines = handle.startsWith("chat:") ? [
|
|
5062
|
+
`set targetChat to 1st chat whose id = ${appleScriptString(handle.slice("chat:".length))}`,
|
|
5063
|
+
`send ${appleScriptString(text)} to targetChat`
|
|
5064
|
+
] : [
|
|
5065
|
+
`set targetBuddy to buddy ${appleScriptString(handle)} of targetService`,
|
|
5066
|
+
`send ${appleScriptString(text)} to targetBuddy`
|
|
5067
|
+
];
|
|
5068
|
+
return [
|
|
5069
|
+
'tell application "Messages"',
|
|
5070
|
+
`set targetService to ${serviceSelector}`,
|
|
5071
|
+
...targetLines,
|
|
5072
|
+
"end tell"
|
|
5073
|
+
].join(`
|
|
5074
|
+
`);
|
|
5075
|
+
}
|
|
5076
|
+
async function defaultRun(command) {
|
|
5077
|
+
const proc = Bun.spawn(command, { stdout: "pipe", stderr: "pipe" });
|
|
5078
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
5079
|
+
proc.exited,
|
|
5080
|
+
new Response(proc.stdout).text(),
|
|
5081
|
+
new Response(proc.stderr).text()
|
|
5082
|
+
]);
|
|
5083
|
+
return { exitCode, stdout, stderr };
|
|
5084
|
+
}
|
|
5085
|
+
async function sendIMessage(channel, handle, text, options = {}) {
|
|
5086
|
+
if (!(options.allowChatTarget && handle.startsWith("chat:")) && !imessageHandleAllowed(channel, handle)) {
|
|
5087
|
+
throw new Error(`iMessage handle is not allowed for channel ${channel.id}: ${handle}`);
|
|
5088
|
+
}
|
|
5089
|
+
const script = renderSendIMessageScript(channel, handle, text);
|
|
5090
|
+
const result = await (options.run || defaultRun)(["osascript", "-e", script]);
|
|
5091
|
+
if (result.exitCode !== 0) {
|
|
5092
|
+
throw new Error(`iMessage send failed: ${result.stderr || result.stdout || `exit ${result.exitCode}`}`);
|
|
5093
|
+
}
|
|
5094
|
+
return { ok: true };
|
|
5095
|
+
}
|
|
5096
|
+
function imessageDateToIso(value) {
|
|
5097
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
|
|
5098
|
+
return new Date().toISOString();
|
|
5099
|
+
const appleEpochMs = Date.UTC(2001, 0, 1);
|
|
5100
|
+
if (value > 1000000000000000)
|
|
5101
|
+
return new Date(appleEpochMs + Math.floor(value / 1e6)).toISOString();
|
|
5102
|
+
if (value > 1e9)
|
|
5103
|
+
return new Date(appleEpochMs + value * 1000).toISOString();
|
|
5104
|
+
return new Date(appleEpochMs + value).toISOString();
|
|
5105
|
+
}
|
|
5106
|
+
function getIMessageDbPath(channel) {
|
|
5107
|
+
return channel.chatDbPath || defaultMessagesDbPath();
|
|
5108
|
+
}
|
|
5109
|
+
function getIMessageMessages(channel, options = {}) {
|
|
5110
|
+
if ((channel.receiveMode || "disabled") !== "chat-db")
|
|
5111
|
+
return [];
|
|
5112
|
+
const db = new Database(getIMessageDbPath(channel), { readonly: true });
|
|
5113
|
+
try {
|
|
5114
|
+
const limit = options.limit || channel.pollLimit || 50;
|
|
5115
|
+
const scanLimit = Math.max(limit * 10, limit);
|
|
5116
|
+
const rows = db.query(`
|
|
5117
|
+
select
|
|
5118
|
+
message.ROWID as rowId,
|
|
5119
|
+
handle.id as handle,
|
|
5120
|
+
chat.guid as chatGuid,
|
|
5121
|
+
chat.display_name as displayName,
|
|
5122
|
+
message.text as text,
|
|
5123
|
+
message.date as date
|
|
5124
|
+
from message
|
|
5125
|
+
left join handle on message.handle_id = handle.ROWID
|
|
5126
|
+
left join chat_message_join on chat_message_join.message_id = message.ROWID
|
|
5127
|
+
left join chat on chat.ROWID = chat_message_join.chat_id
|
|
5128
|
+
where message.ROWID > ?
|
|
5129
|
+
and message.is_from_me = 0
|
|
5130
|
+
and message.text is not null
|
|
5131
|
+
order by message.ROWID asc
|
|
5132
|
+
limit ?
|
|
5133
|
+
`).all(options.afterRowId || 0, scanLimit);
|
|
5134
|
+
return rows.filter((row) => row.handle && row.text && imessageHandleAllowed(channel, row.handle)).slice(0, limit).map((row) => {
|
|
5135
|
+
const item = { rowId: row.rowId, handle: row.handle, text: row.text, date: row.date };
|
|
5136
|
+
if (row.chatGuid)
|
|
5137
|
+
item.chatGuid = row.chatGuid;
|
|
5138
|
+
if (row.displayName)
|
|
5139
|
+
item.displayName = row.displayName;
|
|
5140
|
+
return item;
|
|
5141
|
+
});
|
|
5142
|
+
} finally {
|
|
5143
|
+
db.close();
|
|
5144
|
+
}
|
|
5145
|
+
}
|
|
5146
|
+
function imessageRowToMessage(channelId, row) {
|
|
5147
|
+
return {
|
|
5148
|
+
id: `imessage:${row.rowId}`,
|
|
5149
|
+
channelId,
|
|
5150
|
+
chatId: row.chatGuid ? `chat:${row.chatGuid}` : row.handle,
|
|
5151
|
+
responseTargetId: row.chatGuid ? `chat:${row.chatGuid}` : row.handle,
|
|
5152
|
+
from: row.handle,
|
|
5153
|
+
text: row.text,
|
|
5154
|
+
receivedAt: imessageDateToIso(row.date),
|
|
5155
|
+
raw: row
|
|
5156
|
+
};
|
|
5157
|
+
}
|
|
5158
|
+
async function commandExists(command) {
|
|
5159
|
+
const proc = Bun.spawn(["sh", "-lc", `command -v ${command} >/dev/null 2>&1`], {
|
|
5160
|
+
stdout: "ignore",
|
|
5161
|
+
stderr: "ignore"
|
|
5162
|
+
});
|
|
5163
|
+
return await proc.exited === 0;
|
|
5164
|
+
}
|
|
5165
|
+
async function diagnoseIMessage(channel) {
|
|
5166
|
+
const checks = [];
|
|
5167
|
+
checks.push({
|
|
5168
|
+
name: `imessage-platform:${channel.id}`,
|
|
5169
|
+
ok: process.platform === "darwin",
|
|
5170
|
+
detail: process.platform === "darwin" ? "macOS" : `unsupported platform: ${process.platform}`
|
|
5171
|
+
});
|
|
5172
|
+
checks.push({
|
|
5173
|
+
name: `imessage-osascript:${channel.id}`,
|
|
5174
|
+
ok: await commandExists("osascript"),
|
|
5175
|
+
detail: "required for Messages send automation"
|
|
5176
|
+
});
|
|
5177
|
+
checks.push({
|
|
5178
|
+
name: `imessage-allowlist:${channel.id}`,
|
|
5179
|
+
ok: Boolean(channel.allowAllHandles || channel.allowedHandles?.length),
|
|
5180
|
+
detail: channel.allowAllHandles ? "allowAllHandles=true" : `${channel.allowedHandles?.length || 0} handle(s)`
|
|
5181
|
+
});
|
|
5182
|
+
if ((channel.receiveMode || "disabled") === "chat-db") {
|
|
5183
|
+
const path = getIMessageDbPath(channel);
|
|
5184
|
+
try {
|
|
5185
|
+
await access(path);
|
|
5186
|
+
checks.push({ name: `imessage-chat-db:${channel.id}`, ok: true, detail: path });
|
|
5187
|
+
} catch (err) {
|
|
5188
|
+
checks.push({
|
|
5189
|
+
name: `imessage-chat-db:${channel.id}`,
|
|
5190
|
+
ok: false,
|
|
5191
|
+
detail: `${path}: ${err instanceof Error ? err.message : String(err)}. Grant Full Disk Access to the terminal/daemon host or disable receive mode.`
|
|
5192
|
+
});
|
|
5193
|
+
}
|
|
5194
|
+
} else {
|
|
5195
|
+
checks.push({ name: `imessage-receive:${channel.id}`, ok: true, detail: "receiveMode=disabled" });
|
|
5196
|
+
}
|
|
5197
|
+
return checks;
|
|
5198
|
+
}
|
|
5199
|
+
|
|
5200
|
+
// src/lib/doctor.ts
|
|
4967
5201
|
function isNotFound2(err) {
|
|
4968
5202
|
return Boolean(err && typeof err === "object" && "code" in err && err.code === "ENOENT");
|
|
4969
5203
|
}
|
|
@@ -4991,7 +5225,7 @@ async function privateDirCheck(name, path) {
|
|
|
4991
5225
|
return { name, ok: false, detail: `${path}: ${err instanceof Error ? err.message : String(err)}` };
|
|
4992
5226
|
}
|
|
4993
5227
|
}
|
|
4994
|
-
async function
|
|
5228
|
+
async function commandExists2(command) {
|
|
4995
5229
|
const proc = Bun.spawn(["sh", "-lc", `command -v ${command} >/dev/null 2>&1`], {
|
|
4996
5230
|
stdout: "ignore",
|
|
4997
5231
|
stderr: "ignore"
|
|
@@ -5029,7 +5263,7 @@ async function doctor(configPath = defaultConfigPath(), statePath = defaultState
|
|
|
5029
5263
|
for (const command of ["bridge", "codewith", "claude", "aicopilot"]) {
|
|
5030
5264
|
checks.push({
|
|
5031
5265
|
name: `command:${command}`,
|
|
5032
|
-
ok: command === "bridge" ? true : await
|
|
5266
|
+
ok: command === "bridge" ? true : await commandExists2(command),
|
|
5033
5267
|
detail: command === "bridge" ? "current package" : undefined
|
|
5034
5268
|
});
|
|
5035
5269
|
}
|
|
@@ -5054,6 +5288,10 @@ async function doctor(configPath = defaultConfigPath(), statePath = defaultState
|
|
|
5054
5288
|
detail: `${route.fromChannel} -> ${route.toAgent}`
|
|
5055
5289
|
});
|
|
5056
5290
|
}
|
|
5291
|
+
const imessageChannels = Object.values(config.channels).filter((channel) => channel.kind === "imessage");
|
|
5292
|
+
for (const channel of imessageChannels) {
|
|
5293
|
+
checks.push(...await diagnoseIMessage(channel));
|
|
5294
|
+
}
|
|
5057
5295
|
return { ok: checks.every((check) => check.ok), configPath, checks };
|
|
5058
5296
|
}
|
|
5059
5297
|
// src/lib/router.ts
|
|
@@ -5064,6 +5302,9 @@ function matchingRoutes(config, message) {
|
|
|
5064
5302
|
if (channel?.kind === "telegram" && !telegramChatAllowed(channel, message.chatId)) {
|
|
5065
5303
|
return [];
|
|
5066
5304
|
}
|
|
5305
|
+
if (channel?.kind === "imessage" && !imessageHandleAllowed(channel, message.from || (message.chatId?.startsWith("chat:") ? undefined : message.chatId))) {
|
|
5306
|
+
return [];
|
|
5307
|
+
}
|
|
5067
5308
|
return config.routes.filter((route) => {
|
|
5068
5309
|
if (route.enabled === false)
|
|
5069
5310
|
return false;
|
|
@@ -5103,16 +5344,367 @@ async function routeMessage(config, message, options = {}) {
|
|
|
5103
5344
|
if (options.writeConsole !== false)
|
|
5104
5345
|
(options.writeConsole || console.log)(responseText);
|
|
5105
5346
|
deliveredResponse = true;
|
|
5347
|
+
} else if (responseText && channel?.kind === "imessage") {
|
|
5348
|
+
const handle = message.responseTargetId || message.chatId || message.from;
|
|
5349
|
+
const allowedIdentity = message.from || (handle?.startsWith("chat:") ? undefined : handle);
|
|
5350
|
+
if (handle && imessageHandleAllowed(channel, allowedIdentity)) {
|
|
5351
|
+
await sendIMessage(channel, handle, responseText, { allowChatTarget: handle.startsWith("chat:") });
|
|
5352
|
+
deliveredResponse = true;
|
|
5353
|
+
}
|
|
5106
5354
|
}
|
|
5107
5355
|
results.push({ route, agent, deliveredResponse });
|
|
5108
5356
|
}
|
|
5109
5357
|
return results;
|
|
5110
5358
|
}
|
|
5359
|
+
// src/lib/sessions.ts
|
|
5360
|
+
import { randomUUID } from "crypto";
|
|
5361
|
+
function nowIso() {
|
|
5362
|
+
return new Date().toISOString();
|
|
5363
|
+
}
|
|
5364
|
+
function newSessionId() {
|
|
5365
|
+
return `ses_${randomUUID()}`;
|
|
5366
|
+
}
|
|
5367
|
+
function normalizeConversationId(channel, conversation) {
|
|
5368
|
+
if (conversation.includes(":") && conversation.startsWith(`${channel.kind}:`))
|
|
5369
|
+
return conversation;
|
|
5370
|
+
if (channel.kind === "telegram")
|
|
5371
|
+
return `telegram:${channel.id}:${conversation}`;
|
|
5372
|
+
if (channel.kind === "imessage")
|
|
5373
|
+
return `imessage:${channel.id}:${conversation}`;
|
|
5374
|
+
return `${channel.kind}:${channel.id}:${conversation || "default"}`;
|
|
5375
|
+
}
|
|
5376
|
+
function messageConversationId(config, message) {
|
|
5377
|
+
const channel = config.channels[message.channelId];
|
|
5378
|
+
if (!channel)
|
|
5379
|
+
return;
|
|
5380
|
+
if (channel.kind === "telegram") {
|
|
5381
|
+
if (!message.chatId)
|
|
5382
|
+
return;
|
|
5383
|
+
return normalizeConversationId(channel, message.threadId ? `${message.chatId}:${message.threadId}` : message.chatId);
|
|
5384
|
+
}
|
|
5385
|
+
if (channel.kind === "imessage") {
|
|
5386
|
+
const conversation = message.chatId || message.from;
|
|
5387
|
+
return conversation ? normalizeConversationId(channel, conversation) : undefined;
|
|
5388
|
+
}
|
|
5389
|
+
return normalizeConversationId(channel, message.chatId || message.from || "default");
|
|
5390
|
+
}
|
|
5391
|
+
function bindingId(channelId, conversationId) {
|
|
5392
|
+
return `${channelId}::${conversationId}`;
|
|
5393
|
+
}
|
|
5394
|
+
function ledgerId(message) {
|
|
5395
|
+
return `${message.channelId}::${message.id}`;
|
|
5396
|
+
}
|
|
5397
|
+
function createBridgeSession(config, state, input) {
|
|
5398
|
+
const { agent, profile } = resolveAgent(config, input.agentId);
|
|
5399
|
+
const timestamp = nowIso();
|
|
5400
|
+
const session = {
|
|
5401
|
+
id: input.id || newSessionId(),
|
|
5402
|
+
agentId: agent.id,
|
|
5403
|
+
profileId: agent.profileId,
|
|
5404
|
+
cwd: input.cwd || agent.cwd || profile?.cwd,
|
|
5405
|
+
title: input.title,
|
|
5406
|
+
status: "active",
|
|
5407
|
+
createdAt: timestamp,
|
|
5408
|
+
updatedAt: timestamp,
|
|
5409
|
+
agentSession: createAgentSessionRef(config, agent.id)
|
|
5410
|
+
};
|
|
5411
|
+
state.sessions[session.id] = session;
|
|
5412
|
+
return session;
|
|
5413
|
+
}
|
|
5414
|
+
function getBridgeSession(state, sessionId) {
|
|
5415
|
+
const session = state.sessions[sessionId];
|
|
5416
|
+
if (!session)
|
|
5417
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
5418
|
+
return session;
|
|
5419
|
+
}
|
|
5420
|
+
function listBridgeSessions(state) {
|
|
5421
|
+
return Object.values(state.sessions).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
5422
|
+
}
|
|
5423
|
+
function updateBridgeSessionStatus(state, sessionId, status) {
|
|
5424
|
+
const session = getBridgeSession(state, sessionId);
|
|
5425
|
+
if (status === "closed")
|
|
5426
|
+
closeAgentSession(session);
|
|
5427
|
+
session.status = status;
|
|
5428
|
+
session.updatedAt = nowIso();
|
|
5429
|
+
return session;
|
|
5430
|
+
}
|
|
5431
|
+
function attachBridgeSession(config, state, input) {
|
|
5432
|
+
const channel = config.channels[input.channelId];
|
|
5433
|
+
if (!channel)
|
|
5434
|
+
throw new Error(`Channel not found: ${input.channelId}`);
|
|
5435
|
+
const session = getBridgeSession(state, input.sessionId);
|
|
5436
|
+
if (session.status === "closed")
|
|
5437
|
+
throw new Error(`Cannot attach closed session: ${session.id}`);
|
|
5438
|
+
const conversationId = normalizeConversationId(channel, input.conversation);
|
|
5439
|
+
const id = bindingId(channel.id, conversationId);
|
|
5440
|
+
const existing = state.bindings[id];
|
|
5441
|
+
const timestamp = nowIso();
|
|
5442
|
+
const binding = {
|
|
5443
|
+
id,
|
|
5444
|
+
channelId: channel.id,
|
|
5445
|
+
conversationId,
|
|
5446
|
+
activeSessionId: session.id,
|
|
5447
|
+
defaultSessionId: input.makeDefault ? session.id : existing?.defaultSessionId,
|
|
5448
|
+
createdAt: existing?.createdAt || timestamp,
|
|
5449
|
+
updatedAt: timestamp,
|
|
5450
|
+
authorization: input.authorization || existing?.authorization || (channel.kind === "telegram" ? { chatId: input.conversation.split(":")[0] } : undefined)
|
|
5451
|
+
};
|
|
5452
|
+
state.bindings[id] = binding;
|
|
5453
|
+
return binding;
|
|
5454
|
+
}
|
|
5455
|
+
function detachBridgeBinding(config, state, channelId, conversation) {
|
|
5456
|
+
const channel = config.channels[channelId];
|
|
5457
|
+
if (!channel)
|
|
5458
|
+
throw new Error(`Channel not found: ${channelId}`);
|
|
5459
|
+
const conversationId = normalizeConversationId(channel, conversation);
|
|
5460
|
+
const id = bindingId(channel.id, conversationId);
|
|
5461
|
+
const existing = state.bindings[id];
|
|
5462
|
+
delete state.bindings[id];
|
|
5463
|
+
return existing;
|
|
5464
|
+
}
|
|
5465
|
+
function findBridgeBinding(config, state, message) {
|
|
5466
|
+
const conversationId = messageConversationId(config, message);
|
|
5467
|
+
if (!conversationId)
|
|
5468
|
+
return;
|
|
5469
|
+
return state.bindings[bindingId(message.channelId, conversationId)];
|
|
5470
|
+
}
|
|
5471
|
+
function noSessionText(channelId, conversationId) {
|
|
5472
|
+
return [
|
|
5473
|
+
"No bridge session is attached to this conversation.",
|
|
5474
|
+
"Create and attach one locally:",
|
|
5475
|
+
"bridge sessions create --agent <agent-id>",
|
|
5476
|
+
`bridge sessions attach <session-id> --channel ${channelId}${conversationId ? ` --conversation ${conversationId}` : " --conversation <conversation-id>"}`
|
|
5477
|
+
].join(`
|
|
5478
|
+
`);
|
|
5479
|
+
}
|
|
5480
|
+
async function deliverResponse(config, message, text, options) {
|
|
5481
|
+
const channel = config.channels[message.channelId];
|
|
5482
|
+
if (!text || !channel || channel.enabled === false)
|
|
5483
|
+
return false;
|
|
5484
|
+
if (channel.kind === "telegram" && message.chatId) {
|
|
5485
|
+
if (!telegramChatAllowed(channel, message.chatId))
|
|
5486
|
+
return false;
|
|
5487
|
+
await (options.sendTelegram || sendTelegramMessage)(telegramToken(channel), message.chatId, text);
|
|
5488
|
+
return true;
|
|
5489
|
+
}
|
|
5490
|
+
if (channel.kind === "console") {
|
|
5491
|
+
if (options.writeConsole !== false)
|
|
5492
|
+
(options.writeConsole || console.log)(text);
|
|
5493
|
+
return true;
|
|
5494
|
+
}
|
|
5495
|
+
if (channel.kind === "imessage" && message.chatId) {
|
|
5496
|
+
const allowedIdentity = message.from || (message.chatId.startsWith("chat:") ? undefined : message.chatId);
|
|
5497
|
+
if (!imessageHandleAllowed(channel, allowedIdentity))
|
|
5498
|
+
return false;
|
|
5499
|
+
await sendIMessage(channel, message.responseTargetId || message.chatId, text, { allowChatTarget: Boolean(message.responseTargetId?.startsWith("chat:") || message.chatId.startsWith("chat:")) });
|
|
5500
|
+
return true;
|
|
5501
|
+
}
|
|
5502
|
+
return false;
|
|
5503
|
+
}
|
|
5504
|
+
async function deliverStoredResponse(config, state, binding, message, entry, options) {
|
|
5505
|
+
const session = getBridgeSession(state, binding.activeSessionId);
|
|
5506
|
+
const responseText = entry.responseText || "";
|
|
5507
|
+
const deliveredResponse = responseText ? await deliverResponse(config, message, responseText, options) : false;
|
|
5508
|
+
completeLedger(entry, "delivered", session.id);
|
|
5509
|
+
entry.deliveredResponse = deliveredResponse;
|
|
5510
|
+
return {
|
|
5511
|
+
kind: "session",
|
|
5512
|
+
session,
|
|
5513
|
+
binding,
|
|
5514
|
+
conversationId: binding.conversationId,
|
|
5515
|
+
deliveredResponse,
|
|
5516
|
+
status: responseText ? "delivered" : "no_output"
|
|
5517
|
+
};
|
|
5518
|
+
}
|
|
5519
|
+
function channelAuthorized(config, message) {
|
|
5520
|
+
const channel = config.channels[message.channelId];
|
|
5521
|
+
if (!channel || channel.enabled === false)
|
|
5522
|
+
return false;
|
|
5523
|
+
if (channel.kind === "telegram")
|
|
5524
|
+
return telegramChatAllowed(channel, message.chatId);
|
|
5525
|
+
if (channel.kind === "imessage")
|
|
5526
|
+
return imessageHandleAllowed(channel, message.from || (message.chatId?.startsWith("chat:") ? undefined : message.chatId));
|
|
5527
|
+
return true;
|
|
5528
|
+
}
|
|
5529
|
+
function bindingAuthorized(binding, message) {
|
|
5530
|
+
if (binding.authorization?.chatId && binding.authorization.chatId !== message.chatId)
|
|
5531
|
+
return false;
|
|
5532
|
+
if (binding.authorization?.from && binding.authorization.from !== message.from)
|
|
5533
|
+
return false;
|
|
5534
|
+
return true;
|
|
5535
|
+
}
|
|
5536
|
+
async function sendBridgeSessionMessage(config, state, sessionId, message, options = {}) {
|
|
5537
|
+
const session = getBridgeSession(state, sessionId);
|
|
5538
|
+
if (session.status === "paused")
|
|
5539
|
+
return { kind: "session", session, status: "paused", message: "Session is paused" };
|
|
5540
|
+
if (session.status === "closed")
|
|
5541
|
+
return { kind: "session", session, status: "closed", message: "Session is closed" };
|
|
5542
|
+
const agent = await sendAgentSessionMessage(config, session, message, { run: options.run });
|
|
5543
|
+
const timestamp = nowIso();
|
|
5544
|
+
session.lastMessageAt = timestamp;
|
|
5545
|
+
session.updatedAt = timestamp;
|
|
5546
|
+
if (session.agentSession)
|
|
5547
|
+
session.agentSession.updatedAt = timestamp;
|
|
5548
|
+
if (agent.timedOut || agent.exitCode !== null && agent.exitCode !== 0) {
|
|
5549
|
+
return {
|
|
5550
|
+
kind: "session",
|
|
5551
|
+
session,
|
|
5552
|
+
agent,
|
|
5553
|
+
deliveredResponse: false,
|
|
5554
|
+
status: "failed",
|
|
5555
|
+
message: agent.stderr.trim() || agent.stdout.trim() || (agent.timedOut ? "Agent timed out" : `Agent exited ${agent.exitCode}`)
|
|
5556
|
+
};
|
|
5557
|
+
}
|
|
5558
|
+
const responseText = agent.stdout.trim();
|
|
5559
|
+
await options.beforeDeliver?.(agent, responseText);
|
|
5560
|
+
const deliveredResponse = responseText ? await deliverResponse(config, message, responseText, options) : false;
|
|
5561
|
+
return {
|
|
5562
|
+
kind: "session",
|
|
5563
|
+
session,
|
|
5564
|
+
agent,
|
|
5565
|
+
deliveredResponse,
|
|
5566
|
+
status: responseText ? "delivered" : "no_output"
|
|
5567
|
+
};
|
|
5568
|
+
}
|
|
5569
|
+
async function routeSessionMessage(config, state, message, options = {}) {
|
|
5570
|
+
const channel = config.channels[message.channelId];
|
|
5571
|
+
if (!channel || channel.enabled === false) {
|
|
5572
|
+
return { kind: "session", status: "unauthorized", message: `Channel not enabled: ${message.channelId}` };
|
|
5573
|
+
}
|
|
5574
|
+
if (!channelAuthorized(config, message)) {
|
|
5575
|
+
return { kind: "session", status: "unauthorized", message: "Message is not authorized for this channel" };
|
|
5576
|
+
}
|
|
5577
|
+
const conversationId = messageConversationId(config, message);
|
|
5578
|
+
const binding = conversationId ? state.bindings[bindingId(message.channelId, conversationId)] : undefined;
|
|
5579
|
+
if (!binding) {
|
|
5580
|
+
const text = noSessionText(message.channelId, conversationId);
|
|
5581
|
+
if (options.respondOnNoSession !== false)
|
|
5582
|
+
await deliverResponse(config, message, text, options);
|
|
5583
|
+
return { kind: "session", conversationId, status: "no_session", message: text };
|
|
5584
|
+
}
|
|
5585
|
+
if (!bindingAuthorized(binding, message)) {
|
|
5586
|
+
return { kind: "session", binding, conversationId, status: "unauthorized", message: "Message does not match binding authorization" };
|
|
5587
|
+
}
|
|
5588
|
+
const result = await sendBridgeSessionMessage(config, state, binding.activeSessionId, message, options);
|
|
5589
|
+
return { ...result, binding, conversationId };
|
|
5590
|
+
}
|
|
5591
|
+
function beginLedger(state, message, conversationId) {
|
|
5592
|
+
const id = ledgerId(message);
|
|
5593
|
+
const existing = state.messageLedger[id];
|
|
5594
|
+
if (existing && ["delivered", "skipped", "unauthorized"].includes(existing.status)) {
|
|
5595
|
+
return { entry: existing, shouldProcess: false };
|
|
5596
|
+
}
|
|
5597
|
+
const timestamp = nowIso();
|
|
5598
|
+
const entry = existing || {
|
|
5599
|
+
id,
|
|
5600
|
+
channelId: message.channelId,
|
|
5601
|
+
messageId: message.id,
|
|
5602
|
+
conversationId,
|
|
5603
|
+
status: "processing",
|
|
5604
|
+
attempts: 0,
|
|
5605
|
+
firstSeenAt: timestamp,
|
|
5606
|
+
updatedAt: timestamp
|
|
5607
|
+
};
|
|
5608
|
+
if (entry.status !== "agent_completed")
|
|
5609
|
+
entry.status = "processing";
|
|
5610
|
+
entry.attempts += 1;
|
|
5611
|
+
entry.conversationId = conversationId || entry.conversationId;
|
|
5612
|
+
entry.updatedAt = timestamp;
|
|
5613
|
+
delete entry.error;
|
|
5614
|
+
state.messageLedger[id] = entry;
|
|
5615
|
+
return { entry, shouldProcess: true };
|
|
5616
|
+
}
|
|
5617
|
+
function completeLedger(entry, status, sessionId, error) {
|
|
5618
|
+
const timestamp = nowIso();
|
|
5619
|
+
entry.status = status;
|
|
5620
|
+
entry.sessionId = sessionId || entry.sessionId;
|
|
5621
|
+
entry.updatedAt = timestamp;
|
|
5622
|
+
if (["delivered", "skipped", "unauthorized"].includes(status))
|
|
5623
|
+
entry.terminalAt = timestamp;
|
|
5624
|
+
if (error)
|
|
5625
|
+
entry.error = error;
|
|
5626
|
+
return entry;
|
|
5627
|
+
}
|
|
5628
|
+
function recordAgentCompleted(entry, sessionId, agent, responseText) {
|
|
5629
|
+
const timestamp = nowIso();
|
|
5630
|
+
entry.status = "agent_completed";
|
|
5631
|
+
entry.sessionId = sessionId || entry.sessionId;
|
|
5632
|
+
entry.responseText = responseText;
|
|
5633
|
+
entry.agentExitCode = agent.exitCode;
|
|
5634
|
+
entry.agentTimedOut = agent.timedOut;
|
|
5635
|
+
entry.updatedAt = timestamp;
|
|
5636
|
+
delete entry.error;
|
|
5637
|
+
return entry;
|
|
5638
|
+
}
|
|
5639
|
+
async function dispatchMessageWithSessions(config, state, message, options = {}) {
|
|
5640
|
+
const conversationId = messageConversationId(config, message);
|
|
5641
|
+
const { entry, shouldProcess } = beginLedger(state, message, conversationId);
|
|
5642
|
+
if (!shouldProcess)
|
|
5643
|
+
return { message, ledger: entry };
|
|
5644
|
+
await options.persistState?.(state);
|
|
5645
|
+
try {
|
|
5646
|
+
const binding = conversationId ? state.bindings[bindingId(message.channelId, conversationId)] : undefined;
|
|
5647
|
+
if (binding) {
|
|
5648
|
+
if (!bindingAuthorized(binding, message)) {
|
|
5649
|
+
const session3 = {
|
|
5650
|
+
kind: "session",
|
|
5651
|
+
binding,
|
|
5652
|
+
conversationId,
|
|
5653
|
+
status: "unauthorized",
|
|
5654
|
+
message: "Message does not match binding authorization"
|
|
5655
|
+
};
|
|
5656
|
+
completeLedger(entry, "unauthorized");
|
|
5657
|
+
return { message, session: session3, ledger: entry };
|
|
5658
|
+
}
|
|
5659
|
+
if (entry.status === "agent_completed") {
|
|
5660
|
+
const session3 = await deliverStoredResponse(config, state, binding, message, entry, options);
|
|
5661
|
+
return { message, session: session3, ledger: entry };
|
|
5662
|
+
}
|
|
5663
|
+
const session2 = await routeSessionMessage(config, state, message, {
|
|
5664
|
+
...options,
|
|
5665
|
+
beforeDeliver: async (agent, responseText) => {
|
|
5666
|
+
recordAgentCompleted(entry, binding.activeSessionId, agent, responseText);
|
|
5667
|
+
await options.persistState?.(state);
|
|
5668
|
+
await options.beforeDeliver?.(agent, responseText);
|
|
5669
|
+
}
|
|
5670
|
+
});
|
|
5671
|
+
if (session2.status === "failed") {
|
|
5672
|
+
completeLedger(entry, "failed", session2.session?.id, session2.message);
|
|
5673
|
+
throw new Error(session2.message || "Agent session failed");
|
|
5674
|
+
}
|
|
5675
|
+
const terminal = session2.status === "unauthorized" ? "unauthorized" : session2.status === "delivered" || session2.status === "no_output" ? "delivered" : "skipped";
|
|
5676
|
+
completeLedger(entry, terminal, session2.session?.id);
|
|
5677
|
+
entry.deliveredResponse = session2.deliveredResponse;
|
|
5678
|
+
return { message, session: session2, ledger: entry };
|
|
5679
|
+
}
|
|
5680
|
+
if (options.fallbackToRoutes) {
|
|
5681
|
+
const routes = await routeMessage(config, message, options);
|
|
5682
|
+
if (routes.length) {
|
|
5683
|
+
completeLedger(entry, "delivered");
|
|
5684
|
+
return { message, routes, ledger: entry };
|
|
5685
|
+
}
|
|
5686
|
+
}
|
|
5687
|
+
const session = await routeSessionMessage(config, state, message, options);
|
|
5688
|
+
const status = session.status === "unauthorized" ? "unauthorized" : "skipped";
|
|
5689
|
+
completeLedger(entry, status, session.session?.id);
|
|
5690
|
+
return { message, session, ledger: entry };
|
|
5691
|
+
} catch (err) {
|
|
5692
|
+
const messageText = err instanceof Error ? err.message : String(err);
|
|
5693
|
+
if (entry.status === "agent_completed") {
|
|
5694
|
+
entry.error = messageText;
|
|
5695
|
+
entry.updatedAt = nowIso();
|
|
5696
|
+
} else {
|
|
5697
|
+
completeLedger(entry, "failed", undefined, messageText);
|
|
5698
|
+
}
|
|
5699
|
+
throw err;
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5111
5702
|
export {
|
|
5112
5703
|
upsertRoute,
|
|
5113
5704
|
upsertProfile,
|
|
5114
5705
|
upsertChannel,
|
|
5115
5706
|
upsertAgent,
|
|
5707
|
+
updateBridgeSessionStatus,
|
|
5116
5708
|
uninstallDaemon,
|
|
5117
5709
|
telegramUpdateToMessage,
|
|
5118
5710
|
telegramToken,
|
|
@@ -5124,38 +5716,65 @@ export {
|
|
|
5124
5716
|
startProcessDaemon,
|
|
5125
5717
|
startInstalledDaemon,
|
|
5126
5718
|
sendTelegramMessage,
|
|
5719
|
+
sendIMessage,
|
|
5720
|
+
sendBridgeSessionMessage,
|
|
5721
|
+
sendAgentSessionMessage,
|
|
5127
5722
|
saveState,
|
|
5128
5723
|
saveConfig,
|
|
5129
5724
|
runAgent,
|
|
5725
|
+
routeSessionMessage,
|
|
5130
5726
|
routeMessage,
|
|
5727
|
+
resumeAgentSessionRef,
|
|
5131
5728
|
restartProcessDaemon,
|
|
5132
5729
|
restartInstalledDaemon,
|
|
5133
5730
|
resolveSupervisor,
|
|
5134
5731
|
resolveAgent,
|
|
5135
5732
|
requiredTelegramEnvVars,
|
|
5136
5733
|
renderSystemdUnit,
|
|
5734
|
+
renderSendIMessageScript,
|
|
5137
5735
|
renderLaunchdPlist,
|
|
5138
5736
|
redactConfig,
|
|
5139
5737
|
parseConfig,
|
|
5738
|
+
normalizeConversationId,
|
|
5739
|
+
messageConversationId,
|
|
5140
5740
|
matchingRoutes,
|
|
5141
5741
|
loadState,
|
|
5142
5742
|
loadConfig,
|
|
5743
|
+
listBridgeSessions,
|
|
5744
|
+
ledgerId,
|
|
5143
5745
|
installDaemon,
|
|
5746
|
+
imessageRowToMessage,
|
|
5747
|
+
imessageHandleAllowed,
|
|
5144
5748
|
homeDir,
|
|
5145
5749
|
getTelegramUpdates,
|
|
5750
|
+
getIMessageMessages,
|
|
5751
|
+
getIMessageDbPath,
|
|
5752
|
+
getBridgeSession,
|
|
5753
|
+
findBridgeBinding,
|
|
5146
5754
|
ensureDaemonDir,
|
|
5147
5755
|
ensureConfig,
|
|
5148
5756
|
emptyState,
|
|
5149
5757
|
emptyConfig,
|
|
5150
5758
|
doctor,
|
|
5759
|
+
dispatchMessageWithSessions,
|
|
5760
|
+
diagnoseIMessage,
|
|
5761
|
+
detachBridgeBinding,
|
|
5151
5762
|
defaultStatePath,
|
|
5763
|
+
defaultMessagesDbPath,
|
|
5152
5764
|
defaultDaemonDir,
|
|
5153
5765
|
defaultConfigPath,
|
|
5154
5766
|
daemonStatus,
|
|
5155
5767
|
daemonPaths,
|
|
5156
5768
|
daemonLogs,
|
|
5769
|
+
createBridgeSession,
|
|
5770
|
+
createAgentSessionRef,
|
|
5771
|
+
closeAgentSession,
|
|
5772
|
+
cancelAgentSession,
|
|
5157
5773
|
buildAgentCommand,
|
|
5158
5774
|
bridgeHome,
|
|
5775
|
+
bindingId,
|
|
5776
|
+
attachBridgeSession,
|
|
5777
|
+
STATE_SCHEMA_VERSION,
|
|
5159
5778
|
CONFIG_VERSION,
|
|
5160
5779
|
CHANNEL_KINDS,
|
|
5161
5780
|
AGENT_KINDS
|