@holon-run/agentinbox 1.4.0 → 1.5.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/dist/src/adapters.js +4 -3
- package/dist/src/cli.js +207 -21
- package/dist/src/http.js +213 -0
- package/dist/src/service.js +210 -2
- package/dist/src/sources/remote.js +4 -89
- package/dist/src/sources/remote_modules.js +6 -4
- package/dist/src/sources/telegram.js +1 -0
- package/dist/src/store.js +109 -0
- package/drizzle/migrations/0005_operator_transport_bindings.sql +29 -0
- package/drizzle/schema.ts +32 -0
- package/package.json +1 -1
package/dist/src/adapters.js
CHANGED
|
@@ -39,9 +39,9 @@ class AdapterRegistry {
|
|
|
39
39
|
localEventSource = new NoopSourceAdapter("local_event");
|
|
40
40
|
remoteSource;
|
|
41
41
|
defaultDelivery = new NoopDeliveryAdapter();
|
|
42
|
-
feishuDelivery
|
|
42
|
+
feishuDelivery;
|
|
43
43
|
githubDelivery = new github_1.GithubDeliveryAdapter();
|
|
44
|
-
telegramDelivery
|
|
44
|
+
telegramDelivery;
|
|
45
45
|
homeDir;
|
|
46
46
|
remoteModuleRegistry;
|
|
47
47
|
constructor(store, appendSourceEvent, options) {
|
|
@@ -49,11 +49,12 @@ class AdapterRegistry {
|
|
|
49
49
|
this.remoteModuleRegistry = options?.remoteModuleRegistry ?? new remote_modules_1.RemoteSourceModuleRegistry({
|
|
50
50
|
githubCallClient: options?.githubCallClient,
|
|
51
51
|
});
|
|
52
|
+
this.feishuDelivery = new feishu_1.FeishuDeliveryAdapter(options?.feishuClient);
|
|
53
|
+
this.telegramDelivery = new telegram_1.TelegramDeliveryAdapter(options?.telegramClient);
|
|
52
54
|
this.remoteSource = new remote_1.RemoteSourceRuntime(store, appendSourceEvent, {
|
|
53
55
|
homeDir: this.homeDir,
|
|
54
56
|
client: options?.remoteSourceClient,
|
|
55
57
|
moduleRegistry: this.remoteModuleRegistry,
|
|
56
|
-
telegramClient: options?.telegramClient,
|
|
57
58
|
});
|
|
58
59
|
}
|
|
59
60
|
sourceAdapterFor(type) {
|
package/dist/src/cli.js
CHANGED
|
@@ -22,9 +22,10 @@ const util_1 = require("./util");
|
|
|
22
22
|
async function main() {
|
|
23
23
|
const rawArgs = process.argv.slice(2);
|
|
24
24
|
const detectedArgs = normalizeHelpArgs(rawArgs);
|
|
25
|
-
const
|
|
25
|
+
const jsonStripped = shouldTreatJsonFlagAsNoop(detectedArgs)
|
|
26
26
|
? stripNoopJsonFlag(detectedArgs)
|
|
27
27
|
: detectedArgs;
|
|
28
|
+
const normalized = normalizeAliases(jsonStripped);
|
|
28
29
|
const shell = parseCommanderShell(normalized);
|
|
29
30
|
const command = normalized[0];
|
|
30
31
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
@@ -360,14 +361,23 @@ async function main() {
|
|
|
360
361
|
if (command === "source" && normalized[1] === "invoke") {
|
|
361
362
|
const sourceId = normalized[2];
|
|
362
363
|
const operation = takeFlagValue(normalized, "--operation");
|
|
363
|
-
const inputJson = takeFlagValue(normalized, "--input-json");
|
|
364
|
+
const inputJson = takeFlagValue(normalized, "--input-json") ?? takeFlagValue(normalized, "--input");
|
|
364
365
|
if (!sourceId || !operation || !inputJson) {
|
|
365
366
|
throw new Error("usage: agentinbox source invoke <sourceId> --operation NAME --input-json JSON");
|
|
366
367
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
368
|
+
try {
|
|
369
|
+
await printRemote(client, `/sources/${encodeURIComponent(sourceId)}/invoke`, {
|
|
370
|
+
operation,
|
|
371
|
+
input: (0, util_1.parseJsonArg)(inputJson, "--input-json"),
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
const suggestion = await suggestSourceOperation(client, sourceId, operation);
|
|
376
|
+
if (suggestion) {
|
|
377
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)}\nDid you mean: --operation ${suggestion}?`);
|
|
378
|
+
}
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
371
381
|
return;
|
|
372
382
|
}
|
|
373
383
|
if (command === "source" && normalized[1] === "poll") {
|
|
@@ -731,9 +741,9 @@ async function main() {
|
|
|
731
741
|
}
|
|
732
742
|
if (command === "inbox" && normalized[1] === "read") {
|
|
733
743
|
const args = normalized.slice(2);
|
|
734
|
-
const allowedFlags = ["--agent-id", "--after-entry", "--include-acked", "--limit"];
|
|
744
|
+
const allowedFlags = ["--agent-id", "--after-entry", "--include-acked", "--limit", "--full"];
|
|
735
745
|
if (positionalArgs(args, ["--agent-id", "--after-entry", "--limit"]).length > 0 || unexpectedFlags(args, allowedFlags).length > 0) {
|
|
736
|
-
throw new Error("usage: agentinbox inbox read [--agent-id ID] [--after-entry ID] [--include-acked] [--limit N]");
|
|
746
|
+
throw new Error("usage: agentinbox inbox read [--agent-id ID] [--after-entry ID] [--include-acked] [--limit N] [--full]");
|
|
737
747
|
}
|
|
738
748
|
const selection = await selectAgentForCommand(client, {
|
|
739
749
|
explicitAgentId: takeFlagValue(normalized, "--agent-id"),
|
|
@@ -745,7 +755,11 @@ async function main() {
|
|
|
745
755
|
limit: parseOptionalLimitArg(normalized),
|
|
746
756
|
});
|
|
747
757
|
const response = await requestRemote(client, `/agents/${encodeURIComponent(selection.agentId)}/inbox/entries${query}`, undefined, "GET");
|
|
748
|
-
|
|
758
|
+
const data = response.data;
|
|
759
|
+
if (!hasFlag(normalized, "--full") && Array.isArray(data.entries)) {
|
|
760
|
+
data.entries = data.entries.map(compactInboxEntry);
|
|
761
|
+
}
|
|
762
|
+
console.log((0, util_1.jsonResponse)(withCommandMetadata(data, selection)));
|
|
749
763
|
return;
|
|
750
764
|
}
|
|
751
765
|
if (command === "inbox" && normalized[1] === "send") {
|
|
@@ -768,8 +782,9 @@ async function main() {
|
|
|
768
782
|
if (command === "inbox" && normalized[1] === "watch") {
|
|
769
783
|
const args = normalized.slice(2);
|
|
770
784
|
if (positionalArgs(args, ["--agent-id", "--after-entry", "--heartbeat-ms"]).length > 0) {
|
|
771
|
-
throw new Error("usage: agentinbox inbox watch [--agent-id ID] [--after-entry ID] [--include-acked] [--heartbeat-ms N]");
|
|
785
|
+
throw new Error("usage: agentinbox inbox watch [--agent-id ID] [--after-entry ID] [--include-acked] [--heartbeat-ms N] [--full]");
|
|
772
786
|
}
|
|
787
|
+
const fullMode = hasFlag(normalized, "--full");
|
|
773
788
|
const selection = await selectAgentForCommand(client, {
|
|
774
789
|
explicitAgentId: takeFlagValue(normalized, "--agent-id"),
|
|
775
790
|
autoRegister: true,
|
|
@@ -788,18 +803,22 @@ async function main() {
|
|
|
788
803
|
if (event.event !== "items") {
|
|
789
804
|
continue;
|
|
790
805
|
}
|
|
791
|
-
|
|
806
|
+
const output = fullMode ? event : {
|
|
807
|
+
...event,
|
|
808
|
+
entries: event.entries.map((e) => compactInboxEntry(e)),
|
|
809
|
+
};
|
|
810
|
+
console.log((0, util_1.jsonResponse)(output));
|
|
792
811
|
}
|
|
793
812
|
return;
|
|
794
813
|
}
|
|
795
814
|
if (command === "inbox" && normalized[1] === "ack") {
|
|
796
815
|
const args = normalized.slice(2);
|
|
797
816
|
const itemId = takeFlagValue(normalized, "--entry-id") ?? takeFlagValue(normalized, "--entry");
|
|
798
|
-
const throughEntryId = takeFlagValue(normalized, "--through");
|
|
817
|
+
const throughEntryId = takeFlagValue(normalized, "--through") ?? takeFlagValue(normalized, "--through-entry-id");
|
|
799
818
|
const ackAll = hasFlag(normalized, "--all");
|
|
800
819
|
const modeCount = Number(Boolean(itemId)) + Number(Boolean(throughEntryId)) + Number(ackAll);
|
|
801
|
-
if (positionalArgs(args, ["--agent-id", "--entry", "--entry-id", "--through"]).length > 0 || modeCount !== 1) {
|
|
802
|
-
throw new Error("usage: agentinbox inbox ack [--agent-id ID] (--through <entryId> | --entry <entryId> | --entry-id <entryId> | --all)");
|
|
820
|
+
if (positionalArgs(args, ["--agent-id", "--entry", "--entry-id", "--through", "--through-entry-id"]).length > 0 || modeCount !== 1) {
|
|
821
|
+
throw new Error("usage: agentinbox inbox ack [--agent-id ID] (--through <entryId> | --through-entry-id <entryId> | --entry <entryId> | --entry-id <entryId> | --all)");
|
|
803
822
|
}
|
|
804
823
|
const selection = await selectAgentForCommand(client, {
|
|
805
824
|
explicitAgentId: takeFlagValue(normalized, "--agent-id"),
|
|
@@ -893,11 +912,11 @@ async function main() {
|
|
|
893
912
|
const targetRef = takeFlagValue(normalized, "--target");
|
|
894
913
|
const handleJson = takeFlagValue(normalized, "--handle-json");
|
|
895
914
|
const operation = takeFlagValue(normalized, "--operation");
|
|
896
|
-
const inputJson = takeFlagValue(normalized, "--input-json");
|
|
915
|
+
const inputJson = takeFlagValue(normalized, "--input-json") ?? takeFlagValue(normalized, "--input");
|
|
897
916
|
if (!operation || !inputJson || (!handleJson && (!provider || !surface || !targetRef))) {
|
|
898
917
|
throw new Error("usage: agentinbox deliver invoke (--handle-json JSON | --provider PROVIDER --surface SURFACE --target TARGET) --operation NAME --input-json JSON [--source-id SOURCE_ID]");
|
|
899
918
|
}
|
|
900
|
-
|
|
919
|
+
const invokeBody = handleJson
|
|
901
920
|
? {
|
|
902
921
|
sourceId: takeFlagValue(normalized, "--source-id") ?? undefined,
|
|
903
922
|
deliveryHandle: (0, util_1.parseJsonArg)(handleJson),
|
|
@@ -913,7 +932,17 @@ async function main() {
|
|
|
913
932
|
replyMode: takeFlagValue(normalized, "--reply-mode") ?? undefined,
|
|
914
933
|
operation,
|
|
915
934
|
input: (0, util_1.parseJsonArg)(inputJson, "--input-json"),
|
|
916
|
-
}
|
|
935
|
+
};
|
|
936
|
+
try {
|
|
937
|
+
await printRemote(client, "/deliveries/invoke", invokeBody);
|
|
938
|
+
}
|
|
939
|
+
catch (error) {
|
|
940
|
+
const suggestion = await suggestDeliveryOperation(client, invokeBody);
|
|
941
|
+
if (suggestion) {
|
|
942
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)}\nDid you mean: --operation ${suggestion}?`);
|
|
943
|
+
}
|
|
944
|
+
throw error;
|
|
945
|
+
}
|
|
917
946
|
return;
|
|
918
947
|
}
|
|
919
948
|
if (command === "status") {
|
|
@@ -924,6 +953,10 @@ async function main() {
|
|
|
924
953
|
await printRemote(client, "/gc", {});
|
|
925
954
|
return;
|
|
926
955
|
}
|
|
956
|
+
const suggestion = suggestUnknownCommand(normalized);
|
|
957
|
+
if (suggestion) {
|
|
958
|
+
throw new Error(`unknown command: ${normalized.join(" ")}\nDid you mean: ${suggestion}?`);
|
|
959
|
+
}
|
|
927
960
|
throw new Error(`unknown command: ${normalized.join(" ")}`);
|
|
928
961
|
}
|
|
929
962
|
const COMMAND_SHELL_SPECS = [
|
|
@@ -1258,6 +1291,127 @@ function normalizeHelpArgs(args) {
|
|
|
1258
1291
|
}
|
|
1259
1292
|
return [args[1], "--help"];
|
|
1260
1293
|
}
|
|
1294
|
+
function normalizeAliases(args) {
|
|
1295
|
+
// Top-level command aliases
|
|
1296
|
+
if (args[0] === "delivery") {
|
|
1297
|
+
return ["deliver", ...args.slice(1)];
|
|
1298
|
+
}
|
|
1299
|
+
// Top-level shortcuts -> inbox subcommands
|
|
1300
|
+
if (args[0] === "entries") {
|
|
1301
|
+
return ["inbox", "read", ...args.slice(1)];
|
|
1302
|
+
}
|
|
1303
|
+
if (args[0] === "ack") {
|
|
1304
|
+
return ["inbox", "ack", ...args.slice(1)];
|
|
1305
|
+
}
|
|
1306
|
+
// inbox subcommand aliases: get/entries/peek -> read
|
|
1307
|
+
if (args[0] === "inbox" && (args[1] === "get" || args[1] === "entries" || args[1] === "peek")) {
|
|
1308
|
+
return [args[0], "read", ...args.slice(2)];
|
|
1309
|
+
}
|
|
1310
|
+
return args;
|
|
1311
|
+
}
|
|
1312
|
+
function levenshtein(a, b) {
|
|
1313
|
+
const matrix = [];
|
|
1314
|
+
for (let i = 0; i <= b.length; i++)
|
|
1315
|
+
matrix[i] = [i];
|
|
1316
|
+
for (let j = 0; j <= a.length; j++)
|
|
1317
|
+
matrix[0][j] = j;
|
|
1318
|
+
for (let i = 1; i <= b.length; i++) {
|
|
1319
|
+
for (let j = 1; j <= a.length; j++) {
|
|
1320
|
+
const cost = a[j - 1] === b[i - 1] ? 0 : 1;
|
|
1321
|
+
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
return matrix[b.length][a.length];
|
|
1325
|
+
}
|
|
1326
|
+
function closestMatch(input, candidates) {
|
|
1327
|
+
if (candidates.length === 0)
|
|
1328
|
+
return null;
|
|
1329
|
+
let best = null;
|
|
1330
|
+
let bestDistance = Infinity;
|
|
1331
|
+
for (const candidate of candidates) {
|
|
1332
|
+
const distance = levenshtein(input.toLowerCase(), candidate.toLowerCase());
|
|
1333
|
+
if (distance < bestDistance) {
|
|
1334
|
+
bestDistance = distance;
|
|
1335
|
+
best = candidate;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
// Only suggest if the distance is reasonable (at most half the input length)
|
|
1339
|
+
if (best && bestDistance <= Math.max(1, Math.floor(input.length / 2))) {
|
|
1340
|
+
return best;
|
|
1341
|
+
}
|
|
1342
|
+
return null;
|
|
1343
|
+
}
|
|
1344
|
+
const KNOWN_SUBCOMMANDS = {
|
|
1345
|
+
deliver: ["send", "actions", "invoke"],
|
|
1346
|
+
inbox: ["list", "show", "read", "send", "watch", "ack", "policy", "compact"],
|
|
1347
|
+
daemon: ["start", "stop", "status"],
|
|
1348
|
+
host: ["add", "list", "show", "schema"],
|
|
1349
|
+
stream: ["add", "list", "show", "update", "remove", "pause", "resume", "schema", "poll", "event"],
|
|
1350
|
+
source: ["add", "list", "show", "update", "remove", "pause", "resume", "schema", "actions", "invoke", "poll", "event"],
|
|
1351
|
+
agent: ["register", "list", "current", "show", "remove", "resume", "target"],
|
|
1352
|
+
subscription: ["add", "list", "show", "remove", "poll", "lag", "reset"],
|
|
1353
|
+
timer: ["add", "list", "pause", "resume", "remove"],
|
|
1354
|
+
};
|
|
1355
|
+
function suggestUnknownCommand(args) {
|
|
1356
|
+
const command = args[0];
|
|
1357
|
+
if (!command)
|
|
1358
|
+
return null;
|
|
1359
|
+
// Check top-level command suggestions
|
|
1360
|
+
const topCommands = COMMAND_SHELL_SPECS.map((s) => s.name);
|
|
1361
|
+
const topSuggestion = closestMatch(command, topCommands);
|
|
1362
|
+
if (topSuggestion && topSuggestion !== command) {
|
|
1363
|
+
return topSuggestion;
|
|
1364
|
+
}
|
|
1365
|
+
// Check subcommand suggestions
|
|
1366
|
+
if (args[1]) {
|
|
1367
|
+
const subcommands = KNOWN_SUBCOMMANDS[command];
|
|
1368
|
+
if (subcommands) {
|
|
1369
|
+
const subSuggestion = closestMatch(args[1], subcommands);
|
|
1370
|
+
if (subSuggestion) {
|
|
1371
|
+
return `${command} ${subSuggestion}`;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
return null;
|
|
1376
|
+
}
|
|
1377
|
+
async function suggestDeliveryOperation(client, invokeBody) {
|
|
1378
|
+
try {
|
|
1379
|
+
const actionsBody = {};
|
|
1380
|
+
if (invokeBody.deliveryHandle) {
|
|
1381
|
+
actionsBody.deliveryHandle = invokeBody.deliveryHandle;
|
|
1382
|
+
}
|
|
1383
|
+
else {
|
|
1384
|
+
if (invokeBody.sourceId)
|
|
1385
|
+
actionsBody.sourceId = invokeBody.sourceId;
|
|
1386
|
+
if (invokeBody.provider)
|
|
1387
|
+
actionsBody.provider = invokeBody.provider;
|
|
1388
|
+
if (invokeBody.surface)
|
|
1389
|
+
actionsBody.surface = invokeBody.surface;
|
|
1390
|
+
if (invokeBody.targetRef)
|
|
1391
|
+
actionsBody.targetRef = invokeBody.targetRef;
|
|
1392
|
+
if (invokeBody.threadRef)
|
|
1393
|
+
actionsBody.threadRef = invokeBody.threadRef;
|
|
1394
|
+
if (invokeBody.replyMode)
|
|
1395
|
+
actionsBody.replyMode = invokeBody.replyMode;
|
|
1396
|
+
}
|
|
1397
|
+
const response = await requestRemote(client, "/deliveries/actions", actionsBody, "POST");
|
|
1398
|
+
const operationNames = response.data.operations.map((op) => op.name);
|
|
1399
|
+
return closestMatch(String(invokeBody.operation), operationNames);
|
|
1400
|
+
}
|
|
1401
|
+
catch {
|
|
1402
|
+
return null;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
async function suggestSourceOperation(client, sourceId, operation) {
|
|
1406
|
+
try {
|
|
1407
|
+
const response = await requestRemote(client, `/sources/${encodeURIComponent(sourceId)}/actions`, undefined, "GET");
|
|
1408
|
+
const operationNames = response.data.operations.map((op) => op.name);
|
|
1409
|
+
return closestMatch(operation, operationNames);
|
|
1410
|
+
}
|
|
1411
|
+
catch {
|
|
1412
|
+
return null;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1261
1415
|
function stripNoopJsonFlag(args) {
|
|
1262
1416
|
return args.filter((arg) => arg !== "--json");
|
|
1263
1417
|
}
|
|
@@ -1380,6 +1534,34 @@ function withCommandMetadata(data, selection) {
|
|
|
1380
1534
|
...(selection.warnings.length > 0 ? { warnings: selection.warnings } : {}),
|
|
1381
1535
|
};
|
|
1382
1536
|
}
|
|
1537
|
+
const COMPACT_ENTRY_FIELDS = [
|
|
1538
|
+
"entryId", "kind", "summary", "eventVariant",
|
|
1539
|
+
"occurredAt", "ackedAt", "deliveryHandle", "metadata",
|
|
1540
|
+
];
|
|
1541
|
+
const COMPACT_DIGEST_FIELDS = [
|
|
1542
|
+
"threadId", "revision", "groupKey", "resourceRef", "eventFamily",
|
|
1543
|
+
];
|
|
1544
|
+
/**
|
|
1545
|
+
* Strip verbose/internal fields from an inbox entry, keeping only the fields
|
|
1546
|
+
* an agent needs for decisions and ack. Use `--full` on `inbox read`/`watch`
|
|
1547
|
+
* to bypass this and see the complete entry.
|
|
1548
|
+
*/
|
|
1549
|
+
function compactInboxEntry(entry) {
|
|
1550
|
+
const compact = {};
|
|
1551
|
+
for (const field of COMPACT_ENTRY_FIELDS) {
|
|
1552
|
+
if (entry[field] !== undefined) {
|
|
1553
|
+
compact[field] = entry[field];
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
if (entry.kind === "digest_snapshot") {
|
|
1557
|
+
for (const field of COMPACT_DIGEST_FIELDS) {
|
|
1558
|
+
if (entry[field] !== undefined) {
|
|
1559
|
+
compact[field] = entry[field];
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
return compact;
|
|
1564
|
+
}
|
|
1383
1565
|
function normalizeSubscriptionAddOutput(data, shortcutUsed) {
|
|
1384
1566
|
if (shortcutUsed) {
|
|
1385
1567
|
return data;
|
|
@@ -1568,20 +1750,24 @@ Usage:
|
|
|
1568
1750
|
Usage:
|
|
1569
1751
|
agentinbox inbox list [--limit N]
|
|
1570
1752
|
agentinbox inbox show <agentId>
|
|
1571
|
-
agentinbox inbox read [--agent-id ID] [--after-entry ID] [--include-acked] [--limit N]
|
|
1753
|
+
agentinbox inbox read [--agent-id ID] [--after-entry ID] [--include-acked] [--limit N] [--full] (aliases: inbox get, inbox entries, inbox peek)
|
|
1572
1754
|
agentinbox inbox send --agent-id ID --message TEXT [--sender SENDER]
|
|
1573
|
-
agentinbox inbox watch [--agent-id ID] [--after-entry ID] [--include-acked] [--heartbeat-ms N]
|
|
1574
|
-
agentinbox inbox ack [--agent-id ID] (--through <entryId> | --entry <entryId> | --entry-id <entryId> | --all)
|
|
1755
|
+
agentinbox inbox watch [--agent-id ID] [--after-entry ID] [--include-acked] [--heartbeat-ms N] [--full]
|
|
1756
|
+
agentinbox inbox ack [--agent-id ID] (--through <entryId> | --through-entry-id <entryId> | --entry <entryId> | --entry-id <entryId> | --all)
|
|
1575
1757
|
agentinbox inbox policy show [--agent-id ID]
|
|
1576
1758
|
agentinbox inbox policy set [--agent-id ID] [--enabled|--disabled] [--window-ms N] [--max-items N] [--max-thread-age-ms N]
|
|
1577
1759
|
agentinbox inbox compact <agentId>
|
|
1760
|
+
|
|
1761
|
+
Aliases: inbox get/entries/peek -> inbox read, top-level entries -> inbox read, top-level ack -> inbox ack
|
|
1578
1762
|
`,
|
|
1579
1763
|
deliver: `agentinbox deliver
|
|
1580
1764
|
|
|
1581
1765
|
Usage:
|
|
1582
1766
|
agentinbox deliver send --provider PROVIDER --surface SURFACE --target TARGET [--source-id SOURCE_ID] [--kind KIND] [--payload-json JSON]
|
|
1583
1767
|
agentinbox deliver actions (--handle-json JSON | --provider PROVIDER --surface SURFACE --target TARGET) [--source-id SOURCE_ID]
|
|
1584
|
-
agentinbox deliver invoke (--handle-json JSON | --provider PROVIDER --surface SURFACE --target TARGET) --operation NAME --input-json JSON [--source-id SOURCE_ID]
|
|
1768
|
+
agentinbox deliver invoke (--handle-json JSON | --provider PROVIDER --surface SURFACE --target TARGET) --operation NAME --input-json JSON [--source-id SOURCE_ID] (--input alias for --input-json)
|
|
1769
|
+
|
|
1770
|
+
Aliases: delivery -> deliver
|
|
1585
1771
|
`,
|
|
1586
1772
|
status: `agentinbox status
|
|
1587
1773
|
|
package/dist/src/http.js
CHANGED
|
@@ -75,6 +75,17 @@ const deliveryTargetByFieldsSchema = {
|
|
|
75
75
|
replyMode: { type: "string" },
|
|
76
76
|
},
|
|
77
77
|
};
|
|
78
|
+
const authDescriptorSchema = {
|
|
79
|
+
type: "object",
|
|
80
|
+
additionalProperties: false,
|
|
81
|
+
required: ["type"],
|
|
82
|
+
properties: {
|
|
83
|
+
type: { type: "string", enum: ["bearer", "header"] },
|
|
84
|
+
token: { type: "string" },
|
|
85
|
+
headerName: { type: "string" },
|
|
86
|
+
headerValue: { type: "string" },
|
|
87
|
+
},
|
|
88
|
+
};
|
|
78
89
|
function buildFastifyServer(service) {
|
|
79
90
|
const app = (0, fastify_1.default)({
|
|
80
91
|
logger: false,
|
|
@@ -992,6 +1003,134 @@ function buildFastifyServer(service) {
|
|
|
992
1003
|
const params = request.params;
|
|
993
1004
|
return service.resumeAgent(decodeURIComponent(params.agentId));
|
|
994
1005
|
});
|
|
1006
|
+
app.get("/agents/:agentId/operator-bindings", {
|
|
1007
|
+
schema: {
|
|
1008
|
+
tags: ["operators"],
|
|
1009
|
+
params: {
|
|
1010
|
+
type: "object",
|
|
1011
|
+
required: ["agentId"],
|
|
1012
|
+
properties: {
|
|
1013
|
+
agentId: { type: "string", minLength: 1 },
|
|
1014
|
+
},
|
|
1015
|
+
},
|
|
1016
|
+
response: {
|
|
1017
|
+
200: {
|
|
1018
|
+
type: "object",
|
|
1019
|
+
required: ["bindings"],
|
|
1020
|
+
properties: {
|
|
1021
|
+
bindings: { type: "array", items: jsonObjectSchema },
|
|
1022
|
+
},
|
|
1023
|
+
},
|
|
1024
|
+
},
|
|
1025
|
+
},
|
|
1026
|
+
}, async (request) => {
|
|
1027
|
+
const params = request.params;
|
|
1028
|
+
return { bindings: service.listOperatorTransportBindings(decodeURIComponent(params.agentId)) };
|
|
1029
|
+
});
|
|
1030
|
+
app.post("/agents/:agentId/operator-bindings", {
|
|
1031
|
+
schema: {
|
|
1032
|
+
tags: ["operators"],
|
|
1033
|
+
params: {
|
|
1034
|
+
type: "object",
|
|
1035
|
+
required: ["agentId"],
|
|
1036
|
+
properties: {
|
|
1037
|
+
agentId: { type: "string", minLength: 1 },
|
|
1038
|
+
},
|
|
1039
|
+
},
|
|
1040
|
+
body: {
|
|
1041
|
+
type: "object",
|
|
1042
|
+
additionalProperties: false,
|
|
1043
|
+
required: ["transport", "operatorActorId", "holonBaseUrl"],
|
|
1044
|
+
properties: {
|
|
1045
|
+
bindingId: { type: "string" },
|
|
1046
|
+
transport: { type: "string", minLength: 1 },
|
|
1047
|
+
operatorActorId: { type: "string", minLength: 1 },
|
|
1048
|
+
holonBaseUrl: { type: "string", minLength: 1 },
|
|
1049
|
+
deliveryCallbackUrl: { type: "string" },
|
|
1050
|
+
deliveryAuth: authDescriptorSchema,
|
|
1051
|
+
holonAuth: authDescriptorSchema,
|
|
1052
|
+
defaultRouteId: { type: "string" },
|
|
1053
|
+
capabilities: { type: "array", items: { type: "string" } },
|
|
1054
|
+
provider: { type: "string" },
|
|
1055
|
+
metadata: jsonObjectSchema,
|
|
1056
|
+
syncWithHolon: { type: "boolean" },
|
|
1057
|
+
},
|
|
1058
|
+
},
|
|
1059
|
+
response: {
|
|
1060
|
+
200: jsonObjectSchema,
|
|
1061
|
+
400: errorResponseSchema,
|
|
1062
|
+
},
|
|
1063
|
+
},
|
|
1064
|
+
}, async (request) => {
|
|
1065
|
+
const params = request.params;
|
|
1066
|
+
const body = request.body;
|
|
1067
|
+
return service.registerOperatorTransportBinding(decodeURIComponent(params.agentId), {
|
|
1068
|
+
bindingId: optionalString(body.bindingId) ?? null,
|
|
1069
|
+
transport: String(body.transport),
|
|
1070
|
+
operatorActorId: String(body.operatorActorId),
|
|
1071
|
+
holonBaseUrl: String(body.holonBaseUrl),
|
|
1072
|
+
deliveryCallbackUrl: optionalString(body.deliveryCallbackUrl) ?? null,
|
|
1073
|
+
deliveryAuth: body.deliveryAuth ? body.deliveryAuth : null,
|
|
1074
|
+
holonAuth: body.holonAuth ? body.holonAuth : null,
|
|
1075
|
+
defaultRouteId: optionalString(body.defaultRouteId) ?? null,
|
|
1076
|
+
capabilities: Array.isArray(body.capabilities) ? body.capabilities.map(String) : undefined,
|
|
1077
|
+
provider: optionalString(body.provider) ?? null,
|
|
1078
|
+
metadata: body.metadata && typeof body.metadata === "object" ? body.metadata : undefined,
|
|
1079
|
+
syncWithHolon: typeof body.syncWithHolon === "boolean" ? body.syncWithHolon : undefined,
|
|
1080
|
+
});
|
|
1081
|
+
});
|
|
1082
|
+
app.post("/agents/:agentId/operator-ingress", {
|
|
1083
|
+
schema: {
|
|
1084
|
+
tags: ["operators"],
|
|
1085
|
+
params: {
|
|
1086
|
+
type: "object",
|
|
1087
|
+
required: ["agentId"],
|
|
1088
|
+
properties: {
|
|
1089
|
+
agentId: { type: "string", minLength: 1 },
|
|
1090
|
+
},
|
|
1091
|
+
},
|
|
1092
|
+
body: {
|
|
1093
|
+
type: "object",
|
|
1094
|
+
additionalProperties: false,
|
|
1095
|
+
required: ["text", "bindingId"],
|
|
1096
|
+
properties: {
|
|
1097
|
+
text: { type: "string", minLength: 1 },
|
|
1098
|
+
bindingId: { type: "string", minLength: 1 },
|
|
1099
|
+
actorId: { type: "string" },
|
|
1100
|
+
replyRouteId: { type: "string" },
|
|
1101
|
+
provider: { type: "string" },
|
|
1102
|
+
upstreamProvider: { type: "string" },
|
|
1103
|
+
providerMessageRef: { type: "string" },
|
|
1104
|
+
correlationId: { type: "string" },
|
|
1105
|
+
causationId: { type: "string" },
|
|
1106
|
+
metadata: jsonObjectSchema,
|
|
1107
|
+
sourceItemId: { type: "string" },
|
|
1108
|
+
deliveryHandle: deliveryHandleSchema,
|
|
1109
|
+
},
|
|
1110
|
+
},
|
|
1111
|
+
response: {
|
|
1112
|
+
200: jsonObjectSchema,
|
|
1113
|
+
400: errorResponseSchema,
|
|
1114
|
+
},
|
|
1115
|
+
},
|
|
1116
|
+
}, async (request) => {
|
|
1117
|
+
const params = request.params;
|
|
1118
|
+
const body = request.body;
|
|
1119
|
+
return service.forwardOperatorIngress(decodeURIComponent(params.agentId), {
|
|
1120
|
+
text: String(body.text),
|
|
1121
|
+
bindingId: String(body.bindingId),
|
|
1122
|
+
actorId: optionalString(body.actorId) ?? null,
|
|
1123
|
+
replyRouteId: optionalString(body.replyRouteId) ?? null,
|
|
1124
|
+
provider: optionalString(body.provider) ?? null,
|
|
1125
|
+
upstreamProvider: optionalString(body.upstreamProvider) ?? null,
|
|
1126
|
+
providerMessageRef: optionalString(body.providerMessageRef) ?? null,
|
|
1127
|
+
correlationId: optionalString(body.correlationId) ?? null,
|
|
1128
|
+
causationId: optionalString(body.causationId) ?? null,
|
|
1129
|
+
metadata: body.metadata && typeof body.metadata === "object" ? body.metadata : undefined,
|
|
1130
|
+
sourceItemId: optionalString(body.sourceItemId) ?? null,
|
|
1131
|
+
deliveryHandle: body.deliveryHandle ? body.deliveryHandle : null,
|
|
1132
|
+
});
|
|
1133
|
+
});
|
|
995
1134
|
app.get("/agents/:agentId/targets", {
|
|
996
1135
|
schema: {
|
|
997
1136
|
tags: ["agents"],
|
|
@@ -1812,6 +1951,52 @@ function buildFastifyServer(service) {
|
|
|
1812
1951
|
},
|
|
1813
1952
|
},
|
|
1814
1953
|
}, async (request) => service.invokeDelivery(request.body));
|
|
1954
|
+
app.post("/delivery-routes/:routeId/messages", {
|
|
1955
|
+
schema: {
|
|
1956
|
+
tags: ["deliveries"],
|
|
1957
|
+
params: {
|
|
1958
|
+
type: "object",
|
|
1959
|
+
required: ["routeId"],
|
|
1960
|
+
properties: {
|
|
1961
|
+
routeId: { type: "string", minLength: 1 },
|
|
1962
|
+
},
|
|
1963
|
+
},
|
|
1964
|
+
body: {
|
|
1965
|
+
type: "object",
|
|
1966
|
+
additionalProperties: false,
|
|
1967
|
+
required: ["text"],
|
|
1968
|
+
properties: {
|
|
1969
|
+
text: { type: "string", minLength: 1 },
|
|
1970
|
+
kind: { type: "string" },
|
|
1971
|
+
targetAgentId: { type: "string" },
|
|
1972
|
+
target_agent_id: { type: "string" },
|
|
1973
|
+
delivery_intent_id: { type: "string" },
|
|
1974
|
+
binding_id: { type: "string" },
|
|
1975
|
+
route_id: { type: "string" },
|
|
1976
|
+
created_at: { type: "string" },
|
|
1977
|
+
correlation_id: { type: ["string", "null"] },
|
|
1978
|
+
causation_id: { type: ["string", "null"] },
|
|
1979
|
+
metadata: jsonObjectSchema,
|
|
1980
|
+
},
|
|
1981
|
+
},
|
|
1982
|
+
response: {
|
|
1983
|
+
200: jsonObjectSchema,
|
|
1984
|
+
400: errorResponseSchema,
|
|
1985
|
+
404: errorResponseSchema,
|
|
1986
|
+
},
|
|
1987
|
+
},
|
|
1988
|
+
}, async (request) => {
|
|
1989
|
+
const params = request.params;
|
|
1990
|
+
const routeId = decodeURIComponent(params.routeId);
|
|
1991
|
+
assertDeliveryRouteAuthorized(service, routeId, request.headers.authorization);
|
|
1992
|
+
const body = request.body;
|
|
1993
|
+
return service.deliverOperatorReply(routeId, {
|
|
1994
|
+
text: String(body.text),
|
|
1995
|
+
kind: optionalString(body.kind) ?? null,
|
|
1996
|
+
targetAgentId: optionalString(body.targetAgentId) ?? optionalString(body.target_agent_id) ?? null,
|
|
1997
|
+
metadata: body.metadata && typeof body.metadata === "object" ? body.metadata : undefined,
|
|
1998
|
+
});
|
|
1999
|
+
});
|
|
1815
2000
|
app.setNotFoundHandler((_request, reply) => {
|
|
1816
2001
|
void reply.code(404).send({ error: "not found" });
|
|
1817
2002
|
});
|
|
@@ -1825,6 +2010,14 @@ function buildFastifyServer(service) {
|
|
|
1825
2010
|
void reply.code(404).send({ error: message });
|
|
1826
2011
|
return;
|
|
1827
2012
|
}
|
|
2013
|
+
if (message.startsWith("missing delivery route authorization")) {
|
|
2014
|
+
void reply.code(401).send({ error: message });
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
if (message.startsWith("invalid delivery route authorization")) {
|
|
2018
|
+
void reply.code(403).send({ error: message });
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
1828
2021
|
if (isBadRequestError(message)) {
|
|
1829
2022
|
void reply.code(400).send({ error: message });
|
|
1830
2023
|
return;
|
|
@@ -1872,6 +2065,23 @@ function optionalString(value) {
|
|
|
1872
2065
|
const trimmed = value.trim();
|
|
1873
2066
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
1874
2067
|
}
|
|
2068
|
+
function assertDeliveryRouteAuthorized(service, routeId, authorization) {
|
|
2069
|
+
const binding = service.getOperatorBindingForReplyRoute(routeId);
|
|
2070
|
+
const deliveryAuth = binding?.deliveryAuth;
|
|
2071
|
+
if (!deliveryAuth) {
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
if (deliveryAuth.type !== "bearer" || !deliveryAuth.token) {
|
|
2075
|
+
throw new Error("invalid delivery route authorization");
|
|
2076
|
+
}
|
|
2077
|
+
if (!authorization) {
|
|
2078
|
+
throw new Error("missing delivery route authorization");
|
|
2079
|
+
}
|
|
2080
|
+
const [scheme, token] = authorization.split(/\s+/, 2);
|
|
2081
|
+
if (scheme?.toLowerCase() !== "bearer" || token !== deliveryAuth.token) {
|
|
2082
|
+
throw new Error("invalid delivery route authorization");
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
1875
2085
|
function parseOptionalPositiveInteger(value) {
|
|
1876
2086
|
if (value === undefined) {
|
|
1877
2087
|
return undefined;
|
|
@@ -1909,6 +2119,9 @@ function isBadRequestError(message) {
|
|
|
1909
2119
|
message.startsWith("deliver send is not supported") ||
|
|
1910
2120
|
message.startsWith("deliveryHandle requires") ||
|
|
1911
2121
|
message.startsWith("delivery operations are not supported") ||
|
|
2122
|
+
message.startsWith("operator binding requires") ||
|
|
2123
|
+
message.startsWith("operator ingress requires") ||
|
|
2124
|
+
message.startsWith("operator reply route requires") ||
|
|
1912
2125
|
message.startsWith("unsupported delivery surface") ||
|
|
1913
2126
|
message.startsWith("unsupported delivery operation") ||
|
|
1914
2127
|
message.startsWith("source operations are not supported") ||
|
package/dist/src/service.js
CHANGED
|
@@ -23,7 +23,7 @@ const DEFAULT_ACKED_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
|
23
23
|
const DEFAULT_OFFLINE_AGENT_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
24
24
|
const DEFAULT_GC_INTERVAL_MS = 60 * 1000;
|
|
25
25
|
const DEFAULT_SYNC_INTERVAL_MS = 2_000;
|
|
26
|
-
const DEFAULT_IDLE_SOURCE_GRACE_MS =
|
|
26
|
+
const DEFAULT_IDLE_SOURCE_GRACE_MS = 30 * 60 * 1000;
|
|
27
27
|
const DEFAULT_INBOX_AGGREGATION_WINDOW_MS = 30 * 1000;
|
|
28
28
|
const DEFAULT_INBOX_AGGREGATION_MAX_ITEMS = 20;
|
|
29
29
|
const DEFAULT_INBOX_AGGREGATION_MAX_THREAD_AGE_MS = 24 * 60 * 60 * 1000;
|
|
@@ -404,6 +404,7 @@ class AgentInboxService {
|
|
|
404
404
|
if (!templateSpec) {
|
|
405
405
|
continue;
|
|
406
406
|
}
|
|
407
|
+
validateFollowTemplateArgs(templateSpec.argsSchema ?? [], templateArgs, templateSpec.templateId);
|
|
407
408
|
await this.adapters.sourceAdapterFor(source.sourceType).validateSource?.(source);
|
|
408
409
|
return { source, templateSpec };
|
|
409
410
|
}
|
|
@@ -1686,6 +1687,144 @@ class AgentInboxService {
|
|
|
1686
1687
|
this.store.insertDelivery(storedAttempt);
|
|
1687
1688
|
return { ...storedAttempt, note: result.note, operation: request.operation };
|
|
1688
1689
|
}
|
|
1690
|
+
async registerOperatorTransportBinding(agentId, input) {
|
|
1691
|
+
if (!input.transport) {
|
|
1692
|
+
throw new Error("operator binding requires transport");
|
|
1693
|
+
}
|
|
1694
|
+
if (!input.operatorActorId) {
|
|
1695
|
+
throw new Error("operator binding requires operatorActorId");
|
|
1696
|
+
}
|
|
1697
|
+
if (!input.holonBaseUrl) {
|
|
1698
|
+
throw new Error("operator binding requires holonBaseUrl");
|
|
1699
|
+
}
|
|
1700
|
+
const now = (0, util_1.nowIso)();
|
|
1701
|
+
const current = input.bindingId ? this.store.getOperatorTransportBinding(input.bindingId) : null;
|
|
1702
|
+
const binding = {
|
|
1703
|
+
bindingId: input.bindingId ?? (0, util_1.generateCanonicalId)("opb"),
|
|
1704
|
+
agentId,
|
|
1705
|
+
transport: input.transport,
|
|
1706
|
+
operatorActorId: input.operatorActorId,
|
|
1707
|
+
holonBaseUrl: input.holonBaseUrl.replace(/\/+$/, ""),
|
|
1708
|
+
deliveryCallbackUrl: input.deliveryCallbackUrl ?? null,
|
|
1709
|
+
deliveryAuth: input.deliveryAuth ?? null,
|
|
1710
|
+
holonAuth: input.holonAuth ?? null,
|
|
1711
|
+
defaultRouteId: input.defaultRouteId ?? null,
|
|
1712
|
+
capabilities: input.capabilities ?? ["prompt"],
|
|
1713
|
+
provider: input.provider ?? null,
|
|
1714
|
+
metadata: input.metadata ?? {},
|
|
1715
|
+
createdAt: current?.createdAt ?? now,
|
|
1716
|
+
updatedAt: now,
|
|
1717
|
+
};
|
|
1718
|
+
const stored = this.store.upsertOperatorTransportBinding(binding);
|
|
1719
|
+
const holon = input.syncWithHolon === false ? undefined : await this.syncOperatorBindingToHolon(stored);
|
|
1720
|
+
return holon === undefined ? stored : { ...stored, holon };
|
|
1721
|
+
}
|
|
1722
|
+
listOperatorTransportBindings(agentId) {
|
|
1723
|
+
return this.store.listOperatorTransportBindings(agentId);
|
|
1724
|
+
}
|
|
1725
|
+
getOperatorBindingForReplyRoute(routeId) {
|
|
1726
|
+
const route = this.store.getOperatorReplyRoute(routeId);
|
|
1727
|
+
return route ? this.store.getOperatorTransportBinding(route.bindingId) : null;
|
|
1728
|
+
}
|
|
1729
|
+
async forwardOperatorIngress(agentId, input) {
|
|
1730
|
+
if (!input.text) {
|
|
1731
|
+
throw new Error("operator ingress requires text");
|
|
1732
|
+
}
|
|
1733
|
+
const binding = this.store.getOperatorTransportBinding(input.bindingId);
|
|
1734
|
+
if (!binding || binding.agentId !== agentId) {
|
|
1735
|
+
throw new Error(`unknown operator binding for agent ${agentId}: ${input.bindingId}`);
|
|
1736
|
+
}
|
|
1737
|
+
const replyRouteId = input.replyRouteId ?? binding.defaultRouteId ?? null;
|
|
1738
|
+
if (replyRouteId && input.deliveryHandle) {
|
|
1739
|
+
const now = (0, util_1.nowIso)();
|
|
1740
|
+
this.store.upsertOperatorReplyRoute({
|
|
1741
|
+
routeId: replyRouteId,
|
|
1742
|
+
bindingId: binding.bindingId,
|
|
1743
|
+
agentId,
|
|
1744
|
+
deliveryHandle: input.deliveryHandle,
|
|
1745
|
+
sourceId: typeof input.metadata?.sourceId === "string" ? input.metadata.sourceId : null,
|
|
1746
|
+
metadata: input.metadata ?? {},
|
|
1747
|
+
createdAt: now,
|
|
1748
|
+
updatedAt: now,
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
const holon = await this.postHolonJson(binding, `/control/agents/${encodeURIComponent(agentId)}/operator-ingress`, {
|
|
1752
|
+
text: input.text,
|
|
1753
|
+
actor_id: input.actorId ?? binding.operatorActorId,
|
|
1754
|
+
binding_id: binding.bindingId,
|
|
1755
|
+
reply_route_id: replyRouteId,
|
|
1756
|
+
provider: input.provider ?? binding.provider ?? binding.transport,
|
|
1757
|
+
upstream_provider: input.upstreamProvider ?? binding.provider ?? binding.transport,
|
|
1758
|
+
provider_message_ref: input.providerMessageRef ?? null,
|
|
1759
|
+
correlation_id: input.correlationId ?? null,
|
|
1760
|
+
causation_id: input.causationId ?? null,
|
|
1761
|
+
metadata: {
|
|
1762
|
+
...(input.metadata ?? {}),
|
|
1763
|
+
source_item_id: input.sourceItemId ?? undefined,
|
|
1764
|
+
},
|
|
1765
|
+
});
|
|
1766
|
+
return {
|
|
1767
|
+
accepted: true,
|
|
1768
|
+
bindingId: binding.bindingId,
|
|
1769
|
+
replyRouteId,
|
|
1770
|
+
holon,
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
async deliverOperatorReply(routeId, input) {
|
|
1774
|
+
if (!input.text) {
|
|
1775
|
+
throw new Error("operator reply route requires text");
|
|
1776
|
+
}
|
|
1777
|
+
const route = this.store.getOperatorReplyRoute(routeId);
|
|
1778
|
+
if (!route) {
|
|
1779
|
+
throw new Error(`unknown operator reply route: ${routeId}`);
|
|
1780
|
+
}
|
|
1781
|
+
return this.sendDelivery({
|
|
1782
|
+
sourceId: route.sourceId ?? undefined,
|
|
1783
|
+
deliveryHandle: route.deliveryHandle,
|
|
1784
|
+
kind: input.kind ?? "reply",
|
|
1785
|
+
payload: {
|
|
1786
|
+
text: input.text,
|
|
1787
|
+
targetAgentId: input.targetAgentId ?? route.agentId,
|
|
1788
|
+
routeId,
|
|
1789
|
+
metadata: input.metadata ?? {},
|
|
1790
|
+
},
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
async syncOperatorBindingToHolon(binding) {
|
|
1794
|
+
if (!binding.defaultRouteId) {
|
|
1795
|
+
throw new Error("operator binding sync requires defaultRouteId");
|
|
1796
|
+
}
|
|
1797
|
+
if (!binding.deliveryCallbackUrl) {
|
|
1798
|
+
throw new Error("operator binding sync requires deliveryCallbackUrl");
|
|
1799
|
+
}
|
|
1800
|
+
return this.postHolonJson(binding, `/control/agents/${encodeURIComponent(binding.agentId)}/operator-bindings`, omitNullish({
|
|
1801
|
+
binding_id: binding.bindingId,
|
|
1802
|
+
transport: binding.transport,
|
|
1803
|
+
operator_actor_id: binding.operatorActorId,
|
|
1804
|
+
default_route_id: binding.defaultRouteId,
|
|
1805
|
+
delivery_callback_url: binding.deliveryCallbackUrl,
|
|
1806
|
+
delivery_auth: holonOperatorDeliveryAuth(binding.deliveryAuth),
|
|
1807
|
+
capabilities: holonOperatorCapabilities(binding.capabilities),
|
|
1808
|
+
provider: binding.provider,
|
|
1809
|
+
metadata: binding.metadata,
|
|
1810
|
+
}));
|
|
1811
|
+
}
|
|
1812
|
+
async postHolonJson(binding, path, payload) {
|
|
1813
|
+
const response = await fetch(`${binding.holonBaseUrl}${path}`, {
|
|
1814
|
+
method: "POST",
|
|
1815
|
+
headers: {
|
|
1816
|
+
"content-type": "application/json",
|
|
1817
|
+
...authHeaders(binding.holonAuth),
|
|
1818
|
+
},
|
|
1819
|
+
body: JSON.stringify(payload),
|
|
1820
|
+
});
|
|
1821
|
+
const text = await response.text();
|
|
1822
|
+
const body = text ? parseJsonResponse(text) : {};
|
|
1823
|
+
if (!response.ok) {
|
|
1824
|
+
throw new Error(`Holon operator route call failed: ${response.status} ${text}`);
|
|
1825
|
+
}
|
|
1826
|
+
return body;
|
|
1827
|
+
}
|
|
1689
1828
|
async sendCanonicalDeliveryViaModule(source, handle, payload, attempt) {
|
|
1690
1829
|
const operations = await this.adapters.listDeliveryOperations(source, handle);
|
|
1691
1830
|
const canonicalOperation = operations?.find((operation) => operation.canonicalTextAlias);
|
|
@@ -2111,6 +2250,14 @@ class AgentInboxService {
|
|
|
2111
2250
|
const pendingState = state?.status === "dirty" ? state : null;
|
|
2112
2251
|
const inbox = this.ensureInboxForAgent(buffer.agentId);
|
|
2113
2252
|
const unackedItems = this.store.listInboxEntries(inbox.inboxId, { includeAcked: false });
|
|
2253
|
+
if (unackedItems.length === 0) {
|
|
2254
|
+
// All buffered items were acked before the flush window expired.
|
|
2255
|
+
// Nothing to dispatch — clean up and skip.
|
|
2256
|
+
this.store.deleteActivationDispatchState(buffer.agentId, buffer.targetId);
|
|
2257
|
+
buffer.inFlight = false;
|
|
2258
|
+
this.notificationBuffers.delete(key);
|
|
2259
|
+
return;
|
|
2260
|
+
}
|
|
2114
2261
|
const dispatched = await this.dispatchActivationTarget({
|
|
2115
2262
|
agentId: buffer.agentId,
|
|
2116
2263
|
targetId: buffer.targetId,
|
|
@@ -2928,6 +3075,24 @@ class AgentInboxService {
|
|
|
2928
3075
|
await this.pollSubscription(subscription.subscriptionId);
|
|
2929
3076
|
}
|
|
2930
3077
|
catch (error) {
|
|
3078
|
+
// Self-heal: if the subscription was deleted between listing and polling
|
|
3079
|
+
// (race with lifecycle cleanup), skip silently.
|
|
3080
|
+
if (!this.store.getSubscription(subscription.subscriptionId)) {
|
|
3081
|
+
continue;
|
|
3082
|
+
}
|
|
3083
|
+
// If the source was deleted but the subscription remains (orphaned),
|
|
3084
|
+
// clean up the subscription to stop repeated errors on every cycle.
|
|
3085
|
+
if (!this.store.getSource(subscription.sourceId)) {
|
|
3086
|
+
try {
|
|
3087
|
+
await this.removeSubscription(subscription.subscriptionId);
|
|
3088
|
+
}
|
|
3089
|
+
catch {
|
|
3090
|
+
this.store.deleteSubscription(subscription.subscriptionId);
|
|
3091
|
+
this.clearSubscriptionRuntimeState(subscription);
|
|
3092
|
+
}
|
|
3093
|
+
console.warn(`subscription ${subscription.subscriptionId} orphaned (source ${subscription.sourceId} no longer exists); removed`);
|
|
3094
|
+
continue;
|
|
3095
|
+
}
|
|
2931
3096
|
console.warn(`subscription poll failed for ${subscription.subscriptionId}:`, error);
|
|
2932
3097
|
}
|
|
2933
3098
|
}
|
|
@@ -3439,6 +3604,26 @@ function providerForSourceType(sourceType) {
|
|
|
3439
3604
|
}
|
|
3440
3605
|
return null;
|
|
3441
3606
|
}
|
|
3607
|
+
function authHeaders(auth) {
|
|
3608
|
+
if (!auth) {
|
|
3609
|
+
return {};
|
|
3610
|
+
}
|
|
3611
|
+
if (auth.type === "bearer" && auth.token) {
|
|
3612
|
+
return { authorization: `Bearer ${auth.token}` };
|
|
3613
|
+
}
|
|
3614
|
+
if (auth.type === "header" && auth.headerName && auth.headerValue) {
|
|
3615
|
+
return { [auth.headerName]: auth.headerValue };
|
|
3616
|
+
}
|
|
3617
|
+
return {};
|
|
3618
|
+
}
|
|
3619
|
+
function parseJsonResponse(text) {
|
|
3620
|
+
try {
|
|
3621
|
+
return JSON.parse(text);
|
|
3622
|
+
}
|
|
3623
|
+
catch {
|
|
3624
|
+
return { text };
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3442
3627
|
class ActivationDispatcher {
|
|
3443
3628
|
async dispatch(targetUrl, activation) {
|
|
3444
3629
|
try {
|
|
@@ -3515,8 +3700,11 @@ function inboxAggregationPolicy(inbox) {
|
|
|
3515
3700
|
};
|
|
3516
3701
|
}
|
|
3517
3702
|
function meetsNotificationThreshold(policy, totalUnackedCount) {
|
|
3703
|
+
if (totalUnackedCount === 0) {
|
|
3704
|
+
return false;
|
|
3705
|
+
}
|
|
3518
3706
|
if (policy.minUnackedItems == null) {
|
|
3519
|
-
return
|
|
3707
|
+
return totalUnackedCount > 0;
|
|
3520
3708
|
}
|
|
3521
3709
|
return totalUnackedCount >= policy.minUnackedItems;
|
|
3522
3710
|
}
|
|
@@ -4045,6 +4233,26 @@ function deliverSendUnsupportedMessage(handle, operations) {
|
|
|
4045
4233
|
const names = operations.map((item) => item.name).join(", ");
|
|
4046
4234
|
return `deliver send is not supported for ${handle.provider}/${handle.surface}; use deliver invoke with one of: ${names}. ${deliveryActionsHelp(handle)}`;
|
|
4047
4235
|
}
|
|
4236
|
+
function omitNullish(record) {
|
|
4237
|
+
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== null && value !== undefined));
|
|
4238
|
+
}
|
|
4239
|
+
function holonOperatorDeliveryAuth(auth) {
|
|
4240
|
+
if (!auth) {
|
|
4241
|
+
throw new Error("operator binding sync requires deliveryAuth");
|
|
4242
|
+
}
|
|
4243
|
+
if (auth.type === "bearer" && auth.token) {
|
|
4244
|
+
return { kind: "bearer", bearer_token: auth.token };
|
|
4245
|
+
}
|
|
4246
|
+
throw new Error("operator binding sync supports only bearer deliveryAuth with a token");
|
|
4247
|
+
}
|
|
4248
|
+
function holonOperatorCapabilities(capabilities) {
|
|
4249
|
+
const normalized = new Set(capabilities.map((capability) => capability.trim().toLowerCase()).filter(Boolean));
|
|
4250
|
+
return {
|
|
4251
|
+
text: normalized.size === 0 || normalized.has("prompt") || normalized.has("text") || normalized.has("send_prompt"),
|
|
4252
|
+
...(normalized.has("markdown") ? { markdown: true } : {}),
|
|
4253
|
+
...(normalized.has("attachments") || normalized.has("attachment") ? { attachments: true } : {}),
|
|
4254
|
+
};
|
|
4255
|
+
}
|
|
4048
4256
|
function deliveryActionsHelp(handle) {
|
|
4049
4257
|
return `Run \`agentinbox deliver actions --provider ${handle.provider} --surface ${handle.surface} --target ${handle.targetRef}\` to inspect supported operations and input schemas.`;
|
|
4050
4258
|
}
|
|
@@ -5,7 +5,6 @@ const uxc_daemon_client_1 = require("@holon-run/uxc-daemon-client");
|
|
|
5
5
|
const paths_1 = require("../paths");
|
|
6
6
|
const util_1 = require("../util");
|
|
7
7
|
const remote_modules_1 = require("./remote_modules");
|
|
8
|
-
const telegram_1 = require("./telegram");
|
|
9
8
|
const REMOTE_SOURCE_TYPES = new Set([
|
|
10
9
|
"remote_source",
|
|
11
10
|
"github_repo",
|
|
@@ -71,14 +70,12 @@ class RemoteSourceRuntime {
|
|
|
71
70
|
errorCounts = new Map();
|
|
72
71
|
nextRetryAt = new Map();
|
|
73
72
|
homeDir;
|
|
74
|
-
telegramClient;
|
|
75
73
|
constructor(store, appendSourceEvent, options) {
|
|
76
74
|
this.store = store;
|
|
77
75
|
this.appendSourceEvent = appendSourceEvent;
|
|
78
76
|
this.moduleRegistry = options?.moduleRegistry ?? new remote_modules_1.RemoteSourceModuleRegistry();
|
|
79
77
|
this.client = options?.client ?? new RpcUxcRemoteSourceClient();
|
|
80
78
|
this.homeDir = options?.homeDir ?? (0, paths_1.resolveAgentInboxHome)(process.env);
|
|
81
|
-
this.telegramClient = options?.telegramClient ?? new telegram_1.TelegramBotApiClient();
|
|
82
79
|
}
|
|
83
80
|
async ensureSource(source) {
|
|
84
81
|
if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
|
|
@@ -180,6 +177,10 @@ class RemoteSourceRuntime {
|
|
|
180
177
|
}
|
|
181
178
|
const binding = managedBindingForSource(source);
|
|
182
179
|
await this.client.sourceDelete(binding.namespace, binding.sourceKey);
|
|
180
|
+
// Clean up in-memory state to prevent stale references after DB deletion
|
|
181
|
+
this.errorCounts.delete(sourceId);
|
|
182
|
+
this.nextRetryAt.delete(sourceId);
|
|
183
|
+
this.inFlight.delete(sourceId);
|
|
183
184
|
}
|
|
184
185
|
status() {
|
|
185
186
|
return {
|
|
@@ -385,9 +386,6 @@ class RemoteSourceRuntime {
|
|
|
385
386
|
};
|
|
386
387
|
}
|
|
387
388
|
}
|
|
388
|
-
if (source.sourceType === "telegram_bot") {
|
|
389
|
-
return await this.syncTelegramSource(source);
|
|
390
|
-
}
|
|
391
389
|
const module = await this.moduleRegistry.resolve(source, this.homeDir);
|
|
392
390
|
const moduleSource = moduleInputSource(source);
|
|
393
391
|
module.validateConfig(moduleSource);
|
|
@@ -482,70 +480,6 @@ class RemoteSourceRuntime {
|
|
|
482
480
|
this.inFlight.delete(sourceId);
|
|
483
481
|
}
|
|
484
482
|
}
|
|
485
|
-
async syncTelegramSource(source) {
|
|
486
|
-
const config = (0, telegram_1.parseTelegramSourceConfig)(source);
|
|
487
|
-
const botToken = config.botToken ?? tokenFromEnv(config.tokenEnv);
|
|
488
|
-
if (!botToken) {
|
|
489
|
-
throw new Error("Telegram source requires config.botToken or tokenEnv");
|
|
490
|
-
}
|
|
491
|
-
const checkpoint = parseRemoteCheckpoint(source.checkpoint);
|
|
492
|
-
const updates = await this.telegramClient.getUpdates({
|
|
493
|
-
endpoint: config.endpoint,
|
|
494
|
-
botToken,
|
|
495
|
-
offset: checkpoint.telegramCursor?.nextOffset,
|
|
496
|
-
timeout: 0,
|
|
497
|
-
allowedUpdates: config.allowedUpdates,
|
|
498
|
-
});
|
|
499
|
-
let appended = 0;
|
|
500
|
-
let deduped = 0;
|
|
501
|
-
let nextOffset = checkpoint.telegramCursor?.nextOffset ?? 0;
|
|
502
|
-
for (const update of updates) {
|
|
503
|
-
const normalized = (0, telegram_1.normalizeTelegramBotUpdate)(source, config, update);
|
|
504
|
-
const updateId = numberFromUnknown(update.update_id);
|
|
505
|
-
if (updateId !== undefined) {
|
|
506
|
-
nextOffset = Math.max(nextOffset, updateId + 1);
|
|
507
|
-
}
|
|
508
|
-
if (!normalized) {
|
|
509
|
-
continue;
|
|
510
|
-
}
|
|
511
|
-
const result = await this.appendSourceEvent(normalized);
|
|
512
|
-
appended += result.appended;
|
|
513
|
-
deduped += result.deduped;
|
|
514
|
-
}
|
|
515
|
-
const latestSource = this.store.getSource(source.sourceId);
|
|
516
|
-
if (!latestSource) {
|
|
517
|
-
throw new Error(`unknown source: ${source.sourceId}`);
|
|
518
|
-
}
|
|
519
|
-
const observedAt = (0, util_1.nowIso)();
|
|
520
|
-
this.store.updateSourceRuntime(source.sourceId, {
|
|
521
|
-
status: latestSource.status === "paused" ? "paused" : "active",
|
|
522
|
-
checkpoint: JSON.stringify({
|
|
523
|
-
...checkpoint,
|
|
524
|
-
telegramCursor: { nextOffset },
|
|
525
|
-
managedRuntime: {
|
|
526
|
-
status: "running",
|
|
527
|
-
runId: null,
|
|
528
|
-
streamId: null,
|
|
529
|
-
lastError: null,
|
|
530
|
-
updatedAt: observedAt,
|
|
531
|
-
startedAt: checkpoint.managedRuntime?.startedAt ?? observedAt,
|
|
532
|
-
stoppedAt: null,
|
|
533
|
-
},
|
|
534
|
-
lastEventAt: observedAt,
|
|
535
|
-
lastError: null,
|
|
536
|
-
}),
|
|
537
|
-
});
|
|
538
|
-
this.errorCounts.delete(source.sourceId);
|
|
539
|
-
this.nextRetryAt.delete(source.sourceId);
|
|
540
|
-
return {
|
|
541
|
-
sourceId: source.sourceId,
|
|
542
|
-
sourceType: source.sourceType,
|
|
543
|
-
appended,
|
|
544
|
-
deduped,
|
|
545
|
-
eventsRead: updates.length,
|
|
546
|
-
note: "telegram getUpdates poll completed",
|
|
547
|
-
};
|
|
548
|
-
}
|
|
549
483
|
}
|
|
550
484
|
exports.RemoteSourceRuntime = RemoteSourceRuntime;
|
|
551
485
|
function normalizeExpandedSubscriptionPlan(value) {
|
|
@@ -601,25 +535,6 @@ function managedBindingForSource(source) {
|
|
|
601
535
|
function managedSourceBindingKey(namespace, sourceKey) {
|
|
602
536
|
return `${namespace}:${sourceKey}`;
|
|
603
537
|
}
|
|
604
|
-
function tokenFromEnv(name) {
|
|
605
|
-
if (!name) {
|
|
606
|
-
return null;
|
|
607
|
-
}
|
|
608
|
-
const value = process.env[name];
|
|
609
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
610
|
-
}
|
|
611
|
-
function numberFromUnknown(value) {
|
|
612
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
613
|
-
return value;
|
|
614
|
-
}
|
|
615
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
616
|
-
const parsed = Number(value);
|
|
617
|
-
if (Number.isFinite(parsed)) {
|
|
618
|
-
return parsed;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
return undefined;
|
|
622
|
-
}
|
|
623
538
|
function parseRemoteCheckpoint(checkpoint) {
|
|
624
539
|
if (!checkpoint) {
|
|
625
540
|
return {};
|
|
@@ -565,6 +565,7 @@ const TELEGRAM_BOT_MODULE = {
|
|
|
565
565
|
configSchema: [
|
|
566
566
|
{ name: "botToken", type: "string", required: false, description: "Telegram bot token. Prefer tokenEnv for shared/local configuration." },
|
|
567
567
|
{ name: "tokenEnv", type: "string", required: false, description: "Environment variable name containing the Telegram bot token." },
|
|
568
|
+
{ name: "uxcAuth", type: "string", required: false, description: "Optional UXC Telegram auth profile." },
|
|
568
569
|
{ name: "botUsername", type: "string", required: false, description: "Optional bot username used for source host identity." },
|
|
569
570
|
{ name: "chatIds", type: "string[]", required: false, description: "Optional Telegram chat ID allowlist." },
|
|
570
571
|
{ name: "allowedUpdates", type: "string[]", required: false, description: "Optional Telegram Bot API allowed_updates value." },
|
|
@@ -591,13 +592,10 @@ const TELEGRAM_BOT_MODULE = {
|
|
|
591
592
|
const config = (0, telegram_1.parseTelegramSourceConfig)(source);
|
|
592
593
|
return {
|
|
593
594
|
endpoint: config.endpoint ?? telegram_1.TELEGRAM_BOT_API_ENDPOINT,
|
|
594
|
-
operation_id: "getUpdates",
|
|
595
|
+
operation_id: "post:/getUpdates",
|
|
595
596
|
mode: "poll",
|
|
596
597
|
args: {
|
|
597
598
|
timeout: 5,
|
|
598
|
-
...(config.botToken ? { botToken: config.botToken } : {}),
|
|
599
|
-
...(config.tokenEnv ? { tokenEnv: config.tokenEnv } : {}),
|
|
600
|
-
...(config.chatIds && config.chatIds.length > 0 ? { chatIds: config.chatIds } : {}),
|
|
601
599
|
...(config.allowedUpdates && config.allowedUpdates.length > 0 ? { allowed_updates: config.allowedUpdates } : {}),
|
|
602
600
|
},
|
|
603
601
|
poll_config: {
|
|
@@ -611,6 +609,10 @@ const TELEGRAM_BOT_MODULE = {
|
|
|
611
609
|
item_key_pointer: "/update_id",
|
|
612
610
|
},
|
|
613
611
|
},
|
|
612
|
+
options: {
|
|
613
|
+
auth: config.uxcAuth,
|
|
614
|
+
artifact_compaction: false,
|
|
615
|
+
},
|
|
614
616
|
};
|
|
615
617
|
},
|
|
616
618
|
mapRawEvent(rawPayload, source) {
|
|
@@ -203,6 +203,7 @@ function parseTelegramSourceConfig(source) {
|
|
|
203
203
|
const config = source.config ?? {};
|
|
204
204
|
return {
|
|
205
205
|
endpoint: asString(config.endpoint) ?? asString(config.apiBaseUrl) ?? exports.TELEGRAM_BOT_API_ENDPOINT,
|
|
206
|
+
uxcAuth: asString(config.uxcAuth) ?? asString(config.credentialRef) ?? undefined,
|
|
206
207
|
botToken: asString(config.botToken) ?? undefined,
|
|
207
208
|
tokenEnv: asString(config.tokenEnv) ?? undefined,
|
|
208
209
|
botUsername: asString(config.botUsername) ?? undefined,
|
package/dist/src/store.js
CHANGED
|
@@ -1514,6 +1514,85 @@ class AgentInboxStore {
|
|
|
1514
1514
|
const rows = this.getAll("select * from deliveries order by created_at desc");
|
|
1515
1515
|
return rows.map((row) => this.mapDelivery(row));
|
|
1516
1516
|
}
|
|
1517
|
+
upsertOperatorTransportBinding(binding) {
|
|
1518
|
+
this.run(`
|
|
1519
|
+
insert into operator_transport_bindings (
|
|
1520
|
+
binding_id, agent_id, transport, operator_actor_id, holon_base_url,
|
|
1521
|
+
delivery_callback_url, delivery_auth_json, holon_auth_json, default_route_id,
|
|
1522
|
+
capabilities_json, provider, metadata_json, created_at, updated_at
|
|
1523
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1524
|
+
on conflict(binding_id) do update set
|
|
1525
|
+
agent_id = excluded.agent_id,
|
|
1526
|
+
transport = excluded.transport,
|
|
1527
|
+
operator_actor_id = excluded.operator_actor_id,
|
|
1528
|
+
holon_base_url = excluded.holon_base_url,
|
|
1529
|
+
delivery_callback_url = excluded.delivery_callback_url,
|
|
1530
|
+
delivery_auth_json = excluded.delivery_auth_json,
|
|
1531
|
+
holon_auth_json = excluded.holon_auth_json,
|
|
1532
|
+
default_route_id = excluded.default_route_id,
|
|
1533
|
+
capabilities_json = excluded.capabilities_json,
|
|
1534
|
+
provider = excluded.provider,
|
|
1535
|
+
metadata_json = excluded.metadata_json,
|
|
1536
|
+
updated_at = excluded.updated_at
|
|
1537
|
+
`, [
|
|
1538
|
+
binding.bindingId,
|
|
1539
|
+
binding.agentId,
|
|
1540
|
+
binding.transport,
|
|
1541
|
+
binding.operatorActorId,
|
|
1542
|
+
binding.holonBaseUrl,
|
|
1543
|
+
binding.deliveryCallbackUrl ?? null,
|
|
1544
|
+
binding.deliveryAuth ? JSON.stringify(binding.deliveryAuth) : null,
|
|
1545
|
+
binding.holonAuth ? JSON.stringify(binding.holonAuth) : null,
|
|
1546
|
+
binding.defaultRouteId ?? null,
|
|
1547
|
+
JSON.stringify(binding.capabilities),
|
|
1548
|
+
binding.provider ?? null,
|
|
1549
|
+
JSON.stringify(binding.metadata),
|
|
1550
|
+
binding.createdAt,
|
|
1551
|
+
binding.updatedAt,
|
|
1552
|
+
]);
|
|
1553
|
+
this.persist();
|
|
1554
|
+
return this.getOperatorTransportBinding(binding.bindingId);
|
|
1555
|
+
}
|
|
1556
|
+
getOperatorTransportBinding(bindingId) {
|
|
1557
|
+
const row = this.getOne("select * from operator_transport_bindings where binding_id = ?", [bindingId]);
|
|
1558
|
+
return row ? this.mapOperatorTransportBinding(row) : null;
|
|
1559
|
+
}
|
|
1560
|
+
listOperatorTransportBindings(agentId) {
|
|
1561
|
+
const rows = agentId
|
|
1562
|
+
? this.getAll("select * from operator_transport_bindings where agent_id = ? order by created_at asc", [agentId])
|
|
1563
|
+
: this.getAll("select * from operator_transport_bindings order by created_at asc");
|
|
1564
|
+
return rows.map((row) => this.mapOperatorTransportBinding(row));
|
|
1565
|
+
}
|
|
1566
|
+
upsertOperatorReplyRoute(route) {
|
|
1567
|
+
this.run(`
|
|
1568
|
+
insert into operator_reply_routes (
|
|
1569
|
+
route_id, binding_id, agent_id, source_id, delivery_handle_json,
|
|
1570
|
+
metadata_json, created_at, updated_at
|
|
1571
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1572
|
+
on conflict(route_id) do update set
|
|
1573
|
+
binding_id = excluded.binding_id,
|
|
1574
|
+
agent_id = excluded.agent_id,
|
|
1575
|
+
source_id = excluded.source_id,
|
|
1576
|
+
delivery_handle_json = excluded.delivery_handle_json,
|
|
1577
|
+
metadata_json = excluded.metadata_json,
|
|
1578
|
+
updated_at = excluded.updated_at
|
|
1579
|
+
`, [
|
|
1580
|
+
route.routeId,
|
|
1581
|
+
route.bindingId,
|
|
1582
|
+
route.agentId,
|
|
1583
|
+
route.sourceId ?? null,
|
|
1584
|
+
JSON.stringify(route.deliveryHandle),
|
|
1585
|
+
JSON.stringify(route.metadata),
|
|
1586
|
+
route.createdAt,
|
|
1587
|
+
route.updatedAt,
|
|
1588
|
+
]);
|
|
1589
|
+
this.persist();
|
|
1590
|
+
return this.getOperatorReplyRoute(route.routeId);
|
|
1591
|
+
}
|
|
1592
|
+
getOperatorReplyRoute(routeId) {
|
|
1593
|
+
const row = this.getOne("select * from operator_reply_routes where route_id = ?", [routeId]);
|
|
1594
|
+
return row ? this.mapOperatorReplyRoute(row) : null;
|
|
1595
|
+
}
|
|
1517
1596
|
insertStream(stream) {
|
|
1518
1597
|
this.run("insert into streams (stream_id, source_id, stream_key, backend, created_at) values (?, ?, ?, ?, ?)", [stream.streamId, stream.sourceId, stream.streamKey, stream.backend, stream.createdAt]);
|
|
1519
1598
|
this.persist();
|
|
@@ -2030,6 +2109,36 @@ class AgentInboxStore {
|
|
|
2030
2109
|
createdAt: String(row.created_at),
|
|
2031
2110
|
};
|
|
2032
2111
|
}
|
|
2112
|
+
mapOperatorTransportBinding(row) {
|
|
2113
|
+
return {
|
|
2114
|
+
bindingId: String(row.binding_id),
|
|
2115
|
+
agentId: String(row.agent_id),
|
|
2116
|
+
transport: String(row.transport),
|
|
2117
|
+
operatorActorId: String(row.operator_actor_id),
|
|
2118
|
+
holonBaseUrl: String(row.holon_base_url),
|
|
2119
|
+
deliveryCallbackUrl: row.delivery_callback_url ? String(row.delivery_callback_url) : null,
|
|
2120
|
+
deliveryAuth: row.delivery_auth_json ? parseJson(row.delivery_auth_json) : null,
|
|
2121
|
+
holonAuth: row.holon_auth_json ? parseJson(row.holon_auth_json) : null,
|
|
2122
|
+
defaultRouteId: row.default_route_id ? String(row.default_route_id) : null,
|
|
2123
|
+
capabilities: parseJson(row.capabilities_json),
|
|
2124
|
+
provider: row.provider ? String(row.provider) : null,
|
|
2125
|
+
metadata: parseJson(row.metadata_json),
|
|
2126
|
+
createdAt: String(row.created_at),
|
|
2127
|
+
updatedAt: String(row.updated_at),
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
mapOperatorReplyRoute(row) {
|
|
2131
|
+
return {
|
|
2132
|
+
routeId: String(row.route_id),
|
|
2133
|
+
bindingId: String(row.binding_id),
|
|
2134
|
+
agentId: String(row.agent_id),
|
|
2135
|
+
sourceId: row.source_id ? String(row.source_id) : null,
|
|
2136
|
+
deliveryHandle: parseJson(row.delivery_handle_json),
|
|
2137
|
+
metadata: parseJson(row.metadata_json),
|
|
2138
|
+
createdAt: String(row.created_at),
|
|
2139
|
+
updatedAt: String(row.updated_at),
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2033
2142
|
resolveInboxEntryId(inboxId, refOrItemId) {
|
|
2034
2143
|
try {
|
|
2035
2144
|
return (0, util_1.parseEntryRef)(refOrItemId);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
CREATE TABLE `operator_transport_bindings` (
|
|
2
|
+
`binding_id` text PRIMARY KEY NOT NULL,
|
|
3
|
+
`agent_id` text NOT NULL,
|
|
4
|
+
`transport` text NOT NULL,
|
|
5
|
+
`operator_actor_id` text NOT NULL,
|
|
6
|
+
`holon_base_url` text NOT NULL,
|
|
7
|
+
`delivery_callback_url` text,
|
|
8
|
+
`delivery_auth_json` text,
|
|
9
|
+
`holon_auth_json` text,
|
|
10
|
+
`default_route_id` text,
|
|
11
|
+
`capabilities_json` text NOT NULL,
|
|
12
|
+
`provider` text,
|
|
13
|
+
`metadata_json` text NOT NULL,
|
|
14
|
+
`created_at` text NOT NULL,
|
|
15
|
+
`updated_at` text NOT NULL
|
|
16
|
+
);
|
|
17
|
+
CREATE INDEX `idx_operator_bindings_agent_transport` ON `operator_transport_bindings` (`agent_id`,`transport`);
|
|
18
|
+
|
|
19
|
+
CREATE TABLE `operator_reply_routes` (
|
|
20
|
+
`route_id` text PRIMARY KEY NOT NULL,
|
|
21
|
+
`binding_id` text NOT NULL,
|
|
22
|
+
`agent_id` text NOT NULL,
|
|
23
|
+
`source_id` text,
|
|
24
|
+
`delivery_handle_json` text NOT NULL,
|
|
25
|
+
`metadata_json` text NOT NULL,
|
|
26
|
+
`created_at` text NOT NULL,
|
|
27
|
+
`updated_at` text NOT NULL
|
|
28
|
+
);
|
|
29
|
+
CREATE INDEX `idx_operator_reply_routes_binding` ON `operator_reply_routes` (`binding_id`);
|
package/drizzle/schema.ts
CHANGED
|
@@ -282,6 +282,38 @@ export const deliveries = sqliteTable("deliveries", {
|
|
|
282
282
|
createdAt: text("created_at").notNull(),
|
|
283
283
|
});
|
|
284
284
|
|
|
285
|
+
export const operatorTransportBindings = sqliteTable("operator_transport_bindings", {
|
|
286
|
+
bindingId: text("binding_id").primaryKey(),
|
|
287
|
+
agentId: text("agent_id").notNull(),
|
|
288
|
+
transport: text("transport").notNull(),
|
|
289
|
+
operatorActorId: text("operator_actor_id").notNull(),
|
|
290
|
+
holonBaseUrl: text("holon_base_url").notNull(),
|
|
291
|
+
deliveryCallbackUrl: text("delivery_callback_url"),
|
|
292
|
+
deliveryAuthJson: text("delivery_auth_json"),
|
|
293
|
+
holonAuthJson: text("holon_auth_json"),
|
|
294
|
+
defaultRouteId: text("default_route_id"),
|
|
295
|
+
capabilitiesJson: text("capabilities_json").notNull(),
|
|
296
|
+
provider: text("provider"),
|
|
297
|
+
metadataJson: text("metadata_json").notNull(),
|
|
298
|
+
createdAt: text("created_at").notNull(),
|
|
299
|
+
updatedAt: text("updated_at").notNull(),
|
|
300
|
+
}, (table) => ({
|
|
301
|
+
agentTransportIdx: index("idx_operator_bindings_agent_transport").on(table.agentId, table.transport),
|
|
302
|
+
}));
|
|
303
|
+
|
|
304
|
+
export const operatorReplyRoutes = sqliteTable("operator_reply_routes", {
|
|
305
|
+
routeId: text("route_id").primaryKey(),
|
|
306
|
+
bindingId: text("binding_id").notNull(),
|
|
307
|
+
agentId: text("agent_id").notNull(),
|
|
308
|
+
sourceId: text("source_id"),
|
|
309
|
+
deliveryHandleJson: text("delivery_handle_json").notNull(),
|
|
310
|
+
metadataJson: text("metadata_json").notNull(),
|
|
311
|
+
createdAt: text("created_at").notNull(),
|
|
312
|
+
updatedAt: text("updated_at").notNull(),
|
|
313
|
+
}, (table) => ({
|
|
314
|
+
bindingIdx: index("idx_operator_reply_routes_binding").on(table.bindingId),
|
|
315
|
+
}));
|
|
316
|
+
|
|
285
317
|
export const streams = sqliteTable("streams", {
|
|
286
318
|
streamId: text("stream_id").primaryKey(),
|
|
287
319
|
sourceId: text("source_id").notNull(),
|