negotium 0.2.21 → 0.2.23
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/agent-helpers.js +2 -2
- package/dist/agent-helpers.js.map +2 -2
- package/dist/hosted-agent.js +2 -2
- package/dist/hosted-agent.js.map +2 -2
- package/dist/main.js +644 -2089
- package/dist/main.js.map +21 -24
- package/dist/mcp-factories.js +2 -2
- package/dist/mcp-factories.js.map +2 -2
- package/dist/registry.js +2 -2
- package/dist/registry.js.map +2 -2
- package/dist/runtime/src/node-host.ts +1 -1
- package/dist/runtime/src/version.ts +1 -1
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1867,7 +1867,7 @@ var exports_version = {};
|
|
|
1867
1867
|
__export(exports_version, {
|
|
1868
1868
|
NEGOTIUM_VERSION: () => NEGOTIUM_VERSION
|
|
1869
1869
|
});
|
|
1870
|
-
var NEGOTIUM_VERSION = "0.2.
|
|
1870
|
+
var NEGOTIUM_VERSION = "0.2.23";
|
|
1871
1871
|
|
|
1872
1872
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
1873
1873
|
import { spawn } from "child_process";
|
|
@@ -22842,6 +22842,7 @@ __export(exports_node_host, {
|
|
|
22842
22842
|
killOwnedCodexTreesForShutdown: () => killOwnedCodexTreesForShutdown,
|
|
22843
22843
|
killAllPlaywright: () => killAllPlaywright,
|
|
22844
22844
|
killAllBgBash: () => killAllBgBash,
|
|
22845
|
+
isTopicShared: () => isTopicShared,
|
|
22845
22846
|
isParticipant: () => isParticipant,
|
|
22846
22847
|
getVisibleTopics: () => getVisibleTopics,
|
|
22847
22848
|
getTopicStats: () => getTopicStats,
|
|
@@ -29440,7 +29441,9 @@ function createNodeControlHandler(options) {
|
|
|
29440
29441
|
"turn-submit-idempotent",
|
|
29441
29442
|
"turn-events-sse-resume",
|
|
29442
29443
|
"canonical-topic-read",
|
|
29443
|
-
"canonical-message-read"
|
|
29444
|
+
"canonical-message-read",
|
|
29445
|
+
"canonical-topic-list",
|
|
29446
|
+
"canonical-topic-create"
|
|
29444
29447
|
],
|
|
29445
29448
|
cursor: latestRuntimeEventSeq()
|
|
29446
29449
|
});
|
|
@@ -29504,6 +29507,38 @@ function createNodeControlHandler(options) {
|
|
|
29504
29507
|
});
|
|
29505
29508
|
return Response.json({ ok: true, v: NODE_RUNTIME_CONTRACT_VERSION, ...result });
|
|
29506
29509
|
}
|
|
29510
|
+
if (req.method === "GET" && runtimePath === "/topics") {
|
|
29511
|
+
const accessMode = url.searchParams.get("accessMode")?.trim();
|
|
29512
|
+
if (accessMode && accessMode !== "shared" && accessMode !== "private") {
|
|
29513
|
+
return jsonError(400, "accessMode must be 'shared' or 'private'");
|
|
29514
|
+
}
|
|
29515
|
+
const topics = getVisibleTopics().filter((topic) => accessMode === "shared" ? isTopicShared(topic) : accessMode === "private" ? !isTopicShared(topic) : true);
|
|
29516
|
+
return Response.json({
|
|
29517
|
+
ok: true,
|
|
29518
|
+
v: NODE_RUNTIME_CONTRACT_VERSION,
|
|
29519
|
+
topics,
|
|
29520
|
+
cursor: latestRuntimeEventSeq()
|
|
29521
|
+
});
|
|
29522
|
+
}
|
|
29523
|
+
if (req.method === "POST" && runtimePath === "/topics") {
|
|
29524
|
+
const body = await bodyRecord(req);
|
|
29525
|
+
if (body.v !== NODE_RUNTIME_CONTRACT_VERSION)
|
|
29526
|
+
return jsonError(400, "Unsupported v");
|
|
29527
|
+
const userId = requiredText(body.userId, "userId");
|
|
29528
|
+
const title = requiredText(body.title, "title");
|
|
29529
|
+
const agent = body.agent;
|
|
29530
|
+
if (agent !== undefined && !["claude", "codex", "maestro"].includes(String(agent))) {
|
|
29531
|
+
return jsonError(400, "Invalid agent");
|
|
29532
|
+
}
|
|
29533
|
+
const topic = topicService.create({
|
|
29534
|
+
title,
|
|
29535
|
+
userId,
|
|
29536
|
+
kind: "agent",
|
|
29537
|
+
accessMode: "shared",
|
|
29538
|
+
...agent ? { agent } : {}
|
|
29539
|
+
});
|
|
29540
|
+
return Response.json({ ok: true, v: NODE_RUNTIME_CONTRACT_VERSION, topic }, { status: 201 });
|
|
29541
|
+
}
|
|
29507
29542
|
const runtimeTopicMatch = runtimePath.match(/^\/topics\/([^/]+)$/);
|
|
29508
29543
|
if (runtimeTopicMatch && req.method === "GET") {
|
|
29509
29544
|
const topic = getTopic(decodeURIComponent(runtimeTopicMatch[1]));
|
|
@@ -41424,18 +41459,30 @@ var init_cli2 = __esm(async () => {
|
|
|
41424
41459
|
if (false) {}
|
|
41425
41460
|
});
|
|
41426
41461
|
|
|
41427
|
-
// ../../
|
|
41428
|
-
var
|
|
41429
|
-
|
|
41430
|
-
|
|
41431
|
-
|
|
41432
|
-
|
|
41433
|
-
|
|
41434
|
-
|
|
41435
|
-
|
|
41436
|
-
|
|
41437
|
-
|
|
41462
|
+
// ../../packages/core/src/config-public.ts
|
|
41463
|
+
var init_config_public = __esm(() => {
|
|
41464
|
+
init_config();
|
|
41465
|
+
});
|
|
41466
|
+
|
|
41467
|
+
// ../../adapters/otium/src/join-status.ts
|
|
41468
|
+
var exports_join_status = {};
|
|
41469
|
+
__export(exports_join_status, {
|
|
41470
|
+
hasConfiguredOtiumJoin: () => hasConfiguredOtiumJoin
|
|
41471
|
+
});
|
|
41472
|
+
import { existsSync as existsSync36 } from "fs";
|
|
41473
|
+
import { resolve as resolve23 } from "path";
|
|
41474
|
+
function hasConfiguredOtiumJoin() {
|
|
41475
|
+
const envJoin = Boolean(process.env.OTIUM_CENTRAL_URL?.trim() && process.env.OTIUM_CELL_ID?.trim() && process.env.OTIUM_CELL_SECRET?.trim());
|
|
41476
|
+
return envJoin || existsSync36(resolve23(DATA_DIR, "otium-join.json"));
|
|
41477
|
+
}
|
|
41478
|
+
var init_join_status = __esm(() => {
|
|
41479
|
+
init_config_public();
|
|
41438
41480
|
});
|
|
41481
|
+
|
|
41482
|
+
// ../../adapters/otium/src/control-protocol.ts
|
|
41483
|
+
var OTIUM_ADAPTER_CONTROL_PREFIX = "/api/v1/adapter/otium", OTIUM_ADAPTER_CONTROL_HEADER = "x-negotium-adapter-token";
|
|
41484
|
+
|
|
41485
|
+
// ../../adapters/otium/src/central.ts
|
|
41439
41486
|
function configureOtiumCentral(join42) {
|
|
41440
41487
|
joinConfig = join42;
|
|
41441
41488
|
resetPeerCentralCaches();
|
|
@@ -41540,418 +41587,14 @@ var init_central = __esm(async () => {
|
|
|
41540
41587
|
tokenCache = new Map;
|
|
41541
41588
|
});
|
|
41542
41589
|
|
|
41543
|
-
// ../../adapters/otium/src/secure-transport.ts
|
|
41544
|
-
function isLoopbackHostname(hostname2) {
|
|
41545
|
-
const normalized = hostname2.toLowerCase();
|
|
41546
|
-
if (normalized === "localhost" || normalized === "::1" || normalized === "[::1]")
|
|
41547
|
-
return true;
|
|
41548
|
-
const octets = normalized.split(".");
|
|
41549
|
-
return octets.length === 4 && octets[0] === "127" && octets.every((octet) => /^\d{1,3}$/.test(octet) && Number(octet) <= 255);
|
|
41550
|
-
}
|
|
41551
|
-
function credentialTransportUrl(value, label) {
|
|
41552
|
-
let url;
|
|
41553
|
-
try {
|
|
41554
|
-
url = new URL(value);
|
|
41555
|
-
} catch {
|
|
41556
|
-
throw new Error(`${label} is not a valid URL`);
|
|
41557
|
-
}
|
|
41558
|
-
if (url.username || url.password)
|
|
41559
|
-
throw new Error(`${label} must not contain URL credentials`);
|
|
41560
|
-
return url;
|
|
41561
|
-
}
|
|
41562
|
-
function assertSecureCentralUrl(value) {
|
|
41563
|
-
const url = credentialTransportUrl(value, "Otium central URL");
|
|
41564
|
-
if (url.protocol === "https:")
|
|
41565
|
-
return;
|
|
41566
|
-
if (url.protocol === "http:" && isLoopbackHostname(url.hostname))
|
|
41567
|
-
return;
|
|
41568
|
-
throw new Error("Otium central requires HTTPS or loopback HTTP");
|
|
41569
|
-
}
|
|
41570
|
-
function assertSecureRelayUrl(value) {
|
|
41571
|
-
const url = credentialTransportUrl(value, "Otium relay URL");
|
|
41572
|
-
if (url.protocol === "https:" || url.protocol === "wss:")
|
|
41573
|
-
return;
|
|
41574
|
-
if ((url.protocol === "http:" || url.protocol === "ws:") && isLoopbackHostname(url.hostname)) {
|
|
41575
|
-
return;
|
|
41576
|
-
}
|
|
41577
|
-
throw new Error("Otium relay requires HTTPS/WSS or loopback HTTP/WS");
|
|
41578
|
-
}
|
|
41579
|
-
|
|
41580
|
-
// ../../adapters/otium/src/join.ts
|
|
41581
|
-
var exports_join = {};
|
|
41582
|
-
__export(exports_join, {
|
|
41583
|
-
withJoinCredentialLock: () => withJoinCredentialLock,
|
|
41584
|
-
saveJoinWhileLocked: () => saveJoinWhileLocked,
|
|
41585
|
-
saveJoin: () => saveJoin,
|
|
41586
|
-
removeJoin: () => removeJoin,
|
|
41587
|
-
parseInviteCode: () => parseInviteCode,
|
|
41588
|
-
loadJoin: () => loadJoin,
|
|
41589
|
-
joinFilePath: () => joinFilePath,
|
|
41590
|
-
joinCredentialDigest: () => joinCredentialDigest,
|
|
41591
|
-
isJoinPersisted: () => isJoinPersisted
|
|
41592
|
-
});
|
|
41593
|
-
import { createHash as createHash11, randomUUID as randomUUID27 } from "crypto";
|
|
41594
|
-
import {
|
|
41595
|
-
chmodSync as chmodSync7,
|
|
41596
|
-
closeSync as closeSync5,
|
|
41597
|
-
existsSync as existsSync36,
|
|
41598
|
-
fsyncSync as fsyncSync2,
|
|
41599
|
-
linkSync,
|
|
41600
|
-
lstatSync,
|
|
41601
|
-
mkdirSync as mkdirSync32,
|
|
41602
|
-
openSync as openSync5,
|
|
41603
|
-
readFileSync as readFileSync28,
|
|
41604
|
-
renameSync as renameSync16,
|
|
41605
|
-
rmSync as rmSync10,
|
|
41606
|
-
statSync as statSync20,
|
|
41607
|
-
unlinkSync as unlinkSync23,
|
|
41608
|
-
writeFileSync as writeFileSync23
|
|
41609
|
-
} from "fs";
|
|
41610
|
-
import { dirname as dirname21, resolve as resolve23 } from "path";
|
|
41611
|
-
function joinFilePath() {
|
|
41612
|
-
return resolve23(DATA_DIR, "otium-join.json");
|
|
41613
|
-
}
|
|
41614
|
-
function isHttpUrl(value) {
|
|
41615
|
-
return /^https?:\/\//.test(value);
|
|
41616
|
-
}
|
|
41617
|
-
function isRelayUrl(value) {
|
|
41618
|
-
return /^(?:https?|wss?):\/\//.test(value);
|
|
41619
|
-
}
|
|
41620
|
-
function normalizeJoin(raw) {
|
|
41621
|
-
const central = typeof raw.central === "string" ? raw.central.trim().replace(/\/+$/, "") : "";
|
|
41622
|
-
const relay = typeof raw.relay === "string" ? raw.relay.trim().replace(/\/+$/, "") : "";
|
|
41623
|
-
const cellId = typeof raw.cellId === "string" ? raw.cellId.trim() : "";
|
|
41624
|
-
const secret = typeof raw.secret === "string" ? raw.secret.trim() : "";
|
|
41625
|
-
if (!central || !isHttpUrl(central)) {
|
|
41626
|
-
throw new Error("invite code is missing a valid http(s) central URL");
|
|
41627
|
-
}
|
|
41628
|
-
if (relay && !isRelayUrl(relay))
|
|
41629
|
-
throw new Error("invite code has an invalid relay URL");
|
|
41630
|
-
if (!cellId)
|
|
41631
|
-
throw new Error("invite code is missing cellId");
|
|
41632
|
-
if (!secret)
|
|
41633
|
-
throw new Error("invite code is missing secret");
|
|
41634
|
-
assertSecureCentralUrl(central);
|
|
41635
|
-
if (relay)
|
|
41636
|
-
assertSecureRelayUrl(relay);
|
|
41637
|
-
return {
|
|
41638
|
-
...typeof raw.v === "number" ? { v: raw.v } : {},
|
|
41639
|
-
central,
|
|
41640
|
-
...relay ? { relay } : {},
|
|
41641
|
-
cellId,
|
|
41642
|
-
secret
|
|
41643
|
-
};
|
|
41644
|
-
}
|
|
41645
|
-
function parseInviteCode(code) {
|
|
41646
|
-
const trimmed = code.trim();
|
|
41647
|
-
if (!trimmed)
|
|
41648
|
-
throw new Error("invite code is empty");
|
|
41649
|
-
let decoded;
|
|
41650
|
-
try {
|
|
41651
|
-
decoded = Buffer.from(trimmed, "base64url").toString("utf-8");
|
|
41652
|
-
} catch {
|
|
41653
|
-
throw new Error("invite code is not valid base64url");
|
|
41654
|
-
}
|
|
41655
|
-
let parsed;
|
|
41656
|
-
try {
|
|
41657
|
-
parsed = JSON.parse(decoded);
|
|
41658
|
-
} catch {
|
|
41659
|
-
throw new Error("invite code does not decode to JSON");
|
|
41660
|
-
}
|
|
41661
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
41662
|
-
throw new Error("invite code does not decode to a JSON object");
|
|
41663
|
-
}
|
|
41664
|
-
return normalizeJoin(parsed);
|
|
41665
|
-
}
|
|
41666
|
-
function joinLockPath() {
|
|
41667
|
-
return resolve23(DATA_DIR, ".otium-join.lock");
|
|
41668
|
-
}
|
|
41669
|
-
function processIsAlive(pid) {
|
|
41670
|
-
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
41671
|
-
return false;
|
|
41672
|
-
try {
|
|
41673
|
-
process.kill(pid, 0);
|
|
41674
|
-
return true;
|
|
41675
|
-
} catch (error2) {
|
|
41676
|
-
return error2.code !== "ESRCH";
|
|
41677
|
-
}
|
|
41678
|
-
}
|
|
41679
|
-
function withJoinCredentialLock(operation) {
|
|
41680
|
-
const lockPath = joinLockPath();
|
|
41681
|
-
const ownerPath = resolve23(lockPath, "owner.json");
|
|
41682
|
-
const owner = { pid: process.pid, token: randomUUID27() };
|
|
41683
|
-
mkdirSync32(dirname21(lockPath), { recursive: true });
|
|
41684
|
-
for (let attempt = 0;; attempt += 1) {
|
|
41685
|
-
let created = false;
|
|
41686
|
-
try {
|
|
41687
|
-
mkdirSync32(lockPath, { mode: 448 });
|
|
41688
|
-
created = true;
|
|
41689
|
-
writeFileSync23(ownerPath, `${JSON.stringify(owner)}
|
|
41690
|
-
`, { mode: 384 });
|
|
41691
|
-
const ownerFd = openSync5(ownerPath, "r");
|
|
41692
|
-
try {
|
|
41693
|
-
fsyncSync2(ownerFd);
|
|
41694
|
-
} finally {
|
|
41695
|
-
closeSync5(ownerFd);
|
|
41696
|
-
}
|
|
41697
|
-
break;
|
|
41698
|
-
} catch (error2) {
|
|
41699
|
-
if (created) {
|
|
41700
|
-
rmSync10(lockPath, { recursive: true, force: true });
|
|
41701
|
-
throw error2;
|
|
41702
|
-
}
|
|
41703
|
-
if (error2.code !== "EEXIST")
|
|
41704
|
-
throw error2;
|
|
41705
|
-
let current3 = null;
|
|
41706
|
-
try {
|
|
41707
|
-
current3 = JSON.parse(readFileSync28(ownerPath, "utf8"));
|
|
41708
|
-
} catch {}
|
|
41709
|
-
let ageMs;
|
|
41710
|
-
try {
|
|
41711
|
-
ageMs = Date.now() - statSync20(lockPath).mtimeMs;
|
|
41712
|
-
} catch (statError) {
|
|
41713
|
-
if (statError.code === "ENOENT")
|
|
41714
|
-
continue;
|
|
41715
|
-
throw statError;
|
|
41716
|
-
}
|
|
41717
|
-
if (current3 && processIsAlive(current3.pid) || !current3 && ageMs <= JOIN_LOCK_STALE_MS) {
|
|
41718
|
-
throw new Error(`another Otium join credential operation is in progress at ${lockPath}`);
|
|
41719
|
-
}
|
|
41720
|
-
if (attempt > 0) {
|
|
41721
|
-
throw new Error(`could not recover stale Otium join credential lock at ${lockPath}`);
|
|
41722
|
-
}
|
|
41723
|
-
const stalePath = `${lockPath}.stale.${process.pid}.${randomUUID27()}`;
|
|
41724
|
-
try {
|
|
41725
|
-
renameSync16(lockPath, stalePath);
|
|
41726
|
-
rmSync10(stalePath, { recursive: true, force: true });
|
|
41727
|
-
} catch (staleError) {
|
|
41728
|
-
if (staleError.code !== "ENOENT")
|
|
41729
|
-
throw staleError;
|
|
41730
|
-
}
|
|
41731
|
-
}
|
|
41732
|
-
}
|
|
41733
|
-
try {
|
|
41734
|
-
return operation();
|
|
41735
|
-
} finally {
|
|
41736
|
-
try {
|
|
41737
|
-
const current3 = JSON.parse(readFileSync28(ownerPath, "utf8"));
|
|
41738
|
-
if (current3.pid === owner.pid && current3.token === owner.token) {
|
|
41739
|
-
rmSync10(lockPath, { recursive: true, force: true });
|
|
41740
|
-
}
|
|
41741
|
-
} catch {}
|
|
41742
|
-
}
|
|
41743
|
-
}
|
|
41744
|
-
function joinsEqual(left, right) {
|
|
41745
|
-
return left.central === right.central && left.relay === right.relay && left.cellId === right.cellId && left.secret === right.secret;
|
|
41746
|
-
}
|
|
41747
|
-
function normalizedJoin(join42) {
|
|
41748
|
-
return normalizeJoin({
|
|
41749
|
-
v: join42.v,
|
|
41750
|
-
central: join42.central,
|
|
41751
|
-
relay: join42.relay,
|
|
41752
|
-
cellId: join42.cellId,
|
|
41753
|
-
secret: join42.secret
|
|
41754
|
-
});
|
|
41755
|
-
}
|
|
41756
|
-
function joinCredentialDigest(join42) {
|
|
41757
|
-
return createHash11("sha256").update(JSON.stringify(normalizedJoin(join42))).digest("base64url");
|
|
41758
|
-
}
|
|
41759
|
-
function readPersistedJoin(path = joinFilePath()) {
|
|
41760
|
-
if (!existsSync36(path))
|
|
41761
|
-
return null;
|
|
41762
|
-
const parsed = JSON.parse(readFileSync28(path, "utf-8"));
|
|
41763
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
41764
|
-
throw new Error("persisted join credentials are not a JSON object");
|
|
41765
|
-
}
|
|
41766
|
-
return normalizeJoin(parsed);
|
|
41767
|
-
}
|
|
41768
|
-
function isJoinPersisted(join42) {
|
|
41769
|
-
try {
|
|
41770
|
-
const persisted = readPersistedJoin();
|
|
41771
|
-
return persisted !== null && joinsEqual(persisted, normalizedJoin(join42));
|
|
41772
|
-
} catch {
|
|
41773
|
-
return false;
|
|
41774
|
-
}
|
|
41775
|
-
}
|
|
41776
|
-
function saveJoinWhileLocked(join42, options = {}) {
|
|
41777
|
-
const path = joinFilePath();
|
|
41778
|
-
const directory = dirname21(path);
|
|
41779
|
-
const normalized = normalizedJoin(join42);
|
|
41780
|
-
mkdirSync32(directory, { recursive: true });
|
|
41781
|
-
if (existsSync36(path)) {
|
|
41782
|
-
if (lstatSync(path).isSymbolicLink()) {
|
|
41783
|
-
throw new Error(`refusing to replace symlinked Otium join file at ${path}`);
|
|
41784
|
-
}
|
|
41785
|
-
let existing = null;
|
|
41786
|
-
try {
|
|
41787
|
-
existing = readPersistedJoin(path);
|
|
41788
|
-
} catch (error2) {
|
|
41789
|
-
if (!options.replaceExisting) {
|
|
41790
|
-
throw new Error(`existing Otium join file at ${path} is invalid; pass --replace to replace it`, { cause: error2 });
|
|
41791
|
-
}
|
|
41792
|
-
}
|
|
41793
|
-
if (existing && joinsEqual(existing, normalized)) {
|
|
41794
|
-
chmodSync7(path, 384);
|
|
41795
|
-
const fileFd = openSync5(path, "r");
|
|
41796
|
-
try {
|
|
41797
|
-
fsyncSync2(fileFd);
|
|
41798
|
-
} finally {
|
|
41799
|
-
closeSync5(fileFd);
|
|
41800
|
-
}
|
|
41801
|
-
const directoryFd = openSync5(directory, "r");
|
|
41802
|
-
try {
|
|
41803
|
-
fsyncSync2(directoryFd);
|
|
41804
|
-
} finally {
|
|
41805
|
-
closeSync5(directoryFd);
|
|
41806
|
-
}
|
|
41807
|
-
return path;
|
|
41808
|
-
}
|
|
41809
|
-
if (!options.replaceExisting) {
|
|
41810
|
-
throw new Error(`this node is already joined${existing ? ` as ${existing.cellId}` : " with an invalid join file"}; pass --replace to replace its credentials`);
|
|
41811
|
-
}
|
|
41812
|
-
}
|
|
41813
|
-
const temporaryPath = resolve23(directory, `.otium-join.json.${process.pid}.${randomUUID27()}.tmp`);
|
|
41814
|
-
let fd;
|
|
41815
|
-
try {
|
|
41816
|
-
fd = openSync5(temporaryPath, "wx", 384);
|
|
41817
|
-
writeFileSync23(fd, `${JSON.stringify(normalized, null, 2)}
|
|
41818
|
-
`, "utf8");
|
|
41819
|
-
fsyncSync2(fd);
|
|
41820
|
-
closeSync5(fd);
|
|
41821
|
-
fd = undefined;
|
|
41822
|
-
if (options.replaceExisting) {
|
|
41823
|
-
renameSync16(temporaryPath, path);
|
|
41824
|
-
} else {
|
|
41825
|
-
linkSync(temporaryPath, path);
|
|
41826
|
-
unlinkSync23(temporaryPath);
|
|
41827
|
-
}
|
|
41828
|
-
chmodSync7(path, 384);
|
|
41829
|
-
const directoryFd = openSync5(directory, "r");
|
|
41830
|
-
try {
|
|
41831
|
-
fsyncSync2(directoryFd);
|
|
41832
|
-
} finally {
|
|
41833
|
-
closeSync5(directoryFd);
|
|
41834
|
-
}
|
|
41835
|
-
} catch (error2) {
|
|
41836
|
-
if (fd !== undefined)
|
|
41837
|
-
closeSync5(fd);
|
|
41838
|
-
if (existsSync36(temporaryPath))
|
|
41839
|
-
unlinkSync23(temporaryPath);
|
|
41840
|
-
throw error2;
|
|
41841
|
-
}
|
|
41842
|
-
return path;
|
|
41843
|
-
}
|
|
41844
|
-
function saveJoin(join42, options = {}) {
|
|
41845
|
-
return withJoinCredentialLock(() => saveJoinWhileLocked(join42, options));
|
|
41846
|
-
}
|
|
41847
|
-
function removeJoin() {
|
|
41848
|
-
return withJoinCredentialLock(() => {
|
|
41849
|
-
const path = joinFilePath();
|
|
41850
|
-
if (!existsSync36(path))
|
|
41851
|
-
return false;
|
|
41852
|
-
if (lstatSync(path).isSymbolicLink()) {
|
|
41853
|
-
throw new Error(`refusing to remove symlinked Otium join file at ${path}`);
|
|
41854
|
-
}
|
|
41855
|
-
unlinkSync23(path);
|
|
41856
|
-
const directoryFd = openSync5(dirname21(path), "r");
|
|
41857
|
-
try {
|
|
41858
|
-
fsyncSync2(directoryFd);
|
|
41859
|
-
} finally {
|
|
41860
|
-
closeSync5(directoryFd);
|
|
41861
|
-
}
|
|
41862
|
-
return true;
|
|
41863
|
-
});
|
|
41864
|
-
}
|
|
41865
|
-
function loadJoin() {
|
|
41866
|
-
const central = process.env.OTIUM_CENTRAL_URL?.trim();
|
|
41867
|
-
const cellId = process.env.OTIUM_CELL_ID?.trim();
|
|
41868
|
-
const secret = process.env.OTIUM_CELL_SECRET?.trim();
|
|
41869
|
-
const relay = process.env.OTIUM_RELAY_URL?.trim();
|
|
41870
|
-
if (central && cellId && secret) {
|
|
41871
|
-
try {
|
|
41872
|
-
return normalizeJoin({ central, relay, cellId, secret });
|
|
41873
|
-
} catch (err2) {
|
|
41874
|
-
logger.warn({ err: err2 }, "otium: invalid OTIUM_CENTRAL_URL/OTIUM_CELL_ID/OTIUM_CELL_SECRET env");
|
|
41875
|
-
return null;
|
|
41876
|
-
}
|
|
41877
|
-
}
|
|
41878
|
-
if (central || cellId || secret) {
|
|
41879
|
-
logger.warn("otium: OTIUM_CENTRAL_URL, OTIUM_CELL_ID, OTIUM_CELL_SECRET must be set together \u2014 ignoring partial env");
|
|
41880
|
-
}
|
|
41881
|
-
const path = joinFilePath();
|
|
41882
|
-
if (!existsSync36(path))
|
|
41883
|
-
return null;
|
|
41884
|
-
try {
|
|
41885
|
-
const parsed = JSON.parse(readFileSync28(path, "utf-8"));
|
|
41886
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
41887
|
-
return null;
|
|
41888
|
-
return normalizeJoin(parsed);
|
|
41889
|
-
} catch (err2) {
|
|
41890
|
-
logger.warn({ err: err2, path }, "otium: failed to read join file");
|
|
41891
|
-
return null;
|
|
41892
|
-
}
|
|
41893
|
-
}
|
|
41894
|
-
var JOIN_LOCK_STALE_MS = 30000;
|
|
41895
|
-
var init_join = __esm(async () => {
|
|
41896
|
-
await init_src();
|
|
41897
|
-
});
|
|
41898
|
-
|
|
41899
|
-
// ../../packages/core/src/config-public.ts
|
|
41900
|
-
var init_config_public = __esm(() => {
|
|
41901
|
-
init_config();
|
|
41902
|
-
});
|
|
41903
|
-
|
|
41904
|
-
// ../../adapters/otium/src/join-status.ts
|
|
41905
|
-
var exports_join_status = {};
|
|
41906
|
-
__export(exports_join_status, {
|
|
41907
|
-
hasConfiguredOtiumJoin: () => hasConfiguredOtiumJoin
|
|
41908
|
-
});
|
|
41909
|
-
import { existsSync as existsSync37 } from "fs";
|
|
41910
|
-
import { resolve as resolve24 } from "path";
|
|
41911
|
-
function hasConfiguredOtiumJoin() {
|
|
41912
|
-
const envJoin = Boolean(process.env.OTIUM_CENTRAL_URL?.trim() && process.env.OTIUM_CELL_ID?.trim() && process.env.OTIUM_CELL_SECRET?.trim());
|
|
41913
|
-
return envJoin || existsSync37(resolve24(DATA_DIR, "otium-join.json"));
|
|
41914
|
-
}
|
|
41915
|
-
var init_join_status = __esm(() => {
|
|
41916
|
-
init_config_public();
|
|
41917
|
-
});
|
|
41918
|
-
|
|
41919
|
-
// ../../adapters/otium/src/control-protocol.ts
|
|
41920
|
-
var OTIUM_ADAPTER_CONTROL_PREFIX = "/api/v1/adapter/otium", OTIUM_ADAPTER_CONTROL_HEADER = "x-negotium-adapter-token";
|
|
41921
|
-
|
|
41922
41590
|
// ../../adapters/otium/src/protocol.ts
|
|
41923
|
-
|
|
41924
|
-
const value = body[field];
|
|
41925
|
-
return typeof value === "string" && value.trim() ? value : null;
|
|
41926
|
-
}
|
|
41927
|
-
function parseExecutionSpec(value) {
|
|
41928
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
41929
|
-
return null;
|
|
41930
|
-
const raw = value;
|
|
41931
|
-
const agent = str(raw, "agent");
|
|
41932
|
-
const model = str(raw, "model");
|
|
41933
|
-
const effort = str(raw, "effort");
|
|
41934
|
-
const rawMcp = raw.mcp;
|
|
41935
|
-
if (!agent || !model || !effort || !Array.isArray(rawMcp) || !rawMcp.every((entry) => typeof entry === "string" && entry.trim().length > 0) || typeof raw.canSpawnSubagents !== "boolean") {
|
|
41936
|
-
return null;
|
|
41937
|
-
}
|
|
41938
|
-
return {
|
|
41939
|
-
agent,
|
|
41940
|
-
model,
|
|
41941
|
-
effort,
|
|
41942
|
-
...str(raw, "description") ? { description: str(raw, "description") } : {},
|
|
41943
|
-
mcp: [...new Set(rawMcp.map((entry) => entry.trim()))],
|
|
41944
|
-
canSpawnSubagents: raw.canSpawnSubagents
|
|
41945
|
-
};
|
|
41946
|
-
}
|
|
41947
|
-
var PEER_PROTOCOL_VERSION = 1, MAX_PEER_MESSAGE_LENGTH = 1e4, MAX_PEER_INPUT_FILE_BYTES, MAX_PEER_INPUT_REQUEST_BYTES;
|
|
41591
|
+
var PEER_PROTOCOL_VERSION = 1, MAX_PEER_MESSAGE_LENGTH = 1e4, MAX_PEER_REQUEST_BODY_BYTES;
|
|
41948
41592
|
var init_protocol = __esm(() => {
|
|
41949
|
-
|
|
41950
|
-
MAX_PEER_INPUT_REQUEST_BYTES = MAX_PEER_INPUT_FILE_BYTES + 8 * 1024 * 1024;
|
|
41593
|
+
MAX_PEER_REQUEST_BODY_BYTES = 2 * 1024 * 1024 * 1024 + 8 * 1024 * 1024;
|
|
41951
41594
|
});
|
|
41952
41595
|
|
|
41953
41596
|
// ../../adapters/otium/src/canonical-mcp-bridge.ts
|
|
41954
|
-
import { randomUUID as
|
|
41597
|
+
import { randomUUID as randomUUID27 } from "crypto";
|
|
41955
41598
|
function readAuthorization(request) {
|
|
41956
41599
|
const value = request.headers.get("authorization");
|
|
41957
41600
|
return value?.startsWith("Bearer ") ? value.slice(7) : null;
|
|
@@ -42092,7 +41735,7 @@ function startCanonicalMcpBridge(options = {}) {
|
|
|
42092
41735
|
const url = `http://127.0.0.1:${server.port}/`;
|
|
42093
41736
|
const unregister = registerCanonicalMcpBridgeEnvProvider((scope) => {
|
|
42094
41737
|
sweep();
|
|
42095
|
-
const token = `${
|
|
41738
|
+
const token = `${randomUUID27()}${randomUUID27()}`;
|
|
42096
41739
|
capabilities.set(token, {
|
|
42097
41740
|
surface: scope.surface,
|
|
42098
41741
|
userId: scope.userId,
|
|
@@ -42144,736 +41787,360 @@ var init_canonical_mcp_bridge = __esm(async () => {
|
|
|
42144
41787
|
};
|
|
42145
41788
|
});
|
|
42146
41789
|
|
|
42147
|
-
// ../../adapters/otium/src/
|
|
42148
|
-
|
|
42149
|
-
|
|
42150
|
-
|
|
42151
|
-
return row?.status === "detached";
|
|
42152
|
-
}
|
|
42153
|
-
function enqueueSharedMessage(args) {
|
|
42154
|
-
db.run(`INSERT OR REPLACE INTO otium_shared_message_outbox
|
|
42155
|
-
(local_topic_id, source_message_id, message_json, created_at)
|
|
42156
|
-
VALUES (?, ?, ?, ?)`, [
|
|
42157
|
-
args.localTopicId,
|
|
42158
|
-
args.sourceMessageId,
|
|
42159
|
-
JSON.stringify(args.message),
|
|
42160
|
-
new Date().toISOString()
|
|
42161
|
-
]);
|
|
42162
|
-
}
|
|
42163
|
-
function listSharedMessages(localTopicId) {
|
|
42164
|
-
return db.query("SELECT * FROM otium_shared_message_outbox WHERE local_topic_id = ? ORDER BY created_at").all(localTopicId);
|
|
42165
|
-
}
|
|
42166
|
-
function deleteSharedMessage(localTopicId, sourceMessageId) {
|
|
42167
|
-
return db.run("DELETE FROM otium_shared_message_outbox WHERE local_topic_id = ? AND source_message_id = ?", [localTopicId, sourceMessageId]).changes === 1;
|
|
42168
|
-
}
|
|
42169
|
-
function getSharedTopicState(localTopicId) {
|
|
42170
|
-
return db.query("SELECT * FROM otium_shared_topic_state WHERE local_topic_id = ?").get(localTopicId) ?? null;
|
|
42171
|
-
}
|
|
42172
|
-
function listSharedTopicStates() {
|
|
42173
|
-
return db.query("SELECT * FROM otium_shared_topic_state ORDER BY updated_at").all();
|
|
42174
|
-
}
|
|
42175
|
-
function setSharedTopicState(args) {
|
|
42176
|
-
db.run(`INSERT INTO otium_shared_topic_state (local_topic_id, host_topic_id, status, updated_at)
|
|
42177
|
-
VALUES (?, ?, ?, ?)
|
|
42178
|
-
ON CONFLICT(local_topic_id) DO UPDATE SET
|
|
42179
|
-
host_topic_id = excluded.host_topic_id,
|
|
42180
|
-
status = excluded.status,
|
|
42181
|
-
updated_at = excluded.updated_at`, [args.localTopicId, args.hostTopicId ?? null, args.status, new Date().toISOString()]);
|
|
42182
|
-
}
|
|
42183
|
-
function deleteSharedTopicState(localTopicId) {
|
|
42184
|
-
return db.run("DELETE FROM otium_shared_topic_state WHERE local_topic_id = ?", [localTopicId]).changes === 1;
|
|
42185
|
-
}
|
|
42186
|
-
function getPeerSession(hostNodeId, hostTopicId) {
|
|
42187
|
-
return db.query("SELECT * FROM otium_peer_sessions WHERE host_node_id = ? AND host_topic_id = ?").get(hostNodeId, hostTopicId) ?? null;
|
|
42188
|
-
}
|
|
42189
|
-
function createPeerSession(hostNodeId, hostTopicId, localTopicId) {
|
|
42190
|
-
const row = {
|
|
42191
|
-
host_node_id: hostNodeId,
|
|
42192
|
-
host_topic_id: hostTopicId,
|
|
42193
|
-
local_topic_id: localTopicId,
|
|
42194
|
-
binding_mode: "mirror",
|
|
42195
|
-
created_at: new Date().toISOString()
|
|
42196
|
-
};
|
|
42197
|
-
db.run("INSERT INTO otium_peer_sessions (host_node_id, host_topic_id, local_topic_id, binding_mode, created_at) VALUES (?, ?, ?, ?, ?)", [row.host_node_id, row.host_topic_id, row.local_topic_id, row.binding_mode, row.created_at]);
|
|
42198
|
-
return row;
|
|
42199
|
-
}
|
|
42200
|
-
function bindPeerSession(hostNodeId, hostTopicId, localTopicId, mode = "shared") {
|
|
42201
|
-
const row = {
|
|
42202
|
-
host_node_id: hostNodeId,
|
|
42203
|
-
host_topic_id: hostTopicId,
|
|
42204
|
-
local_topic_id: localTopicId,
|
|
42205
|
-
binding_mode: mode,
|
|
42206
|
-
created_at: new Date().toISOString()
|
|
42207
|
-
};
|
|
42208
|
-
db.run(`INSERT INTO otium_peer_sessions
|
|
42209
|
-
(host_node_id, host_topic_id, local_topic_id, binding_mode, created_at)
|
|
42210
|
-
VALUES (?, ?, ?, ?, ?)
|
|
42211
|
-
ON CONFLICT(host_node_id, host_topic_id) DO UPDATE SET
|
|
42212
|
-
local_topic_id = excluded.local_topic_id,
|
|
42213
|
-
binding_mode = excluded.binding_mode,
|
|
42214
|
-
created_at = excluded.created_at`, [hostNodeId, hostTopicId, localTopicId, mode, row.created_at]);
|
|
42215
|
-
return row;
|
|
42216
|
-
}
|
|
42217
|
-
function unbindPeerSession(hostNodeId, hostTopicId) {
|
|
42218
|
-
return db.run("DELETE FROM otium_peer_sessions WHERE host_node_id = ? AND host_topic_id = ?", [
|
|
42219
|
-
hostNodeId,
|
|
42220
|
-
hostTopicId
|
|
42221
|
-
]).changes === 1;
|
|
42222
|
-
}
|
|
42223
|
-
function unbindSharedPeerSessionsForLocalTopic(localTopicId) {
|
|
42224
|
-
return db.run("DELETE FROM otium_peer_sessions WHERE local_topic_id = ? AND binding_mode = 'shared'", [localTopicId]).changes;
|
|
42225
|
-
}
|
|
42226
|
-
function downgradeSharedTopicsLocally(hubNodeId) {
|
|
42227
|
-
return db.transaction(() => {
|
|
42228
|
-
const topics = db.query("SELECT id FROM api_topics WHERE access_mode = 'shared'").all().map((row) => row.id);
|
|
42229
|
-
db.run("UPDATE api_topics SET access_mode = 'private' WHERE access_mode = 'shared'");
|
|
42230
|
-
db.run("DELETE FROM otium_peer_sessions WHERE host_node_id = ?", [hubNodeId]);
|
|
42231
|
-
db.run("DELETE FROM otium_shared_message_outbox");
|
|
42232
|
-
db.run("DELETE FROM otium_shared_topic_state");
|
|
42233
|
-
db.run(`INSERT INTO otium_peer_lifecycle (hub_node_id, status, updated_at)
|
|
42234
|
-
VALUES (?, 'detached', ?)
|
|
42235
|
-
ON CONFLICT(hub_node_id) DO UPDATE SET status = 'detached', updated_at = excluded.updated_at`, [hubNodeId, new Date().toISOString()]);
|
|
42236
|
-
return topics;
|
|
42237
|
-
})();
|
|
42238
|
-
}
|
|
42239
|
-
function listPeerSessions() {
|
|
42240
|
-
return db.query("SELECT * FROM otium_peer_sessions").all();
|
|
42241
|
-
}
|
|
42242
|
-
function cleanupPeerStateForLocalTopic(localTopicId) {
|
|
42243
|
-
return db.transaction(() => {
|
|
42244
|
-
const terminalOutbox = db.run(`DELETE FROM otium_peer_terminal_outbox
|
|
42245
|
-
WHERE EXISTS (
|
|
42246
|
-
SELECT 1 FROM otium_peer_turn_requests turn_request
|
|
42247
|
-
JOIN otium_peer_sessions session
|
|
42248
|
-
ON session.host_node_id = turn_request.host_node_id
|
|
42249
|
-
AND session.host_topic_id = turn_request.host_topic_id
|
|
42250
|
-
WHERE session.local_topic_id = ?
|
|
42251
|
-
AND turn_request.host_node_id = otium_peer_terminal_outbox.host_node_id
|
|
42252
|
-
AND turn_request.request_id = otium_peer_terminal_outbox.request_id
|
|
42253
|
-
)`, [localTopicId]).changes;
|
|
42254
|
-
const turns = db.run(`DELETE FROM otium_peer_turn_requests
|
|
42255
|
-
WHERE EXISTS (
|
|
42256
|
-
SELECT 1 FROM otium_peer_sessions session
|
|
42257
|
-
WHERE session.local_topic_id = ?
|
|
42258
|
-
AND session.host_node_id = otium_peer_turn_requests.host_node_id
|
|
42259
|
-
AND session.host_topic_id = otium_peer_turn_requests.host_topic_id
|
|
42260
|
-
)`, [localTopicId]).changes;
|
|
42261
|
-
const inboxRequests = db.run("DELETE FROM otium_peer_inbox_requests WHERE topic_id = ?", [
|
|
42262
|
-
localTopicId
|
|
42263
|
-
]).changes;
|
|
42264
|
-
const remoteAsks = db.run("DELETE FROM otium_remote_asks WHERE caller_topic_id = ?", [
|
|
42265
|
-
localTopicId
|
|
42266
|
-
]).changes;
|
|
42267
|
-
const sessions = db.run("DELETE FROM otium_peer_sessions WHERE local_topic_id = ?", [
|
|
42268
|
-
localTopicId
|
|
42269
|
-
]).changes;
|
|
42270
|
-
return { sessions, turns, terminalOutbox, inboxRequests, remoteAsks };
|
|
42271
|
-
})();
|
|
42272
|
-
}
|
|
42273
|
-
function sweepStalePeerBindings(topicExists) {
|
|
42274
|
-
const stale = [
|
|
42275
|
-
...new Set(listPeerSessions().map((row) => row.local_topic_id).filter((localTopicId) => localTopicId && !topicExists(localTopicId)))
|
|
42276
|
-
];
|
|
42277
|
-
const removed = {
|
|
42278
|
-
sessions: 0,
|
|
42279
|
-
turns: 0,
|
|
42280
|
-
terminalOutbox: 0,
|
|
42281
|
-
inboxRequests: 0,
|
|
42282
|
-
remoteAsks: 0
|
|
42283
|
-
};
|
|
42284
|
-
for (const localTopicId of stale) {
|
|
42285
|
-
const result = cleanupPeerStateForLocalTopic(localTopicId);
|
|
42286
|
-
removed.sessions += result.sessions;
|
|
42287
|
-
removed.turns += result.turns;
|
|
42288
|
-
removed.terminalOutbox += result.terminalOutbox;
|
|
42289
|
-
removed.inboxRequests += result.inboxRequests;
|
|
42290
|
-
removed.remoteAsks += result.remoteAsks;
|
|
42291
|
-
}
|
|
42292
|
-
return { topicIds: stale, removed };
|
|
42293
|
-
}
|
|
42294
|
-
function failInterruptedPeerTurnRequestsOnStartup() {
|
|
42295
|
-
return db.run(`UPDATE otium_peer_turn_requests
|
|
42296
|
-
SET status = 'failed', error = 'worker restarted during turn', updated_at = ?
|
|
42297
|
-
WHERE status IN ('claimed', 'running')
|
|
42298
|
-
AND NOT EXISTS (
|
|
42299
|
-
SELECT 1 FROM otium_peer_terminal_outbox terminal
|
|
42300
|
-
WHERE terminal.host_node_id = otium_peer_turn_requests.host_node_id
|
|
42301
|
-
AND terminal.request_id = otium_peer_turn_requests.request_id
|
|
42302
|
-
)`, [new Date().toISOString()]).changes;
|
|
42303
|
-
}
|
|
42304
|
-
function claimPeerTurnRequest(hostNodeId, requestId, hostTopicId) {
|
|
42305
|
-
const now = new Date().toISOString();
|
|
42306
|
-
const inserted = db.run(`INSERT OR IGNORE INTO otium_peer_turn_requests
|
|
42307
|
-
(host_node_id, request_id, host_topic_id, status, created_at, updated_at)
|
|
42308
|
-
VALUES (?, ?, ?, 'claimed', ?, ?)`, [hostNodeId, requestId, hostTopicId, now, now]);
|
|
42309
|
-
const row = getPeerTurnRequest(hostNodeId, requestId);
|
|
42310
|
-
if (!row)
|
|
42311
|
-
throw new Error("otium peer turn request claim disappeared");
|
|
42312
|
-
return { claimed: inserted.changes === 1, row };
|
|
42313
|
-
}
|
|
42314
|
-
function getPeerTurnRequest(hostNodeId, requestId) {
|
|
42315
|
-
return db.query("SELECT * FROM otium_peer_turn_requests WHERE host_node_id = ? AND request_id = ?").get(hostNodeId, requestId) ?? null;
|
|
42316
|
-
}
|
|
42317
|
-
function setPeerTurnRequestStatus(hostNodeId, requestId, status, error2 = null) {
|
|
42318
|
-
db.run(`UPDATE otium_peer_turn_requests
|
|
42319
|
-
SET status = ?, error = ?, updated_at = ?
|
|
42320
|
-
WHERE host_node_id = ? AND request_id = ?`, [status, error2, new Date().toISOString(), hostNodeId, requestId]);
|
|
42321
|
-
}
|
|
42322
|
-
function markPeerTurnRequestRunning(hostNodeId, requestId) {
|
|
42323
|
-
setPeerTurnRequestStatus(hostNodeId, requestId, "running");
|
|
42324
|
-
}
|
|
42325
|
-
function markPeerTurnRequestFinished(hostNodeId, requestId) {
|
|
42326
|
-
setPeerTurnRequestStatus(hostNodeId, requestId, "finished");
|
|
42327
|
-
}
|
|
42328
|
-
function markPeerTurnRequestFailed(hostNodeId, requestId, error2) {
|
|
42329
|
-
setPeerTurnRequestStatus(hostNodeId, requestId, "failed", error2);
|
|
42330
|
-
}
|
|
42331
|
-
function upsertPeerTerminalOutbox(args) {
|
|
42332
|
-
const now = Date.now();
|
|
42333
|
-
db.run(`INSERT INTO otium_peer_terminal_outbox
|
|
42334
|
-
(host_node_id, request_id, seq, event_json, created_at, updated_at)
|
|
42335
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
42336
|
-
ON CONFLICT(host_node_id, request_id) DO UPDATE SET
|
|
42337
|
-
seq = excluded.seq, event_json = excluded.event_json, updated_at = excluded.updated_at`, [args.hostNodeId, args.requestId, args.seq, JSON.stringify(args.event), now, now]);
|
|
42338
|
-
}
|
|
42339
|
-
function listPeerTerminalOutbox(limit = 100) {
|
|
42340
|
-
return db.query("SELECT * FROM otium_peer_terminal_outbox ORDER BY created_at LIMIT ?").all(limit);
|
|
42341
|
-
}
|
|
42342
|
-
function acknowledgePeerTerminal(hostNodeId, requestId) {
|
|
42343
|
-
return db.transaction(() => {
|
|
42344
|
-
const removed = db.run("DELETE FROM otium_peer_terminal_outbox WHERE host_node_id = ? AND request_id = ?", [hostNodeId, requestId]).changes;
|
|
42345
|
-
if (removed !== 1)
|
|
42346
|
-
return false;
|
|
42347
|
-
markPeerTurnRequestFinished(hostNodeId, requestId);
|
|
41790
|
+
// ../../adapters/otium/src/secure-transport.ts
|
|
41791
|
+
function isLoopbackHostname(hostname2) {
|
|
41792
|
+
const normalized = hostname2.toLowerCase();
|
|
41793
|
+
if (normalized === "localhost" || normalized === "::1" || normalized === "[::1]")
|
|
42348
41794
|
return true;
|
|
42349
|
-
|
|
41795
|
+
const octets = normalized.split(".");
|
|
41796
|
+
return octets.length === 4 && octets[0] === "127" && octets.every((octet) => /^\d{1,3}$/.test(octet) && Number(octet) <= 255);
|
|
42350
41797
|
}
|
|
42351
|
-
function
|
|
42352
|
-
|
|
41798
|
+
function credentialTransportUrl(value, label) {
|
|
41799
|
+
let url;
|
|
41800
|
+
try {
|
|
41801
|
+
url = new URL(value);
|
|
41802
|
+
} catch {
|
|
41803
|
+
throw new Error(`${label} is not a valid URL`);
|
|
41804
|
+
}
|
|
41805
|
+
if (url.username || url.password)
|
|
41806
|
+
throw new Error(`${label} must not contain URL credentials`);
|
|
41807
|
+
return url;
|
|
42353
41808
|
}
|
|
42354
|
-
function
|
|
42355
|
-
const
|
|
42356
|
-
|
|
42357
|
-
|
|
42358
|
-
|
|
42359
|
-
|
|
42360
|
-
|
|
42361
|
-
args.topicId,
|
|
42362
|
-
args.payloadHash,
|
|
42363
|
-
new Date().toISOString()
|
|
42364
|
-
]);
|
|
42365
|
-
if (inserted.changes === 1)
|
|
42366
|
-
return { outcome: "claimed" };
|
|
42367
|
-
const existing = db.query("SELECT payload_hash FROM otium_peer_inbox_requests WHERE from_cell_id = ? AND request_id = ? AND kind = ?").get(args.fromCellId, args.requestId, args.kind);
|
|
42368
|
-
if (!existing)
|
|
42369
|
-
return { outcome: "conflict" };
|
|
42370
|
-
return { outcome: existing.payload_hash === args.payloadHash ? "replay" : "conflict" };
|
|
41809
|
+
function assertSecureCentralUrl(value) {
|
|
41810
|
+
const url = credentialTransportUrl(value, "Otium central URL");
|
|
41811
|
+
if (url.protocol === "https:")
|
|
41812
|
+
return;
|
|
41813
|
+
if (url.protocol === "http:" && isLoopbackHostname(url.hostname))
|
|
41814
|
+
return;
|
|
41815
|
+
throw new Error("Otium central requires HTTPS or loopback HTTP");
|
|
42371
41816
|
}
|
|
42372
|
-
function
|
|
42373
|
-
|
|
41817
|
+
function assertSecureRelayUrl(value) {
|
|
41818
|
+
const url = credentialTransportUrl(value, "Otium relay URL");
|
|
41819
|
+
if (url.protocol === "https:" || url.protocol === "wss:")
|
|
41820
|
+
return;
|
|
41821
|
+
if ((url.protocol === "http:" || url.protocol === "ws:") && isLoopbackHostname(url.hostname)) {
|
|
41822
|
+
return;
|
|
41823
|
+
}
|
|
41824
|
+
throw new Error("Otium relay requires HTTPS/WSS or loopback HTTP/WS");
|
|
42374
41825
|
}
|
|
42375
|
-
|
|
42376
|
-
|
|
42377
|
-
|
|
42378
|
-
|
|
42379
|
-
|
|
42380
|
-
|
|
42381
|
-
|
|
42382
|
-
|
|
42383
|
-
|
|
42384
|
-
|
|
42385
|
-
|
|
42386
|
-
|
|
42387
|
-
|
|
42388
|
-
|
|
42389
|
-
|
|
41826
|
+
|
|
41827
|
+
// ../../adapters/otium/src/join.ts
|
|
41828
|
+
var exports_join = {};
|
|
41829
|
+
__export(exports_join, {
|
|
41830
|
+
withJoinCredentialLock: () => withJoinCredentialLock,
|
|
41831
|
+
saveJoinWhileLocked: () => saveJoinWhileLocked,
|
|
41832
|
+
saveJoin: () => saveJoin,
|
|
41833
|
+
removeJoin: () => removeJoin,
|
|
41834
|
+
parseInviteCode: () => parseInviteCode,
|
|
41835
|
+
loadJoin: () => loadJoin,
|
|
41836
|
+
joinFilePath: () => joinFilePath,
|
|
41837
|
+
joinCredentialDigest: () => joinCredentialDigest,
|
|
41838
|
+
isJoinPersisted: () => isJoinPersisted
|
|
41839
|
+
});
|
|
41840
|
+
import { createHash as createHash11, randomUUID as randomUUID28 } from "crypto";
|
|
41841
|
+
import {
|
|
41842
|
+
chmodSync as chmodSync7,
|
|
41843
|
+
closeSync as closeSync5,
|
|
41844
|
+
existsSync as existsSync37,
|
|
41845
|
+
fsyncSync as fsyncSync2,
|
|
41846
|
+
linkSync,
|
|
41847
|
+
lstatSync,
|
|
41848
|
+
mkdirSync as mkdirSync32,
|
|
41849
|
+
openSync as openSync5,
|
|
41850
|
+
readFileSync as readFileSync28,
|
|
41851
|
+
renameSync as renameSync16,
|
|
41852
|
+
rmSync as rmSync10,
|
|
41853
|
+
statSync as statSync20,
|
|
41854
|
+
unlinkSync as unlinkSync23,
|
|
41855
|
+
writeFileSync as writeFileSync23
|
|
41856
|
+
} from "fs";
|
|
41857
|
+
import { dirname as dirname21, resolve as resolve24 } from "path";
|
|
41858
|
+
function joinFilePath() {
|
|
41859
|
+
return resolve24(DATA_DIR, "otium-join.json");
|
|
42390
41860
|
}
|
|
42391
|
-
function
|
|
42392
|
-
return
|
|
41861
|
+
function isHttpUrl(value) {
|
|
41862
|
+
return /^https?:\/\//.test(value);
|
|
42393
41863
|
}
|
|
42394
|
-
function
|
|
42395
|
-
return
|
|
41864
|
+
function isRelayUrl(value) {
|
|
41865
|
+
return /^(?:https?|wss?):\/\//.test(value);
|
|
42396
41866
|
}
|
|
42397
|
-
function
|
|
42398
|
-
|
|
41867
|
+
function normalizeJoin(raw) {
|
|
41868
|
+
const central = typeof raw.central === "string" ? raw.central.trim().replace(/\/+$/, "") : "";
|
|
41869
|
+
const relay = typeof raw.relay === "string" ? raw.relay.trim().replace(/\/+$/, "") : "";
|
|
41870
|
+
const cellId = typeof raw.cellId === "string" ? raw.cellId.trim() : "";
|
|
41871
|
+
const secret = typeof raw.secret === "string" ? raw.secret.trim() : "";
|
|
41872
|
+
if (!central || !isHttpUrl(central)) {
|
|
41873
|
+
throw new Error("invite code is missing a valid http(s) central URL");
|
|
41874
|
+
}
|
|
41875
|
+
if (relay && !isRelayUrl(relay))
|
|
41876
|
+
throw new Error("invite code has an invalid relay URL");
|
|
41877
|
+
if (!cellId)
|
|
41878
|
+
throw new Error("invite code is missing cellId");
|
|
41879
|
+
if (!secret)
|
|
41880
|
+
throw new Error("invite code is missing secret");
|
|
41881
|
+
assertSecureCentralUrl(central);
|
|
41882
|
+
if (relay)
|
|
41883
|
+
assertSecureRelayUrl(relay);
|
|
41884
|
+
return {
|
|
41885
|
+
...typeof raw.v === "number" ? { v: raw.v } : {},
|
|
41886
|
+
central,
|
|
41887
|
+
...relay ? { relay } : {},
|
|
41888
|
+
cellId,
|
|
41889
|
+
secret
|
|
41890
|
+
};
|
|
42399
41891
|
}
|
|
42400
|
-
function
|
|
42401
|
-
const
|
|
42402
|
-
|
|
42403
|
-
|
|
42404
|
-
|
|
42405
|
-
|
|
42406
|
-
|
|
42407
|
-
|
|
42408
|
-
|
|
42409
|
-
|
|
42410
|
-
|
|
42411
|
-
|
|
42412
|
-
|
|
42413
|
-
|
|
42414
|
-
|
|
42415
|
-
|
|
42416
|
-
|
|
42417
|
-
|
|
42418
|
-
|
|
42419
|
-
|
|
42420
|
-
args.replyText,
|
|
42421
|
-
args.kind,
|
|
42422
|
-
now,
|
|
42423
|
-
now
|
|
42424
|
-
]);
|
|
41892
|
+
function parseInviteCode(code) {
|
|
41893
|
+
const trimmed = code.trim();
|
|
41894
|
+
if (!trimmed)
|
|
41895
|
+
throw new Error("invite code is empty");
|
|
41896
|
+
let decoded;
|
|
41897
|
+
try {
|
|
41898
|
+
decoded = Buffer.from(trimmed, "base64url").toString("utf-8");
|
|
41899
|
+
} catch {
|
|
41900
|
+
throw new Error("invite code is not valid base64url");
|
|
41901
|
+
}
|
|
41902
|
+
let parsed;
|
|
41903
|
+
try {
|
|
41904
|
+
parsed = JSON.parse(decoded);
|
|
41905
|
+
} catch {
|
|
41906
|
+
throw new Error("invite code does not decode to JSON");
|
|
41907
|
+
}
|
|
41908
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
41909
|
+
throw new Error("invite code does not decode to a JSON object");
|
|
41910
|
+
}
|
|
41911
|
+
return normalizeJoin(parsed);
|
|
42425
41912
|
}
|
|
42426
|
-
function
|
|
42427
|
-
return
|
|
41913
|
+
function joinLockPath() {
|
|
41914
|
+
return resolve24(DATA_DIR, ".otium-join.lock");
|
|
42428
41915
|
}
|
|
42429
|
-
function
|
|
42430
|
-
|
|
42431
|
-
|
|
42432
|
-
|
|
42433
|
-
|
|
41916
|
+
function processIsAlive(pid) {
|
|
41917
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
41918
|
+
return false;
|
|
41919
|
+
try {
|
|
41920
|
+
process.kill(pid, 0);
|
|
41921
|
+
return true;
|
|
41922
|
+
} catch (error2) {
|
|
41923
|
+
return error2.code !== "ESRCH";
|
|
41924
|
+
}
|
|
42434
41925
|
}
|
|
42435
|
-
|
|
42436
|
-
|
|
42437
|
-
|
|
42438
|
-
|
|
42439
|
-
|
|
42440
|
-
|
|
42441
|
-
|
|
42442
|
-
local_topic_id TEXT NOT NULL,
|
|
42443
|
-
binding_mode TEXT NOT NULL DEFAULT 'mirror',
|
|
42444
|
-
created_at TEXT NOT NULL,
|
|
42445
|
-
PRIMARY KEY (host_node_id, host_topic_id)
|
|
42446
|
-
)
|
|
42447
|
-
`);
|
|
42448
|
-
peerSessionColumns = new Set(db.query("PRAGMA table_info(otium_peer_sessions)").all().map((row) => row.name));
|
|
42449
|
-
if (!peerSessionColumns.has("binding_mode")) {
|
|
42450
|
-
db.exec("ALTER TABLE otium_peer_sessions ADD COLUMN binding_mode TEXT NOT NULL DEFAULT 'mirror'");
|
|
42451
|
-
}
|
|
42452
|
-
db.run(`UPDATE api_topics
|
|
42453
|
-
SET visibility = 'visible', access_mode = 'shared', is_subagent = 0
|
|
42454
|
-
WHERE id IN (
|
|
42455
|
-
SELECT local_topic_id FROM otium_peer_sessions WHERE binding_mode = 'mirror'
|
|
42456
|
-
)`);
|
|
42457
|
-
db.run(`UPDATE api_topics
|
|
42458
|
-
SET access_mode = 'shared'
|
|
42459
|
-
WHERE id IN (SELECT local_topic_id FROM otium_peer_sessions)`);
|
|
42460
|
-
db.exec(`
|
|
42461
|
-
CREATE TABLE IF NOT EXISTS otium_peer_turn_requests (
|
|
42462
|
-
host_node_id TEXT NOT NULL,
|
|
42463
|
-
request_id TEXT NOT NULL,
|
|
42464
|
-
host_topic_id TEXT NOT NULL,
|
|
42465
|
-
status TEXT NOT NULL CHECK (status IN
|
|
42466
|
-
('claimed', 'running', 'finished', 'failed')),
|
|
42467
|
-
error TEXT,
|
|
42468
|
-
created_at TEXT NOT NULL,
|
|
42469
|
-
updated_at TEXT NOT NULL,
|
|
42470
|
-
PRIMARY KEY (host_node_id, request_id)
|
|
42471
|
-
)
|
|
42472
|
-
`);
|
|
42473
|
-
db.exec(`
|
|
42474
|
-
CREATE TABLE IF NOT EXISTS otium_peer_terminal_outbox (
|
|
42475
|
-
host_node_id TEXT NOT NULL,
|
|
42476
|
-
request_id TEXT NOT NULL,
|
|
42477
|
-
seq INTEGER NOT NULL,
|
|
42478
|
-
event_json TEXT NOT NULL,
|
|
42479
|
-
created_at INTEGER NOT NULL,
|
|
42480
|
-
updated_at INTEGER NOT NULL,
|
|
42481
|
-
PRIMARY KEY (host_node_id, request_id)
|
|
42482
|
-
)
|
|
42483
|
-
`);
|
|
42484
|
-
db.exec(`
|
|
42485
|
-
CREATE TABLE IF NOT EXISTS otium_peer_inbox_requests (
|
|
42486
|
-
from_cell_id TEXT NOT NULL,
|
|
42487
|
-
request_id TEXT NOT NULL,
|
|
42488
|
-
kind TEXT NOT NULL CHECK (kind IN ('tell', 'ask')),
|
|
42489
|
-
topic_id TEXT NOT NULL,
|
|
42490
|
-
payload_hash TEXT NOT NULL,
|
|
42491
|
-
created_at TEXT NOT NULL,
|
|
42492
|
-
PRIMARY KEY (from_cell_id, request_id, kind)
|
|
42493
|
-
)
|
|
42494
|
-
`);
|
|
42495
|
-
db.exec(`
|
|
42496
|
-
CREATE TABLE IF NOT EXISTS otium_remote_asks (
|
|
42497
|
-
request_id TEXT PRIMARY KEY,
|
|
42498
|
-
expected_cell_id TEXT NOT NULL,
|
|
42499
|
-
user_id TEXT NOT NULL,
|
|
42500
|
-
caller_topic_id TEXT NOT NULL,
|
|
42501
|
-
from_key TEXT NOT NULL,
|
|
42502
|
-
to_key TEXT NOT NULL,
|
|
42503
|
-
source_query_id TEXT,
|
|
42504
|
-
created_at INTEGER NOT NULL
|
|
42505
|
-
)
|
|
42506
|
-
`);
|
|
42507
|
-
db.exec(`
|
|
42508
|
-
CREATE INDEX IF NOT EXISTS idx_otium_remote_asks_created
|
|
42509
|
-
ON otium_remote_asks(created_at)
|
|
42510
|
-
`);
|
|
42511
|
-
db.exec(`
|
|
42512
|
-
CREATE TABLE IF NOT EXISTS otium_peer_reply_outbox (
|
|
42513
|
-
node_cell_id TEXT NOT NULL,
|
|
42514
|
-
request_id TEXT NOT NULL,
|
|
42515
|
-
node_name TEXT NOT NULL,
|
|
42516
|
-
topic_id TEXT NOT NULL,
|
|
42517
|
-
user_id TEXT NOT NULL,
|
|
42518
|
-
source_title TEXT NOT NULL,
|
|
42519
|
-
reply_text TEXT NOT NULL,
|
|
42520
|
-
kind TEXT NOT NULL CHECK (kind IN ('reply', 'error')),
|
|
42521
|
-
created_at INTEGER NOT NULL,
|
|
42522
|
-
updated_at INTEGER NOT NULL,
|
|
42523
|
-
PRIMARY KEY (node_cell_id, request_id)
|
|
42524
|
-
)
|
|
42525
|
-
`);
|
|
42526
|
-
db.exec(`
|
|
42527
|
-
CREATE TABLE IF NOT EXISTS otium_shared_topic_state (
|
|
42528
|
-
local_topic_id TEXT PRIMARY KEY,
|
|
42529
|
-
host_topic_id TEXT,
|
|
42530
|
-
status TEXT NOT NULL CHECK (status IN ('publishing', 'published', 'unpublishing')),
|
|
42531
|
-
updated_at TEXT NOT NULL
|
|
42532
|
-
)
|
|
42533
|
-
`);
|
|
42534
|
-
db.exec(`
|
|
42535
|
-
CREATE TABLE IF NOT EXISTS otium_shared_message_outbox (
|
|
42536
|
-
local_topic_id TEXT NOT NULL,
|
|
42537
|
-
source_message_id TEXT NOT NULL,
|
|
42538
|
-
message_json TEXT NOT NULL,
|
|
42539
|
-
created_at TEXT NOT NULL,
|
|
42540
|
-
PRIMARY KEY (local_topic_id, source_message_id)
|
|
42541
|
-
)
|
|
42542
|
-
`);
|
|
42543
|
-
db.exec(`
|
|
42544
|
-
CREATE TABLE IF NOT EXISTS otium_peer_lifecycle (
|
|
42545
|
-
hub_node_id TEXT PRIMARY KEY,
|
|
42546
|
-
status TEXT NOT NULL CHECK (status IN ('attached', 'detached')),
|
|
42547
|
-
updated_at TEXT NOT NULL
|
|
42548
|
-
)
|
|
42549
|
-
`);
|
|
42550
|
-
});
|
|
42551
|
-
|
|
42552
|
-
// ../../adapters/otium/src/event-backflow.ts
|
|
42553
|
-
function hubEventSender(hubNode) {
|
|
42554
|
-
return async (payload) => {
|
|
42555
|
-
let token;
|
|
41926
|
+
function withJoinCredentialLock(operation) {
|
|
41927
|
+
const lockPath = joinLockPath();
|
|
41928
|
+
const ownerPath = resolve24(lockPath, "owner.json");
|
|
41929
|
+
const owner = { pid: process.pid, token: randomUUID28() };
|
|
41930
|
+
mkdirSync32(dirname21(lockPath), { recursive: true });
|
|
41931
|
+
for (let attempt = 0;; attempt += 1) {
|
|
41932
|
+
let created = false;
|
|
42556
41933
|
try {
|
|
42557
|
-
|
|
42558
|
-
|
|
42559
|
-
|
|
41934
|
+
mkdirSync32(lockPath, { mode: 448 });
|
|
41935
|
+
created = true;
|
|
41936
|
+
writeFileSync23(ownerPath, `${JSON.stringify(owner)}
|
|
41937
|
+
`, { mode: 384 });
|
|
41938
|
+
const ownerFd = openSync5(ownerPath, "r");
|
|
41939
|
+
try {
|
|
41940
|
+
fsyncSync2(ownerFd);
|
|
41941
|
+
} finally {
|
|
41942
|
+
closeSync5(ownerFd);
|
|
41943
|
+
}
|
|
41944
|
+
break;
|
|
41945
|
+
} catch (error2) {
|
|
41946
|
+
if (created) {
|
|
41947
|
+
rmSync10(lockPath, { recursive: true, force: true });
|
|
41948
|
+
throw error2;
|
|
41949
|
+
}
|
|
41950
|
+
if (error2.code !== "EEXIST")
|
|
41951
|
+
throw error2;
|
|
41952
|
+
let current3 = null;
|
|
41953
|
+
try {
|
|
41954
|
+
current3 = JSON.parse(readFileSync28(ownerPath, "utf8"));
|
|
41955
|
+
} catch {}
|
|
41956
|
+
let ageMs;
|
|
41957
|
+
try {
|
|
41958
|
+
ageMs = Date.now() - statSync20(lockPath).mtimeMs;
|
|
41959
|
+
} catch (statError) {
|
|
41960
|
+
if (statError.code === "ENOENT")
|
|
41961
|
+
continue;
|
|
41962
|
+
throw statError;
|
|
41963
|
+
}
|
|
41964
|
+
if (current3 && processIsAlive(current3.pid) || !current3 && ageMs <= JOIN_LOCK_STALE_MS) {
|
|
41965
|
+
throw new Error(`another Otium join credential operation is in progress at ${lockPath}`);
|
|
41966
|
+
}
|
|
41967
|
+
if (attempt > 0) {
|
|
41968
|
+
throw new Error(`could not recover stale Otium join credential lock at ${lockPath}`);
|
|
41969
|
+
}
|
|
41970
|
+
const stalePath = `${lockPath}.stale.${process.pid}.${randomUUID28()}`;
|
|
41971
|
+
try {
|
|
41972
|
+
renameSync16(lockPath, stalePath);
|
|
41973
|
+
rmSync10(stalePath, { recursive: true, force: true });
|
|
41974
|
+
} catch (staleError) {
|
|
41975
|
+
if (staleError.code !== "ENOENT")
|
|
41976
|
+
throw staleError;
|
|
41977
|
+
}
|
|
42560
41978
|
}
|
|
42561
|
-
|
|
41979
|
+
}
|
|
41980
|
+
try {
|
|
41981
|
+
return operation();
|
|
41982
|
+
} finally {
|
|
42562
41983
|
try {
|
|
42563
|
-
|
|
42564
|
-
|
|
42565
|
-
|
|
42566
|
-
|
|
42567
|
-
|
|
42568
|
-
},
|
|
42569
|
-
body: JSON.stringify(payload),
|
|
42570
|
-
signal: AbortSignal.timeout(PEER_EVENT_TIMEOUT_MS)
|
|
42571
|
-
});
|
|
42572
|
-
} catch {
|
|
42573
|
-
return { ok: false, error: `hub "${hubNode.nodeName ?? hubNode.cellId}" unreachable` };
|
|
42574
|
-
}
|
|
42575
|
-
const parsed = await response.json().catch(() => null);
|
|
42576
|
-
if (!response.ok || !parsed?.ok) {
|
|
42577
|
-
return {
|
|
42578
|
-
ok: false,
|
|
42579
|
-
error: parsed?.error ?? `peer event rejected (${response.status})`,
|
|
42580
|
-
status: response.status
|
|
42581
|
-
};
|
|
42582
|
-
}
|
|
42583
|
-
return { ok: true };
|
|
42584
|
-
};
|
|
42585
|
-
}
|
|
42586
|
-
function defined(record) {
|
|
42587
|
-
const out = {};
|
|
42588
|
-
for (const [key, value] of Object.entries(record)) {
|
|
42589
|
-
if (value !== undefined)
|
|
42590
|
-
out[key] = value;
|
|
41984
|
+
const current3 = JSON.parse(readFileSync28(ownerPath, "utf8"));
|
|
41985
|
+
if (current3.pid === owner.pid && current3.token === owner.token) {
|
|
41986
|
+
rmSync10(lockPath, { recursive: true, force: true });
|
|
41987
|
+
}
|
|
41988
|
+
} catch {}
|
|
42591
41989
|
}
|
|
42592
|
-
return out;
|
|
42593
41990
|
}
|
|
42594
|
-
function
|
|
42595
|
-
|
|
42596
|
-
|
|
42597
|
-
|
|
42598
|
-
|
|
42599
|
-
|
|
42600
|
-
|
|
42601
|
-
|
|
42602
|
-
|
|
42603
|
-
|
|
42604
|
-
|
|
42605
|
-
|
|
42606
|
-
|
|
42607
|
-
|
|
42608
|
-
|
|
42609
|
-
|
|
42610
|
-
|
|
42611
|
-
if (event.type !== "ai-status")
|
|
41991
|
+
function joinsEqual(left, right) {
|
|
41992
|
+
return left.central === right.central && left.relay === right.relay && left.cellId === right.cellId && left.secret === right.secret;
|
|
41993
|
+
}
|
|
41994
|
+
function normalizedJoin(join42) {
|
|
41995
|
+
return normalizeJoin({
|
|
41996
|
+
v: join42.v,
|
|
41997
|
+
central: join42.central,
|
|
41998
|
+
relay: join42.relay,
|
|
41999
|
+
cellId: join42.cellId,
|
|
42000
|
+
secret: join42.secret
|
|
42001
|
+
});
|
|
42002
|
+
}
|
|
42003
|
+
function joinCredentialDigest(join42) {
|
|
42004
|
+
return createHash11("sha256").update(JSON.stringify(normalizedJoin(join42))).digest("base64url");
|
|
42005
|
+
}
|
|
42006
|
+
function readPersistedJoin(path = joinFilePath()) {
|
|
42007
|
+
if (!existsSync37(path))
|
|
42612
42008
|
return null;
|
|
42613
|
-
const
|
|
42614
|
-
|
|
42615
|
-
|
|
42616
|
-
case "typing":
|
|
42617
|
-
return { type: "typing", topicId, userId: status.userId ?? "" };
|
|
42618
|
-
case "tool_call":
|
|
42619
|
-
return defined({
|
|
42620
|
-
type: "tool_call",
|
|
42621
|
-
topicId,
|
|
42622
|
-
queryId: status.queryId,
|
|
42623
|
-
name: status.name,
|
|
42624
|
-
input: status.input,
|
|
42625
|
-
label: status.label,
|
|
42626
|
-
toolUseId: status.toolUseId
|
|
42627
|
-
});
|
|
42628
|
-
case "tool_output":
|
|
42629
|
-
return defined({
|
|
42630
|
-
type: "tool_output",
|
|
42631
|
-
topicId,
|
|
42632
|
-
queryId: status.queryId,
|
|
42633
|
-
toolUseId: status.toolUseId,
|
|
42634
|
-
content: status.content,
|
|
42635
|
-
isError: status.isError
|
|
42636
|
-
});
|
|
42637
|
-
case "tool_status":
|
|
42638
|
-
return defined({
|
|
42639
|
-
type: "tool_status",
|
|
42640
|
-
topicId,
|
|
42641
|
-
queryId: status.queryId,
|
|
42642
|
-
kind: status.statusKind,
|
|
42643
|
-
content: status.content,
|
|
42644
|
-
toolName: status.toolName,
|
|
42645
|
-
elapsed: status.elapsed
|
|
42646
|
-
});
|
|
42647
|
-
case "file_ready":
|
|
42648
|
-
return defined({
|
|
42649
|
-
type: "file_ready",
|
|
42650
|
-
topicId,
|
|
42651
|
-
queryId: status.queryId,
|
|
42652
|
-
path: status.path,
|
|
42653
|
-
source: status.source
|
|
42654
|
-
});
|
|
42655
|
-
case "visual":
|
|
42656
|
-
return defined({
|
|
42657
|
-
type: "visual",
|
|
42658
|
-
topicId,
|
|
42659
|
-
queryId: status.queryId,
|
|
42660
|
-
url: status.url,
|
|
42661
|
-
id: status.id,
|
|
42662
|
-
title: status.title,
|
|
42663
|
-
kind: status.visualKind
|
|
42664
|
-
});
|
|
42665
|
-
case "ai_done":
|
|
42666
|
-
return defined({
|
|
42667
|
-
type: "ai_done",
|
|
42668
|
-
topicId,
|
|
42669
|
-
queryId: status.queryId,
|
|
42670
|
-
usage: status.usage,
|
|
42671
|
-
agent: status.agent,
|
|
42672
|
-
model: status.model
|
|
42673
|
-
});
|
|
42674
|
-
case "ai_error":
|
|
42675
|
-
return defined({
|
|
42676
|
-
type: "ai_error",
|
|
42677
|
-
topicId,
|
|
42678
|
-
queryId: status.queryId,
|
|
42679
|
-
error: status.error
|
|
42680
|
-
});
|
|
42681
|
-
case "ai_aborted":
|
|
42682
|
-
return defined({
|
|
42683
|
-
type: "ai_aborted",
|
|
42684
|
-
topicId,
|
|
42685
|
-
queryId: status.queryId,
|
|
42686
|
-
reason: status.reason
|
|
42687
|
-
});
|
|
42688
|
-
default:
|
|
42689
|
-
return null;
|
|
42009
|
+
const parsed = JSON.parse(readFileSync28(path, "utf-8"));
|
|
42010
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
42011
|
+
throw new Error("persisted join credentials are not a JSON object");
|
|
42690
42012
|
}
|
|
42013
|
+
return normalizeJoin(parsed);
|
|
42691
42014
|
}
|
|
42692
|
-
function
|
|
42693
|
-
|
|
42015
|
+
function isJoinPersisted(join42) {
|
|
42016
|
+
try {
|
|
42017
|
+
const persisted = readPersistedJoin();
|
|
42018
|
+
return persisted !== null && joinsEqual(persisted, normalizedJoin(join42));
|
|
42019
|
+
} catch {
|
|
42020
|
+
return false;
|
|
42021
|
+
}
|
|
42694
42022
|
}
|
|
42695
|
-
function
|
|
42696
|
-
const
|
|
42697
|
-
const
|
|
42698
|
-
const
|
|
42699
|
-
|
|
42700
|
-
|
|
42701
|
-
|
|
42702
|
-
|
|
42703
|
-
|
|
42704
|
-
|
|
42705
|
-
|
|
42706
|
-
|
|
42707
|
-
|
|
42708
|
-
|
|
42709
|
-
|
|
42710
|
-
|
|
42711
|
-
|
|
42712
|
-
|
|
42713
|
-
|
|
42714
|
-
|
|
42715
|
-
}
|
|
42716
|
-
};
|
|
42717
|
-
const post = (event) => {
|
|
42718
|
-
const isTerminal = TERMINAL_TYPES.has(String(event.type ?? ""));
|
|
42719
|
-
if (isTerminal)
|
|
42720
|
-
terminalQueued = true;
|
|
42721
|
-
forwarder.seq += 1;
|
|
42722
|
-
const seq = forwarder.seq;
|
|
42723
|
-
forwarder.pendingEvents += 1;
|
|
42724
|
-
forwarder.chain = forwarder.chain.then(async () => {
|
|
42023
|
+
function saveJoinWhileLocked(join42, options = {}) {
|
|
42024
|
+
const path = joinFilePath();
|
|
42025
|
+
const directory = dirname21(path);
|
|
42026
|
+
const normalized = normalizedJoin(join42);
|
|
42027
|
+
mkdirSync32(directory, { recursive: true });
|
|
42028
|
+
if (existsSync37(path)) {
|
|
42029
|
+
if (lstatSync(path).isSymbolicLink()) {
|
|
42030
|
+
throw new Error(`refusing to replace symlinked Otium join file at ${path}`);
|
|
42031
|
+
}
|
|
42032
|
+
let existing = null;
|
|
42033
|
+
try {
|
|
42034
|
+
existing = readPersistedJoin(path);
|
|
42035
|
+
} catch (error2) {
|
|
42036
|
+
if (!options.replaceExisting) {
|
|
42037
|
+
throw new Error(`existing Otium join file at ${path} is invalid; pass --replace to replace it`, { cause: error2 });
|
|
42038
|
+
}
|
|
42039
|
+
}
|
|
42040
|
+
if (existing && joinsEqual(existing, normalized)) {
|
|
42041
|
+
chmodSync7(path, 384);
|
|
42042
|
+
const fileFd = openSync5(path, "r");
|
|
42725
42043
|
try {
|
|
42726
|
-
|
|
42727
|
-
return;
|
|
42728
|
-
if (isTerminal) {
|
|
42729
|
-
upsertPeerTerminalOutbox({ hostNodeId, requestId, seq, event });
|
|
42730
|
-
}
|
|
42731
|
-
for (let attempt = 1;attempt <= PEER_EVENT_MAX_ATTEMPTS; attempt++) {
|
|
42732
|
-
const result = await sendEvent({ v: PEER_PROTOCOL_VERSION, requestId, seq, event });
|
|
42733
|
-
if (result.ok) {
|
|
42734
|
-
if (isTerminal)
|
|
42735
|
-
acknowledgePeerTerminal(hostNodeId, requestId);
|
|
42736
|
-
return;
|
|
42737
|
-
}
|
|
42738
|
-
logger.warn({ requestId, seq, type: event.type, attempt, error: result.error }, "otium: peer event delivery to hub failed");
|
|
42739
|
-
if (attempt < PEER_EVENT_MAX_ATTEMPTS) {
|
|
42740
|
-
const delayMs = retryBaseMs * 2 ** (attempt - 1);
|
|
42741
|
-
await new Promise((resolve25) => setTimeout(resolve25, delayMs));
|
|
42742
|
-
}
|
|
42743
|
-
}
|
|
42744
|
-
forwarder.deliveryBlocked = true;
|
|
42745
|
-
logger.error({ requestId, seq, type: event.type }, "otium: peer event delivery exhausted");
|
|
42044
|
+
fsyncSync2(fileFd);
|
|
42746
42045
|
} finally {
|
|
42747
|
-
|
|
42748
|
-
}
|
|
42749
|
-
});
|
|
42750
|
-
};
|
|
42751
|
-
forwarder.tap = (raw) => {
|
|
42752
|
-
if (forwarder.finished)
|
|
42753
|
-
return;
|
|
42754
|
-
const type = String(raw.type ?? "");
|
|
42755
|
-
if (!FORWARDED_TYPES.has(type))
|
|
42756
|
-
return;
|
|
42757
|
-
if (type === "message") {
|
|
42758
|
-
const message = raw.message;
|
|
42759
|
-
if (message?.authorId === "ai" && forwarder.queryId && message.queryId !== forwarder.queryId) {
|
|
42760
|
-
return;
|
|
42046
|
+
closeSync5(fileFd);
|
|
42761
42047
|
}
|
|
42762
|
-
|
|
42763
|
-
|
|
42764
|
-
|
|
42765
|
-
|
|
42048
|
+
const directoryFd = openSync5(directory, "r");
|
|
42049
|
+
try {
|
|
42050
|
+
fsyncSync2(directoryFd);
|
|
42051
|
+
} finally {
|
|
42052
|
+
closeSync5(directoryFd);
|
|
42766
42053
|
}
|
|
42054
|
+
return path;
|
|
42767
42055
|
}
|
|
42768
|
-
|
|
42769
|
-
|
|
42770
|
-
if (!terminalQueued) {
|
|
42771
|
-
post({
|
|
42772
|
-
type: "ai_error",
|
|
42773
|
-
topicId: localTopicId,
|
|
42774
|
-
queryId: forwarder.queryId ?? requestId,
|
|
42775
|
-
error: `worker event queue saturated (${maxPendingEvents})`
|
|
42776
|
-
});
|
|
42777
|
-
detach();
|
|
42778
|
-
}
|
|
42779
|
-
return;
|
|
42056
|
+
if (!options.replaceExisting) {
|
|
42057
|
+
throw new Error(`this node is already joined${existing ? ` as ${existing.cellId}` : " with an invalid join file"}; pass --replace to replace its credentials`);
|
|
42780
42058
|
}
|
|
42781
|
-
|
|
42782
|
-
|
|
42783
|
-
|
|
42784
|
-
|
|
42785
|
-
|
|
42786
|
-
|
|
42787
|
-
|
|
42788
|
-
|
|
42789
|
-
|
|
42790
|
-
|
|
42791
|
-
|
|
42059
|
+
}
|
|
42060
|
+
const temporaryPath = resolve24(directory, `.otium-join.json.${process.pid}.${randomUUID28()}.tmp`);
|
|
42061
|
+
let fd;
|
|
42062
|
+
try {
|
|
42063
|
+
fd = openSync5(temporaryPath, "wx", 384);
|
|
42064
|
+
writeFileSync23(fd, `${JSON.stringify(normalized, null, 2)}
|
|
42065
|
+
`, "utf8");
|
|
42066
|
+
fsyncSync2(fd);
|
|
42067
|
+
closeSync5(fd);
|
|
42068
|
+
fd = undefined;
|
|
42069
|
+
if (options.replaceExisting) {
|
|
42070
|
+
renameSync16(temporaryPath, path);
|
|
42071
|
+
} else {
|
|
42072
|
+
linkSync(temporaryPath, path);
|
|
42073
|
+
unlinkSync23(temporaryPath);
|
|
42074
|
+
}
|
|
42075
|
+
chmodSync7(path, 384);
|
|
42076
|
+
const directoryFd = openSync5(directory, "r");
|
|
42077
|
+
try {
|
|
42078
|
+
fsyncSync2(directoryFd);
|
|
42079
|
+
} finally {
|
|
42080
|
+
closeSync5(directoryFd);
|
|
42081
|
+
}
|
|
42082
|
+
} catch (error2) {
|
|
42083
|
+
if (fd !== undefined)
|
|
42084
|
+
closeSync5(fd);
|
|
42085
|
+
if (existsSync37(temporaryPath))
|
|
42086
|
+
unlinkSync23(temporaryPath);
|
|
42087
|
+
throw error2;
|
|
42088
|
+
}
|
|
42089
|
+
return path;
|
|
42792
42090
|
}
|
|
42793
|
-
function
|
|
42794
|
-
|
|
42795
|
-
ensureBackflowSubscription();
|
|
42091
|
+
function saveJoin(join42, options = {}) {
|
|
42092
|
+
return withJoinCredentialLock(() => saveJoinWhileLocked(join42, options));
|
|
42796
42093
|
}
|
|
42797
|
-
|
|
42798
|
-
|
|
42799
|
-
|
|
42800
|
-
|
|
42801
|
-
|
|
42802
|
-
|
|
42803
|
-
|
|
42804
|
-
const node = await resolvePeerNodeByCellId(row.host_node_id).catch(() => null);
|
|
42805
|
-
if (!node)
|
|
42806
|
-
continue;
|
|
42807
|
-
let event;
|
|
42808
|
-
try {
|
|
42809
|
-
event = JSON.parse(row.event_json);
|
|
42810
|
-
} catch {
|
|
42811
|
-
continue;
|
|
42812
|
-
}
|
|
42813
|
-
const result = await hubEventSender(node)({
|
|
42814
|
-
v: PEER_PROTOCOL_VERSION,
|
|
42815
|
-
requestId: row.request_id,
|
|
42816
|
-
seq: row.seq,
|
|
42817
|
-
event
|
|
42818
|
-
});
|
|
42819
|
-
if (result.ok && acknowledgePeerTerminal(row.host_node_id, row.request_id))
|
|
42820
|
-
acknowledged += 1;
|
|
42094
|
+
function removeJoin() {
|
|
42095
|
+
return withJoinCredentialLock(() => {
|
|
42096
|
+
const path = joinFilePath();
|
|
42097
|
+
if (!existsSync37(path))
|
|
42098
|
+
return false;
|
|
42099
|
+
if (lstatSync(path).isSymbolicLink()) {
|
|
42100
|
+
throw new Error(`refusing to remove symlinked Otium join file at ${path}`);
|
|
42821
42101
|
}
|
|
42822
|
-
|
|
42823
|
-
|
|
42824
|
-
|
|
42102
|
+
unlinkSync23(path);
|
|
42103
|
+
const directoryFd = openSync5(dirname21(path), "r");
|
|
42104
|
+
try {
|
|
42105
|
+
fsyncSync2(directoryFd);
|
|
42106
|
+
} finally {
|
|
42107
|
+
closeSync5(directoryFd);
|
|
42108
|
+
}
|
|
42109
|
+
return true;
|
|
42110
|
+
});
|
|
42111
|
+
}
|
|
42112
|
+
function loadJoin() {
|
|
42113
|
+
const central = process.env.OTIUM_CENTRAL_URL?.trim();
|
|
42114
|
+
const cellId = process.env.OTIUM_CELL_ID?.trim();
|
|
42115
|
+
const secret = process.env.OTIUM_CELL_SECRET?.trim();
|
|
42116
|
+
const relay = process.env.OTIUM_RELAY_URL?.trim();
|
|
42117
|
+
if (central && cellId && secret) {
|
|
42118
|
+
try {
|
|
42119
|
+
return normalizeJoin({ central, relay, cellId, secret });
|
|
42120
|
+
} catch (err2) {
|
|
42121
|
+
logger.warn({ err: err2 }, "otium: invalid OTIUM_CENTRAL_URL/OTIUM_CELL_ID/OTIUM_CELL_SECRET env");
|
|
42122
|
+
return null;
|
|
42123
|
+
}
|
|
42124
|
+
}
|
|
42125
|
+
if (central || cellId || secret) {
|
|
42126
|
+
logger.warn("otium: OTIUM_CENTRAL_URL, OTIUM_CELL_ID, OTIUM_CELL_SECRET must be set together \u2014 ignoring partial env");
|
|
42127
|
+
}
|
|
42128
|
+
const path = joinFilePath();
|
|
42129
|
+
if (!existsSync37(path))
|
|
42130
|
+
return null;
|
|
42131
|
+
try {
|
|
42132
|
+
const parsed = JSON.parse(readFileSync28(path, "utf-8"));
|
|
42133
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
42134
|
+
return null;
|
|
42135
|
+
return normalizeJoin(parsed);
|
|
42136
|
+
} catch (err2) {
|
|
42137
|
+
logger.warn({ err: err2, path }, "otium: failed to read join file");
|
|
42138
|
+
return null;
|
|
42825
42139
|
}
|
|
42826
42140
|
}
|
|
42827
|
-
|
|
42828
|
-
|
|
42829
|
-
if (!forwarder)
|
|
42830
|
-
return;
|
|
42831
|
-
const raw = translateBusEvent(event);
|
|
42832
|
-
if (raw)
|
|
42833
|
-
forwarder.tap(raw);
|
|
42834
|
-
}
|
|
42835
|
-
function ensureBackflowSubscription() {
|
|
42836
|
-
if (!unsubscribe)
|
|
42837
|
-
unsubscribe = runtimeBus().subscribe(onBusEvent);
|
|
42838
|
-
}
|
|
42839
|
-
function startEventBackflow() {
|
|
42840
|
-
ensureBackflowSubscription();
|
|
42841
|
-
if (!terminalOutboxTimer) {
|
|
42842
|
-
flushPeerTerminalOutbox();
|
|
42843
|
-
terminalOutboxTimer = setInterval(() => void flushPeerTerminalOutbox(), 5000);
|
|
42844
|
-
terminalOutboxTimer.unref?.();
|
|
42845
|
-
}
|
|
42846
|
-
return stopEventBackflow;
|
|
42847
|
-
}
|
|
42848
|
-
function stopEventBackflow() {
|
|
42849
|
-
unsubscribe?.();
|
|
42850
|
-
unsubscribe = null;
|
|
42851
|
-
if (terminalOutboxTimer)
|
|
42852
|
-
clearInterval(terminalOutboxTimer);
|
|
42853
|
-
terminalOutboxTimer = null;
|
|
42854
|
-
activeForwarders.clear();
|
|
42855
|
-
}
|
|
42856
|
-
var FORWARDED_TYPES, TERMINAL_TYPES, PEER_EVENT_MAX_ATTEMPTS = 5, PEER_EVENT_RETRY_BASE_MS = 100, PEER_EVENT_TIMEOUT_MS = 15000, PEER_EVENT_MAX_PENDING = 256, activeForwarders, unsubscribe = null, terminalOutboxTimer = null, terminalOutboxFlushInFlight = false;
|
|
42857
|
-
var init_event_backflow = __esm(async () => {
|
|
42141
|
+
var JOIN_LOCK_STALE_MS = 30000;
|
|
42142
|
+
var init_join = __esm(async () => {
|
|
42858
42143
|
await init_src();
|
|
42859
|
-
await init_central();
|
|
42860
|
-
init_protocol();
|
|
42861
|
-
await init_store2();
|
|
42862
|
-
FORWARDED_TYPES = new Set([
|
|
42863
|
-
"message",
|
|
42864
|
-
"message_updated",
|
|
42865
|
-
"typing",
|
|
42866
|
-
"tool_call",
|
|
42867
|
-
"tool_output",
|
|
42868
|
-
"tool_status",
|
|
42869
|
-
"visual",
|
|
42870
|
-
"file_ready",
|
|
42871
|
-
"ai_done",
|
|
42872
|
-
"ai_error",
|
|
42873
|
-
"ai_aborted"
|
|
42874
|
-
]);
|
|
42875
|
-
TERMINAL_TYPES = new Set(["ai_done", "ai_error", "ai_aborted"]);
|
|
42876
|
-
activeForwarders = new Map;
|
|
42877
42144
|
});
|
|
42878
42145
|
|
|
42879
42146
|
// ../../adapters/otium/src/peer-files.ts
|
|
@@ -42900,10 +42167,6 @@ function attachment(row) {
|
|
|
42900
42167
|
function rowFor(fileId) {
|
|
42901
42168
|
return db.query("SELECT * FROM otium_peer_files WHERE id = ?").get(fileId) ?? null;
|
|
42902
42169
|
}
|
|
42903
|
-
function peerFileAllowsAccess(fileId, access) {
|
|
42904
|
-
const row = rowFor(fileId);
|
|
42905
|
-
return row?.topic_id === access.topicId && row.owner_user_id === access.ownerUserId;
|
|
42906
|
-
}
|
|
42907
42170
|
function safeFilename(filename) {
|
|
42908
42171
|
const value = basename13(filename).replace(/[^A-Za-z0-9._ -]/g, "_").slice(0, 120);
|
|
42909
42172
|
return value || "upload";
|
|
@@ -42959,21 +42222,6 @@ function deletePeerFilesForTopic(topicId) {
|
|
|
42959
42222
|
for (const row of rows)
|
|
42960
42223
|
rmSync11(row.path, { force: true });
|
|
42961
42224
|
}
|
|
42962
|
-
async function storePeerInputFile(file, access) {
|
|
42963
|
-
const id = randomUUID29();
|
|
42964
|
-
mkdirSync33(PEER_FILES_DIR, { recursive: true });
|
|
42965
|
-
const filename = file.name || "upload";
|
|
42966
|
-
const path = join42(PEER_FILES_DIR, `${id}-${safeFilename(filename)}`);
|
|
42967
|
-
const sizeBytes = await Bun.write(path, file);
|
|
42968
|
-
return recordFile({
|
|
42969
|
-
id,
|
|
42970
|
-
path,
|
|
42971
|
-
sizeBytes,
|
|
42972
|
-
filename,
|
|
42973
|
-
mimeType: file.type || "application/octet-stream",
|
|
42974
|
-
...access
|
|
42975
|
-
});
|
|
42976
|
-
}
|
|
42977
42225
|
function installPeerFileHooks() {
|
|
42978
42226
|
const previous = fileHooks();
|
|
42979
42227
|
const hooks = {
|
|
@@ -43037,15 +42285,7 @@ var PEER_BRIDGE_TIMEOUT_MS2 = 15000, otiumPeerRuntimeBridge;
|
|
|
43037
42285
|
var init_runtime_bridge = __esm(async () => {
|
|
43038
42286
|
await init_src();
|
|
43039
42287
|
await init_central();
|
|
43040
|
-
await init_event_backflow();
|
|
43041
42288
|
otiumPeerRuntimeBridge = {
|
|
43042
|
-
async flushEvents(localTopicId) {
|
|
43043
|
-
const forwarder = getActiveForwarder(localTopicId);
|
|
43044
|
-
if (!forwarder)
|
|
43045
|
-
return false;
|
|
43046
|
-
await forwarder.chain;
|
|
43047
|
-
return !forwarder.deliveryBlocked;
|
|
43048
|
-
},
|
|
43049
42289
|
async spawnSubagent(request) {
|
|
43050
42290
|
const hubNode = await resolvePeerNodeByCellId(request.bridge.hubCellId).catch(() => null);
|
|
43051
42291
|
if (!hubNode)
|
|
@@ -43265,6 +42505,149 @@ var init_runtime_bridge = __esm(async () => {
|
|
|
43265
42505
|
};
|
|
43266
42506
|
});
|
|
43267
42507
|
|
|
42508
|
+
// ../../adapters/otium/src/store.ts
|
|
42509
|
+
import { createHash as createHash12 } from "crypto";
|
|
42510
|
+
function cleanupPeerStateForLocalTopic(localTopicId) {
|
|
42511
|
+
return db.transaction(() => {
|
|
42512
|
+
const inboxRequests = db.run("DELETE FROM otium_peer_inbox_requests WHERE topic_id = ?", [
|
|
42513
|
+
localTopicId
|
|
42514
|
+
]).changes;
|
|
42515
|
+
const remoteAsks = db.run("DELETE FROM otium_remote_asks WHERE caller_topic_id = ?", [
|
|
42516
|
+
localTopicId
|
|
42517
|
+
]).changes;
|
|
42518
|
+
return { inboxRequests, remoteAsks };
|
|
42519
|
+
})();
|
|
42520
|
+
}
|
|
42521
|
+
function peerInboxPayloadHash(value) {
|
|
42522
|
+
return createHash12("sha256").update(JSON.stringify(value)).digest("hex");
|
|
42523
|
+
}
|
|
42524
|
+
function claimPeerInboxRequest(args) {
|
|
42525
|
+
const inserted = db.run(`INSERT OR IGNORE INTO otium_peer_inbox_requests
|
|
42526
|
+
(from_cell_id, request_id, kind, topic_id, payload_hash, created_at)
|
|
42527
|
+
VALUES (?, ?, ?, ?, ?, ?)`, [
|
|
42528
|
+
args.fromCellId,
|
|
42529
|
+
args.requestId,
|
|
42530
|
+
args.kind,
|
|
42531
|
+
args.topicId,
|
|
42532
|
+
args.payloadHash,
|
|
42533
|
+
new Date().toISOString()
|
|
42534
|
+
]);
|
|
42535
|
+
if (inserted.changes === 1)
|
|
42536
|
+
return { outcome: "claimed" };
|
|
42537
|
+
const existing = db.query("SELECT payload_hash FROM otium_peer_inbox_requests WHERE from_cell_id = ? AND request_id = ? AND kind = ?").get(args.fromCellId, args.requestId, args.kind);
|
|
42538
|
+
if (!existing)
|
|
42539
|
+
return { outcome: "conflict" };
|
|
42540
|
+
return { outcome: existing.payload_hash === args.payloadHash ? "replay" : "conflict" };
|
|
42541
|
+
}
|
|
42542
|
+
function releasePeerInboxRequest(fromCellId, requestId, kind) {
|
|
42543
|
+
db.run("DELETE FROM otium_peer_inbox_requests WHERE from_cell_id = ? AND request_id = ? AND kind = ?", [fromCellId, requestId, kind]);
|
|
42544
|
+
}
|
|
42545
|
+
function createRemoteAsk(args) {
|
|
42546
|
+
const result = db.run(`INSERT OR IGNORE INTO otium_remote_asks
|
|
42547
|
+
(request_id, expected_cell_id, user_id, caller_topic_id, from_key, to_key,
|
|
42548
|
+
source_query_id, created_at)
|
|
42549
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
42550
|
+
args.requestId,
|
|
42551
|
+
args.expectedCellId,
|
|
42552
|
+
args.userId,
|
|
42553
|
+
args.callerTopicId,
|
|
42554
|
+
args.from,
|
|
42555
|
+
args.to,
|
|
42556
|
+
args.sourceQueryId ?? null,
|
|
42557
|
+
args.createdAt ?? Date.now()
|
|
42558
|
+
]);
|
|
42559
|
+
return result.changes === 1;
|
|
42560
|
+
}
|
|
42561
|
+
function getRemoteAsk(requestId) {
|
|
42562
|
+
return db.query("SELECT * FROM otium_remote_asks WHERE request_id = ?").get(requestId) ?? null;
|
|
42563
|
+
}
|
|
42564
|
+
function deleteRemoteAsk(requestId) {
|
|
42565
|
+
return db.run("DELETE FROM otium_remote_asks WHERE request_id = ?", [requestId]).changes === 1;
|
|
42566
|
+
}
|
|
42567
|
+
function pruneRemoteAsks(olderThan) {
|
|
42568
|
+
return db.run("DELETE FROM otium_remote_asks WHERE created_at < ?", [olderThan]).changes;
|
|
42569
|
+
}
|
|
42570
|
+
function upsertPeerReplyOutbox(args) {
|
|
42571
|
+
const now = Date.now();
|
|
42572
|
+
db.run(`INSERT INTO otium_peer_reply_outbox
|
|
42573
|
+
(node_cell_id, request_id, node_name, topic_id, user_id, source_title,
|
|
42574
|
+
reply_text, kind, created_at, updated_at)
|
|
42575
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
42576
|
+
ON CONFLICT(node_cell_id, request_id) DO UPDATE SET
|
|
42577
|
+
node_name = excluded.node_name,
|
|
42578
|
+
topic_id = excluded.topic_id,
|
|
42579
|
+
user_id = excluded.user_id,
|
|
42580
|
+
source_title = excluded.source_title,
|
|
42581
|
+
reply_text = excluded.reply_text,
|
|
42582
|
+
kind = excluded.kind,
|
|
42583
|
+
updated_at = excluded.updated_at`, [
|
|
42584
|
+
args.nodeCellId,
|
|
42585
|
+
args.requestId,
|
|
42586
|
+
args.nodeName,
|
|
42587
|
+
args.topicId,
|
|
42588
|
+
args.userId,
|
|
42589
|
+
args.sourceTitle,
|
|
42590
|
+
args.replyText,
|
|
42591
|
+
args.kind,
|
|
42592
|
+
now,
|
|
42593
|
+
now
|
|
42594
|
+
]);
|
|
42595
|
+
}
|
|
42596
|
+
function listPeerReplyOutbox(limit = 100) {
|
|
42597
|
+
return db.query("SELECT * FROM otium_peer_reply_outbox ORDER BY created_at LIMIT ?").all(limit);
|
|
42598
|
+
}
|
|
42599
|
+
function deletePeerReplyOutbox(nodeCellId, requestId) {
|
|
42600
|
+
return db.run("DELETE FROM otium_peer_reply_outbox WHERE node_cell_id = ? AND request_id = ?", [
|
|
42601
|
+
nodeCellId,
|
|
42602
|
+
requestId
|
|
42603
|
+
]).changes === 1;
|
|
42604
|
+
}
|
|
42605
|
+
var init_store2 = __esm(async () => {
|
|
42606
|
+
await init_src();
|
|
42607
|
+
db.exec(`
|
|
42608
|
+
CREATE TABLE IF NOT EXISTS otium_peer_inbox_requests (
|
|
42609
|
+
from_cell_id TEXT NOT NULL,
|
|
42610
|
+
request_id TEXT NOT NULL,
|
|
42611
|
+
kind TEXT NOT NULL CHECK (kind IN ('tell', 'ask')),
|
|
42612
|
+
topic_id TEXT NOT NULL,
|
|
42613
|
+
payload_hash TEXT NOT NULL,
|
|
42614
|
+
created_at TEXT NOT NULL,
|
|
42615
|
+
PRIMARY KEY (from_cell_id, request_id, kind)
|
|
42616
|
+
)
|
|
42617
|
+
`);
|
|
42618
|
+
db.exec(`
|
|
42619
|
+
CREATE TABLE IF NOT EXISTS otium_remote_asks (
|
|
42620
|
+
request_id TEXT PRIMARY KEY,
|
|
42621
|
+
expected_cell_id TEXT NOT NULL,
|
|
42622
|
+
user_id TEXT NOT NULL,
|
|
42623
|
+
caller_topic_id TEXT NOT NULL,
|
|
42624
|
+
from_key TEXT NOT NULL,
|
|
42625
|
+
to_key TEXT NOT NULL,
|
|
42626
|
+
source_query_id TEXT,
|
|
42627
|
+
created_at INTEGER NOT NULL
|
|
42628
|
+
)
|
|
42629
|
+
`);
|
|
42630
|
+
db.exec(`
|
|
42631
|
+
CREATE INDEX IF NOT EXISTS idx_otium_remote_asks_created
|
|
42632
|
+
ON otium_remote_asks(created_at)
|
|
42633
|
+
`);
|
|
42634
|
+
db.exec(`
|
|
42635
|
+
CREATE TABLE IF NOT EXISTS otium_peer_reply_outbox (
|
|
42636
|
+
node_cell_id TEXT NOT NULL,
|
|
42637
|
+
request_id TEXT NOT NULL,
|
|
42638
|
+
node_name TEXT NOT NULL,
|
|
42639
|
+
topic_id TEXT NOT NULL,
|
|
42640
|
+
user_id TEXT NOT NULL,
|
|
42641
|
+
source_title TEXT NOT NULL,
|
|
42642
|
+
reply_text TEXT NOT NULL,
|
|
42643
|
+
kind TEXT NOT NULL CHECK (kind IN ('reply', 'error')),
|
|
42644
|
+
created_at INTEGER NOT NULL,
|
|
42645
|
+
updated_at INTEGER NOT NULL,
|
|
42646
|
+
PRIMARY KEY (node_cell_id, request_id)
|
|
42647
|
+
)
|
|
42648
|
+
`);
|
|
42649
|
+
});
|
|
42650
|
+
|
|
43268
42651
|
// ../../adapters/otium/src/session-bridge.ts
|
|
43269
42652
|
function prunePendingRemoteAsks(now = Date.now()) {
|
|
43270
42653
|
pruneRemoteAsks(now - PENDING_ASK_TTL_MS2);
|
|
@@ -43656,287 +43039,6 @@ var init_session_bridge_ipc = __esm(() => {
|
|
|
43656
43039
|
MAX_BODY_BYTES2 = 1024 * 1024;
|
|
43657
43040
|
});
|
|
43658
43041
|
|
|
43659
|
-
// ../../adapters/otium/src/shared-topic-sync.ts
|
|
43660
|
-
var exports_shared_topic_sync = {};
|
|
43661
|
-
__export(exports_shared_topic_sync, {
|
|
43662
|
-
startSharedTopicSync: () => startSharedTopicSync,
|
|
43663
|
-
forwardSharedTopicMessage: () => forwardSharedTopicMessage,
|
|
43664
|
-
downgradeSharedTopicsForHub: () => downgradeSharedTopicsForHub,
|
|
43665
|
-
disconnectSharedTopics: () => disconnectSharedTopics,
|
|
43666
|
-
checkPeerAttachment: () => checkPeerAttachment,
|
|
43667
|
-
acceptSharedTopicMessages: () => acceptSharedTopicMessages
|
|
43668
|
-
});
|
|
43669
|
-
function isMirrorTopic(topicId) {
|
|
43670
|
-
return listPeerSessions().some((row) => row.local_topic_id === topicId && row.binding_mode === "mirror");
|
|
43671
|
-
}
|
|
43672
|
-
function metadata(topic) {
|
|
43673
|
-
if (!topic.agent)
|
|
43674
|
-
throw new Error(`topic ${topic.id} has no executable agent`);
|
|
43675
|
-
const registry = getRegistry(topic.agent);
|
|
43676
|
-
return {
|
|
43677
|
-
localTopicId: topic.id,
|
|
43678
|
-
title: topic.title,
|
|
43679
|
-
...topic.description ? { description: topic.description } : {},
|
|
43680
|
-
agent: topic.agent,
|
|
43681
|
-
model: topic.defaultModel || registry.defaultModel,
|
|
43682
|
-
effort: topic.defaultEffort ?? "medium"
|
|
43683
|
-
};
|
|
43684
|
-
}
|
|
43685
|
-
async function peerRequest(node, path, init) {
|
|
43686
|
-
const token = await mintPeerToken(node.cellId);
|
|
43687
|
-
const response = await fetch(`${node.baseUrl.replace(/\/+$/, "")}${path}`, {
|
|
43688
|
-
...init,
|
|
43689
|
-
headers: {
|
|
43690
|
-
authorization: `Bearer ${token}`,
|
|
43691
|
-
"content-type": "application/json",
|
|
43692
|
-
...init.headers ?? {}
|
|
43693
|
-
},
|
|
43694
|
-
signal: AbortSignal.timeout(15000)
|
|
43695
|
-
});
|
|
43696
|
-
const body = await response.json().catch(() => null);
|
|
43697
|
-
if (!response.ok || !body?.ok)
|
|
43698
|
-
throw new Error(String(body?.error ?? `peer request failed (${response.status})`));
|
|
43699
|
-
return body;
|
|
43700
|
-
}
|
|
43701
|
-
function messageEnvelope(message) {
|
|
43702
|
-
const author = message.agentType ? "ai" : message.kind === "system" || message.kind === "tool" ? "system" : "user";
|
|
43703
|
-
return {
|
|
43704
|
-
sourceMessageId: message.sourceMessageId ?? message.id,
|
|
43705
|
-
author,
|
|
43706
|
-
text: message.text,
|
|
43707
|
-
createdAt: message.createdAt,
|
|
43708
|
-
...message.agentType ? { agent: message.agentType } : {},
|
|
43709
|
-
...message.model ? { model: message.model } : {},
|
|
43710
|
-
...message.kind ? { kind: message.kind } : {}
|
|
43711
|
-
};
|
|
43712
|
-
}
|
|
43713
|
-
async function publishTopic(join43, topicId, node) {
|
|
43714
|
-
const topic = getTopic(topicId);
|
|
43715
|
-
if (!topic || topic.kind !== "agent" || !topic.agent || !isTopicShared(topic) || !topic.visibility || topic.visibility === "hidden" || isMirrorTopic(topicId))
|
|
43716
|
-
return;
|
|
43717
|
-
const state2 = getSharedTopicState(topicId);
|
|
43718
|
-
setSharedTopicState({
|
|
43719
|
-
localTopicId: topicId,
|
|
43720
|
-
hostTopicId: state2?.host_topic_id,
|
|
43721
|
-
status: "publishing"
|
|
43722
|
-
});
|
|
43723
|
-
try {
|
|
43724
|
-
const body = await peerRequest(node, "/api/v1/peer/shared-topic", {
|
|
43725
|
-
method: "POST",
|
|
43726
|
-
body: JSON.stringify({
|
|
43727
|
-
v: PEER_PROTOCOL_VERSION,
|
|
43728
|
-
...metadata(topic)
|
|
43729
|
-
})
|
|
43730
|
-
});
|
|
43731
|
-
const hostTopicId = typeof body.hostTopicId === "string" ? body.hostTopicId : state2?.host_topic_id;
|
|
43732
|
-
if (!hostTopicId)
|
|
43733
|
-
throw new Error("shared-topic response omitted hostTopicId");
|
|
43734
|
-
setSharedTopicState({ localTopicId: topicId, hostTopicId, status: "published" });
|
|
43735
|
-
const messages = listApiMessages(topicId, { limit: 200 }).page.filter((message) => !message.sourceNode).map(messageEnvelope);
|
|
43736
|
-
if (messages.length) {
|
|
43737
|
-
await peerRequest(node, "/api/v1/peer/shared-topic/messages", {
|
|
43738
|
-
method: "POST",
|
|
43739
|
-
body: JSON.stringify({
|
|
43740
|
-
v: PEER_PROTOCOL_VERSION,
|
|
43741
|
-
localTopicId: topicId,
|
|
43742
|
-
hostTopicId,
|
|
43743
|
-
messages
|
|
43744
|
-
})
|
|
43745
|
-
});
|
|
43746
|
-
}
|
|
43747
|
-
for (const row of listSharedMessages(topicId)) {
|
|
43748
|
-
await peerRequest(node, "/api/v1/peer/shared-topic/messages", {
|
|
43749
|
-
method: "POST",
|
|
43750
|
-
body: JSON.stringify({
|
|
43751
|
-
v: PEER_PROTOCOL_VERSION,
|
|
43752
|
-
localTopicId: topicId,
|
|
43753
|
-
hostTopicId,
|
|
43754
|
-
messages: [JSON.parse(row.message_json)]
|
|
43755
|
-
})
|
|
43756
|
-
});
|
|
43757
|
-
deleteSharedMessage(topicId, row.source_message_id);
|
|
43758
|
-
}
|
|
43759
|
-
} catch (error2) {
|
|
43760
|
-
logger.warn({ error: error2, topicId }, "otium: shared topic publish deferred");
|
|
43761
|
-
setTimeout(() => void reconcileTopic(join43, topicId), RETRY_MS).unref?.();
|
|
43762
|
-
}
|
|
43763
|
-
}
|
|
43764
|
-
async function unpublishTopic(join43, topicId, node) {
|
|
43765
|
-
const state2 = getSharedTopicState(topicId);
|
|
43766
|
-
if (!state2)
|
|
43767
|
-
return;
|
|
43768
|
-
setSharedTopicState({
|
|
43769
|
-
localTopicId: topicId,
|
|
43770
|
-
hostTopicId: state2.host_topic_id,
|
|
43771
|
-
status: "unpublishing"
|
|
43772
|
-
});
|
|
43773
|
-
try {
|
|
43774
|
-
await peerRequest(node, `/api/v1/peer/shared-topic/${encodeURIComponent(topicId)}`, {
|
|
43775
|
-
method: "DELETE"
|
|
43776
|
-
});
|
|
43777
|
-
deleteSharedTopicState(topicId);
|
|
43778
|
-
} catch (error2) {
|
|
43779
|
-
logger.warn({ error: error2, topicId }, "otium: shared topic unpublish deferred");
|
|
43780
|
-
setTimeout(() => void reconcileTopic(join43, topicId), RETRY_MS).unref?.();
|
|
43781
|
-
}
|
|
43782
|
-
}
|
|
43783
|
-
async function reconcileTopic(join43, topicId, explicit = false) {
|
|
43784
|
-
if (!otiumCentralConfig())
|
|
43785
|
-
return;
|
|
43786
|
-
if (!explicit && isPeerDetached(join43.cellId))
|
|
43787
|
-
return;
|
|
43788
|
-
const target = (await listPeerNodes()).find((node) => node.isPrimary) ?? null;
|
|
43789
|
-
if (!target)
|
|
43790
|
-
return;
|
|
43791
|
-
const topic = getTopic(topicId);
|
|
43792
|
-
if (topic?.kind === "agent" && topic.agent && isTopicShared(topic) && topic.visibility !== "hidden" && !isMirrorTopic(topicId))
|
|
43793
|
-
return publishTopic(join43, topicId, target);
|
|
43794
|
-
return unpublishTopic(join43, topicId, target);
|
|
43795
|
-
}
|
|
43796
|
-
function disconnectSharedTopics(join43) {
|
|
43797
|
-
const states = listSharedTopicStates();
|
|
43798
|
-
const hubPromise = listPeerNodes({ fresh: true }).then((nodes) => nodes.find((node) => node.isPrimary) ?? null, () => null);
|
|
43799
|
-
const updated = downgradeSharedTopicsLocally(join43.cellId);
|
|
43800
|
-
for (const topicId of updated)
|
|
43801
|
-
WsHub.get().broadcastTopicUpdated(topicId);
|
|
43802
|
-
const deletions = states.map(async (state2) => {
|
|
43803
|
-
const hub = await hubPromise;
|
|
43804
|
-
if (!hub)
|
|
43805
|
-
return;
|
|
43806
|
-
try {
|
|
43807
|
-
await peerRequest(hub, `/api/v1/peer/shared-topic/${encodeURIComponent(state2.local_topic_id)}`, {
|
|
43808
|
-
method: "DELETE"
|
|
43809
|
-
});
|
|
43810
|
-
} catch {}
|
|
43811
|
-
});
|
|
43812
|
-
return Promise.all(deletions).then(() => {
|
|
43813
|
-
return;
|
|
43814
|
-
});
|
|
43815
|
-
}
|
|
43816
|
-
function downgradeSharedTopicsForHub(hubNodeId) {
|
|
43817
|
-
const updated = downgradeSharedTopicsLocally(hubNodeId);
|
|
43818
|
-
for (const topicId of updated)
|
|
43819
|
-
WsHub.get().broadcastTopicUpdated(topicId);
|
|
43820
|
-
return updated.length;
|
|
43821
|
-
}
|
|
43822
|
-
async function checkPeerAttachment(join43) {
|
|
43823
|
-
const nodes = await listPeerNodes({ fresh: true });
|
|
43824
|
-
if (nodes.some((node) => node.cellId === join43.cellId))
|
|
43825
|
-
return true;
|
|
43826
|
-
downgradeSharedTopicsForHub(join43.cellId);
|
|
43827
|
-
return false;
|
|
43828
|
-
}
|
|
43829
|
-
function startSharedTopicSync(join43) {
|
|
43830
|
-
let stopped = false;
|
|
43831
|
-
let detached = isPeerDetached(join43.cellId);
|
|
43832
|
-
const queue = new Set;
|
|
43833
|
-
const schedule = (topicId, explicit = false) => {
|
|
43834
|
-
if (stopped || queue.has(topicId))
|
|
43835
|
-
return;
|
|
43836
|
-
queue.add(topicId);
|
|
43837
|
-
queueMicrotask(async () => {
|
|
43838
|
-
queue.delete(topicId);
|
|
43839
|
-
try {
|
|
43840
|
-
await reconcileTopic(join43, topicId, explicit);
|
|
43841
|
-
} catch (error2) {
|
|
43842
|
-
logger.warn({ error: error2, topicId }, "otium: shared topic reconciliation failed");
|
|
43843
|
-
}
|
|
43844
|
-
});
|
|
43845
|
-
};
|
|
43846
|
-
for (const topic of listTopics())
|
|
43847
|
-
schedule(topic.id, false);
|
|
43848
|
-
for (const state2 of listSharedTopicStates())
|
|
43849
|
-
schedule(state2.local_topic_id);
|
|
43850
|
-
const attachmentCheck = setInterval(async () => {
|
|
43851
|
-
if (stopped || detached)
|
|
43852
|
-
return;
|
|
43853
|
-
try {
|
|
43854
|
-
if (!await checkPeerAttachment(join43))
|
|
43855
|
-
detached = true;
|
|
43856
|
-
} catch {}
|
|
43857
|
-
}, 30000);
|
|
43858
|
-
attachmentCheck.unref?.();
|
|
43859
|
-
const unsubscribe2 = runtimeBus().subscribe((event) => {
|
|
43860
|
-
if (event.type === "topic-created" || event.type === "topic-updated" || event.type === "topic-deleted")
|
|
43861
|
-
schedule(event.topicId, true);
|
|
43862
|
-
if (event.type === "message") {
|
|
43863
|
-
const message = event.payload;
|
|
43864
|
-
if (!message.sourceNode) {
|
|
43865
|
-
schedule(event.topicId);
|
|
43866
|
-
forwardSharedTopicMessage(join43, message).catch((error2) => logger.warn({ error: error2, topicId: event.topicId }, "otium: shared message deferred"));
|
|
43867
|
-
}
|
|
43868
|
-
}
|
|
43869
|
-
});
|
|
43870
|
-
return () => {
|
|
43871
|
-
stopped = true;
|
|
43872
|
-
clearInterval(attachmentCheck);
|
|
43873
|
-
unsubscribe2();
|
|
43874
|
-
};
|
|
43875
|
-
}
|
|
43876
|
-
async function forwardSharedTopicMessage(_join, message) {
|
|
43877
|
-
if (message.sourceNode || isMirrorTopic(message.topicId))
|
|
43878
|
-
return;
|
|
43879
|
-
if (getActiveForwarder(message.topicId))
|
|
43880
|
-
return;
|
|
43881
|
-
const state2 = getSharedTopicState(message.topicId);
|
|
43882
|
-
if (!state2?.host_topic_id || state2.status !== "published")
|
|
43883
|
-
return;
|
|
43884
|
-
const hub = (await listPeerNodes()).find((node) => node.isPrimary) ?? null;
|
|
43885
|
-
if (!hub)
|
|
43886
|
-
return;
|
|
43887
|
-
const envelope = messageEnvelope(message);
|
|
43888
|
-
try {
|
|
43889
|
-
await peerRequest(hub, "/api/v1/peer/shared-topic/messages", {
|
|
43890
|
-
method: "POST",
|
|
43891
|
-
body: JSON.stringify({
|
|
43892
|
-
v: PEER_PROTOCOL_VERSION,
|
|
43893
|
-
localTopicId: message.topicId,
|
|
43894
|
-
hostTopicId: state2.host_topic_id,
|
|
43895
|
-
messages: [envelope]
|
|
43896
|
-
})
|
|
43897
|
-
});
|
|
43898
|
-
} catch (error2) {
|
|
43899
|
-
enqueueSharedMessage({
|
|
43900
|
-
localTopicId: message.topicId,
|
|
43901
|
-
sourceMessageId: envelope.sourceMessageId,
|
|
43902
|
-
message: envelope
|
|
43903
|
-
});
|
|
43904
|
-
throw error2;
|
|
43905
|
-
}
|
|
43906
|
-
}
|
|
43907
|
-
function acceptSharedTopicMessages(messages, localTopicId, sourceNode) {
|
|
43908
|
-
let inserted = 0;
|
|
43909
|
-
for (const incoming of messages) {
|
|
43910
|
-
if (!incoming.sourceMessageId || typeof incoming.text !== "string")
|
|
43911
|
-
continue;
|
|
43912
|
-
if (getApiMessage(localTopicId, incoming.sourceMessageId))
|
|
43913
|
-
continue;
|
|
43914
|
-
const message = {
|
|
43915
|
-
id: incoming.sourceMessageId,
|
|
43916
|
-
topicId: localTopicId,
|
|
43917
|
-
authorId: sourceNode,
|
|
43918
|
-
text: incoming.text,
|
|
43919
|
-
createdAt: incoming.createdAt,
|
|
43920
|
-
...incoming.agent ? { agentType: incoming.agent } : {},
|
|
43921
|
-
...incoming.model ? { model: incoming.model } : {},
|
|
43922
|
-
...incoming.kind ? { kind: incoming.kind } : {},
|
|
43923
|
-
sourceNode,
|
|
43924
|
-
sourceMessageId: incoming.sourceMessageId
|
|
43925
|
-
};
|
|
43926
|
-
appendApiMessage(message);
|
|
43927
|
-
inserted++;
|
|
43928
|
-
}
|
|
43929
|
-
return inserted;
|
|
43930
|
-
}
|
|
43931
|
-
var RETRY_MS = 1000;
|
|
43932
|
-
var init_shared_topic_sync = __esm(async () => {
|
|
43933
|
-
await init_src();
|
|
43934
|
-
await init_central();
|
|
43935
|
-
await init_event_backflow();
|
|
43936
|
-
init_protocol();
|
|
43937
|
-
await init_store2();
|
|
43938
|
-
});
|
|
43939
|
-
|
|
43940
43042
|
// ../../adapters/otium/src/relay-protocol.ts
|
|
43941
43043
|
function encodeFrame(frame) {
|
|
43942
43044
|
const encoded = JSON.stringify(frame);
|
|
@@ -44505,109 +43607,6 @@ var init_tunnel_client = __esm(() => {
|
|
|
44505
43607
|
silentLogger = { info: noop, warn: noop, error: noop };
|
|
44506
43608
|
});
|
|
44507
43609
|
|
|
44508
|
-
// ../../adapters/otium/src/bindings.ts
|
|
44509
|
-
var exports_bindings = {};
|
|
44510
|
-
__export(exports_bindings, {
|
|
44511
|
-
unbindOtiumTopic: () => unbindOtiumTopic,
|
|
44512
|
-
shareOtiumTopic: () => shareOtiumTopic,
|
|
44513
|
-
setOtiumTopicPrivate: () => setOtiumTopicPrivate,
|
|
44514
|
-
listOtiumTopicBindings: () => listOtiumTopicBindings,
|
|
44515
|
-
bindOtiumTopic: () => bindOtiumTopic
|
|
44516
|
-
});
|
|
44517
|
-
function ownsTopic(localTopicId, userId) {
|
|
44518
|
-
const topic = getTopic(localTopicId);
|
|
44519
|
-
if (!topic)
|
|
44520
|
-
return { ok: false, error: "local topic not found", status: 404 };
|
|
44521
|
-
if (!isTopicVisible(topic)) {
|
|
44522
|
-
return { ok: false, error: "internal topics have no user access mode", status: 409 };
|
|
44523
|
-
}
|
|
44524
|
-
if (!topic.participants.some((participant) => participant.userId === userId)) {
|
|
44525
|
-
return { ok: false, error: "local topic is not visible to this user", status: 403 };
|
|
44526
|
-
}
|
|
44527
|
-
if (!topic.participants.some((participant) => participant.userId === userId && participant.role === "owner")) {
|
|
44528
|
-
return {
|
|
44529
|
-
ok: false,
|
|
44530
|
-
error: "only a topic owner can change its access mode",
|
|
44531
|
-
status: 403
|
|
44532
|
-
};
|
|
44533
|
-
}
|
|
44534
|
-
return { ok: true, topic };
|
|
44535
|
-
}
|
|
44536
|
-
function bindOtiumTopic(options) {
|
|
44537
|
-
const topic = getTopic(options.localTopicId);
|
|
44538
|
-
if (!topic)
|
|
44539
|
-
return { ok: false, error: "local topic not found", status: 404 };
|
|
44540
|
-
if (!isTopicVisible(topic)) {
|
|
44541
|
-
return { ok: false, error: "hidden local topics cannot be shared", status: 409 };
|
|
44542
|
-
}
|
|
44543
|
-
if (!topic.participants.some((participant) => participant.userId === options.userId)) {
|
|
44544
|
-
return { ok: false, error: "local topic is not visible to this user", status: 403 };
|
|
44545
|
-
}
|
|
44546
|
-
if (!isTopicShared(topic)) {
|
|
44547
|
-
return { ok: false, error: "private topics must be shared explicitly first", status: 409 };
|
|
44548
|
-
}
|
|
44549
|
-
const previous = getPeerSession(options.hostNodeId, options.hostTopicId);
|
|
44550
|
-
bindPeerSession(options.hostNodeId, options.hostTopicId, options.localTopicId, "shared");
|
|
44551
|
-
return {
|
|
44552
|
-
ok: true,
|
|
44553
|
-
localTopicId: options.localTopicId,
|
|
44554
|
-
replaced: Boolean(previous && previous.local_topic_id !== options.localTopicId)
|
|
44555
|
-
};
|
|
44556
|
-
}
|
|
44557
|
-
function shareOtiumTopic(options) {
|
|
44558
|
-
const owned = ownsTopic(options.localTopicId, options.userId);
|
|
44559
|
-
if (!owned.ok)
|
|
44560
|
-
return owned;
|
|
44561
|
-
if (!isTopicShared(owned.topic)) {
|
|
44562
|
-
const switched = switchTopicAccessMode({
|
|
44563
|
-
topicId: options.localTopicId,
|
|
44564
|
-
userId: options.userId,
|
|
44565
|
-
accessMode: "shared"
|
|
44566
|
-
});
|
|
44567
|
-
if (!switched.ok)
|
|
44568
|
-
return { ok: false, error: switched.error, status: 409 };
|
|
44569
|
-
}
|
|
44570
|
-
return bindOtiumTopic(options);
|
|
44571
|
-
}
|
|
44572
|
-
function setOtiumTopicPrivate(options) {
|
|
44573
|
-
const owned = ownsTopic(options.localTopicId, options.userId);
|
|
44574
|
-
if (!owned.ok)
|
|
44575
|
-
return owned;
|
|
44576
|
-
const removedBindings = unbindSharedPeerSessionsForLocalTopic(options.localTopicId);
|
|
44577
|
-
if (isTopicShared(owned.topic)) {
|
|
44578
|
-
const switched = switchTopicAccessMode({
|
|
44579
|
-
topicId: options.localTopicId,
|
|
44580
|
-
userId: options.userId,
|
|
44581
|
-
accessMode: "private"
|
|
44582
|
-
});
|
|
44583
|
-
if (!switched.ok)
|
|
44584
|
-
return { ok: false, error: switched.error, status: 409 };
|
|
44585
|
-
}
|
|
44586
|
-
return { ok: true, localTopicId: options.localTopicId, removedBindings };
|
|
44587
|
-
}
|
|
44588
|
-
function unbindOtiumTopic(hostNodeId, hostTopicId) {
|
|
44589
|
-
return unbindPeerSession(hostNodeId, hostTopicId);
|
|
44590
|
-
}
|
|
44591
|
-
function listOtiumTopicBindings() {
|
|
44592
|
-
return listPeerSessions().map((row) => {
|
|
44593
|
-
const topic = getTopic(row.local_topic_id);
|
|
44594
|
-
return {
|
|
44595
|
-
hostNodeId: row.host_node_id,
|
|
44596
|
-
hostTopicId: row.host_topic_id,
|
|
44597
|
-
localTopicId: row.local_topic_id,
|
|
44598
|
-
transport: row.binding_mode === "shared" ? "shared-binding" : "internal-mirror",
|
|
44599
|
-
...topic ? { topicAccessMode: topic.accessMode ?? "private" } : {},
|
|
44600
|
-
...topic ? { localTopicTitle: topic.title } : {},
|
|
44601
|
-
localTopicExists: Boolean(topic),
|
|
44602
|
-
createdAt: row.created_at
|
|
44603
|
-
};
|
|
44604
|
-
});
|
|
44605
|
-
}
|
|
44606
|
-
var init_bindings = __esm(async () => {
|
|
44607
|
-
await init_src();
|
|
44608
|
-
await init_store2();
|
|
44609
|
-
});
|
|
44610
|
-
|
|
44611
43610
|
// ../../adapters/otium/src/enrollment.ts
|
|
44612
43611
|
import {
|
|
44613
43612
|
createDecipheriv as createDecipheriv2,
|
|
@@ -44850,203 +43849,46 @@ var init_enrollment = __esm(async () => {
|
|
|
44850
43849
|
INFO = Buffer.from("otium-node-enrollment-v1", "utf8");
|
|
44851
43850
|
});
|
|
44852
43851
|
|
|
44853
|
-
// ../../adapters/otium/src/
|
|
44854
|
-
|
|
44855
|
-
|
|
44856
|
-
|
|
44857
|
-
|
|
44858
|
-
|
|
44859
|
-
return null;
|
|
44860
|
-
return {
|
|
44861
|
-
agent: payload.agent,
|
|
44862
|
-
model: payload.model ?? "",
|
|
44863
|
-
effort: payload.effort ?? "medium",
|
|
44864
|
-
mcp: [],
|
|
44865
|
-
canSpawnSubagents: false
|
|
44866
|
-
};
|
|
44867
|
-
}
|
|
44868
|
-
function provisionMirrorTopic(hostCellId, payload) {
|
|
44869
|
-
const execution = payload.execution;
|
|
44870
|
-
if (!isAgentKind(execution.agent)) {
|
|
44871
|
-
return { ok: false, error: `unknown agent "${execution.agent}"`, status: 400 };
|
|
44872
|
-
}
|
|
44873
|
-
const existing = getPeerSession(hostCellId, payload.hostTopicId);
|
|
44874
|
-
if (existing?.binding_mode === "shared") {
|
|
44875
|
-
const shared = getTopic(existing.local_topic_id);
|
|
44876
|
-
if (!shared) {
|
|
44877
|
-
return { ok: false, error: "bound local topic no longer exists", status: 404 };
|
|
44878
|
-
}
|
|
44879
|
-
if (!isTopicVisible(shared)) {
|
|
44880
|
-
return { ok: false, error: "bound local topic is hidden", status: 409 };
|
|
44881
|
-
}
|
|
44882
|
-
if (!isTopicShared(shared)) {
|
|
44883
|
-
return { ok: false, error: "bound local topic is private", status: 409 };
|
|
44884
|
-
}
|
|
44885
|
-
if (!shared.participants.some((participant) => participant.userId === payload.userId)) {
|
|
44886
|
-
return { ok: false, error: "bound local topic is not visible to this user", status: 403 };
|
|
44887
|
-
}
|
|
44888
|
-
return { ok: true, localTopicId: shared.id, bindingMode: "shared" };
|
|
44889
|
-
}
|
|
44890
|
-
const localTopicId = existing?.local_topic_id ?? `peer-${randomUUID32()}`;
|
|
44891
|
-
const now = new Date().toISOString();
|
|
44892
|
-
const current3 = existing ? getTopic(localTopicId) : null;
|
|
44893
|
-
const currentConfig = current3 ? getApiTopicConfig(localTopicId) : undefined;
|
|
44894
|
-
const currentModel = currentConfig?.model ?? current3?.defaultModel ?? "";
|
|
44895
|
-
const nextModel = execution.model || current3?.defaultModel || "";
|
|
44896
|
-
const providerSessionIsStale = Boolean(current3) && (current3?.agent !== execution.agent || currentModel !== nextModel);
|
|
44897
|
-
upsertTopic({
|
|
44898
|
-
id: localTopicId,
|
|
44899
|
-
title: payload.topicTitle,
|
|
44900
|
-
kind: "agent",
|
|
44901
|
-
agent: execution.agent,
|
|
44902
|
-
aiMode: "always",
|
|
44903
|
-
defaultModel: nextModel,
|
|
44904
|
-
defaultEffort: EFFORT_LEVELS.includes(execution.effort) ? execution.effort : current3?.defaultEffort ?? "medium",
|
|
44905
|
-
participants: [{ userId: payload.userId, role: "owner" }],
|
|
44906
|
-
isSubagent: undefined,
|
|
44907
|
-
visibility: "visible",
|
|
44908
|
-
accessMode: "shared",
|
|
44909
|
-
...execution.description ? { description: execution.description } : {},
|
|
44910
|
-
createdAt: current3?.createdAt ?? now,
|
|
44911
|
-
lastMessageAt: now
|
|
44912
|
-
});
|
|
44913
|
-
setApiTopicConfig(localTopicId, {
|
|
44914
|
-
...execution.model ? { model: execution.model } : {},
|
|
44915
|
-
...EFFORT_LEVELS.includes(execution.effort) ? { effort: execution.effort } : {},
|
|
44916
|
-
mcp: execution.mcp
|
|
44917
|
-
});
|
|
44918
|
-
if (providerSessionIsStale) {
|
|
44919
|
-
clearTopicSessionId(localTopicId, "peer-execution-spec-changed");
|
|
44920
|
-
}
|
|
44921
|
-
if (!existing)
|
|
44922
|
-
createPeerSession(hostCellId, payload.hostTopicId, localTopicId);
|
|
44923
|
-
return { ok: true, localTopicId, bindingMode: "mirror" };
|
|
44924
|
-
}
|
|
44925
|
-
function runPeerTurn(hubNode, hostCellId, payload, opts = {}) {
|
|
44926
|
-
const execution = executionFor(payload);
|
|
44927
|
-
if (!execution || !isAgentKind(execution.agent)) {
|
|
44928
|
-
return { ok: false, error: `unknown agent "${execution?.agent ?? ""}"`, status: 400 };
|
|
44929
|
-
}
|
|
44930
|
-
const claim = claimPeerTurnRequest(hostCellId, payload.requestId, payload.hostTopicId);
|
|
44931
|
-
if (!claim.claimed) {
|
|
44932
|
-
if (claim.row.host_topic_id !== payload.hostTopicId) {
|
|
44933
|
-
return { ok: false, error: "requestId already belongs to another room", status: 409 };
|
|
44934
|
-
}
|
|
44935
|
-
if (claim.row.status === "failed") {
|
|
44936
|
-
return { ok: false, error: claim.row.error ?? "previous attempt failed", status: 409 };
|
|
44937
|
-
}
|
|
44938
|
-
logger.info({ requestId: payload.requestId, hostTopicId: payload.hostTopicId, status: claim.row.status }, "otium: peer turn replay acknowledged without re-execution");
|
|
44939
|
-
return { ok: true };
|
|
44940
|
-
}
|
|
44941
|
-
const provisioned = provisionMirrorTopic(hostCellId, {
|
|
44942
|
-
userId: payload.userId,
|
|
44943
|
-
hostTopicId: payload.hostTopicId,
|
|
44944
|
-
topicTitle: payload.topicTitle,
|
|
44945
|
-
execution
|
|
44946
|
-
});
|
|
44947
|
-
if (!provisioned.ok) {
|
|
44948
|
-
markPeerTurnRequestFailed(hostCellId, payload.requestId, provisioned.error);
|
|
44949
|
-
return provisioned;
|
|
44950
|
-
}
|
|
44951
|
-
const localTopicId = provisioned.localTopicId;
|
|
44952
|
-
const localTopic = getTopic(localTopicId);
|
|
44953
|
-
if (payload.attachments?.some((fileId) => !peerFileAllowsAccess(fileId, { topicId: localTopicId, ownerUserId: payload.userId }))) {
|
|
44954
|
-
markPeerTurnRequestFailed(hostCellId, payload.requestId, "attachment access denied");
|
|
44955
|
-
return { ok: false, error: "attachment access denied", status: 403 };
|
|
44956
|
-
}
|
|
44957
|
-
const turnAgent = provisioned.bindingMode === "shared" && localTopic?.agent ? localTopic.agent : execution.agent;
|
|
44958
|
-
const previous = getActiveForwarder(localTopicId);
|
|
44959
|
-
if (previous) {
|
|
44960
|
-
previous.finish({
|
|
44961
|
-
type: "ai_aborted",
|
|
44962
|
-
queryId: previous.queryId ?? previous.requestId,
|
|
44963
|
-
topicId: localTopicId,
|
|
44964
|
-
reason: "superseded"
|
|
44965
|
-
});
|
|
44966
|
-
}
|
|
44967
|
-
const forwarder = createTurnForwarder({
|
|
44968
|
-
hostNodeId: hostCellId,
|
|
44969
|
-
requestId: payload.requestId,
|
|
44970
|
-
localTopicId,
|
|
44971
|
-
sendEvent: opts.sendEvent ?? hubEventSender(hubNode)
|
|
44972
|
-
});
|
|
44973
|
-
registerTurnForwarder(localTopicId, forwarder);
|
|
44974
|
-
let queryId = null;
|
|
44975
|
-
try {
|
|
44976
|
-
queryId = executeExternalUserTurn({
|
|
44977
|
-
topicId: localTopicId,
|
|
44978
|
-
userId: payload.userId,
|
|
44979
|
-
text: payload.message,
|
|
44980
|
-
agent: turnAgent,
|
|
44981
|
-
options: {
|
|
44982
|
-
origin: "user",
|
|
44983
|
-
requestId: payload.requestId,
|
|
44984
|
-
injectAuthorId: payload.userId,
|
|
44985
|
-
injectSourceNode: hostCellId,
|
|
44986
|
-
...payload.sourceMessageId ? { injectSourceMessageId: payload.sourceMessageId } : {},
|
|
44987
|
-
attachments: payload.attachments,
|
|
44988
|
-
visualTools: true,
|
|
44989
|
-
fileDeliveryTools: true,
|
|
44990
|
-
onDispatched: (dispatchedQueryId) => {
|
|
44991
|
-
forwarder.queryId = dispatchedQueryId;
|
|
44992
|
-
},
|
|
44993
|
-
peerBridge: {
|
|
44994
|
-
hubCellId: hostCellId,
|
|
44995
|
-
hostTopicId: payload.hostTopicId,
|
|
44996
|
-
hostQueryId: payload.requestId,
|
|
44997
|
-
canSpawnSubagents: execution.canSpawnSubagents
|
|
44998
|
-
}
|
|
44999
|
-
},
|
|
45000
|
-
...turnTrigger ? { dispatch: turnTrigger } : {}
|
|
45001
|
-
});
|
|
45002
|
-
} catch (err2) {
|
|
45003
|
-
forwarder.finish({
|
|
45004
|
-
type: "ai_error",
|
|
45005
|
-
queryId: payload.requestId,
|
|
45006
|
-
topicId: localTopicId,
|
|
45007
|
-
error: `worker dispatch crashed: ${err2.message}`
|
|
45008
|
-
});
|
|
45009
|
-
return { ok: false, error: "failed to start turn", status: 500 };
|
|
45010
|
-
}
|
|
45011
|
-
if (!queryId) {
|
|
45012
|
-
forwarder.finish({
|
|
45013
|
-
type: "ai_error",
|
|
45014
|
-
queryId: payload.requestId,
|
|
45015
|
-
topicId: localTopicId,
|
|
45016
|
-
error: "worker could not start the turn"
|
|
45017
|
-
});
|
|
45018
|
-
return { ok: false, error: "failed to start turn", status: 500 };
|
|
43852
|
+
// ../../adapters/otium/src/gateway-forward.ts
|
|
43853
|
+
function allowedRuntimePath(path, method) {
|
|
43854
|
+
if (method === "GET") {
|
|
43855
|
+
if (path === "/health" || path === "/events" || path === "/topics")
|
|
43856
|
+
return true;
|
|
43857
|
+
return /^\/topics\/[^/]+(\/messages)?$/.test(path);
|
|
45019
43858
|
}
|
|
45020
|
-
|
|
45021
|
-
|
|
45022
|
-
|
|
45023
|
-
return { ok: true };
|
|
43859
|
+
if (method === "POST")
|
|
43860
|
+
return path === "/turns";
|
|
43861
|
+
return false;
|
|
45024
43862
|
}
|
|
45025
|
-
function
|
|
45026
|
-
const
|
|
45027
|
-
if (!
|
|
45028
|
-
return
|
|
45029
|
-
const
|
|
45030
|
-
if (!
|
|
45031
|
-
return false;
|
|
45032
|
-
const topic = getTopic(session.local_topic_id);
|
|
45033
|
-
if (!topic || topic.title !== topicTitle || !topic.participants.some((participant) => participant.userId === userId)) {
|
|
45034
|
-
return false;
|
|
43863
|
+
async function forwardGatewayRequest(req, options) {
|
|
43864
|
+
const url = new URL(req.url);
|
|
43865
|
+
if (!url.pathname.startsWith(OTIUM_GATEWAY_FORWARD_PREFIX))
|
|
43866
|
+
return null;
|
|
43867
|
+
const runtimePath = url.pathname.slice(OTIUM_GATEWAY_FORWARD_PREFIX.length) || "/";
|
|
43868
|
+
if (!allowedRuntimePath(runtimePath, req.method)) {
|
|
43869
|
+
return Response.json({ ok: false, error: "gateway route not forwarded" }, { status: 404 });
|
|
45035
43870
|
}
|
|
45036
|
-
const
|
|
45037
|
-
|
|
45038
|
-
|
|
45039
|
-
|
|
45040
|
-
|
|
45041
|
-
|
|
43871
|
+
const target = new URL(`${options.nodeOrigin.replace(/\/+$/, "")}${RUNTIME_CONTRACT_PATH}${runtimePath}`);
|
|
43872
|
+
target.search = url.search;
|
|
43873
|
+
const headers = new Headers(req.headers);
|
|
43874
|
+
headers.set("authorization", `Bearer ${NODE_CONTROL_TOKEN}`);
|
|
43875
|
+
headers.delete("transfer-encoding");
|
|
43876
|
+
headers.delete("content-length");
|
|
43877
|
+
headers.delete("host");
|
|
43878
|
+
const body = req.method === "GET" || req.method === "HEAD" ? undefined : await req.arrayBuffer();
|
|
43879
|
+
if (body)
|
|
43880
|
+
headers.set("content-length", String(body.byteLength));
|
|
43881
|
+
const fetchRequest = options.fetch ?? fetch;
|
|
43882
|
+
return fetchRequest(new Request(target.toString(), {
|
|
43883
|
+
method: req.method,
|
|
43884
|
+
headers,
|
|
43885
|
+
body,
|
|
43886
|
+
signal: req.signal
|
|
43887
|
+
}));
|
|
45042
43888
|
}
|
|
45043
|
-
var
|
|
45044
|
-
var
|
|
43889
|
+
var RUNTIME_CONTRACT_PATH = "/api/v1/control/runtime/v1", OTIUM_GATEWAY_FORWARD_PREFIX = "/api/v1/peer/runtime";
|
|
43890
|
+
var init_gateway_forward = __esm(async () => {
|
|
45045
43891
|
await init_src();
|
|
45046
|
-
await init_event_backflow();
|
|
45047
|
-
await init_peer_files();
|
|
45048
|
-
await init_store2();
|
|
45049
|
-
EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
45050
43892
|
});
|
|
45051
43893
|
|
|
45052
43894
|
// ../../adapters/otium/src/peer-server.ts
|
|
@@ -45065,7 +43907,7 @@ async function readBody(req) {
|
|
|
45065
43907
|
return null;
|
|
45066
43908
|
return body;
|
|
45067
43909
|
}
|
|
45068
|
-
function
|
|
43910
|
+
function str(body, field) {
|
|
45069
43911
|
const value = body[field];
|
|
45070
43912
|
return typeof value === "string" && value.trim() ? value : null;
|
|
45071
43913
|
}
|
|
@@ -45091,6 +43933,9 @@ async function requirePeer(req) {
|
|
|
45091
43933
|
function requirePrimaryOrigin(peer) {
|
|
45092
43934
|
return peer.verified.fromIsPrimary ? null : jsonError2("only the workspace hub may call this endpoint", 403);
|
|
45093
43935
|
}
|
|
43936
|
+
function peerAddressable(topic) {
|
|
43937
|
+
return isTopicShared(topic);
|
|
43938
|
+
}
|
|
45094
43939
|
function localCapabilities() {
|
|
45095
43940
|
const agents = SUPPORTED_AGENTS.map((kind) => {
|
|
45096
43941
|
const registry = getRegistry(kind);
|
|
@@ -45108,7 +43953,7 @@ function localCapabilities() {
|
|
|
45108
43953
|
runtimeVersion: RUNTIME_VERSION,
|
|
45109
43954
|
features: {
|
|
45110
43955
|
remoteAsk: true,
|
|
45111
|
-
inputFiles:
|
|
43956
|
+
inputFiles: false,
|
|
45112
43957
|
outputFiles: true,
|
|
45113
43958
|
visualBridge: true,
|
|
45114
43959
|
askUserBridge: true,
|
|
@@ -45142,180 +43987,6 @@ function localHealth() {
|
|
|
45142
43987
|
...disk ? { disk } : {}
|
|
45143
43988
|
};
|
|
45144
43989
|
}
|
|
45145
|
-
async function handleProvision(req) {
|
|
45146
|
-
const peer = await requirePeer(req);
|
|
45147
|
-
if (!peer.ok)
|
|
45148
|
-
return peer.response;
|
|
45149
|
-
const originError = requirePrimaryOrigin(peer);
|
|
45150
|
-
if (originError)
|
|
45151
|
-
return originError;
|
|
45152
|
-
const body = await readBody(req);
|
|
45153
|
-
if (!body)
|
|
45154
|
-
return jsonError2("invalid JSON body", 400);
|
|
45155
|
-
const protocolError = checkProtocol(body);
|
|
45156
|
-
if (protocolError)
|
|
45157
|
-
return protocolError;
|
|
45158
|
-
const userId = str2(body, "userId");
|
|
45159
|
-
const hostTopicId = str2(body, "hostTopicId");
|
|
45160
|
-
const topicTitle = str2(body, "topicTitle");
|
|
45161
|
-
const execution = parseExecutionSpec(body.execution);
|
|
45162
|
-
if (!userId || !hostTopicId || !topicTitle || !execution) {
|
|
45163
|
-
return jsonError2("invalid peer provision request", 400);
|
|
45164
|
-
}
|
|
45165
|
-
const result = provisionMirrorTopic(peer.verified.fromCellId, {
|
|
45166
|
-
userId,
|
|
45167
|
-
hostTopicId,
|
|
45168
|
-
topicTitle,
|
|
45169
|
-
execution
|
|
45170
|
-
});
|
|
45171
|
-
if (!result.ok)
|
|
45172
|
-
return jsonError2(result.error, result.status);
|
|
45173
|
-
logger.info({ hostTopicId, localTopicId: result.localTopicId, fromNode: peer.verified.fromNodeName }, "otium: mirror room provisioned");
|
|
45174
|
-
return Response.json({ ok: true });
|
|
45175
|
-
}
|
|
45176
|
-
async function handleBind(req) {
|
|
45177
|
-
const peer = await requirePeer(req);
|
|
45178
|
-
if (!peer.ok)
|
|
45179
|
-
return peer.response;
|
|
45180
|
-
const originError = requirePrimaryOrigin(peer);
|
|
45181
|
-
if (originError)
|
|
45182
|
-
return originError;
|
|
45183
|
-
const body = await readBody(req);
|
|
45184
|
-
if (!body)
|
|
45185
|
-
return jsonError2("invalid JSON body", 400);
|
|
45186
|
-
const protocolError = checkProtocol(body);
|
|
45187
|
-
if (protocolError)
|
|
45188
|
-
return protocolError;
|
|
45189
|
-
const userId = str2(body, "userId");
|
|
45190
|
-
const hostTopicId = str2(body, "hostTopicId");
|
|
45191
|
-
const localTopicId = str2(body, "localTopicId");
|
|
45192
|
-
if (!userId || !hostTopicId || !localTopicId) {
|
|
45193
|
-
return jsonError2("invalid peer bind request", 400);
|
|
45194
|
-
}
|
|
45195
|
-
const result = bindOtiumTopic({
|
|
45196
|
-
hostNodeId: peer.verified.fromCellId,
|
|
45197
|
-
hostTopicId,
|
|
45198
|
-
localTopicId,
|
|
45199
|
-
userId
|
|
45200
|
-
});
|
|
45201
|
-
if (!result.ok)
|
|
45202
|
-
return jsonError2(result.error, result.status);
|
|
45203
|
-
return Response.json({ ok: true, localTopicId: result.localTopicId, replaced: result.replaced });
|
|
45204
|
-
}
|
|
45205
|
-
async function handleSharedTopicMessages(req) {
|
|
45206
|
-
const peer = await requirePeer(req);
|
|
45207
|
-
if (!peer.ok)
|
|
45208
|
-
return peer.response;
|
|
45209
|
-
const originError = requirePrimaryOrigin(peer);
|
|
45210
|
-
if (originError)
|
|
45211
|
-
return originError;
|
|
45212
|
-
const body = await readBody(req);
|
|
45213
|
-
if (!body)
|
|
45214
|
-
return jsonError2("invalid JSON body", 400);
|
|
45215
|
-
const protocolError = checkProtocol(body);
|
|
45216
|
-
if (protocolError)
|
|
45217
|
-
return protocolError;
|
|
45218
|
-
const localTopicId = str2(body, "localTopicId");
|
|
45219
|
-
const hostTopicId = str2(body, "hostTopicId");
|
|
45220
|
-
const messages = body.messages;
|
|
45221
|
-
if (!localTopicId || !hostTopicId || !Array.isArray(messages)) {
|
|
45222
|
-
return jsonError2("localTopicId, hostTopicId and messages are required", 400);
|
|
45223
|
-
}
|
|
45224
|
-
const session = getPeerSession(peer.verified.fromCellId, hostTopicId);
|
|
45225
|
-
if (!session || session.local_topic_id !== localTopicId) {
|
|
45226
|
-
return jsonError2("shared topic binding not found", 404);
|
|
45227
|
-
}
|
|
45228
|
-
const accepted = acceptSharedTopicMessages(messages, localTopicId, peer.verified.fromCellId);
|
|
45229
|
-
return Response.json({ ok: true, accepted });
|
|
45230
|
-
}
|
|
45231
|
-
async function handleSharedTopicsPrivate(req) {
|
|
45232
|
-
const peer = await requirePeer(req);
|
|
45233
|
-
if (!peer.ok)
|
|
45234
|
-
return peer.response;
|
|
45235
|
-
const originError = requirePrimaryOrigin(peer);
|
|
45236
|
-
if (originError)
|
|
45237
|
-
return originError;
|
|
45238
|
-
const body = await readBody(req);
|
|
45239
|
-
if (!body)
|
|
45240
|
-
return jsonError2("invalid JSON body", 400);
|
|
45241
|
-
const protocolError = checkProtocol(body);
|
|
45242
|
-
if (protocolError)
|
|
45243
|
-
return protocolError;
|
|
45244
|
-
if (body.reason !== "hub-removal")
|
|
45245
|
-
return jsonError2("reason must be hub-removal", 400);
|
|
45246
|
-
const updated = downgradeSharedTopicsForHub(peer.verified.fromCellId);
|
|
45247
|
-
return Response.json({ ok: true, updated });
|
|
45248
|
-
}
|
|
45249
|
-
async function handleUnbind(req) {
|
|
45250
|
-
const peer = await requirePeer(req);
|
|
45251
|
-
if (!peer.ok)
|
|
45252
|
-
return peer.response;
|
|
45253
|
-
const originError = requirePrimaryOrigin(peer);
|
|
45254
|
-
if (originError)
|
|
45255
|
-
return originError;
|
|
45256
|
-
const body = await readBody(req);
|
|
45257
|
-
if (!body)
|
|
45258
|
-
return jsonError2("invalid JSON body", 400);
|
|
45259
|
-
const protocolError = checkProtocol(body);
|
|
45260
|
-
if (protocolError)
|
|
45261
|
-
return protocolError;
|
|
45262
|
-
const hostTopicId = str2(body, "hostTopicId");
|
|
45263
|
-
if (!hostTopicId)
|
|
45264
|
-
return jsonError2("hostTopicId is required", 400);
|
|
45265
|
-
const removed = unbindOtiumTopic(peer.verified.fromCellId, hostTopicId);
|
|
45266
|
-
return Response.json({ ok: true, removed });
|
|
45267
|
-
}
|
|
45268
|
-
async function handleTurn(req) {
|
|
45269
|
-
const peer = await requirePeer(req);
|
|
45270
|
-
if (!peer.ok)
|
|
45271
|
-
return peer.response;
|
|
45272
|
-
const originError = requirePrimaryOrigin(peer);
|
|
45273
|
-
if (originError)
|
|
45274
|
-
return originError;
|
|
45275
|
-
const body = await readBody(req);
|
|
45276
|
-
if (!body)
|
|
45277
|
-
return jsonError2("invalid JSON body", 400);
|
|
45278
|
-
const protocolError = checkProtocol(body);
|
|
45279
|
-
if (protocolError)
|
|
45280
|
-
return protocolError;
|
|
45281
|
-
const requestId = str2(body, "requestId");
|
|
45282
|
-
const userId = str2(body, "userId");
|
|
45283
|
-
const hostTopicId = str2(body, "hostTopicId");
|
|
45284
|
-
const topicTitle = str2(body, "topicTitle");
|
|
45285
|
-
const execution = parseExecutionSpec(body.execution);
|
|
45286
|
-
if (body.execution !== undefined && !execution) {
|
|
45287
|
-
return jsonError2("invalid placed-topic execution spec", 400);
|
|
45288
|
-
}
|
|
45289
|
-
const agent = execution?.agent ?? str2(body, "agent");
|
|
45290
|
-
const message = str2(body, "message");
|
|
45291
|
-
if (!requestId || !userId || !hostTopicId || !topicTitle || !agent || !message) {
|
|
45292
|
-
return jsonError2("requestId, userId, hostTopicId, topicTitle, agent, message are required", 400);
|
|
45293
|
-
}
|
|
45294
|
-
if (message.length > MAX_PEER_MESSAGE_LENGTH)
|
|
45295
|
-
return jsonError2("message too long", 400);
|
|
45296
|
-
const hubNode = await resolvePeerNodeByCellId(peer.verified.fromCellId).catch(() => null);
|
|
45297
|
-
if (!hubNode)
|
|
45298
|
-
return jsonError2("calling node is not in this workspace", 403);
|
|
45299
|
-
const payload = {
|
|
45300
|
-
v: PEER_PROTOCOL_VERSION,
|
|
45301
|
-
requestId,
|
|
45302
|
-
userId,
|
|
45303
|
-
hostTopicId,
|
|
45304
|
-
topicTitle,
|
|
45305
|
-
...execution ? { execution } : {},
|
|
45306
|
-
...agent ? { agent } : {},
|
|
45307
|
-
...str2(body, "model") ? { model: str2(body, "model") } : {},
|
|
45308
|
-
...str2(body, "effort") ? { effort: str2(body, "effort") } : {},
|
|
45309
|
-
...Array.isArray(body.attachments) && body.attachments.every((entry) => typeof entry === "string") ? { attachments: body.attachments } : {},
|
|
45310
|
-
...str2(body, "sourceMessageId") ? { sourceMessageId: str2(body, "sourceMessageId") } : {},
|
|
45311
|
-
message
|
|
45312
|
-
};
|
|
45313
|
-
const result = runPeerTurn(hubNode, peer.verified.fromCellId, payload);
|
|
45314
|
-
if (!result.ok)
|
|
45315
|
-
return jsonError2(result.error, result.status);
|
|
45316
|
-
logger.info({ requestId, hostTopicId, fromNode: peer.verified.fromNodeName }, "otium: peer turn accepted");
|
|
45317
|
-
return Response.json({ ok: true });
|
|
45318
|
-
}
|
|
45319
43990
|
async function handleAbort(req) {
|
|
45320
43991
|
const peer = await requirePeer(req);
|
|
45321
43992
|
if (!peer.ok)
|
|
@@ -45329,20 +44000,12 @@ async function handleAbort(req) {
|
|
|
45329
44000
|
const protocolError = checkProtocol(body);
|
|
45330
44001
|
if (protocolError)
|
|
45331
44002
|
return protocolError;
|
|
45332
|
-
const userId =
|
|
45333
|
-
const toTopic =
|
|
44003
|
+
const userId = str(body, "userId");
|
|
44004
|
+
const toTopic = str(body, "toTopic");
|
|
45334
44005
|
if (!userId || !toTopic)
|
|
45335
44006
|
return jsonError2("userId and toTopic are required", 400);
|
|
45336
|
-
const requestId = str2(body, "requestId");
|
|
45337
|
-
if (requestId) {
|
|
45338
|
-
const aborted = abortHostedPeerTurn(peer.verified.fromCellId, requestId, userId, toTopic);
|
|
45339
|
-
if (!aborted)
|
|
45340
|
-
return jsonError2("turn not found or already completed", 404);
|
|
45341
|
-
logger.info({ fromNode: peer.verified.fromNodeName, toTopic, requestId }, "otium: exact peer turn abort accepted");
|
|
45342
|
-
return Response.json({ ok: true });
|
|
45343
|
-
}
|
|
45344
44007
|
const topic = getTopicByNameForUser(toTopic, userId);
|
|
45345
|
-
if (!topic || !
|
|
44008
|
+
if (!topic || !peerAddressable(topic)) {
|
|
45346
44009
|
return jsonError2(`shared topic "${toTopic}" not found on this node`, 404);
|
|
45347
44010
|
}
|
|
45348
44011
|
appendJsonlEntry(sessionInboxPath(userId, topic.id), {
|
|
@@ -45366,11 +44029,11 @@ async function handleTell(req) {
|
|
|
45366
44029
|
const protocolError = checkProtocol(body);
|
|
45367
44030
|
if (protocolError)
|
|
45368
44031
|
return protocolError;
|
|
45369
|
-
const requestId =
|
|
45370
|
-
const userId =
|
|
45371
|
-
const toTopic =
|
|
45372
|
-
const fromLabel =
|
|
45373
|
-
const message =
|
|
44032
|
+
const requestId = str(body, "requestId");
|
|
44033
|
+
const userId = str(body, "userId");
|
|
44034
|
+
const toTopic = str(body, "toTopic");
|
|
44035
|
+
const fromLabel = str(body, "fromLabel");
|
|
44036
|
+
const message = str(body, "message");
|
|
45374
44037
|
const depth = typeof body.depth === "number" ? body.depth : Number.NaN;
|
|
45375
44038
|
if (!requestId || !userId || !toTopic || !fromLabel || !message) {
|
|
45376
44039
|
return jsonError2("requestId, userId, toTopic, fromLabel, message are required", 400);
|
|
@@ -45384,7 +44047,7 @@ async function handleTell(req) {
|
|
|
45384
44047
|
return jsonError2(`tell depth limit exceeded (max ${MAX_TELL_DEPTH})`, 400);
|
|
45385
44048
|
}
|
|
45386
44049
|
const topic = getTopicByNameForUser(toTopic, userId);
|
|
45387
|
-
if (!topic || !
|
|
44050
|
+
if (!topic || !peerAddressable(topic)) {
|
|
45388
44051
|
return jsonError2(`shared topic "${toTopic}" not found on this node`, 404);
|
|
45389
44052
|
}
|
|
45390
44053
|
const claim = claimInboundPeerMessage({
|
|
@@ -45438,10 +44101,10 @@ async function handleSessions(req) {
|
|
|
45438
44101
|
const protocolError = checkProtocol(body);
|
|
45439
44102
|
if (protocolError)
|
|
45440
44103
|
return protocolError;
|
|
45441
|
-
const userId =
|
|
44104
|
+
const userId = str(body, "userId");
|
|
45442
44105
|
if (!userId)
|
|
45443
44106
|
return jsonError2("userId is required", 400);
|
|
45444
|
-
const topics = listTopics().filter((topic) => topic.kind !== "manager" && !topic.isSubagent &&
|
|
44107
|
+
const topics = listTopics().filter((topic) => topic.kind !== "manager" && !topic.isSubagent && peerAddressable(topic) && topic.participants.some((p) => p.userId === userId));
|
|
45445
44108
|
const titleCounts = new Map;
|
|
45446
44109
|
for (const topic of topics) {
|
|
45447
44110
|
const normalized = topic.title.toLowerCase();
|
|
@@ -45473,11 +44136,11 @@ async function handleAsk(req) {
|
|
|
45473
44136
|
const protocolError = checkProtocol(body);
|
|
45474
44137
|
if (protocolError)
|
|
45475
44138
|
return protocolError;
|
|
45476
|
-
const requestId =
|
|
45477
|
-
const userId =
|
|
45478
|
-
const toTopic =
|
|
45479
|
-
const fromLabel =
|
|
45480
|
-
const message =
|
|
44139
|
+
const requestId = str(body, "requestId");
|
|
44140
|
+
const userId = str(body, "userId");
|
|
44141
|
+
const toTopic = str(body, "toTopic");
|
|
44142
|
+
const fromLabel = str(body, "fromLabel");
|
|
44143
|
+
const message = str(body, "message");
|
|
45481
44144
|
const fromDepth = body.fromDepth === undefined ? 0 : typeof body.fromDepth === "number" ? body.fromDepth : Number.NaN;
|
|
45482
44145
|
const replyTo = body.replyTo;
|
|
45483
44146
|
const replyTopicId = typeof replyTo?.topicId === "string" ? replyTo.topicId : null;
|
|
@@ -45490,7 +44153,7 @@ async function handleAsk(req) {
|
|
|
45490
44153
|
return jsonError2("fromDepth must be a non-negative integer", 400);
|
|
45491
44154
|
}
|
|
45492
44155
|
const topic = getTopicByNameForUser(toTopic, userId);
|
|
45493
|
-
if (!topic || !
|
|
44156
|
+
if (!topic || !peerAddressable(topic)) {
|
|
45494
44157
|
return jsonError2(`shared topic "${toTopic}" not found on this node`, 404);
|
|
45495
44158
|
}
|
|
45496
44159
|
if (!topic.agent)
|
|
@@ -45543,10 +44206,10 @@ async function handleReply(req) {
|
|
|
45543
44206
|
const protocolError = checkProtocol(body);
|
|
45544
44207
|
if (protocolError)
|
|
45545
44208
|
return protocolError;
|
|
45546
|
-
const requestId =
|
|
45547
|
-
const userId =
|
|
44209
|
+
const requestId = str(body, "requestId");
|
|
44210
|
+
const userId = str(body, "userId");
|
|
45548
44211
|
const replyText = typeof body.replyText === "string" ? body.replyText : null;
|
|
45549
|
-
const fromLabel =
|
|
44212
|
+
const fromLabel = str(body, "fromLabel") ?? "peer";
|
|
45550
44213
|
const kind = body.kind === "error" ? "error" : "reply";
|
|
45551
44214
|
if (!requestId || !userId || replyText === null) {
|
|
45552
44215
|
return jsonError2("requestId, userId and replyText are required", 400);
|
|
@@ -45565,39 +44228,6 @@ async function handleReply(req) {
|
|
|
45565
44228
|
return jsonError2("ask callback delivery is retryable", 503);
|
|
45566
44229
|
return jsonError2("no pending ask for this requestId", 404);
|
|
45567
44230
|
}
|
|
45568
|
-
async function handleInputFile(req) {
|
|
45569
|
-
const peer = await requirePeer(req);
|
|
45570
|
-
if (!peer.ok)
|
|
45571
|
-
return peer.response;
|
|
45572
|
-
const originError = requirePrimaryOrigin(peer);
|
|
45573
|
-
if (originError)
|
|
45574
|
-
return originError;
|
|
45575
|
-
const contentLength = Number(req.headers.get("content-length"));
|
|
45576
|
-
if (Number.isFinite(contentLength) && contentLength > MAX_PEER_INPUT_REQUEST_BYTES) {
|
|
45577
|
-
return jsonError2("file too large", 413);
|
|
45578
|
-
}
|
|
45579
|
-
const form = await req.formData().catch(() => null);
|
|
45580
|
-
if (!form)
|
|
45581
|
-
return jsonError2("expected multipart/form-data", 400);
|
|
45582
|
-
const hostTopicId = form.get("hostTopicId");
|
|
45583
|
-
const userId = form.get("userId");
|
|
45584
|
-
const file = form.get("file");
|
|
45585
|
-
if (typeof hostTopicId !== "string" || typeof userId !== "string" || !(file instanceof File)) {
|
|
45586
|
-
return jsonError2("hostTopicId, userId, and file are required", 400);
|
|
45587
|
-
}
|
|
45588
|
-
if (file.size > MAX_PEER_INPUT_FILE_BYTES)
|
|
45589
|
-
return jsonError2("file too large", 413);
|
|
45590
|
-
const session = getPeerSession(peer.verified.fromCellId, hostTopicId);
|
|
45591
|
-
const topic = session ? getTopic(session.local_topic_id) : null;
|
|
45592
|
-
if (!session || !topic?.participants.some((participant) => participant.userId === userId)) {
|
|
45593
|
-
return jsonError2("provisioned peer room not found", 404);
|
|
45594
|
-
}
|
|
45595
|
-
const stored = await storePeerInputFile(file, {
|
|
45596
|
-
topicId: session.local_topic_id,
|
|
45597
|
-
ownerUserId: userId
|
|
45598
|
-
});
|
|
45599
|
-
return Response.json({ ok: true, fileId: stored.id });
|
|
45600
|
-
}
|
|
45601
44231
|
async function handleDeviceVault(req) {
|
|
45602
44232
|
const peer = await requirePeer(req);
|
|
45603
44233
|
if (!peer.ok)
|
|
@@ -45611,14 +44241,14 @@ async function handleDeviceVault(req) {
|
|
|
45611
44241
|
const protocolError = checkProtocol(body);
|
|
45612
44242
|
if (protocolError)
|
|
45613
44243
|
return protocolError;
|
|
45614
|
-
const userId =
|
|
45615
|
-
const operation =
|
|
44244
|
+
const userId = str(body, "userId");
|
|
44245
|
+
const operation = str(body, "operation");
|
|
45616
44246
|
if (!userId || !operation)
|
|
45617
44247
|
return jsonError2("userId and operation are required", 400);
|
|
45618
44248
|
if (operation === "list") {
|
|
45619
44249
|
return Response.json({ ok: true, entries: vaultList(userId) });
|
|
45620
44250
|
}
|
|
45621
|
-
const rawKey =
|
|
44251
|
+
const rawKey = str(body, "key");
|
|
45622
44252
|
if (!rawKey || !validateVaultKey(rawKey))
|
|
45623
44253
|
return jsonError2("invalid vault key", 400);
|
|
45624
44254
|
const key = normalizeVaultKey(rawKey);
|
|
@@ -45649,6 +44279,22 @@ async function handleOtiumPeerRequest(req) {
|
|
|
45649
44279
|
}
|
|
45650
44280
|
if (!path.startsWith("/api/v1/peer/"))
|
|
45651
44281
|
return null;
|
|
44282
|
+
if (path.startsWith(OTIUM_GATEWAY_FORWARD_PREFIX)) {
|
|
44283
|
+
const peer = await requirePeer(req);
|
|
44284
|
+
if (!peer.ok)
|
|
44285
|
+
return peer.response;
|
|
44286
|
+
const notPrimary = requirePrimaryOrigin(peer);
|
|
44287
|
+
if (notPrimary)
|
|
44288
|
+
return notPrimary;
|
|
44289
|
+
const { inspectNodeDaemon: inspectNodeDaemon2 } = await init_src5().then(() => exports_src3);
|
|
44290
|
+
const node = await inspectNodeDaemon2();
|
|
44291
|
+
if (!node.running || !node.info) {
|
|
44292
|
+
return jsonError2("canonical Negotium node is unavailable", 503);
|
|
44293
|
+
}
|
|
44294
|
+
return forwardGatewayRequest(req, {
|
|
44295
|
+
nodeOrigin: `http://127.0.0.1:${node.info.port}`
|
|
44296
|
+
});
|
|
44297
|
+
}
|
|
45652
44298
|
if (req.method === "GET") {
|
|
45653
44299
|
if (path === "/api/v1/peer/capabilities") {
|
|
45654
44300
|
const peer = await requirePeer(req);
|
|
@@ -45667,18 +44313,6 @@ async function handleOtiumPeerRequest(req) {
|
|
|
45667
44313
|
if (req.method !== "POST")
|
|
45668
44314
|
return jsonError2("not found", 404);
|
|
45669
44315
|
switch (path) {
|
|
45670
|
-
case "/api/v1/peer/provision":
|
|
45671
|
-
return handleProvision(req);
|
|
45672
|
-
case "/api/v1/peer/bind":
|
|
45673
|
-
return handleBind(req);
|
|
45674
|
-
case "/api/v1/peer/shared-topic/messages":
|
|
45675
|
-
return handleSharedTopicMessages(req);
|
|
45676
|
-
case "/api/v1/peer/shared-topics/private":
|
|
45677
|
-
return handleSharedTopicsPrivate(req);
|
|
45678
|
-
case "/api/v1/peer/unbind":
|
|
45679
|
-
return handleUnbind(req);
|
|
45680
|
-
case "/api/v1/peer/turn":
|
|
45681
|
-
return handleTurn(req);
|
|
45682
44316
|
case "/api/v1/peer/abort":
|
|
45683
44317
|
return handleAbort(req);
|
|
45684
44318
|
case "/api/v1/peer/tell":
|
|
@@ -45689,8 +44323,6 @@ async function handleOtiumPeerRequest(req) {
|
|
|
45689
44323
|
return handleSessions(req);
|
|
45690
44324
|
case "/api/v1/peer/reply":
|
|
45691
44325
|
return handleReply(req);
|
|
45692
|
-
case "/api/v1/peer/input-file":
|
|
45693
|
-
return handleInputFile(req);
|
|
45694
44326
|
case "/api/v1/peer/device-vault":
|
|
45695
44327
|
return handleDeviceVault(req);
|
|
45696
44328
|
default:
|
|
@@ -45700,14 +44332,11 @@ async function handleOtiumPeerRequest(req) {
|
|
|
45700
44332
|
var RUNTIME_VERSION;
|
|
45701
44333
|
var init_peer_server = __esm(async () => {
|
|
45702
44334
|
await init_src();
|
|
45703
|
-
await init_bindings();
|
|
45704
44335
|
await init_central();
|
|
45705
|
-
await
|
|
44336
|
+
await init_gateway_forward();
|
|
45706
44337
|
init_protocol();
|
|
45707
44338
|
await init_session_bridge();
|
|
45708
|
-
await init_shared_topic_sync();
|
|
45709
44339
|
await init_store2();
|
|
45710
|
-
await init_turn_bridge();
|
|
45711
44340
|
RUNTIME_VERSION = NEGOTIUM_VERSION;
|
|
45712
44341
|
});
|
|
45713
44342
|
|
|
@@ -45715,16 +44344,6 @@ var init_peer_server = __esm(async () => {
|
|
|
45715
44344
|
function startOtiumNodeRuntime(options) {
|
|
45716
44345
|
const { join: join43 } = options;
|
|
45717
44346
|
configureOtiumCentral(join43);
|
|
45718
|
-
const failed = failInterruptedPeerTurnRequestsOnStartup();
|
|
45719
|
-
if (failed > 0) {
|
|
45720
|
-
logger.warn({ failed }, "otium: failed interrupted peer turns from previous process");
|
|
45721
|
-
}
|
|
45722
|
-
const staleBindings = sweepStalePeerBindings((localTopicId) => getTopic(localTopicId) !== null);
|
|
45723
|
-
if (staleBindings.topicIds.length > 0) {
|
|
45724
|
-
logger.info({ topicIds: staleBindings.topicIds, ...staleBindings.removed }, "otium: removed peer state for local topics deleted while this node was offline");
|
|
45725
|
-
}
|
|
45726
|
-
const stopBackflow = startEventBackflow();
|
|
45727
|
-
const stopSharedTopicSync = startSharedTopicSync(join43);
|
|
45728
44347
|
const unregisterRuntimeBridge = registerPeerRuntimeBridge(otiumPeerRuntimeBridge);
|
|
45729
44348
|
const unregisterSessionBridge = registerPeerSessionBridge(otiumPeerSessionBridge);
|
|
45730
44349
|
const sessionBridgeIpc = startPeerSessionBridgeIpc(otiumPeerSessionBridge);
|
|
@@ -45740,7 +44359,7 @@ function startOtiumNodeRuntime(options) {
|
|
|
45740
44359
|
if (event.type !== "topic-deleted")
|
|
45741
44360
|
return;
|
|
45742
44361
|
const removed = cleanupPeerStateForLocalTopic(event.topicId);
|
|
45743
|
-
if (removed.
|
|
44362
|
+
if (removed.inboxRequests + removed.remoteAsks > 0) {
|
|
45744
44363
|
logger.info({ topicId: event.topicId, ...removed }, "otium: removed peer state for deleted local topic");
|
|
45745
44364
|
}
|
|
45746
44365
|
});
|
|
@@ -45767,8 +44386,6 @@ function startOtiumNodeRuntime(options) {
|
|
|
45767
44386
|
canonicalMcpBridge.stop();
|
|
45768
44387
|
stopPeerReplyOutbox();
|
|
45769
44388
|
uninstallFileHooks();
|
|
45770
|
-
stopBackflow();
|
|
45771
|
-
stopSharedTopicSync();
|
|
45772
44389
|
configureOtiumCentral(null);
|
|
45773
44390
|
}
|
|
45774
44391
|
};
|
|
@@ -45812,34 +44429,28 @@ var init_src8 = __esm(async () => {
|
|
|
45812
44429
|
await init_src();
|
|
45813
44430
|
await init_canonical_mcp_bridge();
|
|
45814
44431
|
await init_central();
|
|
45815
|
-
await init_event_backflow();
|
|
45816
44432
|
await init_join();
|
|
45817
44433
|
await init_peer_files();
|
|
45818
44434
|
await init_runtime_bridge();
|
|
45819
44435
|
await init_session_bridge();
|
|
45820
44436
|
init_session_bridge_ipc();
|
|
45821
|
-
await init_shared_topic_sync();
|
|
45822
44437
|
await init_store2();
|
|
45823
44438
|
init_tunnel_client();
|
|
45824
|
-
await init_bindings();
|
|
45825
44439
|
await init_central();
|
|
45826
44440
|
await init_enrollment();
|
|
45827
|
-
await init_event_backflow();
|
|
45828
44441
|
await init_join();
|
|
45829
44442
|
await init_peer_server();
|
|
45830
44443
|
init_protocol();
|
|
45831
44444
|
init_relay_protocol();
|
|
45832
44445
|
await init_runtime_bridge();
|
|
45833
|
-
await init_shared_topic_sync();
|
|
45834
44446
|
await init_store2();
|
|
45835
44447
|
init_tunnel_client();
|
|
45836
|
-
await init_turn_bridge();
|
|
45837
44448
|
otiumAdapter = defineNegotiumAdapter({
|
|
45838
44449
|
name: "otium",
|
|
45839
44450
|
capabilities: {
|
|
45840
44451
|
localUserInput: false,
|
|
45841
44452
|
topicManagement: false,
|
|
45842
|
-
externalPlacedTurn:
|
|
44453
|
+
externalPlacedTurn: false
|
|
45843
44454
|
},
|
|
45844
44455
|
projection: {
|
|
45845
44456
|
transcript: "full",
|
|
@@ -45857,7 +44468,7 @@ __export(exports_node_runtime, {
|
|
|
45857
44468
|
handleOtiumAdapterControlRequest: () => handleOtiumAdapterControlRequest,
|
|
45858
44469
|
OTIUM_ADAPTER_CONTROL_PREFIX: () => OTIUM_ADAPTER_CONTROL_PREFIX,
|
|
45859
44470
|
OTIUM_ADAPTER_CONTROL_HEADER: () => OTIUM_ADAPTER_CONTROL_HEADER,
|
|
45860
|
-
|
|
44471
|
+
MAX_PEER_REQUEST_BODY_BYTES: () => MAX_PEER_REQUEST_BODY_BYTES
|
|
45861
44472
|
});
|
|
45862
44473
|
async function handleOtiumAdapterControlRequest(req) {
|
|
45863
44474
|
const url = new URL(req.url);
|
|
@@ -46057,7 +44668,7 @@ async function runOtiumSidecar(options) {
|
|
|
46057
44668
|
port: options.port,
|
|
46058
44669
|
hostname: "127.0.0.1",
|
|
46059
44670
|
idleTimeout: 240,
|
|
46060
|
-
maxRequestBodySize:
|
|
44671
|
+
maxRequestBodySize: MAX_PEER_REQUEST_BODY_BYTES,
|
|
46061
44672
|
fetch: (req) => proxyOtiumPeerRequest(req)
|
|
46062
44673
|
});
|
|
46063
44674
|
} catch (error2) {
|
|
@@ -46149,27 +44760,6 @@ function parseOtiumServeRelayUrl(args) {
|
|
|
46149
44760
|
}
|
|
46150
44761
|
return raw.replace(/\/+$/, "");
|
|
46151
44762
|
}
|
|
46152
|
-
async function resolveHostNodeId(explicit) {
|
|
46153
|
-
if (explicit?.trim())
|
|
46154
|
-
return explicit.trim();
|
|
46155
|
-
const [{ configureOtiumCentral: configureOtiumCentral2, listPeerNodes: listPeerNodes2 }, { loadJoin: loadJoin2 }] = await Promise.all([
|
|
46156
|
-
init_central().then(() => exports_central),
|
|
46157
|
-
init_join().then(() => exports_join)
|
|
46158
|
-
]);
|
|
46159
|
-
const join43 = loadJoin2();
|
|
46160
|
-
if (!join43)
|
|
46161
|
-
throw new Error("not joined to an Otium workspace; pass --host-node or join first");
|
|
46162
|
-
configureOtiumCentral2(join43);
|
|
46163
|
-
try {
|
|
46164
|
-
const nodes = await listPeerNodes2({ fresh: true });
|
|
46165
|
-
const primary = nodes.find((node) => node.isPrimary && !node.self) ?? nodes.find((node) => node.isPrimary);
|
|
46166
|
-
if (!primary)
|
|
46167
|
-
throw new Error("workspace has no primary Otium node");
|
|
46168
|
-
return primary.cellId;
|
|
46169
|
-
} finally {
|
|
46170
|
-
configureOtiumCentral2(null);
|
|
46171
|
-
}
|
|
46172
|
-
}
|
|
46173
44763
|
async function spawnCanonicalNode2() {
|
|
46174
44764
|
const entry = process.argv[1];
|
|
46175
44765
|
if (!entry)
|
|
@@ -46201,11 +44791,11 @@ async function runCanonicalNodeChild2() {
|
|
|
46201
44791
|
let maxRequestBodySize;
|
|
46202
44792
|
if (hasConfiguredOtiumJoin2()) {
|
|
46203
44793
|
const { onShutdown: onShutdown2 } = await init_node_host().then(() => exports_node_host);
|
|
46204
|
-
const {
|
|
44794
|
+
const { MAX_PEER_REQUEST_BODY_BYTES: MAX_PEER_REQUEST_BODY_BYTES2, mountConfiguredOtiumNodeRuntime: mountConfiguredOtiumNodeRuntime2 } = await init_node_runtime().then(() => exports_node_runtime);
|
|
46205
44795
|
const runtime2 = mountConfiguredOtiumNodeRuntime2();
|
|
46206
44796
|
if (runtime2)
|
|
46207
44797
|
onShutdown2("otium-node-runtime", 125, () => runtime2.stop());
|
|
46208
|
-
maxRequestBodySize =
|
|
44798
|
+
maxRequestBodySize = MAX_PEER_REQUEST_BODY_BYTES2;
|
|
46209
44799
|
}
|
|
46210
44800
|
await runNodeDaemon2({ port: 0, ...maxRequestBodySize ? { maxRequestBodySize } : {} });
|
|
46211
44801
|
}
|
|
@@ -46227,20 +44817,29 @@ async function runOtiumCli(args = process.argv.slice(2)) {
|
|
|
46227
44817
|
if (process.env.OTIUM_CENTRAL_URL || process.env.OTIUM_CELL_ID || process.env.OTIUM_CELL_SECRET) {
|
|
46228
44818
|
throw new Error("Otium join is configured by environment; remove OTIUM_CENTRAL_URL, OTIUM_CELL_ID, and OTIUM_CELL_SECRET to disconnect");
|
|
46229
44819
|
}
|
|
46230
|
-
const { configureOtiumCentral: configureOtiumCentral2 } = await init_central().then(() => exports_central);
|
|
46231
44820
|
const { loadJoin: loadJoin2, removeJoin: removeJoin2 } = await init_join().then(() => exports_join);
|
|
46232
|
-
|
|
46233
|
-
const join43 = loadJoin2();
|
|
46234
|
-
if (!join43)
|
|
44821
|
+
if (!loadJoin2())
|
|
46235
44822
|
throw new Error("not joined to an Otium workspace");
|
|
46236
|
-
|
|
46237
|
-
|
|
46238
|
-
|
|
46239
|
-
|
|
46240
|
-
|
|
46241
|
-
|
|
44823
|
+
const { getVisibleTopics: getVisibleTopics2, isTopicShared: isTopicShared2, switchTopicAccessMode: switchTopicAccessMode2 } = await init_src().then(() => exports_src);
|
|
44824
|
+
let downgraded = 0;
|
|
44825
|
+
for (const topic of getVisibleTopics2()) {
|
|
44826
|
+
if (!isTopicShared2(topic))
|
|
44827
|
+
continue;
|
|
44828
|
+
const owner = topic.participants.find((participant) => participant.role === "owner");
|
|
44829
|
+
if (!owner)
|
|
44830
|
+
continue;
|
|
44831
|
+
if (topic.isSubagent)
|
|
44832
|
+
continue;
|
|
44833
|
+
const switched = switchTopicAccessMode2({
|
|
44834
|
+
topicId: topic.id,
|
|
44835
|
+
userId: owner.userId,
|
|
44836
|
+
accessMode: "private"
|
|
44837
|
+
});
|
|
44838
|
+
if (switched.ok)
|
|
44839
|
+
downgraded += switched.topicIds.length;
|
|
46242
44840
|
}
|
|
46243
|
-
|
|
44841
|
+
removeJoin2();
|
|
44842
|
+
console.log(`disconnected from Otium; workspace credentials removed` + (downgraded > 0 ? `; ${downgraded} topic(s) are now private` : ""));
|
|
46244
44843
|
break;
|
|
46245
44844
|
}
|
|
46246
44845
|
case "serve": {
|
|
@@ -46252,63 +44851,19 @@ async function runOtiumCli(args = process.argv.slice(2)) {
|
|
|
46252
44851
|
await runOtiumSidecar2({ port, relayUrl });
|
|
46253
44852
|
break;
|
|
46254
44853
|
}
|
|
46255
|
-
case "bindings": {
|
|
46256
|
-
const { listOtiumTopicBindings: listOtiumTopicBindings2 } = await init_bindings().then(() => exports_bindings);
|
|
46257
|
-
const bindings = listOtiumTopicBindings2();
|
|
46258
|
-
if (bindings.length === 0) {
|
|
46259
|
-
console.log("no Otium topic bindings");
|
|
46260
|
-
break;
|
|
46261
|
-
}
|
|
46262
|
-
for (const binding of bindings) {
|
|
46263
|
-
const local = binding.localTopicTitle ? `${binding.localTopicTitle} (${binding.localTopicId})` : `${binding.localTopicId} [missing]`;
|
|
46264
|
-
console.log(`${binding.transport.padEnd(16)} ${binding.hostNodeId}/${binding.hostTopicId} -> ${local}`);
|
|
46265
|
-
}
|
|
46266
|
-
break;
|
|
46267
|
-
}
|
|
46268
|
-
case "share": {
|
|
46269
|
-
const parsed = parseArgs(commandArgs);
|
|
46270
|
-
const [hostTopicId, localTopicId] = parsed.positional;
|
|
46271
|
-
const userId = parsed.options.get("user")?.trim();
|
|
46272
|
-
if (!hostTopicId || !localTopicId || !userId) {
|
|
46273
|
-
throw new Error("usage: negotium otium share <host-topic-id> <local-topic-id> --user <user-id> [--host-node <cell-id>]");
|
|
46274
|
-
}
|
|
46275
|
-
const hostNodeId = await resolveHostNodeId(parsed.options.get("host-node"));
|
|
46276
|
-
const { shareOtiumTopic: shareOtiumTopic2 } = await init_bindings().then(() => exports_bindings);
|
|
46277
|
-
const result = shareOtiumTopic2({ hostNodeId, hostTopicId, localTopicId, userId });
|
|
46278
|
-
if (!result.ok)
|
|
46279
|
-
throw new Error(result.error);
|
|
46280
|
-
console.log(`shared ${hostNodeId}/${hostTopicId} with local topic ${result.localTopicId}` + (result.replaced ? " (replaced previous binding)" : ""));
|
|
46281
|
-
break;
|
|
46282
|
-
}
|
|
46283
|
-
case "private": {
|
|
46284
|
-
const parsed = parseArgs(commandArgs);
|
|
46285
|
-
const [localTopicId] = parsed.positional;
|
|
46286
|
-
const userId = parsed.options.get("user")?.trim();
|
|
46287
|
-
if (!localTopicId || !userId) {
|
|
46288
|
-
throw new Error("usage: negotium otium private <local-topic-id> --user <user-id>");
|
|
46289
|
-
}
|
|
46290
|
-
const { setOtiumTopicPrivate: setOtiumTopicPrivate2 } = await init_bindings().then(() => exports_bindings);
|
|
46291
|
-
const result = setOtiumTopicPrivate2({ localTopicId, userId });
|
|
46292
|
-
if (!result.ok)
|
|
46293
|
-
throw new Error(result.error);
|
|
46294
|
-
console.log(`private mode selected for ${result.localTopicId}; removed ${result.removedBindings} Otium binding(s)`);
|
|
46295
|
-
break;
|
|
46296
|
-
}
|
|
46297
44854
|
default: {
|
|
46298
44855
|
console.log([
|
|
46299
44856
|
"negotium otium \u2014 attach a Negotium node to an Otium workspace",
|
|
46300
44857
|
"",
|
|
46301
|
-
"usage: negotium otium <join|leave|serve
|
|
44858
|
+
"usage: negotium otium <join|leave|serve> [args]",
|
|
46302
44859
|
"",
|
|
46303
44860
|
" join <code> store credentials from an Otium invite code",
|
|
46304
|
-
" leave
|
|
44861
|
+
" leave remove the stored workspace credentials",
|
|
46305
44862
|
" serve [--port <port>] [--relay <url>]",
|
|
46306
44863
|
" run peer routes and an outbound relay tunnel",
|
|
46307
|
-
"
|
|
46308
|
-
"
|
|
46309
|
-
"
|
|
46310
|
-
" private <local-topic> --user <id>",
|
|
46311
|
-
" remove all Otium bindings; keep Terminal/Telegram access"
|
|
44864
|
+
"",
|
|
44865
|
+
"Publish a topic to the workspace with /public in that topic; the hub",
|
|
44866
|
+
"discovers it over the Runtime Gateway. /private withdraws it."
|
|
46312
44867
|
].join(`
|
|
46313
44868
|
`));
|
|
46314
44869
|
if (command && command !== "help" && command !== "--help")
|
|
@@ -46824,8 +45379,8 @@ var CLI_COMMANDS = [
|
|
|
46824
45379
|
},
|
|
46825
45380
|
{
|
|
46826
45381
|
name: "otium",
|
|
46827
|
-
usage: "otium join|
|
|
46828
|
-
description: "manage the Otium workspace connection
|
|
45382
|
+
usage: "otium join|leave|serve",
|
|
45383
|
+
description: "manage the Otium workspace connection",
|
|
46829
45384
|
group: "Channels"
|
|
46830
45385
|
}
|
|
46831
45386
|
];
|
|
@@ -46865,11 +45420,11 @@ async function runCanonicalNode(port) {
|
|
|
46865
45420
|
let maxRequestBodySize;
|
|
46866
45421
|
if (hasConfiguredOtiumJoin2()) {
|
|
46867
45422
|
const { onShutdown: onShutdown2 } = await init_node_host().then(() => exports_node_host);
|
|
46868
|
-
const {
|
|
45423
|
+
const { MAX_PEER_REQUEST_BODY_BYTES: MAX_PEER_REQUEST_BODY_BYTES2, mountConfiguredOtiumNodeRuntime: mountConfiguredOtiumNodeRuntime2 } = await init_node_runtime().then(() => exports_node_runtime);
|
|
46869
45424
|
const otiumRuntime = mountConfiguredOtiumNodeRuntime2();
|
|
46870
45425
|
if (otiumRuntime)
|
|
46871
45426
|
onShutdown2("otium-node-runtime", 125, () => otiumRuntime.stop());
|
|
46872
|
-
maxRequestBodySize =
|
|
45427
|
+
maxRequestBodySize = MAX_PEER_REQUEST_BODY_BYTES2;
|
|
46873
45428
|
}
|
|
46874
45429
|
const node = await startDefaultNode2({
|
|
46875
45430
|
port,
|
|
@@ -47010,4 +45565,4 @@ switch (command) {
|
|
|
47010
45565
|
}
|
|
47011
45566
|
}
|
|
47012
45567
|
|
|
47013
|
-
//# debugId=
|
|
45568
|
+
//# debugId=4A37A5D1997871CA64756E2164756E21
|