negotium 0.2.22 → 0.2.24
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 +4 -3
- package/dist/agent-helpers.js.map +4 -4
- package/dist/background-bash.js +2 -1
- package/dist/background-bash.js.map +3 -3
- package/dist/browser-runtime.js +2 -1
- package/dist/browser-runtime.js.map +3 -3
- package/dist/{chunk-rhp26p2c.js → chunk-1y2xw0xe.js} +2 -1
- package/dist/{chunk-rhp26p2c.js.map → chunk-1y2xw0xe.js.map} +3 -3
- package/dist/hosted-agent.js +3 -2
- package/dist/hosted-agent.js.map +4 -4
- package/dist/main.js +633 -2097
- package/dist/main.js.map +21 -25
- package/dist/mcp-factories.js +4 -3
- package/dist/mcp-factories.js.map +4 -4
- package/dist/prompts.js +2 -1
- package/dist/prompts.js.map +3 -3
- package/dist/query-runtime.js +2 -1
- package/dist/query-runtime.js.map +3 -3
- package/dist/registry.js +3 -3
- package/dist/registry.js.map +2 -2
- package/dist/rollout.js +1 -1
- package/dist/runtime/src/node-host.ts +3 -2
- package/dist/runtime/src/platform/config.ts +12 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js +2 -1
- package/dist/runtime-helpers.js.map +3 -3
- package/dist/types/packages/core/src/platform/config.d.ts +12 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/dist/vault.js +2 -1
- package/dist/vault.js.map +3 -3
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -273,7 +273,7 @@ function resolveDefaultModel(agent, registryDefaultModel) {
|
|
|
273
273
|
function codexAuthFilePath() {
|
|
274
274
|
return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(process.env.CODEX_HOME || join(homedir(), ".codex"), "auth.json");
|
|
275
275
|
}
|
|
276
|
-
var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CRON_WORKSPACE_DIR, BROWSER_DIR, BROWSER_PROFILES_DIR, BINARIES_DIR, SECRETS_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", BROWSER_RS_VERSION = "v0.1.19", BROWSER_RS_MIN_SECURE_VERSION = "0.1.15", BROWSER_RS_VERSION_PROBE_TIMEOUT_MS = 15000, SNIPPETS_API_URL, BROWSER_RS_BIN, BASH_RS_VERSION = "v0.1.5", BASH_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, BROWSER_MCP_SSE_PROXY_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, UPLOADS_DIR, VAULT_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, BASHRS_SPILL_ROOT, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5", MODEL_FABLE = "claude-fable-5", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, _envMaxTellDepth, MAX_TELL_DEPTH;
|
|
276
|
+
var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CRON_WORKSPACE_DIR, BROWSER_DIR, BROWSER_PROFILES_DIR, BINARIES_DIR, SECRETS_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", BROWSER_RS_VERSION = "v0.1.19", BROWSER_RS_MIN_SECURE_VERSION = "0.1.15", BROWSER_RS_VERSION_PROBE_TIMEOUT_MS = 15000, SNIPPETS_API_URL, BROWSER_RS_BIN, BASH_RS_VERSION = "v0.1.5", BASH_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, BROWSER_MCP_SSE_PROXY_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, NODE_ID, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, UPLOADS_DIR, VAULT_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, BASHRS_SPILL_ROOT, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5", MODEL_FABLE = "claude-fable-5", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, _envMaxTellDepth, MAX_TELL_DEPTH;
|
|
277
277
|
var init_config = __esm(() => {
|
|
278
278
|
init_config_helpers();
|
|
279
279
|
init_logger();
|
|
@@ -315,6 +315,7 @@ var init_config = __esm(() => {
|
|
|
315
315
|
BG_BASH_MAX_PORT = parsePortEnv(process.env.BG_BASH_MAX_PORT, 9799);
|
|
316
316
|
RUNTIME_MCP_SECRET = loadOrCreateLocalSecret("RUNTIME_MCP_SECRET", "runtime-mcp-secret");
|
|
317
317
|
NODE_CONTROL_TOKEN = loadOrCreateLocalSecret("NEGOTIUM_CONTROL_TOKEN", "node-control-token");
|
|
318
|
+
NODE_ID = loadOrCreateLocalSecret("NEGOTIUM_NODE_ID", "node-id");
|
|
318
319
|
VAULT_MASTER_KEY = loadOrCreateLocalSecret("NEGOTIUM_VAULT_MASTER_KEY", "vault-master-key", { persistEnvValue: true });
|
|
319
320
|
delete process.env.NEGOTIUM_VAULT_MASTER_KEY;
|
|
320
321
|
NEGOTIUM_PORT = parseInt(process.env.NEGOTIUM_PORT || "7777", 10);
|
|
@@ -1867,7 +1868,7 @@ var exports_version = {};
|
|
|
1867
1868
|
__export(exports_version, {
|
|
1868
1869
|
NEGOTIUM_VERSION: () => NEGOTIUM_VERSION
|
|
1869
1870
|
});
|
|
1870
|
-
var NEGOTIUM_VERSION = "0.2.
|
|
1871
|
+
var NEGOTIUM_VERSION = "0.2.24";
|
|
1871
1872
|
|
|
1872
1873
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
1873
1874
|
import { spawn } from "child_process";
|
|
@@ -22805,6 +22806,7 @@ var init_conversation_migration = __esm(async () => {
|
|
|
22805
22806
|
// ../../packages/core/src/node-host.ts
|
|
22806
22807
|
var exports_node_host = {};
|
|
22807
22808
|
__export(exports_node_host, {
|
|
22809
|
+
upsertTopic: () => upsertTopic,
|
|
22808
22810
|
topicService: () => topicService,
|
|
22809
22811
|
switchTopicModel: () => switchTopicModel,
|
|
22810
22812
|
switchTopicEffort: () => switchTopicEffort,
|
|
@@ -22852,6 +22854,7 @@ __export(exports_node_host, {
|
|
|
22852
22854
|
ensurePersonalGeneral: () => ensurePersonalGeneral,
|
|
22853
22855
|
deleteVaultEntry: () => deleteVaultEntry,
|
|
22854
22856
|
compactTopicSession: () => compactTopicSession,
|
|
22857
|
+
appendApiMessage: () => appendApiMessage,
|
|
22855
22858
|
acquireRuntimeProcessLease: () => acquireRuntimeProcessLease,
|
|
22856
22859
|
abortAllRooms: () => abortAllRooms,
|
|
22857
22860
|
WsHub: () => WsHub,
|
|
@@ -22863,6 +22866,7 @@ __export(exports_node_host, {
|
|
|
22863
22866
|
STATE_DIR: () => STATE_DIR,
|
|
22864
22867
|
RuntimeGatewayIdempotencyConflictError: () => RuntimeGatewayIdempotencyConflictError,
|
|
22865
22868
|
RUN_DIR: () => RUN_DIR,
|
|
22869
|
+
NODE_ID: () => NODE_ID,
|
|
22866
22870
|
NODE_CONTROL_TOKEN: () => NODE_CONTROL_TOKEN,
|
|
22867
22871
|
NEGOTIUM_VERSION: () => NEGOTIUM_VERSION,
|
|
22868
22872
|
NEGOTIUM_PORT: () => NEGOTIUM_PORT,
|
|
@@ -29437,12 +29441,15 @@ function createNodeControlHandler(options) {
|
|
|
29437
29441
|
ok: true,
|
|
29438
29442
|
v: NODE_RUNTIME_CONTRACT_VERSION,
|
|
29439
29443
|
nodeVersion: NODE_VERSION,
|
|
29444
|
+
nodeId: NODE_ID,
|
|
29440
29445
|
capabilities: [
|
|
29441
29446
|
"turn-submit-idempotent",
|
|
29442
29447
|
"turn-events-sse-resume",
|
|
29443
29448
|
"canonical-topic-read",
|
|
29444
29449
|
"canonical-message-read",
|
|
29445
|
-
"canonical-topic-list"
|
|
29450
|
+
"canonical-topic-list",
|
|
29451
|
+
"canonical-topic-create",
|
|
29452
|
+
"canonical-history-import"
|
|
29446
29453
|
],
|
|
29447
29454
|
cursor: latestRuntimeEventSeq()
|
|
29448
29455
|
});
|
|
@@ -29519,6 +29526,74 @@ function createNodeControlHandler(options) {
|
|
|
29519
29526
|
cursor: latestRuntimeEventSeq()
|
|
29520
29527
|
});
|
|
29521
29528
|
}
|
|
29529
|
+
if (req.method === "POST" && runtimePath === "/topics") {
|
|
29530
|
+
const body = await bodyRecord(req);
|
|
29531
|
+
if (body.v !== NODE_RUNTIME_CONTRACT_VERSION)
|
|
29532
|
+
return jsonError(400, "Unsupported v");
|
|
29533
|
+
const userId = requiredText(body.userId, "userId");
|
|
29534
|
+
const title = requiredText(body.title, "title");
|
|
29535
|
+
const agent = body.agent;
|
|
29536
|
+
if (agent !== undefined && !["claude", "codex", "maestro"].includes(String(agent))) {
|
|
29537
|
+
return jsonError(400, "Invalid agent");
|
|
29538
|
+
}
|
|
29539
|
+
const topic = topicService.create({
|
|
29540
|
+
title,
|
|
29541
|
+
userId,
|
|
29542
|
+
kind: "agent",
|
|
29543
|
+
accessMode: "shared",
|
|
29544
|
+
...agent ? { agent } : {}
|
|
29545
|
+
});
|
|
29546
|
+
return Response.json({ ok: true, v: NODE_RUNTIME_CONTRACT_VERSION, topic }, { status: 201 });
|
|
29547
|
+
}
|
|
29548
|
+
const importMatch = runtimePath.match(/^\/topics\/([^/]+)\/import$/);
|
|
29549
|
+
if (importMatch && req.method === "POST") {
|
|
29550
|
+
const body = await bodyRecord(req);
|
|
29551
|
+
if (body.v !== NODE_RUNTIME_CONTRACT_VERSION)
|
|
29552
|
+
return jsonError(400, "Unsupported v");
|
|
29553
|
+
const topicId = decodeURIComponent(importMatch[1]);
|
|
29554
|
+
const topic = getTopic(topicId);
|
|
29555
|
+
if (!topic)
|
|
29556
|
+
return jsonError(404, "Topic not found");
|
|
29557
|
+
if (!Array.isArray(body.messages)) {
|
|
29558
|
+
return jsonError(400, "messages must be an array");
|
|
29559
|
+
}
|
|
29560
|
+
if (listApiMessages(topicId, { limit: 1 }).page.length > 0) {
|
|
29561
|
+
return jsonError(409, "topic already has messages");
|
|
29562
|
+
}
|
|
29563
|
+
const messages = body.messages;
|
|
29564
|
+
for (const [index, message] of messages.entries()) {
|
|
29565
|
+
if (typeof message?.id !== "string" || typeof message?.authorId !== "string" || typeof message?.text !== "string" || typeof message?.createdAt !== "string") {
|
|
29566
|
+
return jsonError(400, `messages[${index}] needs id, authorId, text and createdAt`);
|
|
29567
|
+
}
|
|
29568
|
+
}
|
|
29569
|
+
let imported = 0;
|
|
29570
|
+
for (const message of messages) {
|
|
29571
|
+
appendApiMessage({
|
|
29572
|
+
id: message.id,
|
|
29573
|
+
topicId,
|
|
29574
|
+
authorId: message.authorId,
|
|
29575
|
+
text: message.text,
|
|
29576
|
+
createdAt: message.createdAt,
|
|
29577
|
+
deleted: false,
|
|
29578
|
+
...typeof message.agentType === "string" ? { agentType: message.agentType } : {},
|
|
29579
|
+
...typeof message.model === "string" ? { model: message.model } : {}
|
|
29580
|
+
}, { notify: false, updateTopicLastMessageAt: false });
|
|
29581
|
+
imported += 1;
|
|
29582
|
+
}
|
|
29583
|
+
if (imported > 0) {
|
|
29584
|
+
const stamps = messages.map((message) => message.createdAt).sort();
|
|
29585
|
+
upsertTopic({
|
|
29586
|
+
...topic,
|
|
29587
|
+
createdAt: stamps[0] < topic.createdAt ? stamps[0] : topic.createdAt,
|
|
29588
|
+
lastMessageAt: stamps[stamps.length - 1]
|
|
29589
|
+
});
|
|
29590
|
+
}
|
|
29591
|
+
return Response.json({
|
|
29592
|
+
ok: true,
|
|
29593
|
+
v: NODE_RUNTIME_CONTRACT_VERSION,
|
|
29594
|
+
imported
|
|
29595
|
+
});
|
|
29596
|
+
}
|
|
29522
29597
|
const runtimeTopicMatch = runtimePath.match(/^\/topics\/([^/]+)$/);
|
|
29523
29598
|
if (runtimeTopicMatch && req.method === "GET") {
|
|
29524
29599
|
const topic = getTopic(decodeURIComponent(runtimeTopicMatch[1]));
|
|
@@ -41439,18 +41514,30 @@ var init_cli2 = __esm(async () => {
|
|
|
41439
41514
|
if (false) {}
|
|
41440
41515
|
});
|
|
41441
41516
|
|
|
41442
|
-
// ../../
|
|
41443
|
-
var
|
|
41444
|
-
|
|
41445
|
-
|
|
41446
|
-
|
|
41447
|
-
|
|
41448
|
-
|
|
41449
|
-
|
|
41450
|
-
|
|
41451
|
-
listPeerNodes: () => listPeerNodes,
|
|
41452
|
-
configureOtiumCentral: () => configureOtiumCentral
|
|
41517
|
+
// ../../packages/core/src/config-public.ts
|
|
41518
|
+
var init_config_public = __esm(() => {
|
|
41519
|
+
init_config();
|
|
41520
|
+
});
|
|
41521
|
+
|
|
41522
|
+
// ../../adapters/otium/src/join-status.ts
|
|
41523
|
+
var exports_join_status = {};
|
|
41524
|
+
__export(exports_join_status, {
|
|
41525
|
+
hasConfiguredOtiumJoin: () => hasConfiguredOtiumJoin
|
|
41453
41526
|
});
|
|
41527
|
+
import { existsSync as existsSync36 } from "fs";
|
|
41528
|
+
import { resolve as resolve23 } from "path";
|
|
41529
|
+
function hasConfiguredOtiumJoin() {
|
|
41530
|
+
const envJoin = Boolean(process.env.OTIUM_CENTRAL_URL?.trim() && process.env.OTIUM_CELL_ID?.trim() && process.env.OTIUM_CELL_SECRET?.trim());
|
|
41531
|
+
return envJoin || existsSync36(resolve23(DATA_DIR, "otium-join.json"));
|
|
41532
|
+
}
|
|
41533
|
+
var init_join_status = __esm(() => {
|
|
41534
|
+
init_config_public();
|
|
41535
|
+
});
|
|
41536
|
+
|
|
41537
|
+
// ../../adapters/otium/src/control-protocol.ts
|
|
41538
|
+
var OTIUM_ADAPTER_CONTROL_PREFIX = "/api/v1/adapter/otium", OTIUM_ADAPTER_CONTROL_HEADER = "x-negotium-adapter-token";
|
|
41539
|
+
|
|
41540
|
+
// ../../adapters/otium/src/central.ts
|
|
41454
41541
|
function configureOtiumCentral(join42) {
|
|
41455
41542
|
joinConfig = join42;
|
|
41456
41543
|
resetPeerCentralCaches();
|
|
@@ -41555,418 +41642,14 @@ var init_central = __esm(async () => {
|
|
|
41555
41642
|
tokenCache = new Map;
|
|
41556
41643
|
});
|
|
41557
41644
|
|
|
41558
|
-
// ../../adapters/otium/src/secure-transport.ts
|
|
41559
|
-
function isLoopbackHostname(hostname2) {
|
|
41560
|
-
const normalized = hostname2.toLowerCase();
|
|
41561
|
-
if (normalized === "localhost" || normalized === "::1" || normalized === "[::1]")
|
|
41562
|
-
return true;
|
|
41563
|
-
const octets = normalized.split(".");
|
|
41564
|
-
return octets.length === 4 && octets[0] === "127" && octets.every((octet) => /^\d{1,3}$/.test(octet) && Number(octet) <= 255);
|
|
41565
|
-
}
|
|
41566
|
-
function credentialTransportUrl(value, label) {
|
|
41567
|
-
let url;
|
|
41568
|
-
try {
|
|
41569
|
-
url = new URL(value);
|
|
41570
|
-
} catch {
|
|
41571
|
-
throw new Error(`${label} is not a valid URL`);
|
|
41572
|
-
}
|
|
41573
|
-
if (url.username || url.password)
|
|
41574
|
-
throw new Error(`${label} must not contain URL credentials`);
|
|
41575
|
-
return url;
|
|
41576
|
-
}
|
|
41577
|
-
function assertSecureCentralUrl(value) {
|
|
41578
|
-
const url = credentialTransportUrl(value, "Otium central URL");
|
|
41579
|
-
if (url.protocol === "https:")
|
|
41580
|
-
return;
|
|
41581
|
-
if (url.protocol === "http:" && isLoopbackHostname(url.hostname))
|
|
41582
|
-
return;
|
|
41583
|
-
throw new Error("Otium central requires HTTPS or loopback HTTP");
|
|
41584
|
-
}
|
|
41585
|
-
function assertSecureRelayUrl(value) {
|
|
41586
|
-
const url = credentialTransportUrl(value, "Otium relay URL");
|
|
41587
|
-
if (url.protocol === "https:" || url.protocol === "wss:")
|
|
41588
|
-
return;
|
|
41589
|
-
if ((url.protocol === "http:" || url.protocol === "ws:") && isLoopbackHostname(url.hostname)) {
|
|
41590
|
-
return;
|
|
41591
|
-
}
|
|
41592
|
-
throw new Error("Otium relay requires HTTPS/WSS or loopback HTTP/WS");
|
|
41593
|
-
}
|
|
41594
|
-
|
|
41595
|
-
// ../../adapters/otium/src/join.ts
|
|
41596
|
-
var exports_join = {};
|
|
41597
|
-
__export(exports_join, {
|
|
41598
|
-
withJoinCredentialLock: () => withJoinCredentialLock,
|
|
41599
|
-
saveJoinWhileLocked: () => saveJoinWhileLocked,
|
|
41600
|
-
saveJoin: () => saveJoin,
|
|
41601
|
-
removeJoin: () => removeJoin,
|
|
41602
|
-
parseInviteCode: () => parseInviteCode,
|
|
41603
|
-
loadJoin: () => loadJoin,
|
|
41604
|
-
joinFilePath: () => joinFilePath,
|
|
41605
|
-
joinCredentialDigest: () => joinCredentialDigest,
|
|
41606
|
-
isJoinPersisted: () => isJoinPersisted
|
|
41607
|
-
});
|
|
41608
|
-
import { createHash as createHash11, randomUUID as randomUUID27 } from "crypto";
|
|
41609
|
-
import {
|
|
41610
|
-
chmodSync as chmodSync7,
|
|
41611
|
-
closeSync as closeSync5,
|
|
41612
|
-
existsSync as existsSync36,
|
|
41613
|
-
fsyncSync as fsyncSync2,
|
|
41614
|
-
linkSync,
|
|
41615
|
-
lstatSync,
|
|
41616
|
-
mkdirSync as mkdirSync32,
|
|
41617
|
-
openSync as openSync5,
|
|
41618
|
-
readFileSync as readFileSync28,
|
|
41619
|
-
renameSync as renameSync16,
|
|
41620
|
-
rmSync as rmSync10,
|
|
41621
|
-
statSync as statSync20,
|
|
41622
|
-
unlinkSync as unlinkSync23,
|
|
41623
|
-
writeFileSync as writeFileSync23
|
|
41624
|
-
} from "fs";
|
|
41625
|
-
import { dirname as dirname21, resolve as resolve23 } from "path";
|
|
41626
|
-
function joinFilePath() {
|
|
41627
|
-
return resolve23(DATA_DIR, "otium-join.json");
|
|
41628
|
-
}
|
|
41629
|
-
function isHttpUrl(value) {
|
|
41630
|
-
return /^https?:\/\//.test(value);
|
|
41631
|
-
}
|
|
41632
|
-
function isRelayUrl(value) {
|
|
41633
|
-
return /^(?:https?|wss?):\/\//.test(value);
|
|
41634
|
-
}
|
|
41635
|
-
function normalizeJoin(raw) {
|
|
41636
|
-
const central = typeof raw.central === "string" ? raw.central.trim().replace(/\/+$/, "") : "";
|
|
41637
|
-
const relay = typeof raw.relay === "string" ? raw.relay.trim().replace(/\/+$/, "") : "";
|
|
41638
|
-
const cellId = typeof raw.cellId === "string" ? raw.cellId.trim() : "";
|
|
41639
|
-
const secret = typeof raw.secret === "string" ? raw.secret.trim() : "";
|
|
41640
|
-
if (!central || !isHttpUrl(central)) {
|
|
41641
|
-
throw new Error("invite code is missing a valid http(s) central URL");
|
|
41642
|
-
}
|
|
41643
|
-
if (relay && !isRelayUrl(relay))
|
|
41644
|
-
throw new Error("invite code has an invalid relay URL");
|
|
41645
|
-
if (!cellId)
|
|
41646
|
-
throw new Error("invite code is missing cellId");
|
|
41647
|
-
if (!secret)
|
|
41648
|
-
throw new Error("invite code is missing secret");
|
|
41649
|
-
assertSecureCentralUrl(central);
|
|
41650
|
-
if (relay)
|
|
41651
|
-
assertSecureRelayUrl(relay);
|
|
41652
|
-
return {
|
|
41653
|
-
...typeof raw.v === "number" ? { v: raw.v } : {},
|
|
41654
|
-
central,
|
|
41655
|
-
...relay ? { relay } : {},
|
|
41656
|
-
cellId,
|
|
41657
|
-
secret
|
|
41658
|
-
};
|
|
41659
|
-
}
|
|
41660
|
-
function parseInviteCode(code) {
|
|
41661
|
-
const trimmed = code.trim();
|
|
41662
|
-
if (!trimmed)
|
|
41663
|
-
throw new Error("invite code is empty");
|
|
41664
|
-
let decoded;
|
|
41665
|
-
try {
|
|
41666
|
-
decoded = Buffer.from(trimmed, "base64url").toString("utf-8");
|
|
41667
|
-
} catch {
|
|
41668
|
-
throw new Error("invite code is not valid base64url");
|
|
41669
|
-
}
|
|
41670
|
-
let parsed;
|
|
41671
|
-
try {
|
|
41672
|
-
parsed = JSON.parse(decoded);
|
|
41673
|
-
} catch {
|
|
41674
|
-
throw new Error("invite code does not decode to JSON");
|
|
41675
|
-
}
|
|
41676
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
41677
|
-
throw new Error("invite code does not decode to a JSON object");
|
|
41678
|
-
}
|
|
41679
|
-
return normalizeJoin(parsed);
|
|
41680
|
-
}
|
|
41681
|
-
function joinLockPath() {
|
|
41682
|
-
return resolve23(DATA_DIR, ".otium-join.lock");
|
|
41683
|
-
}
|
|
41684
|
-
function processIsAlive(pid) {
|
|
41685
|
-
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
41686
|
-
return false;
|
|
41687
|
-
try {
|
|
41688
|
-
process.kill(pid, 0);
|
|
41689
|
-
return true;
|
|
41690
|
-
} catch (error2) {
|
|
41691
|
-
return error2.code !== "ESRCH";
|
|
41692
|
-
}
|
|
41693
|
-
}
|
|
41694
|
-
function withJoinCredentialLock(operation) {
|
|
41695
|
-
const lockPath = joinLockPath();
|
|
41696
|
-
const ownerPath = resolve23(lockPath, "owner.json");
|
|
41697
|
-
const owner = { pid: process.pid, token: randomUUID27() };
|
|
41698
|
-
mkdirSync32(dirname21(lockPath), { recursive: true });
|
|
41699
|
-
for (let attempt = 0;; attempt += 1) {
|
|
41700
|
-
let created = false;
|
|
41701
|
-
try {
|
|
41702
|
-
mkdirSync32(lockPath, { mode: 448 });
|
|
41703
|
-
created = true;
|
|
41704
|
-
writeFileSync23(ownerPath, `${JSON.stringify(owner)}
|
|
41705
|
-
`, { mode: 384 });
|
|
41706
|
-
const ownerFd = openSync5(ownerPath, "r");
|
|
41707
|
-
try {
|
|
41708
|
-
fsyncSync2(ownerFd);
|
|
41709
|
-
} finally {
|
|
41710
|
-
closeSync5(ownerFd);
|
|
41711
|
-
}
|
|
41712
|
-
break;
|
|
41713
|
-
} catch (error2) {
|
|
41714
|
-
if (created) {
|
|
41715
|
-
rmSync10(lockPath, { recursive: true, force: true });
|
|
41716
|
-
throw error2;
|
|
41717
|
-
}
|
|
41718
|
-
if (error2.code !== "EEXIST")
|
|
41719
|
-
throw error2;
|
|
41720
|
-
let current3 = null;
|
|
41721
|
-
try {
|
|
41722
|
-
current3 = JSON.parse(readFileSync28(ownerPath, "utf8"));
|
|
41723
|
-
} catch {}
|
|
41724
|
-
let ageMs;
|
|
41725
|
-
try {
|
|
41726
|
-
ageMs = Date.now() - statSync20(lockPath).mtimeMs;
|
|
41727
|
-
} catch (statError) {
|
|
41728
|
-
if (statError.code === "ENOENT")
|
|
41729
|
-
continue;
|
|
41730
|
-
throw statError;
|
|
41731
|
-
}
|
|
41732
|
-
if (current3 && processIsAlive(current3.pid) || !current3 && ageMs <= JOIN_LOCK_STALE_MS) {
|
|
41733
|
-
throw new Error(`another Otium join credential operation is in progress at ${lockPath}`);
|
|
41734
|
-
}
|
|
41735
|
-
if (attempt > 0) {
|
|
41736
|
-
throw new Error(`could not recover stale Otium join credential lock at ${lockPath}`);
|
|
41737
|
-
}
|
|
41738
|
-
const stalePath = `${lockPath}.stale.${process.pid}.${randomUUID27()}`;
|
|
41739
|
-
try {
|
|
41740
|
-
renameSync16(lockPath, stalePath);
|
|
41741
|
-
rmSync10(stalePath, { recursive: true, force: true });
|
|
41742
|
-
} catch (staleError) {
|
|
41743
|
-
if (staleError.code !== "ENOENT")
|
|
41744
|
-
throw staleError;
|
|
41745
|
-
}
|
|
41746
|
-
}
|
|
41747
|
-
}
|
|
41748
|
-
try {
|
|
41749
|
-
return operation();
|
|
41750
|
-
} finally {
|
|
41751
|
-
try {
|
|
41752
|
-
const current3 = JSON.parse(readFileSync28(ownerPath, "utf8"));
|
|
41753
|
-
if (current3.pid === owner.pid && current3.token === owner.token) {
|
|
41754
|
-
rmSync10(lockPath, { recursive: true, force: true });
|
|
41755
|
-
}
|
|
41756
|
-
} catch {}
|
|
41757
|
-
}
|
|
41758
|
-
}
|
|
41759
|
-
function joinsEqual(left, right) {
|
|
41760
|
-
return left.central === right.central && left.relay === right.relay && left.cellId === right.cellId && left.secret === right.secret;
|
|
41761
|
-
}
|
|
41762
|
-
function normalizedJoin(join42) {
|
|
41763
|
-
return normalizeJoin({
|
|
41764
|
-
v: join42.v,
|
|
41765
|
-
central: join42.central,
|
|
41766
|
-
relay: join42.relay,
|
|
41767
|
-
cellId: join42.cellId,
|
|
41768
|
-
secret: join42.secret
|
|
41769
|
-
});
|
|
41770
|
-
}
|
|
41771
|
-
function joinCredentialDigest(join42) {
|
|
41772
|
-
return createHash11("sha256").update(JSON.stringify(normalizedJoin(join42))).digest("base64url");
|
|
41773
|
-
}
|
|
41774
|
-
function readPersistedJoin(path = joinFilePath()) {
|
|
41775
|
-
if (!existsSync36(path))
|
|
41776
|
-
return null;
|
|
41777
|
-
const parsed = JSON.parse(readFileSync28(path, "utf-8"));
|
|
41778
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
41779
|
-
throw new Error("persisted join credentials are not a JSON object");
|
|
41780
|
-
}
|
|
41781
|
-
return normalizeJoin(parsed);
|
|
41782
|
-
}
|
|
41783
|
-
function isJoinPersisted(join42) {
|
|
41784
|
-
try {
|
|
41785
|
-
const persisted = readPersistedJoin();
|
|
41786
|
-
return persisted !== null && joinsEqual(persisted, normalizedJoin(join42));
|
|
41787
|
-
} catch {
|
|
41788
|
-
return false;
|
|
41789
|
-
}
|
|
41790
|
-
}
|
|
41791
|
-
function saveJoinWhileLocked(join42, options = {}) {
|
|
41792
|
-
const path = joinFilePath();
|
|
41793
|
-
const directory = dirname21(path);
|
|
41794
|
-
const normalized = normalizedJoin(join42);
|
|
41795
|
-
mkdirSync32(directory, { recursive: true });
|
|
41796
|
-
if (existsSync36(path)) {
|
|
41797
|
-
if (lstatSync(path).isSymbolicLink()) {
|
|
41798
|
-
throw new Error(`refusing to replace symlinked Otium join file at ${path}`);
|
|
41799
|
-
}
|
|
41800
|
-
let existing = null;
|
|
41801
|
-
try {
|
|
41802
|
-
existing = readPersistedJoin(path);
|
|
41803
|
-
} catch (error2) {
|
|
41804
|
-
if (!options.replaceExisting) {
|
|
41805
|
-
throw new Error(`existing Otium join file at ${path} is invalid; pass --replace to replace it`, { cause: error2 });
|
|
41806
|
-
}
|
|
41807
|
-
}
|
|
41808
|
-
if (existing && joinsEqual(existing, normalized)) {
|
|
41809
|
-
chmodSync7(path, 384);
|
|
41810
|
-
const fileFd = openSync5(path, "r");
|
|
41811
|
-
try {
|
|
41812
|
-
fsyncSync2(fileFd);
|
|
41813
|
-
} finally {
|
|
41814
|
-
closeSync5(fileFd);
|
|
41815
|
-
}
|
|
41816
|
-
const directoryFd = openSync5(directory, "r");
|
|
41817
|
-
try {
|
|
41818
|
-
fsyncSync2(directoryFd);
|
|
41819
|
-
} finally {
|
|
41820
|
-
closeSync5(directoryFd);
|
|
41821
|
-
}
|
|
41822
|
-
return path;
|
|
41823
|
-
}
|
|
41824
|
-
if (!options.replaceExisting) {
|
|
41825
|
-
throw new Error(`this node is already joined${existing ? ` as ${existing.cellId}` : " with an invalid join file"}; pass --replace to replace its credentials`);
|
|
41826
|
-
}
|
|
41827
|
-
}
|
|
41828
|
-
const temporaryPath = resolve23(directory, `.otium-join.json.${process.pid}.${randomUUID27()}.tmp`);
|
|
41829
|
-
let fd;
|
|
41830
|
-
try {
|
|
41831
|
-
fd = openSync5(temporaryPath, "wx", 384);
|
|
41832
|
-
writeFileSync23(fd, `${JSON.stringify(normalized, null, 2)}
|
|
41833
|
-
`, "utf8");
|
|
41834
|
-
fsyncSync2(fd);
|
|
41835
|
-
closeSync5(fd);
|
|
41836
|
-
fd = undefined;
|
|
41837
|
-
if (options.replaceExisting) {
|
|
41838
|
-
renameSync16(temporaryPath, path);
|
|
41839
|
-
} else {
|
|
41840
|
-
linkSync(temporaryPath, path);
|
|
41841
|
-
unlinkSync23(temporaryPath);
|
|
41842
|
-
}
|
|
41843
|
-
chmodSync7(path, 384);
|
|
41844
|
-
const directoryFd = openSync5(directory, "r");
|
|
41845
|
-
try {
|
|
41846
|
-
fsyncSync2(directoryFd);
|
|
41847
|
-
} finally {
|
|
41848
|
-
closeSync5(directoryFd);
|
|
41849
|
-
}
|
|
41850
|
-
} catch (error2) {
|
|
41851
|
-
if (fd !== undefined)
|
|
41852
|
-
closeSync5(fd);
|
|
41853
|
-
if (existsSync36(temporaryPath))
|
|
41854
|
-
unlinkSync23(temporaryPath);
|
|
41855
|
-
throw error2;
|
|
41856
|
-
}
|
|
41857
|
-
return path;
|
|
41858
|
-
}
|
|
41859
|
-
function saveJoin(join42, options = {}) {
|
|
41860
|
-
return withJoinCredentialLock(() => saveJoinWhileLocked(join42, options));
|
|
41861
|
-
}
|
|
41862
|
-
function removeJoin() {
|
|
41863
|
-
return withJoinCredentialLock(() => {
|
|
41864
|
-
const path = joinFilePath();
|
|
41865
|
-
if (!existsSync36(path))
|
|
41866
|
-
return false;
|
|
41867
|
-
if (lstatSync(path).isSymbolicLink()) {
|
|
41868
|
-
throw new Error(`refusing to remove symlinked Otium join file at ${path}`);
|
|
41869
|
-
}
|
|
41870
|
-
unlinkSync23(path);
|
|
41871
|
-
const directoryFd = openSync5(dirname21(path), "r");
|
|
41872
|
-
try {
|
|
41873
|
-
fsyncSync2(directoryFd);
|
|
41874
|
-
} finally {
|
|
41875
|
-
closeSync5(directoryFd);
|
|
41876
|
-
}
|
|
41877
|
-
return true;
|
|
41878
|
-
});
|
|
41879
|
-
}
|
|
41880
|
-
function loadJoin() {
|
|
41881
|
-
const central = process.env.OTIUM_CENTRAL_URL?.trim();
|
|
41882
|
-
const cellId = process.env.OTIUM_CELL_ID?.trim();
|
|
41883
|
-
const secret = process.env.OTIUM_CELL_SECRET?.trim();
|
|
41884
|
-
const relay = process.env.OTIUM_RELAY_URL?.trim();
|
|
41885
|
-
if (central && cellId && secret) {
|
|
41886
|
-
try {
|
|
41887
|
-
return normalizeJoin({ central, relay, cellId, secret });
|
|
41888
|
-
} catch (err2) {
|
|
41889
|
-
logger.warn({ err: err2 }, "otium: invalid OTIUM_CENTRAL_URL/OTIUM_CELL_ID/OTIUM_CELL_SECRET env");
|
|
41890
|
-
return null;
|
|
41891
|
-
}
|
|
41892
|
-
}
|
|
41893
|
-
if (central || cellId || secret) {
|
|
41894
|
-
logger.warn("otium: OTIUM_CENTRAL_URL, OTIUM_CELL_ID, OTIUM_CELL_SECRET must be set together \u2014 ignoring partial env");
|
|
41895
|
-
}
|
|
41896
|
-
const path = joinFilePath();
|
|
41897
|
-
if (!existsSync36(path))
|
|
41898
|
-
return null;
|
|
41899
|
-
try {
|
|
41900
|
-
const parsed = JSON.parse(readFileSync28(path, "utf-8"));
|
|
41901
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
41902
|
-
return null;
|
|
41903
|
-
return normalizeJoin(parsed);
|
|
41904
|
-
} catch (err2) {
|
|
41905
|
-
logger.warn({ err: err2, path }, "otium: failed to read join file");
|
|
41906
|
-
return null;
|
|
41907
|
-
}
|
|
41908
|
-
}
|
|
41909
|
-
var JOIN_LOCK_STALE_MS = 30000;
|
|
41910
|
-
var init_join = __esm(async () => {
|
|
41911
|
-
await init_src();
|
|
41912
|
-
});
|
|
41913
|
-
|
|
41914
|
-
// ../../packages/core/src/config-public.ts
|
|
41915
|
-
var init_config_public = __esm(() => {
|
|
41916
|
-
init_config();
|
|
41917
|
-
});
|
|
41918
|
-
|
|
41919
|
-
// ../../adapters/otium/src/join-status.ts
|
|
41920
|
-
var exports_join_status = {};
|
|
41921
|
-
__export(exports_join_status, {
|
|
41922
|
-
hasConfiguredOtiumJoin: () => hasConfiguredOtiumJoin
|
|
41923
|
-
});
|
|
41924
|
-
import { existsSync as existsSync37 } from "fs";
|
|
41925
|
-
import { resolve as resolve24 } from "path";
|
|
41926
|
-
function hasConfiguredOtiumJoin() {
|
|
41927
|
-
const envJoin = Boolean(process.env.OTIUM_CENTRAL_URL?.trim() && process.env.OTIUM_CELL_ID?.trim() && process.env.OTIUM_CELL_SECRET?.trim());
|
|
41928
|
-
return envJoin || existsSync37(resolve24(DATA_DIR, "otium-join.json"));
|
|
41929
|
-
}
|
|
41930
|
-
var init_join_status = __esm(() => {
|
|
41931
|
-
init_config_public();
|
|
41932
|
-
});
|
|
41933
|
-
|
|
41934
|
-
// ../../adapters/otium/src/control-protocol.ts
|
|
41935
|
-
var OTIUM_ADAPTER_CONTROL_PREFIX = "/api/v1/adapter/otium", OTIUM_ADAPTER_CONTROL_HEADER = "x-negotium-adapter-token";
|
|
41936
|
-
|
|
41937
41645
|
// ../../adapters/otium/src/protocol.ts
|
|
41938
|
-
|
|
41939
|
-
const value = body[field];
|
|
41940
|
-
return typeof value === "string" && value.trim() ? value : null;
|
|
41941
|
-
}
|
|
41942
|
-
function parseExecutionSpec(value) {
|
|
41943
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
41944
|
-
return null;
|
|
41945
|
-
const raw = value;
|
|
41946
|
-
const agent = str(raw, "agent");
|
|
41947
|
-
const model = str(raw, "model");
|
|
41948
|
-
const effort = str(raw, "effort");
|
|
41949
|
-
const rawMcp = raw.mcp;
|
|
41950
|
-
if (!agent || !model || !effort || !Array.isArray(rawMcp) || !rawMcp.every((entry) => typeof entry === "string" && entry.trim().length > 0) || typeof raw.canSpawnSubagents !== "boolean") {
|
|
41951
|
-
return null;
|
|
41952
|
-
}
|
|
41953
|
-
return {
|
|
41954
|
-
agent,
|
|
41955
|
-
model,
|
|
41956
|
-
effort,
|
|
41957
|
-
...str(raw, "description") ? { description: str(raw, "description") } : {},
|
|
41958
|
-
mcp: [...new Set(rawMcp.map((entry) => entry.trim()))],
|
|
41959
|
-
canSpawnSubagents: raw.canSpawnSubagents
|
|
41960
|
-
};
|
|
41961
|
-
}
|
|
41962
|
-
var PEER_PROTOCOL_VERSION = 1, MAX_PEER_MESSAGE_LENGTH = 1e4, MAX_PEER_INPUT_FILE_BYTES, MAX_PEER_INPUT_REQUEST_BYTES;
|
|
41646
|
+
var PEER_PROTOCOL_VERSION = 1, MAX_PEER_MESSAGE_LENGTH = 1e4, MAX_PEER_REQUEST_BODY_BYTES;
|
|
41963
41647
|
var init_protocol = __esm(() => {
|
|
41964
|
-
|
|
41965
|
-
MAX_PEER_INPUT_REQUEST_BYTES = MAX_PEER_INPUT_FILE_BYTES + 8 * 1024 * 1024;
|
|
41648
|
+
MAX_PEER_REQUEST_BODY_BYTES = 2 * 1024 * 1024 * 1024 + 8 * 1024 * 1024;
|
|
41966
41649
|
});
|
|
41967
41650
|
|
|
41968
41651
|
// ../../adapters/otium/src/canonical-mcp-bridge.ts
|
|
41969
|
-
import { randomUUID as
|
|
41652
|
+
import { randomUUID as randomUUID27 } from "crypto";
|
|
41970
41653
|
function readAuthorization(request) {
|
|
41971
41654
|
const value = request.headers.get("authorization");
|
|
41972
41655
|
return value?.startsWith("Bearer ") ? value.slice(7) : null;
|
|
@@ -42107,7 +41790,7 @@ function startCanonicalMcpBridge(options = {}) {
|
|
|
42107
41790
|
const url = `http://127.0.0.1:${server.port}/`;
|
|
42108
41791
|
const unregister = registerCanonicalMcpBridgeEnvProvider((scope) => {
|
|
42109
41792
|
sweep();
|
|
42110
|
-
const token = `${
|
|
41793
|
+
const token = `${randomUUID27()}${randomUUID27()}`;
|
|
42111
41794
|
capabilities.set(token, {
|
|
42112
41795
|
surface: scope.surface,
|
|
42113
41796
|
userId: scope.userId,
|
|
@@ -42159,736 +41842,360 @@ var init_canonical_mcp_bridge = __esm(async () => {
|
|
|
42159
41842
|
};
|
|
42160
41843
|
});
|
|
42161
41844
|
|
|
42162
|
-
// ../../adapters/otium/src/
|
|
42163
|
-
|
|
42164
|
-
|
|
42165
|
-
|
|
42166
|
-
return row?.status === "detached";
|
|
42167
|
-
}
|
|
42168
|
-
function enqueueSharedMessage(args) {
|
|
42169
|
-
db.run(`INSERT OR REPLACE INTO otium_shared_message_outbox
|
|
42170
|
-
(local_topic_id, source_message_id, message_json, created_at)
|
|
42171
|
-
VALUES (?, ?, ?, ?)`, [
|
|
42172
|
-
args.localTopicId,
|
|
42173
|
-
args.sourceMessageId,
|
|
42174
|
-
JSON.stringify(args.message),
|
|
42175
|
-
new Date().toISOString()
|
|
42176
|
-
]);
|
|
42177
|
-
}
|
|
42178
|
-
function listSharedMessages(localTopicId) {
|
|
42179
|
-
return db.query("SELECT * FROM otium_shared_message_outbox WHERE local_topic_id = ? ORDER BY created_at").all(localTopicId);
|
|
42180
|
-
}
|
|
42181
|
-
function deleteSharedMessage(localTopicId, sourceMessageId) {
|
|
42182
|
-
return db.run("DELETE FROM otium_shared_message_outbox WHERE local_topic_id = ? AND source_message_id = ?", [localTopicId, sourceMessageId]).changes === 1;
|
|
42183
|
-
}
|
|
42184
|
-
function getSharedTopicState(localTopicId) {
|
|
42185
|
-
return db.query("SELECT * FROM otium_shared_topic_state WHERE local_topic_id = ?").get(localTopicId) ?? null;
|
|
42186
|
-
}
|
|
42187
|
-
function listSharedTopicStates() {
|
|
42188
|
-
return db.query("SELECT * FROM otium_shared_topic_state ORDER BY updated_at").all();
|
|
42189
|
-
}
|
|
42190
|
-
function setSharedTopicState(args) {
|
|
42191
|
-
db.run(`INSERT INTO otium_shared_topic_state (local_topic_id, host_topic_id, status, updated_at)
|
|
42192
|
-
VALUES (?, ?, ?, ?)
|
|
42193
|
-
ON CONFLICT(local_topic_id) DO UPDATE SET
|
|
42194
|
-
host_topic_id = excluded.host_topic_id,
|
|
42195
|
-
status = excluded.status,
|
|
42196
|
-
updated_at = excluded.updated_at`, [args.localTopicId, args.hostTopicId ?? null, args.status, new Date().toISOString()]);
|
|
42197
|
-
}
|
|
42198
|
-
function deleteSharedTopicState(localTopicId) {
|
|
42199
|
-
return db.run("DELETE FROM otium_shared_topic_state WHERE local_topic_id = ?", [localTopicId]).changes === 1;
|
|
42200
|
-
}
|
|
42201
|
-
function getPeerSession(hostNodeId, hostTopicId) {
|
|
42202
|
-
return db.query("SELECT * FROM otium_peer_sessions WHERE host_node_id = ? AND host_topic_id = ?").get(hostNodeId, hostTopicId) ?? null;
|
|
42203
|
-
}
|
|
42204
|
-
function createPeerSession(hostNodeId, hostTopicId, localTopicId) {
|
|
42205
|
-
const row = {
|
|
42206
|
-
host_node_id: hostNodeId,
|
|
42207
|
-
host_topic_id: hostTopicId,
|
|
42208
|
-
local_topic_id: localTopicId,
|
|
42209
|
-
binding_mode: "mirror",
|
|
42210
|
-
created_at: new Date().toISOString()
|
|
42211
|
-
};
|
|
42212
|
-
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]);
|
|
42213
|
-
return row;
|
|
42214
|
-
}
|
|
42215
|
-
function bindPeerSession(hostNodeId, hostTopicId, localTopicId, mode = "shared") {
|
|
42216
|
-
const row = {
|
|
42217
|
-
host_node_id: hostNodeId,
|
|
42218
|
-
host_topic_id: hostTopicId,
|
|
42219
|
-
local_topic_id: localTopicId,
|
|
42220
|
-
binding_mode: mode,
|
|
42221
|
-
created_at: new Date().toISOString()
|
|
42222
|
-
};
|
|
42223
|
-
db.run(`INSERT INTO otium_peer_sessions
|
|
42224
|
-
(host_node_id, host_topic_id, local_topic_id, binding_mode, created_at)
|
|
42225
|
-
VALUES (?, ?, ?, ?, ?)
|
|
42226
|
-
ON CONFLICT(host_node_id, host_topic_id) DO UPDATE SET
|
|
42227
|
-
local_topic_id = excluded.local_topic_id,
|
|
42228
|
-
binding_mode = excluded.binding_mode,
|
|
42229
|
-
created_at = excluded.created_at`, [hostNodeId, hostTopicId, localTopicId, mode, row.created_at]);
|
|
42230
|
-
return row;
|
|
42231
|
-
}
|
|
42232
|
-
function unbindPeerSession(hostNodeId, hostTopicId) {
|
|
42233
|
-
return db.run("DELETE FROM otium_peer_sessions WHERE host_node_id = ? AND host_topic_id = ?", [
|
|
42234
|
-
hostNodeId,
|
|
42235
|
-
hostTopicId
|
|
42236
|
-
]).changes === 1;
|
|
42237
|
-
}
|
|
42238
|
-
function unbindSharedPeerSessionsForLocalTopic(localTopicId) {
|
|
42239
|
-
return db.run("DELETE FROM otium_peer_sessions WHERE local_topic_id = ? AND binding_mode = 'shared'", [localTopicId]).changes;
|
|
42240
|
-
}
|
|
42241
|
-
function downgradeSharedTopicsLocally(hubNodeId) {
|
|
42242
|
-
return db.transaction(() => {
|
|
42243
|
-
const topics = db.query("SELECT id FROM api_topics WHERE access_mode = 'shared'").all().map((row) => row.id);
|
|
42244
|
-
db.run("UPDATE api_topics SET access_mode = 'private' WHERE access_mode = 'shared'");
|
|
42245
|
-
db.run("DELETE FROM otium_peer_sessions WHERE host_node_id = ?", [hubNodeId]);
|
|
42246
|
-
db.run("DELETE FROM otium_shared_message_outbox");
|
|
42247
|
-
db.run("DELETE FROM otium_shared_topic_state");
|
|
42248
|
-
db.run(`INSERT INTO otium_peer_lifecycle (hub_node_id, status, updated_at)
|
|
42249
|
-
VALUES (?, 'detached', ?)
|
|
42250
|
-
ON CONFLICT(hub_node_id) DO UPDATE SET status = 'detached', updated_at = excluded.updated_at`, [hubNodeId, new Date().toISOString()]);
|
|
42251
|
-
return topics;
|
|
42252
|
-
})();
|
|
42253
|
-
}
|
|
42254
|
-
function listPeerSessions() {
|
|
42255
|
-
return db.query("SELECT * FROM otium_peer_sessions").all();
|
|
42256
|
-
}
|
|
42257
|
-
function cleanupPeerStateForLocalTopic(localTopicId) {
|
|
42258
|
-
return db.transaction(() => {
|
|
42259
|
-
const terminalOutbox = db.run(`DELETE FROM otium_peer_terminal_outbox
|
|
42260
|
-
WHERE EXISTS (
|
|
42261
|
-
SELECT 1 FROM otium_peer_turn_requests turn_request
|
|
42262
|
-
JOIN otium_peer_sessions session
|
|
42263
|
-
ON session.host_node_id = turn_request.host_node_id
|
|
42264
|
-
AND session.host_topic_id = turn_request.host_topic_id
|
|
42265
|
-
WHERE session.local_topic_id = ?
|
|
42266
|
-
AND turn_request.host_node_id = otium_peer_terminal_outbox.host_node_id
|
|
42267
|
-
AND turn_request.request_id = otium_peer_terminal_outbox.request_id
|
|
42268
|
-
)`, [localTopicId]).changes;
|
|
42269
|
-
const turns = db.run(`DELETE FROM otium_peer_turn_requests
|
|
42270
|
-
WHERE EXISTS (
|
|
42271
|
-
SELECT 1 FROM otium_peer_sessions session
|
|
42272
|
-
WHERE session.local_topic_id = ?
|
|
42273
|
-
AND session.host_node_id = otium_peer_turn_requests.host_node_id
|
|
42274
|
-
AND session.host_topic_id = otium_peer_turn_requests.host_topic_id
|
|
42275
|
-
)`, [localTopicId]).changes;
|
|
42276
|
-
const inboxRequests = db.run("DELETE FROM otium_peer_inbox_requests WHERE topic_id = ?", [
|
|
42277
|
-
localTopicId
|
|
42278
|
-
]).changes;
|
|
42279
|
-
const remoteAsks = db.run("DELETE FROM otium_remote_asks WHERE caller_topic_id = ?", [
|
|
42280
|
-
localTopicId
|
|
42281
|
-
]).changes;
|
|
42282
|
-
const sessions = db.run("DELETE FROM otium_peer_sessions WHERE local_topic_id = ?", [
|
|
42283
|
-
localTopicId
|
|
42284
|
-
]).changes;
|
|
42285
|
-
return { sessions, turns, terminalOutbox, inboxRequests, remoteAsks };
|
|
42286
|
-
})();
|
|
42287
|
-
}
|
|
42288
|
-
function sweepStalePeerBindings(topicExists) {
|
|
42289
|
-
const stale = [
|
|
42290
|
-
...new Set(listPeerSessions().map((row) => row.local_topic_id).filter((localTopicId) => localTopicId && !topicExists(localTopicId)))
|
|
42291
|
-
];
|
|
42292
|
-
const removed = {
|
|
42293
|
-
sessions: 0,
|
|
42294
|
-
turns: 0,
|
|
42295
|
-
terminalOutbox: 0,
|
|
42296
|
-
inboxRequests: 0,
|
|
42297
|
-
remoteAsks: 0
|
|
42298
|
-
};
|
|
42299
|
-
for (const localTopicId of stale) {
|
|
42300
|
-
const result = cleanupPeerStateForLocalTopic(localTopicId);
|
|
42301
|
-
removed.sessions += result.sessions;
|
|
42302
|
-
removed.turns += result.turns;
|
|
42303
|
-
removed.terminalOutbox += result.terminalOutbox;
|
|
42304
|
-
removed.inboxRequests += result.inboxRequests;
|
|
42305
|
-
removed.remoteAsks += result.remoteAsks;
|
|
42306
|
-
}
|
|
42307
|
-
return { topicIds: stale, removed };
|
|
42308
|
-
}
|
|
42309
|
-
function failInterruptedPeerTurnRequestsOnStartup() {
|
|
42310
|
-
return db.run(`UPDATE otium_peer_turn_requests
|
|
42311
|
-
SET status = 'failed', error = 'worker restarted during turn', updated_at = ?
|
|
42312
|
-
WHERE status IN ('claimed', 'running')
|
|
42313
|
-
AND NOT EXISTS (
|
|
42314
|
-
SELECT 1 FROM otium_peer_terminal_outbox terminal
|
|
42315
|
-
WHERE terminal.host_node_id = otium_peer_turn_requests.host_node_id
|
|
42316
|
-
AND terminal.request_id = otium_peer_turn_requests.request_id
|
|
42317
|
-
)`, [new Date().toISOString()]).changes;
|
|
42318
|
-
}
|
|
42319
|
-
function claimPeerTurnRequest(hostNodeId, requestId, hostTopicId) {
|
|
42320
|
-
const now = new Date().toISOString();
|
|
42321
|
-
const inserted = db.run(`INSERT OR IGNORE INTO otium_peer_turn_requests
|
|
42322
|
-
(host_node_id, request_id, host_topic_id, status, created_at, updated_at)
|
|
42323
|
-
VALUES (?, ?, ?, 'claimed', ?, ?)`, [hostNodeId, requestId, hostTopicId, now, now]);
|
|
42324
|
-
const row = getPeerTurnRequest(hostNodeId, requestId);
|
|
42325
|
-
if (!row)
|
|
42326
|
-
throw new Error("otium peer turn request claim disappeared");
|
|
42327
|
-
return { claimed: inserted.changes === 1, row };
|
|
42328
|
-
}
|
|
42329
|
-
function getPeerTurnRequest(hostNodeId, requestId) {
|
|
42330
|
-
return db.query("SELECT * FROM otium_peer_turn_requests WHERE host_node_id = ? AND request_id = ?").get(hostNodeId, requestId) ?? null;
|
|
42331
|
-
}
|
|
42332
|
-
function setPeerTurnRequestStatus(hostNodeId, requestId, status, error2 = null) {
|
|
42333
|
-
db.run(`UPDATE otium_peer_turn_requests
|
|
42334
|
-
SET status = ?, error = ?, updated_at = ?
|
|
42335
|
-
WHERE host_node_id = ? AND request_id = ?`, [status, error2, new Date().toISOString(), hostNodeId, requestId]);
|
|
42336
|
-
}
|
|
42337
|
-
function markPeerTurnRequestRunning(hostNodeId, requestId) {
|
|
42338
|
-
setPeerTurnRequestStatus(hostNodeId, requestId, "running");
|
|
42339
|
-
}
|
|
42340
|
-
function markPeerTurnRequestFinished(hostNodeId, requestId) {
|
|
42341
|
-
setPeerTurnRequestStatus(hostNodeId, requestId, "finished");
|
|
42342
|
-
}
|
|
42343
|
-
function markPeerTurnRequestFailed(hostNodeId, requestId, error2) {
|
|
42344
|
-
setPeerTurnRequestStatus(hostNodeId, requestId, "failed", error2);
|
|
42345
|
-
}
|
|
42346
|
-
function upsertPeerTerminalOutbox(args) {
|
|
42347
|
-
const now = Date.now();
|
|
42348
|
-
db.run(`INSERT INTO otium_peer_terminal_outbox
|
|
42349
|
-
(host_node_id, request_id, seq, event_json, created_at, updated_at)
|
|
42350
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
42351
|
-
ON CONFLICT(host_node_id, request_id) DO UPDATE SET
|
|
42352
|
-
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]);
|
|
42353
|
-
}
|
|
42354
|
-
function listPeerTerminalOutbox(limit = 100) {
|
|
42355
|
-
return db.query("SELECT * FROM otium_peer_terminal_outbox ORDER BY created_at LIMIT ?").all(limit);
|
|
42356
|
-
}
|
|
42357
|
-
function acknowledgePeerTerminal(hostNodeId, requestId) {
|
|
42358
|
-
return db.transaction(() => {
|
|
42359
|
-
const removed = db.run("DELETE FROM otium_peer_terminal_outbox WHERE host_node_id = ? AND request_id = ?", [hostNodeId, requestId]).changes;
|
|
42360
|
-
if (removed !== 1)
|
|
42361
|
-
return false;
|
|
42362
|
-
markPeerTurnRequestFinished(hostNodeId, requestId);
|
|
41845
|
+
// ../../adapters/otium/src/secure-transport.ts
|
|
41846
|
+
function isLoopbackHostname(hostname2) {
|
|
41847
|
+
const normalized = hostname2.toLowerCase();
|
|
41848
|
+
if (normalized === "localhost" || normalized === "::1" || normalized === "[::1]")
|
|
42363
41849
|
return true;
|
|
42364
|
-
|
|
41850
|
+
const octets = normalized.split(".");
|
|
41851
|
+
return octets.length === 4 && octets[0] === "127" && octets.every((octet) => /^\d{1,3}$/.test(octet) && Number(octet) <= 255);
|
|
42365
41852
|
}
|
|
42366
|
-
function
|
|
42367
|
-
|
|
41853
|
+
function credentialTransportUrl(value, label) {
|
|
41854
|
+
let url;
|
|
41855
|
+
try {
|
|
41856
|
+
url = new URL(value);
|
|
41857
|
+
} catch {
|
|
41858
|
+
throw new Error(`${label} is not a valid URL`);
|
|
41859
|
+
}
|
|
41860
|
+
if (url.username || url.password)
|
|
41861
|
+
throw new Error(`${label} must not contain URL credentials`);
|
|
41862
|
+
return url;
|
|
42368
41863
|
}
|
|
42369
|
-
function
|
|
42370
|
-
const
|
|
42371
|
-
|
|
42372
|
-
|
|
42373
|
-
|
|
42374
|
-
|
|
42375
|
-
|
|
42376
|
-
args.topicId,
|
|
42377
|
-
args.payloadHash,
|
|
42378
|
-
new Date().toISOString()
|
|
42379
|
-
]);
|
|
42380
|
-
if (inserted.changes === 1)
|
|
42381
|
-
return { outcome: "claimed" };
|
|
42382
|
-
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);
|
|
42383
|
-
if (!existing)
|
|
42384
|
-
return { outcome: "conflict" };
|
|
42385
|
-
return { outcome: existing.payload_hash === args.payloadHash ? "replay" : "conflict" };
|
|
41864
|
+
function assertSecureCentralUrl(value) {
|
|
41865
|
+
const url = credentialTransportUrl(value, "Otium central URL");
|
|
41866
|
+
if (url.protocol === "https:")
|
|
41867
|
+
return;
|
|
41868
|
+
if (url.protocol === "http:" && isLoopbackHostname(url.hostname))
|
|
41869
|
+
return;
|
|
41870
|
+
throw new Error("Otium central requires HTTPS or loopback HTTP");
|
|
42386
41871
|
}
|
|
42387
|
-
function
|
|
42388
|
-
|
|
41872
|
+
function assertSecureRelayUrl(value) {
|
|
41873
|
+
const url = credentialTransportUrl(value, "Otium relay URL");
|
|
41874
|
+
if (url.protocol === "https:" || url.protocol === "wss:")
|
|
41875
|
+
return;
|
|
41876
|
+
if ((url.protocol === "http:" || url.protocol === "ws:") && isLoopbackHostname(url.hostname)) {
|
|
41877
|
+
return;
|
|
41878
|
+
}
|
|
41879
|
+
throw new Error("Otium relay requires HTTPS/WSS or loopback HTTP/WS");
|
|
42389
41880
|
}
|
|
42390
|
-
|
|
42391
|
-
|
|
42392
|
-
|
|
42393
|
-
|
|
42394
|
-
|
|
42395
|
-
|
|
42396
|
-
|
|
42397
|
-
|
|
42398
|
-
|
|
42399
|
-
|
|
42400
|
-
|
|
42401
|
-
|
|
42402
|
-
|
|
42403
|
-
|
|
42404
|
-
|
|
41881
|
+
|
|
41882
|
+
// ../../adapters/otium/src/join.ts
|
|
41883
|
+
var exports_join = {};
|
|
41884
|
+
__export(exports_join, {
|
|
41885
|
+
withJoinCredentialLock: () => withJoinCredentialLock,
|
|
41886
|
+
saveJoinWhileLocked: () => saveJoinWhileLocked,
|
|
41887
|
+
saveJoin: () => saveJoin,
|
|
41888
|
+
removeJoin: () => removeJoin,
|
|
41889
|
+
parseInviteCode: () => parseInviteCode,
|
|
41890
|
+
loadJoin: () => loadJoin,
|
|
41891
|
+
joinFilePath: () => joinFilePath,
|
|
41892
|
+
joinCredentialDigest: () => joinCredentialDigest,
|
|
41893
|
+
isJoinPersisted: () => isJoinPersisted
|
|
41894
|
+
});
|
|
41895
|
+
import { createHash as createHash11, randomUUID as randomUUID28 } from "crypto";
|
|
41896
|
+
import {
|
|
41897
|
+
chmodSync as chmodSync7,
|
|
41898
|
+
closeSync as closeSync5,
|
|
41899
|
+
existsSync as existsSync37,
|
|
41900
|
+
fsyncSync as fsyncSync2,
|
|
41901
|
+
linkSync,
|
|
41902
|
+
lstatSync,
|
|
41903
|
+
mkdirSync as mkdirSync32,
|
|
41904
|
+
openSync as openSync5,
|
|
41905
|
+
readFileSync as readFileSync28,
|
|
41906
|
+
renameSync as renameSync16,
|
|
41907
|
+
rmSync as rmSync10,
|
|
41908
|
+
statSync as statSync20,
|
|
41909
|
+
unlinkSync as unlinkSync23,
|
|
41910
|
+
writeFileSync as writeFileSync23
|
|
41911
|
+
} from "fs";
|
|
41912
|
+
import { dirname as dirname21, resolve as resolve24 } from "path";
|
|
41913
|
+
function joinFilePath() {
|
|
41914
|
+
return resolve24(DATA_DIR, "otium-join.json");
|
|
42405
41915
|
}
|
|
42406
|
-
function
|
|
42407
|
-
return
|
|
41916
|
+
function isHttpUrl(value) {
|
|
41917
|
+
return /^https?:\/\//.test(value);
|
|
42408
41918
|
}
|
|
42409
|
-
function
|
|
42410
|
-
return
|
|
41919
|
+
function isRelayUrl(value) {
|
|
41920
|
+
return /^(?:https?|wss?):\/\//.test(value);
|
|
42411
41921
|
}
|
|
42412
|
-
function
|
|
42413
|
-
|
|
41922
|
+
function normalizeJoin(raw) {
|
|
41923
|
+
const central = typeof raw.central === "string" ? raw.central.trim().replace(/\/+$/, "") : "";
|
|
41924
|
+
const relay = typeof raw.relay === "string" ? raw.relay.trim().replace(/\/+$/, "") : "";
|
|
41925
|
+
const cellId = typeof raw.cellId === "string" ? raw.cellId.trim() : "";
|
|
41926
|
+
const secret = typeof raw.secret === "string" ? raw.secret.trim() : "";
|
|
41927
|
+
if (!central || !isHttpUrl(central)) {
|
|
41928
|
+
throw new Error("invite code is missing a valid http(s) central URL");
|
|
41929
|
+
}
|
|
41930
|
+
if (relay && !isRelayUrl(relay))
|
|
41931
|
+
throw new Error("invite code has an invalid relay URL");
|
|
41932
|
+
if (!cellId)
|
|
41933
|
+
throw new Error("invite code is missing cellId");
|
|
41934
|
+
if (!secret)
|
|
41935
|
+
throw new Error("invite code is missing secret");
|
|
41936
|
+
assertSecureCentralUrl(central);
|
|
41937
|
+
if (relay)
|
|
41938
|
+
assertSecureRelayUrl(relay);
|
|
41939
|
+
return {
|
|
41940
|
+
...typeof raw.v === "number" ? { v: raw.v } : {},
|
|
41941
|
+
central,
|
|
41942
|
+
...relay ? { relay } : {},
|
|
41943
|
+
cellId,
|
|
41944
|
+
secret
|
|
41945
|
+
};
|
|
42414
41946
|
}
|
|
42415
|
-
function
|
|
42416
|
-
const
|
|
42417
|
-
|
|
42418
|
-
|
|
42419
|
-
|
|
42420
|
-
|
|
42421
|
-
|
|
42422
|
-
|
|
42423
|
-
|
|
42424
|
-
|
|
42425
|
-
|
|
42426
|
-
|
|
42427
|
-
|
|
42428
|
-
|
|
42429
|
-
|
|
42430
|
-
|
|
42431
|
-
|
|
42432
|
-
|
|
42433
|
-
|
|
42434
|
-
|
|
42435
|
-
args.replyText,
|
|
42436
|
-
args.kind,
|
|
42437
|
-
now,
|
|
42438
|
-
now
|
|
42439
|
-
]);
|
|
41947
|
+
function parseInviteCode(code) {
|
|
41948
|
+
const trimmed = code.trim();
|
|
41949
|
+
if (!trimmed)
|
|
41950
|
+
throw new Error("invite code is empty");
|
|
41951
|
+
let decoded;
|
|
41952
|
+
try {
|
|
41953
|
+
decoded = Buffer.from(trimmed, "base64url").toString("utf-8");
|
|
41954
|
+
} catch {
|
|
41955
|
+
throw new Error("invite code is not valid base64url");
|
|
41956
|
+
}
|
|
41957
|
+
let parsed;
|
|
41958
|
+
try {
|
|
41959
|
+
parsed = JSON.parse(decoded);
|
|
41960
|
+
} catch {
|
|
41961
|
+
throw new Error("invite code does not decode to JSON");
|
|
41962
|
+
}
|
|
41963
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
41964
|
+
throw new Error("invite code does not decode to a JSON object");
|
|
41965
|
+
}
|
|
41966
|
+
return normalizeJoin(parsed);
|
|
42440
41967
|
}
|
|
42441
|
-
function
|
|
42442
|
-
return
|
|
41968
|
+
function joinLockPath() {
|
|
41969
|
+
return resolve24(DATA_DIR, ".otium-join.lock");
|
|
42443
41970
|
}
|
|
42444
|
-
function
|
|
42445
|
-
|
|
42446
|
-
|
|
42447
|
-
|
|
42448
|
-
|
|
41971
|
+
function processIsAlive(pid) {
|
|
41972
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
41973
|
+
return false;
|
|
41974
|
+
try {
|
|
41975
|
+
process.kill(pid, 0);
|
|
41976
|
+
return true;
|
|
41977
|
+
} catch (error2) {
|
|
41978
|
+
return error2.code !== "ESRCH";
|
|
41979
|
+
}
|
|
42449
41980
|
}
|
|
42450
|
-
|
|
42451
|
-
|
|
42452
|
-
|
|
42453
|
-
|
|
42454
|
-
|
|
42455
|
-
|
|
42456
|
-
|
|
42457
|
-
local_topic_id TEXT NOT NULL,
|
|
42458
|
-
binding_mode TEXT NOT NULL DEFAULT 'mirror',
|
|
42459
|
-
created_at TEXT NOT NULL,
|
|
42460
|
-
PRIMARY KEY (host_node_id, host_topic_id)
|
|
42461
|
-
)
|
|
42462
|
-
`);
|
|
42463
|
-
peerSessionColumns = new Set(db.query("PRAGMA table_info(otium_peer_sessions)").all().map((row) => row.name));
|
|
42464
|
-
if (!peerSessionColumns.has("binding_mode")) {
|
|
42465
|
-
db.exec("ALTER TABLE otium_peer_sessions ADD COLUMN binding_mode TEXT NOT NULL DEFAULT 'mirror'");
|
|
42466
|
-
}
|
|
42467
|
-
db.run(`UPDATE api_topics
|
|
42468
|
-
SET visibility = 'visible', access_mode = 'shared', is_subagent = 0
|
|
42469
|
-
WHERE id IN (
|
|
42470
|
-
SELECT local_topic_id FROM otium_peer_sessions WHERE binding_mode = 'mirror'
|
|
42471
|
-
)`);
|
|
42472
|
-
db.run(`UPDATE api_topics
|
|
42473
|
-
SET access_mode = 'shared'
|
|
42474
|
-
WHERE id IN (SELECT local_topic_id FROM otium_peer_sessions)`);
|
|
42475
|
-
db.exec(`
|
|
42476
|
-
CREATE TABLE IF NOT EXISTS otium_peer_turn_requests (
|
|
42477
|
-
host_node_id TEXT NOT NULL,
|
|
42478
|
-
request_id TEXT NOT NULL,
|
|
42479
|
-
host_topic_id TEXT NOT NULL,
|
|
42480
|
-
status TEXT NOT NULL CHECK (status IN
|
|
42481
|
-
('claimed', 'running', 'finished', 'failed')),
|
|
42482
|
-
error TEXT,
|
|
42483
|
-
created_at TEXT NOT NULL,
|
|
42484
|
-
updated_at TEXT NOT NULL,
|
|
42485
|
-
PRIMARY KEY (host_node_id, request_id)
|
|
42486
|
-
)
|
|
42487
|
-
`);
|
|
42488
|
-
db.exec(`
|
|
42489
|
-
CREATE TABLE IF NOT EXISTS otium_peer_terminal_outbox (
|
|
42490
|
-
host_node_id TEXT NOT NULL,
|
|
42491
|
-
request_id TEXT NOT NULL,
|
|
42492
|
-
seq INTEGER NOT NULL,
|
|
42493
|
-
event_json TEXT NOT NULL,
|
|
42494
|
-
created_at INTEGER NOT NULL,
|
|
42495
|
-
updated_at INTEGER NOT NULL,
|
|
42496
|
-
PRIMARY KEY (host_node_id, request_id)
|
|
42497
|
-
)
|
|
42498
|
-
`);
|
|
42499
|
-
db.exec(`
|
|
42500
|
-
CREATE TABLE IF NOT EXISTS otium_peer_inbox_requests (
|
|
42501
|
-
from_cell_id TEXT NOT NULL,
|
|
42502
|
-
request_id TEXT NOT NULL,
|
|
42503
|
-
kind TEXT NOT NULL CHECK (kind IN ('tell', 'ask')),
|
|
42504
|
-
topic_id TEXT NOT NULL,
|
|
42505
|
-
payload_hash TEXT NOT NULL,
|
|
42506
|
-
created_at TEXT NOT NULL,
|
|
42507
|
-
PRIMARY KEY (from_cell_id, request_id, kind)
|
|
42508
|
-
)
|
|
42509
|
-
`);
|
|
42510
|
-
db.exec(`
|
|
42511
|
-
CREATE TABLE IF NOT EXISTS otium_remote_asks (
|
|
42512
|
-
request_id TEXT PRIMARY KEY,
|
|
42513
|
-
expected_cell_id TEXT NOT NULL,
|
|
42514
|
-
user_id TEXT NOT NULL,
|
|
42515
|
-
caller_topic_id TEXT NOT NULL,
|
|
42516
|
-
from_key TEXT NOT NULL,
|
|
42517
|
-
to_key TEXT NOT NULL,
|
|
42518
|
-
source_query_id TEXT,
|
|
42519
|
-
created_at INTEGER NOT NULL
|
|
42520
|
-
)
|
|
42521
|
-
`);
|
|
42522
|
-
db.exec(`
|
|
42523
|
-
CREATE INDEX IF NOT EXISTS idx_otium_remote_asks_created
|
|
42524
|
-
ON otium_remote_asks(created_at)
|
|
42525
|
-
`);
|
|
42526
|
-
db.exec(`
|
|
42527
|
-
CREATE TABLE IF NOT EXISTS otium_peer_reply_outbox (
|
|
42528
|
-
node_cell_id TEXT NOT NULL,
|
|
42529
|
-
request_id TEXT NOT NULL,
|
|
42530
|
-
node_name TEXT NOT NULL,
|
|
42531
|
-
topic_id TEXT NOT NULL,
|
|
42532
|
-
user_id TEXT NOT NULL,
|
|
42533
|
-
source_title TEXT NOT NULL,
|
|
42534
|
-
reply_text TEXT NOT NULL,
|
|
42535
|
-
kind TEXT NOT NULL CHECK (kind IN ('reply', 'error')),
|
|
42536
|
-
created_at INTEGER NOT NULL,
|
|
42537
|
-
updated_at INTEGER NOT NULL,
|
|
42538
|
-
PRIMARY KEY (node_cell_id, request_id)
|
|
42539
|
-
)
|
|
42540
|
-
`);
|
|
42541
|
-
db.exec(`
|
|
42542
|
-
CREATE TABLE IF NOT EXISTS otium_shared_topic_state (
|
|
42543
|
-
local_topic_id TEXT PRIMARY KEY,
|
|
42544
|
-
host_topic_id TEXT,
|
|
42545
|
-
status TEXT NOT NULL CHECK (status IN ('publishing', 'published', 'unpublishing')),
|
|
42546
|
-
updated_at TEXT NOT NULL
|
|
42547
|
-
)
|
|
42548
|
-
`);
|
|
42549
|
-
db.exec(`
|
|
42550
|
-
CREATE TABLE IF NOT EXISTS otium_shared_message_outbox (
|
|
42551
|
-
local_topic_id TEXT NOT NULL,
|
|
42552
|
-
source_message_id TEXT NOT NULL,
|
|
42553
|
-
message_json TEXT NOT NULL,
|
|
42554
|
-
created_at TEXT NOT NULL,
|
|
42555
|
-
PRIMARY KEY (local_topic_id, source_message_id)
|
|
42556
|
-
)
|
|
42557
|
-
`);
|
|
42558
|
-
db.exec(`
|
|
42559
|
-
CREATE TABLE IF NOT EXISTS otium_peer_lifecycle (
|
|
42560
|
-
hub_node_id TEXT PRIMARY KEY,
|
|
42561
|
-
status TEXT NOT NULL CHECK (status IN ('attached', 'detached')),
|
|
42562
|
-
updated_at TEXT NOT NULL
|
|
42563
|
-
)
|
|
42564
|
-
`);
|
|
42565
|
-
});
|
|
42566
|
-
|
|
42567
|
-
// ../../adapters/otium/src/event-backflow.ts
|
|
42568
|
-
function hubEventSender(hubNode) {
|
|
42569
|
-
return async (payload) => {
|
|
42570
|
-
let token;
|
|
41981
|
+
function withJoinCredentialLock(operation) {
|
|
41982
|
+
const lockPath = joinLockPath();
|
|
41983
|
+
const ownerPath = resolve24(lockPath, "owner.json");
|
|
41984
|
+
const owner = { pid: process.pid, token: randomUUID28() };
|
|
41985
|
+
mkdirSync32(dirname21(lockPath), { recursive: true });
|
|
41986
|
+
for (let attempt = 0;; attempt += 1) {
|
|
41987
|
+
let created = false;
|
|
42571
41988
|
try {
|
|
42572
|
-
|
|
42573
|
-
|
|
42574
|
-
|
|
41989
|
+
mkdirSync32(lockPath, { mode: 448 });
|
|
41990
|
+
created = true;
|
|
41991
|
+
writeFileSync23(ownerPath, `${JSON.stringify(owner)}
|
|
41992
|
+
`, { mode: 384 });
|
|
41993
|
+
const ownerFd = openSync5(ownerPath, "r");
|
|
41994
|
+
try {
|
|
41995
|
+
fsyncSync2(ownerFd);
|
|
41996
|
+
} finally {
|
|
41997
|
+
closeSync5(ownerFd);
|
|
41998
|
+
}
|
|
41999
|
+
break;
|
|
42000
|
+
} catch (error2) {
|
|
42001
|
+
if (created) {
|
|
42002
|
+
rmSync10(lockPath, { recursive: true, force: true });
|
|
42003
|
+
throw error2;
|
|
42004
|
+
}
|
|
42005
|
+
if (error2.code !== "EEXIST")
|
|
42006
|
+
throw error2;
|
|
42007
|
+
let current3 = null;
|
|
42008
|
+
try {
|
|
42009
|
+
current3 = JSON.parse(readFileSync28(ownerPath, "utf8"));
|
|
42010
|
+
} catch {}
|
|
42011
|
+
let ageMs;
|
|
42012
|
+
try {
|
|
42013
|
+
ageMs = Date.now() - statSync20(lockPath).mtimeMs;
|
|
42014
|
+
} catch (statError) {
|
|
42015
|
+
if (statError.code === "ENOENT")
|
|
42016
|
+
continue;
|
|
42017
|
+
throw statError;
|
|
42018
|
+
}
|
|
42019
|
+
if (current3 && processIsAlive(current3.pid) || !current3 && ageMs <= JOIN_LOCK_STALE_MS) {
|
|
42020
|
+
throw new Error(`another Otium join credential operation is in progress at ${lockPath}`);
|
|
42021
|
+
}
|
|
42022
|
+
if (attempt > 0) {
|
|
42023
|
+
throw new Error(`could not recover stale Otium join credential lock at ${lockPath}`);
|
|
42024
|
+
}
|
|
42025
|
+
const stalePath = `${lockPath}.stale.${process.pid}.${randomUUID28()}`;
|
|
42026
|
+
try {
|
|
42027
|
+
renameSync16(lockPath, stalePath);
|
|
42028
|
+
rmSync10(stalePath, { recursive: true, force: true });
|
|
42029
|
+
} catch (staleError) {
|
|
42030
|
+
if (staleError.code !== "ENOENT")
|
|
42031
|
+
throw staleError;
|
|
42032
|
+
}
|
|
42575
42033
|
}
|
|
42576
|
-
|
|
42034
|
+
}
|
|
42035
|
+
try {
|
|
42036
|
+
return operation();
|
|
42037
|
+
} finally {
|
|
42577
42038
|
try {
|
|
42578
|
-
|
|
42579
|
-
|
|
42580
|
-
|
|
42581
|
-
|
|
42582
|
-
|
|
42583
|
-
},
|
|
42584
|
-
body: JSON.stringify(payload),
|
|
42585
|
-
signal: AbortSignal.timeout(PEER_EVENT_TIMEOUT_MS)
|
|
42586
|
-
});
|
|
42587
|
-
} catch {
|
|
42588
|
-
return { ok: false, error: `hub "${hubNode.nodeName ?? hubNode.cellId}" unreachable` };
|
|
42589
|
-
}
|
|
42590
|
-
const parsed = await response.json().catch(() => null);
|
|
42591
|
-
if (!response.ok || !parsed?.ok) {
|
|
42592
|
-
return {
|
|
42593
|
-
ok: false,
|
|
42594
|
-
error: parsed?.error ?? `peer event rejected (${response.status})`,
|
|
42595
|
-
status: response.status
|
|
42596
|
-
};
|
|
42597
|
-
}
|
|
42598
|
-
return { ok: true };
|
|
42599
|
-
};
|
|
42600
|
-
}
|
|
42601
|
-
function defined(record) {
|
|
42602
|
-
const out = {};
|
|
42603
|
-
for (const [key, value] of Object.entries(record)) {
|
|
42604
|
-
if (value !== undefined)
|
|
42605
|
-
out[key] = value;
|
|
42039
|
+
const current3 = JSON.parse(readFileSync28(ownerPath, "utf8"));
|
|
42040
|
+
if (current3.pid === owner.pid && current3.token === owner.token) {
|
|
42041
|
+
rmSync10(lockPath, { recursive: true, force: true });
|
|
42042
|
+
}
|
|
42043
|
+
} catch {}
|
|
42606
42044
|
}
|
|
42607
|
-
return out;
|
|
42608
42045
|
}
|
|
42609
|
-
function
|
|
42610
|
-
|
|
42611
|
-
|
|
42612
|
-
|
|
42613
|
-
|
|
42614
|
-
|
|
42615
|
-
|
|
42616
|
-
|
|
42617
|
-
|
|
42618
|
-
|
|
42619
|
-
|
|
42620
|
-
|
|
42621
|
-
|
|
42622
|
-
|
|
42623
|
-
|
|
42624
|
-
|
|
42625
|
-
|
|
42626
|
-
if (event.type !== "ai-status")
|
|
42046
|
+
function joinsEqual(left, right) {
|
|
42047
|
+
return left.central === right.central && left.relay === right.relay && left.cellId === right.cellId && left.secret === right.secret;
|
|
42048
|
+
}
|
|
42049
|
+
function normalizedJoin(join42) {
|
|
42050
|
+
return normalizeJoin({
|
|
42051
|
+
v: join42.v,
|
|
42052
|
+
central: join42.central,
|
|
42053
|
+
relay: join42.relay,
|
|
42054
|
+
cellId: join42.cellId,
|
|
42055
|
+
secret: join42.secret
|
|
42056
|
+
});
|
|
42057
|
+
}
|
|
42058
|
+
function joinCredentialDigest(join42) {
|
|
42059
|
+
return createHash11("sha256").update(JSON.stringify(normalizedJoin(join42))).digest("base64url");
|
|
42060
|
+
}
|
|
42061
|
+
function readPersistedJoin(path = joinFilePath()) {
|
|
42062
|
+
if (!existsSync37(path))
|
|
42627
42063
|
return null;
|
|
42628
|
-
const
|
|
42629
|
-
|
|
42630
|
-
|
|
42631
|
-
case "typing":
|
|
42632
|
-
return { type: "typing", topicId, userId: status.userId ?? "" };
|
|
42633
|
-
case "tool_call":
|
|
42634
|
-
return defined({
|
|
42635
|
-
type: "tool_call",
|
|
42636
|
-
topicId,
|
|
42637
|
-
queryId: status.queryId,
|
|
42638
|
-
name: status.name,
|
|
42639
|
-
input: status.input,
|
|
42640
|
-
label: status.label,
|
|
42641
|
-
toolUseId: status.toolUseId
|
|
42642
|
-
});
|
|
42643
|
-
case "tool_output":
|
|
42644
|
-
return defined({
|
|
42645
|
-
type: "tool_output",
|
|
42646
|
-
topicId,
|
|
42647
|
-
queryId: status.queryId,
|
|
42648
|
-
toolUseId: status.toolUseId,
|
|
42649
|
-
content: status.content,
|
|
42650
|
-
isError: status.isError
|
|
42651
|
-
});
|
|
42652
|
-
case "tool_status":
|
|
42653
|
-
return defined({
|
|
42654
|
-
type: "tool_status",
|
|
42655
|
-
topicId,
|
|
42656
|
-
queryId: status.queryId,
|
|
42657
|
-
kind: status.statusKind,
|
|
42658
|
-
content: status.content,
|
|
42659
|
-
toolName: status.toolName,
|
|
42660
|
-
elapsed: status.elapsed
|
|
42661
|
-
});
|
|
42662
|
-
case "file_ready":
|
|
42663
|
-
return defined({
|
|
42664
|
-
type: "file_ready",
|
|
42665
|
-
topicId,
|
|
42666
|
-
queryId: status.queryId,
|
|
42667
|
-
path: status.path,
|
|
42668
|
-
source: status.source
|
|
42669
|
-
});
|
|
42670
|
-
case "visual":
|
|
42671
|
-
return defined({
|
|
42672
|
-
type: "visual",
|
|
42673
|
-
topicId,
|
|
42674
|
-
queryId: status.queryId,
|
|
42675
|
-
url: status.url,
|
|
42676
|
-
id: status.id,
|
|
42677
|
-
title: status.title,
|
|
42678
|
-
kind: status.visualKind
|
|
42679
|
-
});
|
|
42680
|
-
case "ai_done":
|
|
42681
|
-
return defined({
|
|
42682
|
-
type: "ai_done",
|
|
42683
|
-
topicId,
|
|
42684
|
-
queryId: status.queryId,
|
|
42685
|
-
usage: status.usage,
|
|
42686
|
-
agent: status.agent,
|
|
42687
|
-
model: status.model
|
|
42688
|
-
});
|
|
42689
|
-
case "ai_error":
|
|
42690
|
-
return defined({
|
|
42691
|
-
type: "ai_error",
|
|
42692
|
-
topicId,
|
|
42693
|
-
queryId: status.queryId,
|
|
42694
|
-
error: status.error
|
|
42695
|
-
});
|
|
42696
|
-
case "ai_aborted":
|
|
42697
|
-
return defined({
|
|
42698
|
-
type: "ai_aborted",
|
|
42699
|
-
topicId,
|
|
42700
|
-
queryId: status.queryId,
|
|
42701
|
-
reason: status.reason
|
|
42702
|
-
});
|
|
42703
|
-
default:
|
|
42704
|
-
return null;
|
|
42064
|
+
const parsed = JSON.parse(readFileSync28(path, "utf-8"));
|
|
42065
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
42066
|
+
throw new Error("persisted join credentials are not a JSON object");
|
|
42705
42067
|
}
|
|
42068
|
+
return normalizeJoin(parsed);
|
|
42706
42069
|
}
|
|
42707
|
-
function
|
|
42708
|
-
|
|
42070
|
+
function isJoinPersisted(join42) {
|
|
42071
|
+
try {
|
|
42072
|
+
const persisted = readPersistedJoin();
|
|
42073
|
+
return persisted !== null && joinsEqual(persisted, normalizedJoin(join42));
|
|
42074
|
+
} catch {
|
|
42075
|
+
return false;
|
|
42076
|
+
}
|
|
42709
42077
|
}
|
|
42710
|
-
function
|
|
42711
|
-
const
|
|
42712
|
-
const
|
|
42713
|
-
const
|
|
42714
|
-
|
|
42715
|
-
|
|
42716
|
-
|
|
42717
|
-
|
|
42718
|
-
|
|
42719
|
-
|
|
42720
|
-
|
|
42721
|
-
|
|
42722
|
-
|
|
42723
|
-
|
|
42724
|
-
|
|
42725
|
-
|
|
42726
|
-
|
|
42727
|
-
|
|
42728
|
-
|
|
42729
|
-
|
|
42730
|
-
}
|
|
42731
|
-
};
|
|
42732
|
-
const post = (event) => {
|
|
42733
|
-
const isTerminal = TERMINAL_TYPES.has(String(event.type ?? ""));
|
|
42734
|
-
if (isTerminal)
|
|
42735
|
-
terminalQueued = true;
|
|
42736
|
-
forwarder.seq += 1;
|
|
42737
|
-
const seq = forwarder.seq;
|
|
42738
|
-
forwarder.pendingEvents += 1;
|
|
42739
|
-
forwarder.chain = forwarder.chain.then(async () => {
|
|
42078
|
+
function saveJoinWhileLocked(join42, options = {}) {
|
|
42079
|
+
const path = joinFilePath();
|
|
42080
|
+
const directory = dirname21(path);
|
|
42081
|
+
const normalized = normalizedJoin(join42);
|
|
42082
|
+
mkdirSync32(directory, { recursive: true });
|
|
42083
|
+
if (existsSync37(path)) {
|
|
42084
|
+
if (lstatSync(path).isSymbolicLink()) {
|
|
42085
|
+
throw new Error(`refusing to replace symlinked Otium join file at ${path}`);
|
|
42086
|
+
}
|
|
42087
|
+
let existing = null;
|
|
42088
|
+
try {
|
|
42089
|
+
existing = readPersistedJoin(path);
|
|
42090
|
+
} catch (error2) {
|
|
42091
|
+
if (!options.replaceExisting) {
|
|
42092
|
+
throw new Error(`existing Otium join file at ${path} is invalid; pass --replace to replace it`, { cause: error2 });
|
|
42093
|
+
}
|
|
42094
|
+
}
|
|
42095
|
+
if (existing && joinsEqual(existing, normalized)) {
|
|
42096
|
+
chmodSync7(path, 384);
|
|
42097
|
+
const fileFd = openSync5(path, "r");
|
|
42740
42098
|
try {
|
|
42741
|
-
|
|
42742
|
-
return;
|
|
42743
|
-
if (isTerminal) {
|
|
42744
|
-
upsertPeerTerminalOutbox({ hostNodeId, requestId, seq, event });
|
|
42745
|
-
}
|
|
42746
|
-
for (let attempt = 1;attempt <= PEER_EVENT_MAX_ATTEMPTS; attempt++) {
|
|
42747
|
-
const result = await sendEvent({ v: PEER_PROTOCOL_VERSION, requestId, seq, event });
|
|
42748
|
-
if (result.ok) {
|
|
42749
|
-
if (isTerminal)
|
|
42750
|
-
acknowledgePeerTerminal(hostNodeId, requestId);
|
|
42751
|
-
return;
|
|
42752
|
-
}
|
|
42753
|
-
logger.warn({ requestId, seq, type: event.type, attempt, error: result.error }, "otium: peer event delivery to hub failed");
|
|
42754
|
-
if (attempt < PEER_EVENT_MAX_ATTEMPTS) {
|
|
42755
|
-
const delayMs = retryBaseMs * 2 ** (attempt - 1);
|
|
42756
|
-
await new Promise((resolve25) => setTimeout(resolve25, delayMs));
|
|
42757
|
-
}
|
|
42758
|
-
}
|
|
42759
|
-
forwarder.deliveryBlocked = true;
|
|
42760
|
-
logger.error({ requestId, seq, type: event.type }, "otium: peer event delivery exhausted");
|
|
42099
|
+
fsyncSync2(fileFd);
|
|
42761
42100
|
} finally {
|
|
42762
|
-
|
|
42763
|
-
}
|
|
42764
|
-
});
|
|
42765
|
-
};
|
|
42766
|
-
forwarder.tap = (raw) => {
|
|
42767
|
-
if (forwarder.finished)
|
|
42768
|
-
return;
|
|
42769
|
-
const type = String(raw.type ?? "");
|
|
42770
|
-
if (!FORWARDED_TYPES.has(type))
|
|
42771
|
-
return;
|
|
42772
|
-
if (type === "message") {
|
|
42773
|
-
const message = raw.message;
|
|
42774
|
-
if (message?.authorId === "ai" && forwarder.queryId && message.queryId !== forwarder.queryId) {
|
|
42775
|
-
return;
|
|
42101
|
+
closeSync5(fileFd);
|
|
42776
42102
|
}
|
|
42777
|
-
|
|
42778
|
-
|
|
42779
|
-
|
|
42780
|
-
|
|
42103
|
+
const directoryFd = openSync5(directory, "r");
|
|
42104
|
+
try {
|
|
42105
|
+
fsyncSync2(directoryFd);
|
|
42106
|
+
} finally {
|
|
42107
|
+
closeSync5(directoryFd);
|
|
42781
42108
|
}
|
|
42109
|
+
return path;
|
|
42782
42110
|
}
|
|
42783
|
-
|
|
42784
|
-
|
|
42785
|
-
if (!terminalQueued) {
|
|
42786
|
-
post({
|
|
42787
|
-
type: "ai_error",
|
|
42788
|
-
topicId: localTopicId,
|
|
42789
|
-
queryId: forwarder.queryId ?? requestId,
|
|
42790
|
-
error: `worker event queue saturated (${maxPendingEvents})`
|
|
42791
|
-
});
|
|
42792
|
-
detach();
|
|
42793
|
-
}
|
|
42794
|
-
return;
|
|
42111
|
+
if (!options.replaceExisting) {
|
|
42112
|
+
throw new Error(`this node is already joined${existing ? ` as ${existing.cellId}` : " with an invalid join file"}; pass --replace to replace its credentials`);
|
|
42795
42113
|
}
|
|
42796
|
-
|
|
42797
|
-
|
|
42798
|
-
|
|
42799
|
-
|
|
42800
|
-
|
|
42801
|
-
|
|
42802
|
-
|
|
42803
|
-
|
|
42804
|
-
|
|
42805
|
-
|
|
42806
|
-
|
|
42114
|
+
}
|
|
42115
|
+
const temporaryPath = resolve24(directory, `.otium-join.json.${process.pid}.${randomUUID28()}.tmp`);
|
|
42116
|
+
let fd;
|
|
42117
|
+
try {
|
|
42118
|
+
fd = openSync5(temporaryPath, "wx", 384);
|
|
42119
|
+
writeFileSync23(fd, `${JSON.stringify(normalized, null, 2)}
|
|
42120
|
+
`, "utf8");
|
|
42121
|
+
fsyncSync2(fd);
|
|
42122
|
+
closeSync5(fd);
|
|
42123
|
+
fd = undefined;
|
|
42124
|
+
if (options.replaceExisting) {
|
|
42125
|
+
renameSync16(temporaryPath, path);
|
|
42126
|
+
} else {
|
|
42127
|
+
linkSync(temporaryPath, path);
|
|
42128
|
+
unlinkSync23(temporaryPath);
|
|
42129
|
+
}
|
|
42130
|
+
chmodSync7(path, 384);
|
|
42131
|
+
const directoryFd = openSync5(directory, "r");
|
|
42132
|
+
try {
|
|
42133
|
+
fsyncSync2(directoryFd);
|
|
42134
|
+
} finally {
|
|
42135
|
+
closeSync5(directoryFd);
|
|
42136
|
+
}
|
|
42137
|
+
} catch (error2) {
|
|
42138
|
+
if (fd !== undefined)
|
|
42139
|
+
closeSync5(fd);
|
|
42140
|
+
if (existsSync37(temporaryPath))
|
|
42141
|
+
unlinkSync23(temporaryPath);
|
|
42142
|
+
throw error2;
|
|
42143
|
+
}
|
|
42144
|
+
return path;
|
|
42807
42145
|
}
|
|
42808
|
-
function
|
|
42809
|
-
|
|
42810
|
-
ensureBackflowSubscription();
|
|
42146
|
+
function saveJoin(join42, options = {}) {
|
|
42147
|
+
return withJoinCredentialLock(() => saveJoinWhileLocked(join42, options));
|
|
42811
42148
|
}
|
|
42812
|
-
|
|
42813
|
-
|
|
42814
|
-
|
|
42815
|
-
|
|
42816
|
-
|
|
42817
|
-
|
|
42818
|
-
|
|
42819
|
-
const node = await resolvePeerNodeByCellId(row.host_node_id).catch(() => null);
|
|
42820
|
-
if (!node)
|
|
42821
|
-
continue;
|
|
42822
|
-
let event;
|
|
42823
|
-
try {
|
|
42824
|
-
event = JSON.parse(row.event_json);
|
|
42825
|
-
} catch {
|
|
42826
|
-
continue;
|
|
42827
|
-
}
|
|
42828
|
-
const result = await hubEventSender(node)({
|
|
42829
|
-
v: PEER_PROTOCOL_VERSION,
|
|
42830
|
-
requestId: row.request_id,
|
|
42831
|
-
seq: row.seq,
|
|
42832
|
-
event
|
|
42833
|
-
});
|
|
42834
|
-
if (result.ok && acknowledgePeerTerminal(row.host_node_id, row.request_id))
|
|
42835
|
-
acknowledged += 1;
|
|
42149
|
+
function removeJoin() {
|
|
42150
|
+
return withJoinCredentialLock(() => {
|
|
42151
|
+
const path = joinFilePath();
|
|
42152
|
+
if (!existsSync37(path))
|
|
42153
|
+
return false;
|
|
42154
|
+
if (lstatSync(path).isSymbolicLink()) {
|
|
42155
|
+
throw new Error(`refusing to remove symlinked Otium join file at ${path}`);
|
|
42836
42156
|
}
|
|
42837
|
-
|
|
42838
|
-
|
|
42839
|
-
|
|
42157
|
+
unlinkSync23(path);
|
|
42158
|
+
const directoryFd = openSync5(dirname21(path), "r");
|
|
42159
|
+
try {
|
|
42160
|
+
fsyncSync2(directoryFd);
|
|
42161
|
+
} finally {
|
|
42162
|
+
closeSync5(directoryFd);
|
|
42163
|
+
}
|
|
42164
|
+
return true;
|
|
42165
|
+
});
|
|
42166
|
+
}
|
|
42167
|
+
function loadJoin() {
|
|
42168
|
+
const central = process.env.OTIUM_CENTRAL_URL?.trim();
|
|
42169
|
+
const cellId = process.env.OTIUM_CELL_ID?.trim();
|
|
42170
|
+
const secret = process.env.OTIUM_CELL_SECRET?.trim();
|
|
42171
|
+
const relay = process.env.OTIUM_RELAY_URL?.trim();
|
|
42172
|
+
if (central && cellId && secret) {
|
|
42173
|
+
try {
|
|
42174
|
+
return normalizeJoin({ central, relay, cellId, secret });
|
|
42175
|
+
} catch (err2) {
|
|
42176
|
+
logger.warn({ err: err2 }, "otium: invalid OTIUM_CENTRAL_URL/OTIUM_CELL_ID/OTIUM_CELL_SECRET env");
|
|
42177
|
+
return null;
|
|
42178
|
+
}
|
|
42179
|
+
}
|
|
42180
|
+
if (central || cellId || secret) {
|
|
42181
|
+
logger.warn("otium: OTIUM_CENTRAL_URL, OTIUM_CELL_ID, OTIUM_CELL_SECRET must be set together \u2014 ignoring partial env");
|
|
42182
|
+
}
|
|
42183
|
+
const path = joinFilePath();
|
|
42184
|
+
if (!existsSync37(path))
|
|
42185
|
+
return null;
|
|
42186
|
+
try {
|
|
42187
|
+
const parsed = JSON.parse(readFileSync28(path, "utf-8"));
|
|
42188
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
42189
|
+
return null;
|
|
42190
|
+
return normalizeJoin(parsed);
|
|
42191
|
+
} catch (err2) {
|
|
42192
|
+
logger.warn({ err: err2, path }, "otium: failed to read join file");
|
|
42193
|
+
return null;
|
|
42840
42194
|
}
|
|
42841
42195
|
}
|
|
42842
|
-
|
|
42843
|
-
|
|
42844
|
-
if (!forwarder)
|
|
42845
|
-
return;
|
|
42846
|
-
const raw = translateBusEvent(event);
|
|
42847
|
-
if (raw)
|
|
42848
|
-
forwarder.tap(raw);
|
|
42849
|
-
}
|
|
42850
|
-
function ensureBackflowSubscription() {
|
|
42851
|
-
if (!unsubscribe)
|
|
42852
|
-
unsubscribe = runtimeBus().subscribe(onBusEvent);
|
|
42853
|
-
}
|
|
42854
|
-
function startEventBackflow() {
|
|
42855
|
-
ensureBackflowSubscription();
|
|
42856
|
-
if (!terminalOutboxTimer) {
|
|
42857
|
-
flushPeerTerminalOutbox();
|
|
42858
|
-
terminalOutboxTimer = setInterval(() => void flushPeerTerminalOutbox(), 5000);
|
|
42859
|
-
terminalOutboxTimer.unref?.();
|
|
42860
|
-
}
|
|
42861
|
-
return stopEventBackflow;
|
|
42862
|
-
}
|
|
42863
|
-
function stopEventBackflow() {
|
|
42864
|
-
unsubscribe?.();
|
|
42865
|
-
unsubscribe = null;
|
|
42866
|
-
if (terminalOutboxTimer)
|
|
42867
|
-
clearInterval(terminalOutboxTimer);
|
|
42868
|
-
terminalOutboxTimer = null;
|
|
42869
|
-
activeForwarders.clear();
|
|
42870
|
-
}
|
|
42871
|
-
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;
|
|
42872
|
-
var init_event_backflow = __esm(async () => {
|
|
42196
|
+
var JOIN_LOCK_STALE_MS = 30000;
|
|
42197
|
+
var init_join = __esm(async () => {
|
|
42873
42198
|
await init_src();
|
|
42874
|
-
await init_central();
|
|
42875
|
-
init_protocol();
|
|
42876
|
-
await init_store2();
|
|
42877
|
-
FORWARDED_TYPES = new Set([
|
|
42878
|
-
"message",
|
|
42879
|
-
"message_updated",
|
|
42880
|
-
"typing",
|
|
42881
|
-
"tool_call",
|
|
42882
|
-
"tool_output",
|
|
42883
|
-
"tool_status",
|
|
42884
|
-
"visual",
|
|
42885
|
-
"file_ready",
|
|
42886
|
-
"ai_done",
|
|
42887
|
-
"ai_error",
|
|
42888
|
-
"ai_aborted"
|
|
42889
|
-
]);
|
|
42890
|
-
TERMINAL_TYPES = new Set(["ai_done", "ai_error", "ai_aborted"]);
|
|
42891
|
-
activeForwarders = new Map;
|
|
42892
42199
|
});
|
|
42893
42200
|
|
|
42894
42201
|
// ../../adapters/otium/src/peer-files.ts
|
|
@@ -42915,10 +42222,6 @@ function attachment(row) {
|
|
|
42915
42222
|
function rowFor(fileId) {
|
|
42916
42223
|
return db.query("SELECT * FROM otium_peer_files WHERE id = ?").get(fileId) ?? null;
|
|
42917
42224
|
}
|
|
42918
|
-
function peerFileAllowsAccess(fileId, access) {
|
|
42919
|
-
const row = rowFor(fileId);
|
|
42920
|
-
return row?.topic_id === access.topicId && row.owner_user_id === access.ownerUserId;
|
|
42921
|
-
}
|
|
42922
42225
|
function safeFilename(filename) {
|
|
42923
42226
|
const value = basename13(filename).replace(/[^A-Za-z0-9._ -]/g, "_").slice(0, 120);
|
|
42924
42227
|
return value || "upload";
|
|
@@ -42974,21 +42277,6 @@ function deletePeerFilesForTopic(topicId) {
|
|
|
42974
42277
|
for (const row of rows)
|
|
42975
42278
|
rmSync11(row.path, { force: true });
|
|
42976
42279
|
}
|
|
42977
|
-
async function storePeerInputFile(file, access) {
|
|
42978
|
-
const id = randomUUID29();
|
|
42979
|
-
mkdirSync33(PEER_FILES_DIR, { recursive: true });
|
|
42980
|
-
const filename = file.name || "upload";
|
|
42981
|
-
const path = join42(PEER_FILES_DIR, `${id}-${safeFilename(filename)}`);
|
|
42982
|
-
const sizeBytes = await Bun.write(path, file);
|
|
42983
|
-
return recordFile({
|
|
42984
|
-
id,
|
|
42985
|
-
path,
|
|
42986
|
-
sizeBytes,
|
|
42987
|
-
filename,
|
|
42988
|
-
mimeType: file.type || "application/octet-stream",
|
|
42989
|
-
...access
|
|
42990
|
-
});
|
|
42991
|
-
}
|
|
42992
42280
|
function installPeerFileHooks() {
|
|
42993
42281
|
const previous = fileHooks();
|
|
42994
42282
|
const hooks = {
|
|
@@ -43052,15 +42340,7 @@ var PEER_BRIDGE_TIMEOUT_MS2 = 15000, otiumPeerRuntimeBridge;
|
|
|
43052
42340
|
var init_runtime_bridge = __esm(async () => {
|
|
43053
42341
|
await init_src();
|
|
43054
42342
|
await init_central();
|
|
43055
|
-
await init_event_backflow();
|
|
43056
42343
|
otiumPeerRuntimeBridge = {
|
|
43057
|
-
async flushEvents(localTopicId) {
|
|
43058
|
-
const forwarder = getActiveForwarder(localTopicId);
|
|
43059
|
-
if (!forwarder)
|
|
43060
|
-
return false;
|
|
43061
|
-
await forwarder.chain;
|
|
43062
|
-
return !forwarder.deliveryBlocked;
|
|
43063
|
-
},
|
|
43064
42344
|
async spawnSubagent(request) {
|
|
43065
42345
|
const hubNode = await resolvePeerNodeByCellId(request.bridge.hubCellId).catch(() => null);
|
|
43066
42346
|
if (!hubNode)
|
|
@@ -43280,6 +42560,149 @@ var init_runtime_bridge = __esm(async () => {
|
|
|
43280
42560
|
};
|
|
43281
42561
|
});
|
|
43282
42562
|
|
|
42563
|
+
// ../../adapters/otium/src/store.ts
|
|
42564
|
+
import { createHash as createHash12 } from "crypto";
|
|
42565
|
+
function cleanupPeerStateForLocalTopic(localTopicId) {
|
|
42566
|
+
return db.transaction(() => {
|
|
42567
|
+
const inboxRequests = db.run("DELETE FROM otium_peer_inbox_requests WHERE topic_id = ?", [
|
|
42568
|
+
localTopicId
|
|
42569
|
+
]).changes;
|
|
42570
|
+
const remoteAsks = db.run("DELETE FROM otium_remote_asks WHERE caller_topic_id = ?", [
|
|
42571
|
+
localTopicId
|
|
42572
|
+
]).changes;
|
|
42573
|
+
return { inboxRequests, remoteAsks };
|
|
42574
|
+
})();
|
|
42575
|
+
}
|
|
42576
|
+
function peerInboxPayloadHash(value) {
|
|
42577
|
+
return createHash12("sha256").update(JSON.stringify(value)).digest("hex");
|
|
42578
|
+
}
|
|
42579
|
+
function claimPeerInboxRequest(args) {
|
|
42580
|
+
const inserted = db.run(`INSERT OR IGNORE INTO otium_peer_inbox_requests
|
|
42581
|
+
(from_cell_id, request_id, kind, topic_id, payload_hash, created_at)
|
|
42582
|
+
VALUES (?, ?, ?, ?, ?, ?)`, [
|
|
42583
|
+
args.fromCellId,
|
|
42584
|
+
args.requestId,
|
|
42585
|
+
args.kind,
|
|
42586
|
+
args.topicId,
|
|
42587
|
+
args.payloadHash,
|
|
42588
|
+
new Date().toISOString()
|
|
42589
|
+
]);
|
|
42590
|
+
if (inserted.changes === 1)
|
|
42591
|
+
return { outcome: "claimed" };
|
|
42592
|
+
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);
|
|
42593
|
+
if (!existing)
|
|
42594
|
+
return { outcome: "conflict" };
|
|
42595
|
+
return { outcome: existing.payload_hash === args.payloadHash ? "replay" : "conflict" };
|
|
42596
|
+
}
|
|
42597
|
+
function releasePeerInboxRequest(fromCellId, requestId, kind) {
|
|
42598
|
+
db.run("DELETE FROM otium_peer_inbox_requests WHERE from_cell_id = ? AND request_id = ? AND kind = ?", [fromCellId, requestId, kind]);
|
|
42599
|
+
}
|
|
42600
|
+
function createRemoteAsk(args) {
|
|
42601
|
+
const result = db.run(`INSERT OR IGNORE INTO otium_remote_asks
|
|
42602
|
+
(request_id, expected_cell_id, user_id, caller_topic_id, from_key, to_key,
|
|
42603
|
+
source_query_id, created_at)
|
|
42604
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
42605
|
+
args.requestId,
|
|
42606
|
+
args.expectedCellId,
|
|
42607
|
+
args.userId,
|
|
42608
|
+
args.callerTopicId,
|
|
42609
|
+
args.from,
|
|
42610
|
+
args.to,
|
|
42611
|
+
args.sourceQueryId ?? null,
|
|
42612
|
+
args.createdAt ?? Date.now()
|
|
42613
|
+
]);
|
|
42614
|
+
return result.changes === 1;
|
|
42615
|
+
}
|
|
42616
|
+
function getRemoteAsk(requestId) {
|
|
42617
|
+
return db.query("SELECT * FROM otium_remote_asks WHERE request_id = ?").get(requestId) ?? null;
|
|
42618
|
+
}
|
|
42619
|
+
function deleteRemoteAsk(requestId) {
|
|
42620
|
+
return db.run("DELETE FROM otium_remote_asks WHERE request_id = ?", [requestId]).changes === 1;
|
|
42621
|
+
}
|
|
42622
|
+
function pruneRemoteAsks(olderThan) {
|
|
42623
|
+
return db.run("DELETE FROM otium_remote_asks WHERE created_at < ?", [olderThan]).changes;
|
|
42624
|
+
}
|
|
42625
|
+
function upsertPeerReplyOutbox(args) {
|
|
42626
|
+
const now = Date.now();
|
|
42627
|
+
db.run(`INSERT INTO otium_peer_reply_outbox
|
|
42628
|
+
(node_cell_id, request_id, node_name, topic_id, user_id, source_title,
|
|
42629
|
+
reply_text, kind, created_at, updated_at)
|
|
42630
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
42631
|
+
ON CONFLICT(node_cell_id, request_id) DO UPDATE SET
|
|
42632
|
+
node_name = excluded.node_name,
|
|
42633
|
+
topic_id = excluded.topic_id,
|
|
42634
|
+
user_id = excluded.user_id,
|
|
42635
|
+
source_title = excluded.source_title,
|
|
42636
|
+
reply_text = excluded.reply_text,
|
|
42637
|
+
kind = excluded.kind,
|
|
42638
|
+
updated_at = excluded.updated_at`, [
|
|
42639
|
+
args.nodeCellId,
|
|
42640
|
+
args.requestId,
|
|
42641
|
+
args.nodeName,
|
|
42642
|
+
args.topicId,
|
|
42643
|
+
args.userId,
|
|
42644
|
+
args.sourceTitle,
|
|
42645
|
+
args.replyText,
|
|
42646
|
+
args.kind,
|
|
42647
|
+
now,
|
|
42648
|
+
now
|
|
42649
|
+
]);
|
|
42650
|
+
}
|
|
42651
|
+
function listPeerReplyOutbox(limit = 100) {
|
|
42652
|
+
return db.query("SELECT * FROM otium_peer_reply_outbox ORDER BY created_at LIMIT ?").all(limit);
|
|
42653
|
+
}
|
|
42654
|
+
function deletePeerReplyOutbox(nodeCellId, requestId) {
|
|
42655
|
+
return db.run("DELETE FROM otium_peer_reply_outbox WHERE node_cell_id = ? AND request_id = ?", [
|
|
42656
|
+
nodeCellId,
|
|
42657
|
+
requestId
|
|
42658
|
+
]).changes === 1;
|
|
42659
|
+
}
|
|
42660
|
+
var init_store2 = __esm(async () => {
|
|
42661
|
+
await init_src();
|
|
42662
|
+
db.exec(`
|
|
42663
|
+
CREATE TABLE IF NOT EXISTS otium_peer_inbox_requests (
|
|
42664
|
+
from_cell_id TEXT NOT NULL,
|
|
42665
|
+
request_id TEXT NOT NULL,
|
|
42666
|
+
kind TEXT NOT NULL CHECK (kind IN ('tell', 'ask')),
|
|
42667
|
+
topic_id TEXT NOT NULL,
|
|
42668
|
+
payload_hash TEXT NOT NULL,
|
|
42669
|
+
created_at TEXT NOT NULL,
|
|
42670
|
+
PRIMARY KEY (from_cell_id, request_id, kind)
|
|
42671
|
+
)
|
|
42672
|
+
`);
|
|
42673
|
+
db.exec(`
|
|
42674
|
+
CREATE TABLE IF NOT EXISTS otium_remote_asks (
|
|
42675
|
+
request_id TEXT PRIMARY KEY,
|
|
42676
|
+
expected_cell_id TEXT NOT NULL,
|
|
42677
|
+
user_id TEXT NOT NULL,
|
|
42678
|
+
caller_topic_id TEXT NOT NULL,
|
|
42679
|
+
from_key TEXT NOT NULL,
|
|
42680
|
+
to_key TEXT NOT NULL,
|
|
42681
|
+
source_query_id TEXT,
|
|
42682
|
+
created_at INTEGER NOT NULL
|
|
42683
|
+
)
|
|
42684
|
+
`);
|
|
42685
|
+
db.exec(`
|
|
42686
|
+
CREATE INDEX IF NOT EXISTS idx_otium_remote_asks_created
|
|
42687
|
+
ON otium_remote_asks(created_at)
|
|
42688
|
+
`);
|
|
42689
|
+
db.exec(`
|
|
42690
|
+
CREATE TABLE IF NOT EXISTS otium_peer_reply_outbox (
|
|
42691
|
+
node_cell_id TEXT NOT NULL,
|
|
42692
|
+
request_id TEXT NOT NULL,
|
|
42693
|
+
node_name TEXT NOT NULL,
|
|
42694
|
+
topic_id TEXT NOT NULL,
|
|
42695
|
+
user_id TEXT NOT NULL,
|
|
42696
|
+
source_title TEXT NOT NULL,
|
|
42697
|
+
reply_text TEXT NOT NULL,
|
|
42698
|
+
kind TEXT NOT NULL CHECK (kind IN ('reply', 'error')),
|
|
42699
|
+
created_at INTEGER NOT NULL,
|
|
42700
|
+
updated_at INTEGER NOT NULL,
|
|
42701
|
+
PRIMARY KEY (node_cell_id, request_id)
|
|
42702
|
+
)
|
|
42703
|
+
`);
|
|
42704
|
+
});
|
|
42705
|
+
|
|
43283
42706
|
// ../../adapters/otium/src/session-bridge.ts
|
|
43284
42707
|
function prunePendingRemoteAsks(now = Date.now()) {
|
|
43285
42708
|
pruneRemoteAsks(now - PENDING_ASK_TTL_MS2);
|
|
@@ -43671,287 +43094,6 @@ var init_session_bridge_ipc = __esm(() => {
|
|
|
43671
43094
|
MAX_BODY_BYTES2 = 1024 * 1024;
|
|
43672
43095
|
});
|
|
43673
43096
|
|
|
43674
|
-
// ../../adapters/otium/src/shared-topic-sync.ts
|
|
43675
|
-
var exports_shared_topic_sync = {};
|
|
43676
|
-
__export(exports_shared_topic_sync, {
|
|
43677
|
-
startSharedTopicSync: () => startSharedTopicSync,
|
|
43678
|
-
forwardSharedTopicMessage: () => forwardSharedTopicMessage,
|
|
43679
|
-
downgradeSharedTopicsForHub: () => downgradeSharedTopicsForHub,
|
|
43680
|
-
disconnectSharedTopics: () => disconnectSharedTopics,
|
|
43681
|
-
checkPeerAttachment: () => checkPeerAttachment,
|
|
43682
|
-
acceptSharedTopicMessages: () => acceptSharedTopicMessages
|
|
43683
|
-
});
|
|
43684
|
-
function isMirrorTopic(topicId) {
|
|
43685
|
-
return listPeerSessions().some((row) => row.local_topic_id === topicId && row.binding_mode === "mirror");
|
|
43686
|
-
}
|
|
43687
|
-
function metadata(topic) {
|
|
43688
|
-
if (!topic.agent)
|
|
43689
|
-
throw new Error(`topic ${topic.id} has no executable agent`);
|
|
43690
|
-
const registry = getRegistry(topic.agent);
|
|
43691
|
-
return {
|
|
43692
|
-
localTopicId: topic.id,
|
|
43693
|
-
title: topic.title,
|
|
43694
|
-
...topic.description ? { description: topic.description } : {},
|
|
43695
|
-
agent: topic.agent,
|
|
43696
|
-
model: topic.defaultModel || registry.defaultModel,
|
|
43697
|
-
effort: topic.defaultEffort ?? "medium"
|
|
43698
|
-
};
|
|
43699
|
-
}
|
|
43700
|
-
async function peerRequest(node, path, init) {
|
|
43701
|
-
const token = await mintPeerToken(node.cellId);
|
|
43702
|
-
const response = await fetch(`${node.baseUrl.replace(/\/+$/, "")}${path}`, {
|
|
43703
|
-
...init,
|
|
43704
|
-
headers: {
|
|
43705
|
-
authorization: `Bearer ${token}`,
|
|
43706
|
-
"content-type": "application/json",
|
|
43707
|
-
...init.headers ?? {}
|
|
43708
|
-
},
|
|
43709
|
-
signal: AbortSignal.timeout(15000)
|
|
43710
|
-
});
|
|
43711
|
-
const body = await response.json().catch(() => null);
|
|
43712
|
-
if (!response.ok || !body?.ok)
|
|
43713
|
-
throw new Error(String(body?.error ?? `peer request failed (${response.status})`));
|
|
43714
|
-
return body;
|
|
43715
|
-
}
|
|
43716
|
-
function messageEnvelope(message) {
|
|
43717
|
-
const author = message.agentType ? "ai" : message.kind === "system" || message.kind === "tool" ? "system" : "user";
|
|
43718
|
-
return {
|
|
43719
|
-
sourceMessageId: message.sourceMessageId ?? message.id,
|
|
43720
|
-
author,
|
|
43721
|
-
text: message.text,
|
|
43722
|
-
createdAt: message.createdAt,
|
|
43723
|
-
...message.agentType ? { agent: message.agentType } : {},
|
|
43724
|
-
...message.model ? { model: message.model } : {},
|
|
43725
|
-
...message.kind ? { kind: message.kind } : {}
|
|
43726
|
-
};
|
|
43727
|
-
}
|
|
43728
|
-
async function publishTopic(join43, topicId, node) {
|
|
43729
|
-
const topic = getTopic(topicId);
|
|
43730
|
-
if (!topic || topic.kind !== "agent" || !topic.agent || !isTopicShared(topic) || !topic.visibility || topic.visibility === "hidden" || isMirrorTopic(topicId))
|
|
43731
|
-
return;
|
|
43732
|
-
const state2 = getSharedTopicState(topicId);
|
|
43733
|
-
setSharedTopicState({
|
|
43734
|
-
localTopicId: topicId,
|
|
43735
|
-
hostTopicId: state2?.host_topic_id,
|
|
43736
|
-
status: "publishing"
|
|
43737
|
-
});
|
|
43738
|
-
try {
|
|
43739
|
-
const body = await peerRequest(node, "/api/v1/peer/shared-topic", {
|
|
43740
|
-
method: "POST",
|
|
43741
|
-
body: JSON.stringify({
|
|
43742
|
-
v: PEER_PROTOCOL_VERSION,
|
|
43743
|
-
...metadata(topic)
|
|
43744
|
-
})
|
|
43745
|
-
});
|
|
43746
|
-
const hostTopicId = typeof body.hostTopicId === "string" ? body.hostTopicId : state2?.host_topic_id;
|
|
43747
|
-
if (!hostTopicId)
|
|
43748
|
-
throw new Error("shared-topic response omitted hostTopicId");
|
|
43749
|
-
setSharedTopicState({ localTopicId: topicId, hostTopicId, status: "published" });
|
|
43750
|
-
const messages = listApiMessages(topicId, { limit: 200 }).page.filter((message) => !message.sourceNode).map(messageEnvelope);
|
|
43751
|
-
if (messages.length) {
|
|
43752
|
-
await peerRequest(node, "/api/v1/peer/shared-topic/messages", {
|
|
43753
|
-
method: "POST",
|
|
43754
|
-
body: JSON.stringify({
|
|
43755
|
-
v: PEER_PROTOCOL_VERSION,
|
|
43756
|
-
localTopicId: topicId,
|
|
43757
|
-
hostTopicId,
|
|
43758
|
-
messages
|
|
43759
|
-
})
|
|
43760
|
-
});
|
|
43761
|
-
}
|
|
43762
|
-
for (const row of listSharedMessages(topicId)) {
|
|
43763
|
-
await peerRequest(node, "/api/v1/peer/shared-topic/messages", {
|
|
43764
|
-
method: "POST",
|
|
43765
|
-
body: JSON.stringify({
|
|
43766
|
-
v: PEER_PROTOCOL_VERSION,
|
|
43767
|
-
localTopicId: topicId,
|
|
43768
|
-
hostTopicId,
|
|
43769
|
-
messages: [JSON.parse(row.message_json)]
|
|
43770
|
-
})
|
|
43771
|
-
});
|
|
43772
|
-
deleteSharedMessage(topicId, row.source_message_id);
|
|
43773
|
-
}
|
|
43774
|
-
} catch (error2) {
|
|
43775
|
-
logger.warn({ error: error2, topicId }, "otium: shared topic publish deferred");
|
|
43776
|
-
setTimeout(() => void reconcileTopic(join43, topicId), RETRY_MS).unref?.();
|
|
43777
|
-
}
|
|
43778
|
-
}
|
|
43779
|
-
async function unpublishTopic(join43, topicId, node) {
|
|
43780
|
-
const state2 = getSharedTopicState(topicId);
|
|
43781
|
-
if (!state2)
|
|
43782
|
-
return;
|
|
43783
|
-
setSharedTopicState({
|
|
43784
|
-
localTopicId: topicId,
|
|
43785
|
-
hostTopicId: state2.host_topic_id,
|
|
43786
|
-
status: "unpublishing"
|
|
43787
|
-
});
|
|
43788
|
-
try {
|
|
43789
|
-
await peerRequest(node, `/api/v1/peer/shared-topic/${encodeURIComponent(topicId)}`, {
|
|
43790
|
-
method: "DELETE"
|
|
43791
|
-
});
|
|
43792
|
-
deleteSharedTopicState(topicId);
|
|
43793
|
-
} catch (error2) {
|
|
43794
|
-
logger.warn({ error: error2, topicId }, "otium: shared topic unpublish deferred");
|
|
43795
|
-
setTimeout(() => void reconcileTopic(join43, topicId), RETRY_MS).unref?.();
|
|
43796
|
-
}
|
|
43797
|
-
}
|
|
43798
|
-
async function reconcileTopic(join43, topicId, explicit = false) {
|
|
43799
|
-
if (!otiumCentralConfig())
|
|
43800
|
-
return;
|
|
43801
|
-
if (!explicit && isPeerDetached(join43.cellId))
|
|
43802
|
-
return;
|
|
43803
|
-
const target = (await listPeerNodes()).find((node) => node.isPrimary) ?? null;
|
|
43804
|
-
if (!target)
|
|
43805
|
-
return;
|
|
43806
|
-
const topic = getTopic(topicId);
|
|
43807
|
-
if (topic?.kind === "agent" && topic.agent && isTopicShared(topic) && topic.visibility !== "hidden" && !isMirrorTopic(topicId))
|
|
43808
|
-
return publishTopic(join43, topicId, target);
|
|
43809
|
-
return unpublishTopic(join43, topicId, target);
|
|
43810
|
-
}
|
|
43811
|
-
function disconnectSharedTopics(join43) {
|
|
43812
|
-
const states = listSharedTopicStates();
|
|
43813
|
-
const hubPromise = listPeerNodes({ fresh: true }).then((nodes) => nodes.find((node) => node.isPrimary) ?? null, () => null);
|
|
43814
|
-
const updated = downgradeSharedTopicsLocally(join43.cellId);
|
|
43815
|
-
for (const topicId of updated)
|
|
43816
|
-
WsHub.get().broadcastTopicUpdated(topicId);
|
|
43817
|
-
const deletions = states.map(async (state2) => {
|
|
43818
|
-
const hub = await hubPromise;
|
|
43819
|
-
if (!hub)
|
|
43820
|
-
return;
|
|
43821
|
-
try {
|
|
43822
|
-
await peerRequest(hub, `/api/v1/peer/shared-topic/${encodeURIComponent(state2.local_topic_id)}`, {
|
|
43823
|
-
method: "DELETE"
|
|
43824
|
-
});
|
|
43825
|
-
} catch {}
|
|
43826
|
-
});
|
|
43827
|
-
return Promise.all(deletions).then(() => {
|
|
43828
|
-
return;
|
|
43829
|
-
});
|
|
43830
|
-
}
|
|
43831
|
-
function downgradeSharedTopicsForHub(hubNodeId) {
|
|
43832
|
-
const updated = downgradeSharedTopicsLocally(hubNodeId);
|
|
43833
|
-
for (const topicId of updated)
|
|
43834
|
-
WsHub.get().broadcastTopicUpdated(topicId);
|
|
43835
|
-
return updated.length;
|
|
43836
|
-
}
|
|
43837
|
-
async function checkPeerAttachment(join43) {
|
|
43838
|
-
const nodes = await listPeerNodes({ fresh: true });
|
|
43839
|
-
if (nodes.some((node) => node.cellId === join43.cellId))
|
|
43840
|
-
return true;
|
|
43841
|
-
downgradeSharedTopicsForHub(join43.cellId);
|
|
43842
|
-
return false;
|
|
43843
|
-
}
|
|
43844
|
-
function startSharedTopicSync(join43) {
|
|
43845
|
-
let stopped = false;
|
|
43846
|
-
let detached = isPeerDetached(join43.cellId);
|
|
43847
|
-
const queue = new Set;
|
|
43848
|
-
const schedule = (topicId, explicit = false) => {
|
|
43849
|
-
if (stopped || queue.has(topicId))
|
|
43850
|
-
return;
|
|
43851
|
-
queue.add(topicId);
|
|
43852
|
-
queueMicrotask(async () => {
|
|
43853
|
-
queue.delete(topicId);
|
|
43854
|
-
try {
|
|
43855
|
-
await reconcileTopic(join43, topicId, explicit);
|
|
43856
|
-
} catch (error2) {
|
|
43857
|
-
logger.warn({ error: error2, topicId }, "otium: shared topic reconciliation failed");
|
|
43858
|
-
}
|
|
43859
|
-
});
|
|
43860
|
-
};
|
|
43861
|
-
for (const topic of listTopics())
|
|
43862
|
-
schedule(topic.id, false);
|
|
43863
|
-
for (const state2 of listSharedTopicStates())
|
|
43864
|
-
schedule(state2.local_topic_id);
|
|
43865
|
-
const attachmentCheck = setInterval(async () => {
|
|
43866
|
-
if (stopped || detached)
|
|
43867
|
-
return;
|
|
43868
|
-
try {
|
|
43869
|
-
if (!await checkPeerAttachment(join43))
|
|
43870
|
-
detached = true;
|
|
43871
|
-
} catch {}
|
|
43872
|
-
}, 30000);
|
|
43873
|
-
attachmentCheck.unref?.();
|
|
43874
|
-
const unsubscribe2 = runtimeBus().subscribe((event) => {
|
|
43875
|
-
if (event.type === "topic-created" || event.type === "topic-updated" || event.type === "topic-deleted")
|
|
43876
|
-
schedule(event.topicId, true);
|
|
43877
|
-
if (event.type === "message") {
|
|
43878
|
-
const message = event.payload;
|
|
43879
|
-
if (!message.sourceNode) {
|
|
43880
|
-
schedule(event.topicId);
|
|
43881
|
-
forwardSharedTopicMessage(join43, message).catch((error2) => logger.warn({ error: error2, topicId: event.topicId }, "otium: shared message deferred"));
|
|
43882
|
-
}
|
|
43883
|
-
}
|
|
43884
|
-
});
|
|
43885
|
-
return () => {
|
|
43886
|
-
stopped = true;
|
|
43887
|
-
clearInterval(attachmentCheck);
|
|
43888
|
-
unsubscribe2();
|
|
43889
|
-
};
|
|
43890
|
-
}
|
|
43891
|
-
async function forwardSharedTopicMessage(_join, message) {
|
|
43892
|
-
if (message.sourceNode || isMirrorTopic(message.topicId))
|
|
43893
|
-
return;
|
|
43894
|
-
if (getActiveForwarder(message.topicId))
|
|
43895
|
-
return;
|
|
43896
|
-
const state2 = getSharedTopicState(message.topicId);
|
|
43897
|
-
if (!state2?.host_topic_id || state2.status !== "published")
|
|
43898
|
-
return;
|
|
43899
|
-
const hub = (await listPeerNodes()).find((node) => node.isPrimary) ?? null;
|
|
43900
|
-
if (!hub)
|
|
43901
|
-
return;
|
|
43902
|
-
const envelope = messageEnvelope(message);
|
|
43903
|
-
try {
|
|
43904
|
-
await peerRequest(hub, "/api/v1/peer/shared-topic/messages", {
|
|
43905
|
-
method: "POST",
|
|
43906
|
-
body: JSON.stringify({
|
|
43907
|
-
v: PEER_PROTOCOL_VERSION,
|
|
43908
|
-
localTopicId: message.topicId,
|
|
43909
|
-
hostTopicId: state2.host_topic_id,
|
|
43910
|
-
messages: [envelope]
|
|
43911
|
-
})
|
|
43912
|
-
});
|
|
43913
|
-
} catch (error2) {
|
|
43914
|
-
enqueueSharedMessage({
|
|
43915
|
-
localTopicId: message.topicId,
|
|
43916
|
-
sourceMessageId: envelope.sourceMessageId,
|
|
43917
|
-
message: envelope
|
|
43918
|
-
});
|
|
43919
|
-
throw error2;
|
|
43920
|
-
}
|
|
43921
|
-
}
|
|
43922
|
-
function acceptSharedTopicMessages(messages, localTopicId, sourceNode) {
|
|
43923
|
-
let inserted = 0;
|
|
43924
|
-
for (const incoming of messages) {
|
|
43925
|
-
if (!incoming.sourceMessageId || typeof incoming.text !== "string")
|
|
43926
|
-
continue;
|
|
43927
|
-
if (getApiMessage(localTopicId, incoming.sourceMessageId))
|
|
43928
|
-
continue;
|
|
43929
|
-
const message = {
|
|
43930
|
-
id: incoming.sourceMessageId,
|
|
43931
|
-
topicId: localTopicId,
|
|
43932
|
-
authorId: sourceNode,
|
|
43933
|
-
text: incoming.text,
|
|
43934
|
-
createdAt: incoming.createdAt,
|
|
43935
|
-
...incoming.agent ? { agentType: incoming.agent } : {},
|
|
43936
|
-
...incoming.model ? { model: incoming.model } : {},
|
|
43937
|
-
...incoming.kind ? { kind: incoming.kind } : {},
|
|
43938
|
-
sourceNode,
|
|
43939
|
-
sourceMessageId: incoming.sourceMessageId
|
|
43940
|
-
};
|
|
43941
|
-
appendApiMessage(message);
|
|
43942
|
-
inserted++;
|
|
43943
|
-
}
|
|
43944
|
-
return inserted;
|
|
43945
|
-
}
|
|
43946
|
-
var RETRY_MS = 1000;
|
|
43947
|
-
var init_shared_topic_sync = __esm(async () => {
|
|
43948
|
-
await init_src();
|
|
43949
|
-
await init_central();
|
|
43950
|
-
await init_event_backflow();
|
|
43951
|
-
init_protocol();
|
|
43952
|
-
await init_store2();
|
|
43953
|
-
});
|
|
43954
|
-
|
|
43955
43097
|
// ../../adapters/otium/src/relay-protocol.ts
|
|
43956
43098
|
function encodeFrame(frame) {
|
|
43957
43099
|
const encoded = JSON.stringify(frame);
|
|
@@ -44520,109 +43662,6 @@ var init_tunnel_client = __esm(() => {
|
|
|
44520
43662
|
silentLogger = { info: noop, warn: noop, error: noop };
|
|
44521
43663
|
});
|
|
44522
43664
|
|
|
44523
|
-
// ../../adapters/otium/src/bindings.ts
|
|
44524
|
-
var exports_bindings = {};
|
|
44525
|
-
__export(exports_bindings, {
|
|
44526
|
-
unbindOtiumTopic: () => unbindOtiumTopic,
|
|
44527
|
-
shareOtiumTopic: () => shareOtiumTopic,
|
|
44528
|
-
setOtiumTopicPrivate: () => setOtiumTopicPrivate,
|
|
44529
|
-
listOtiumTopicBindings: () => listOtiumTopicBindings,
|
|
44530
|
-
bindOtiumTopic: () => bindOtiumTopic
|
|
44531
|
-
});
|
|
44532
|
-
function ownsTopic(localTopicId, userId) {
|
|
44533
|
-
const topic = getTopic(localTopicId);
|
|
44534
|
-
if (!topic)
|
|
44535
|
-
return { ok: false, error: "local topic not found", status: 404 };
|
|
44536
|
-
if (!isTopicVisible(topic)) {
|
|
44537
|
-
return { ok: false, error: "internal topics have no user access mode", status: 409 };
|
|
44538
|
-
}
|
|
44539
|
-
if (!topic.participants.some((participant) => participant.userId === userId)) {
|
|
44540
|
-
return { ok: false, error: "local topic is not visible to this user", status: 403 };
|
|
44541
|
-
}
|
|
44542
|
-
if (!topic.participants.some((participant) => participant.userId === userId && participant.role === "owner")) {
|
|
44543
|
-
return {
|
|
44544
|
-
ok: false,
|
|
44545
|
-
error: "only a topic owner can change its access mode",
|
|
44546
|
-
status: 403
|
|
44547
|
-
};
|
|
44548
|
-
}
|
|
44549
|
-
return { ok: true, topic };
|
|
44550
|
-
}
|
|
44551
|
-
function bindOtiumTopic(options) {
|
|
44552
|
-
const topic = getTopic(options.localTopicId);
|
|
44553
|
-
if (!topic)
|
|
44554
|
-
return { ok: false, error: "local topic not found", status: 404 };
|
|
44555
|
-
if (!isTopicVisible(topic)) {
|
|
44556
|
-
return { ok: false, error: "hidden local topics cannot be shared", status: 409 };
|
|
44557
|
-
}
|
|
44558
|
-
if (!topic.participants.some((participant) => participant.userId === options.userId)) {
|
|
44559
|
-
return { ok: false, error: "local topic is not visible to this user", status: 403 };
|
|
44560
|
-
}
|
|
44561
|
-
if (!isTopicShared(topic)) {
|
|
44562
|
-
return { ok: false, error: "private topics must be shared explicitly first", status: 409 };
|
|
44563
|
-
}
|
|
44564
|
-
const previous = getPeerSession(options.hostNodeId, options.hostTopicId);
|
|
44565
|
-
bindPeerSession(options.hostNodeId, options.hostTopicId, options.localTopicId, "shared");
|
|
44566
|
-
return {
|
|
44567
|
-
ok: true,
|
|
44568
|
-
localTopicId: options.localTopicId,
|
|
44569
|
-
replaced: Boolean(previous && previous.local_topic_id !== options.localTopicId)
|
|
44570
|
-
};
|
|
44571
|
-
}
|
|
44572
|
-
function shareOtiumTopic(options) {
|
|
44573
|
-
const owned = ownsTopic(options.localTopicId, options.userId);
|
|
44574
|
-
if (!owned.ok)
|
|
44575
|
-
return owned;
|
|
44576
|
-
if (!isTopicShared(owned.topic)) {
|
|
44577
|
-
const switched = switchTopicAccessMode({
|
|
44578
|
-
topicId: options.localTopicId,
|
|
44579
|
-
userId: options.userId,
|
|
44580
|
-
accessMode: "shared"
|
|
44581
|
-
});
|
|
44582
|
-
if (!switched.ok)
|
|
44583
|
-
return { ok: false, error: switched.error, status: 409 };
|
|
44584
|
-
}
|
|
44585
|
-
return bindOtiumTopic(options);
|
|
44586
|
-
}
|
|
44587
|
-
function setOtiumTopicPrivate(options) {
|
|
44588
|
-
const owned = ownsTopic(options.localTopicId, options.userId);
|
|
44589
|
-
if (!owned.ok)
|
|
44590
|
-
return owned;
|
|
44591
|
-
const removedBindings = unbindSharedPeerSessionsForLocalTopic(options.localTopicId);
|
|
44592
|
-
if (isTopicShared(owned.topic)) {
|
|
44593
|
-
const switched = switchTopicAccessMode({
|
|
44594
|
-
topicId: options.localTopicId,
|
|
44595
|
-
userId: options.userId,
|
|
44596
|
-
accessMode: "private"
|
|
44597
|
-
});
|
|
44598
|
-
if (!switched.ok)
|
|
44599
|
-
return { ok: false, error: switched.error, status: 409 };
|
|
44600
|
-
}
|
|
44601
|
-
return { ok: true, localTopicId: options.localTopicId, removedBindings };
|
|
44602
|
-
}
|
|
44603
|
-
function unbindOtiumTopic(hostNodeId, hostTopicId) {
|
|
44604
|
-
return unbindPeerSession(hostNodeId, hostTopicId);
|
|
44605
|
-
}
|
|
44606
|
-
function listOtiumTopicBindings() {
|
|
44607
|
-
return listPeerSessions().map((row) => {
|
|
44608
|
-
const topic = getTopic(row.local_topic_id);
|
|
44609
|
-
return {
|
|
44610
|
-
hostNodeId: row.host_node_id,
|
|
44611
|
-
hostTopicId: row.host_topic_id,
|
|
44612
|
-
localTopicId: row.local_topic_id,
|
|
44613
|
-
transport: row.binding_mode === "shared" ? "shared-binding" : "internal-mirror",
|
|
44614
|
-
...topic ? { topicAccessMode: topic.accessMode ?? "private" } : {},
|
|
44615
|
-
...topic ? { localTopicTitle: topic.title } : {},
|
|
44616
|
-
localTopicExists: Boolean(topic),
|
|
44617
|
-
createdAt: row.created_at
|
|
44618
|
-
};
|
|
44619
|
-
});
|
|
44620
|
-
}
|
|
44621
|
-
var init_bindings = __esm(async () => {
|
|
44622
|
-
await init_src();
|
|
44623
|
-
await init_store2();
|
|
44624
|
-
});
|
|
44625
|
-
|
|
44626
43665
|
// ../../adapters/otium/src/enrollment.ts
|
|
44627
43666
|
import {
|
|
44628
43667
|
createDecipheriv as createDecipheriv2,
|
|
@@ -44907,205 +43946,6 @@ var init_gateway_forward = __esm(async () => {
|
|
|
44907
43946
|
await init_src();
|
|
44908
43947
|
});
|
|
44909
43948
|
|
|
44910
|
-
// ../../adapters/otium/src/turn-bridge.ts
|
|
44911
|
-
import { randomUUID as randomUUID32 } from "crypto";
|
|
44912
|
-
function executionFor(payload) {
|
|
44913
|
-
if (payload.execution)
|
|
44914
|
-
return payload.execution;
|
|
44915
|
-
if (!payload.agent)
|
|
44916
|
-
return null;
|
|
44917
|
-
return {
|
|
44918
|
-
agent: payload.agent,
|
|
44919
|
-
model: payload.model ?? "",
|
|
44920
|
-
effort: payload.effort ?? "medium",
|
|
44921
|
-
mcp: [],
|
|
44922
|
-
canSpawnSubagents: false
|
|
44923
|
-
};
|
|
44924
|
-
}
|
|
44925
|
-
function provisionMirrorTopic(hostCellId, payload) {
|
|
44926
|
-
const execution = payload.execution;
|
|
44927
|
-
if (!isAgentKind(execution.agent)) {
|
|
44928
|
-
return { ok: false, error: `unknown agent "${execution.agent}"`, status: 400 };
|
|
44929
|
-
}
|
|
44930
|
-
const existing = getPeerSession(hostCellId, payload.hostTopicId);
|
|
44931
|
-
if (existing?.binding_mode === "shared") {
|
|
44932
|
-
const shared = getTopic(existing.local_topic_id);
|
|
44933
|
-
if (!shared) {
|
|
44934
|
-
return { ok: false, error: "bound local topic no longer exists", status: 404 };
|
|
44935
|
-
}
|
|
44936
|
-
if (!isTopicVisible(shared)) {
|
|
44937
|
-
return { ok: false, error: "bound local topic is hidden", status: 409 };
|
|
44938
|
-
}
|
|
44939
|
-
if (!isTopicShared(shared)) {
|
|
44940
|
-
return { ok: false, error: "bound local topic is private", status: 409 };
|
|
44941
|
-
}
|
|
44942
|
-
if (!shared.participants.some((participant) => participant.userId === payload.userId)) {
|
|
44943
|
-
return { ok: false, error: "bound local topic is not visible to this user", status: 403 };
|
|
44944
|
-
}
|
|
44945
|
-
return { ok: true, localTopicId: shared.id, bindingMode: "shared" };
|
|
44946
|
-
}
|
|
44947
|
-
const localTopicId = existing?.local_topic_id ?? `peer-${randomUUID32()}`;
|
|
44948
|
-
const now = new Date().toISOString();
|
|
44949
|
-
const current3 = existing ? getTopic(localTopicId) : null;
|
|
44950
|
-
const currentConfig = current3 ? getApiTopicConfig(localTopicId) : undefined;
|
|
44951
|
-
const currentModel = currentConfig?.model ?? current3?.defaultModel ?? "";
|
|
44952
|
-
const nextModel = execution.model || current3?.defaultModel || "";
|
|
44953
|
-
const providerSessionIsStale = Boolean(current3) && (current3?.agent !== execution.agent || currentModel !== nextModel);
|
|
44954
|
-
upsertTopic({
|
|
44955
|
-
id: localTopicId,
|
|
44956
|
-
title: payload.topicTitle,
|
|
44957
|
-
kind: "agent",
|
|
44958
|
-
agent: execution.agent,
|
|
44959
|
-
aiMode: "always",
|
|
44960
|
-
defaultModel: nextModel,
|
|
44961
|
-
defaultEffort: EFFORT_LEVELS.includes(execution.effort) ? execution.effort : current3?.defaultEffort ?? "medium",
|
|
44962
|
-
participants: [{ userId: payload.userId, role: "owner" }],
|
|
44963
|
-
isSubagent: undefined,
|
|
44964
|
-
visibility: "visible",
|
|
44965
|
-
accessMode: "shared",
|
|
44966
|
-
...execution.description ? { description: execution.description } : {},
|
|
44967
|
-
createdAt: current3?.createdAt ?? now,
|
|
44968
|
-
lastMessageAt: now
|
|
44969
|
-
});
|
|
44970
|
-
setApiTopicConfig(localTopicId, {
|
|
44971
|
-
...execution.model ? { model: execution.model } : {},
|
|
44972
|
-
...EFFORT_LEVELS.includes(execution.effort) ? { effort: execution.effort } : {},
|
|
44973
|
-
mcp: execution.mcp
|
|
44974
|
-
});
|
|
44975
|
-
if (providerSessionIsStale) {
|
|
44976
|
-
clearTopicSessionId(localTopicId, "peer-execution-spec-changed");
|
|
44977
|
-
}
|
|
44978
|
-
if (!existing)
|
|
44979
|
-
createPeerSession(hostCellId, payload.hostTopicId, localTopicId);
|
|
44980
|
-
return { ok: true, localTopicId, bindingMode: "mirror" };
|
|
44981
|
-
}
|
|
44982
|
-
function runPeerTurn(hubNode, hostCellId, payload, opts = {}) {
|
|
44983
|
-
const execution = executionFor(payload);
|
|
44984
|
-
if (!execution || !isAgentKind(execution.agent)) {
|
|
44985
|
-
return { ok: false, error: `unknown agent "${execution?.agent ?? ""}"`, status: 400 };
|
|
44986
|
-
}
|
|
44987
|
-
const claim = claimPeerTurnRequest(hostCellId, payload.requestId, payload.hostTopicId);
|
|
44988
|
-
if (!claim.claimed) {
|
|
44989
|
-
if (claim.row.host_topic_id !== payload.hostTopicId) {
|
|
44990
|
-
return { ok: false, error: "requestId already belongs to another room", status: 409 };
|
|
44991
|
-
}
|
|
44992
|
-
if (claim.row.status === "failed") {
|
|
44993
|
-
return { ok: false, error: claim.row.error ?? "previous attempt failed", status: 409 };
|
|
44994
|
-
}
|
|
44995
|
-
logger.info({ requestId: payload.requestId, hostTopicId: payload.hostTopicId, status: claim.row.status }, "otium: peer turn replay acknowledged without re-execution");
|
|
44996
|
-
return { ok: true };
|
|
44997
|
-
}
|
|
44998
|
-
const provisioned = provisionMirrorTopic(hostCellId, {
|
|
44999
|
-
userId: payload.userId,
|
|
45000
|
-
hostTopicId: payload.hostTopicId,
|
|
45001
|
-
topicTitle: payload.topicTitle,
|
|
45002
|
-
execution
|
|
45003
|
-
});
|
|
45004
|
-
if (!provisioned.ok) {
|
|
45005
|
-
markPeerTurnRequestFailed(hostCellId, payload.requestId, provisioned.error);
|
|
45006
|
-
return provisioned;
|
|
45007
|
-
}
|
|
45008
|
-
const localTopicId = provisioned.localTopicId;
|
|
45009
|
-
const localTopic = getTopic(localTopicId);
|
|
45010
|
-
if (payload.attachments?.some((fileId) => !peerFileAllowsAccess(fileId, { topicId: localTopicId, ownerUserId: payload.userId }))) {
|
|
45011
|
-
markPeerTurnRequestFailed(hostCellId, payload.requestId, "attachment access denied");
|
|
45012
|
-
return { ok: false, error: "attachment access denied", status: 403 };
|
|
45013
|
-
}
|
|
45014
|
-
const turnAgent = provisioned.bindingMode === "shared" && localTopic?.agent ? localTopic.agent : execution.agent;
|
|
45015
|
-
const previous = getActiveForwarder(localTopicId);
|
|
45016
|
-
if (previous) {
|
|
45017
|
-
previous.finish({
|
|
45018
|
-
type: "ai_aborted",
|
|
45019
|
-
queryId: previous.queryId ?? previous.requestId,
|
|
45020
|
-
topicId: localTopicId,
|
|
45021
|
-
reason: "superseded"
|
|
45022
|
-
});
|
|
45023
|
-
}
|
|
45024
|
-
const forwarder = createTurnForwarder({
|
|
45025
|
-
hostNodeId: hostCellId,
|
|
45026
|
-
requestId: payload.requestId,
|
|
45027
|
-
localTopicId,
|
|
45028
|
-
sendEvent: opts.sendEvent ?? hubEventSender(hubNode)
|
|
45029
|
-
});
|
|
45030
|
-
registerTurnForwarder(localTopicId, forwarder);
|
|
45031
|
-
let queryId = null;
|
|
45032
|
-
try {
|
|
45033
|
-
queryId = executeExternalUserTurn({
|
|
45034
|
-
topicId: localTopicId,
|
|
45035
|
-
userId: payload.userId,
|
|
45036
|
-
text: payload.message,
|
|
45037
|
-
agent: turnAgent,
|
|
45038
|
-
options: {
|
|
45039
|
-
origin: "user",
|
|
45040
|
-
requestId: payload.requestId,
|
|
45041
|
-
injectAuthorId: payload.userId,
|
|
45042
|
-
injectSourceNode: hostCellId,
|
|
45043
|
-
...payload.sourceMessageId ? { injectSourceMessageId: payload.sourceMessageId } : {},
|
|
45044
|
-
attachments: payload.attachments,
|
|
45045
|
-
visualTools: true,
|
|
45046
|
-
fileDeliveryTools: true,
|
|
45047
|
-
onDispatched: (dispatchedQueryId) => {
|
|
45048
|
-
forwarder.queryId = dispatchedQueryId;
|
|
45049
|
-
},
|
|
45050
|
-
peerBridge: {
|
|
45051
|
-
hubCellId: hostCellId,
|
|
45052
|
-
hostTopicId: payload.hostTopicId,
|
|
45053
|
-
hostQueryId: payload.requestId,
|
|
45054
|
-
canSpawnSubagents: execution.canSpawnSubagents
|
|
45055
|
-
}
|
|
45056
|
-
},
|
|
45057
|
-
...turnTrigger ? { dispatch: turnTrigger } : {}
|
|
45058
|
-
});
|
|
45059
|
-
} catch (err2) {
|
|
45060
|
-
forwarder.finish({
|
|
45061
|
-
type: "ai_error",
|
|
45062
|
-
queryId: payload.requestId,
|
|
45063
|
-
topicId: localTopicId,
|
|
45064
|
-
error: `worker dispatch crashed: ${err2.message}`
|
|
45065
|
-
});
|
|
45066
|
-
return { ok: false, error: "failed to start turn", status: 500 };
|
|
45067
|
-
}
|
|
45068
|
-
if (!queryId) {
|
|
45069
|
-
forwarder.finish({
|
|
45070
|
-
type: "ai_error",
|
|
45071
|
-
queryId: payload.requestId,
|
|
45072
|
-
topicId: localTopicId,
|
|
45073
|
-
error: "worker could not start the turn"
|
|
45074
|
-
});
|
|
45075
|
-
return { ok: false, error: "failed to start turn", status: 500 };
|
|
45076
|
-
}
|
|
45077
|
-
forwarder.queryId = queryId;
|
|
45078
|
-
markPeerTurnRequestRunning(hostCellId, payload.requestId);
|
|
45079
|
-
logger.info({ requestId: payload.requestId, localTopicId, queryId, title: payload.topicTitle }, "otium: peer turn running");
|
|
45080
|
-
return { ok: true };
|
|
45081
|
-
}
|
|
45082
|
-
function abortHostedPeerTurn(hostNodeId, requestId, userId, topicTitle) {
|
|
45083
|
-
const request = getPeerTurnRequest(hostNodeId, requestId);
|
|
45084
|
-
if (!request || request.status !== "running")
|
|
45085
|
-
return false;
|
|
45086
|
-
const session = getPeerSession(hostNodeId, request.host_topic_id);
|
|
45087
|
-
if (!session)
|
|
45088
|
-
return false;
|
|
45089
|
-
const topic = getTopic(session.local_topic_id);
|
|
45090
|
-
if (!topic || topic.title !== topicTitle || !topic.participants.some((participant) => participant.userId === userId)) {
|
|
45091
|
-
return false;
|
|
45092
|
-
}
|
|
45093
|
-
const forwarder = getActiveForwarder(session.local_topic_id);
|
|
45094
|
-
if (!forwarder || forwarder.requestId !== requestId)
|
|
45095
|
-
return false;
|
|
45096
|
-
if (!getRoomQuery(session.local_topic_id))
|
|
45097
|
-
return false;
|
|
45098
|
-
return abortRoom(session.local_topic_id);
|
|
45099
|
-
}
|
|
45100
|
-
var EFFORT_LEVELS, turnTrigger;
|
|
45101
|
-
var init_turn_bridge = __esm(async () => {
|
|
45102
|
-
await init_src();
|
|
45103
|
-
await init_event_backflow();
|
|
45104
|
-
await init_peer_files();
|
|
45105
|
-
await init_store2();
|
|
45106
|
-
EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
45107
|
-
});
|
|
45108
|
-
|
|
45109
43949
|
// ../../adapters/otium/src/peer-server.ts
|
|
45110
43950
|
import { statfsSync } from "fs";
|
|
45111
43951
|
import { cpus as cpus2, freemem as freemem2, loadavg as loadavg2, totalmem as totalmem2 } from "os";
|
|
@@ -45122,7 +43962,7 @@ async function readBody(req) {
|
|
|
45122
43962
|
return null;
|
|
45123
43963
|
return body;
|
|
45124
43964
|
}
|
|
45125
|
-
function
|
|
43965
|
+
function str(body, field) {
|
|
45126
43966
|
const value = body[field];
|
|
45127
43967
|
return typeof value === "string" && value.trim() ? value : null;
|
|
45128
43968
|
}
|
|
@@ -45148,6 +43988,9 @@ async function requirePeer(req) {
|
|
|
45148
43988
|
function requirePrimaryOrigin(peer) {
|
|
45149
43989
|
return peer.verified.fromIsPrimary ? null : jsonError2("only the workspace hub may call this endpoint", 403);
|
|
45150
43990
|
}
|
|
43991
|
+
function peerAddressable(topic) {
|
|
43992
|
+
return isTopicShared(topic);
|
|
43993
|
+
}
|
|
45151
43994
|
function localCapabilities() {
|
|
45152
43995
|
const agents = SUPPORTED_AGENTS.map((kind) => {
|
|
45153
43996
|
const registry = getRegistry(kind);
|
|
@@ -45165,7 +44008,7 @@ function localCapabilities() {
|
|
|
45165
44008
|
runtimeVersion: RUNTIME_VERSION,
|
|
45166
44009
|
features: {
|
|
45167
44010
|
remoteAsk: true,
|
|
45168
|
-
inputFiles:
|
|
44011
|
+
inputFiles: false,
|
|
45169
44012
|
outputFiles: true,
|
|
45170
44013
|
visualBridge: true,
|
|
45171
44014
|
askUserBridge: true,
|
|
@@ -45199,180 +44042,6 @@ function localHealth() {
|
|
|
45199
44042
|
...disk ? { disk } : {}
|
|
45200
44043
|
};
|
|
45201
44044
|
}
|
|
45202
|
-
async function handleProvision(req) {
|
|
45203
|
-
const peer = await requirePeer(req);
|
|
45204
|
-
if (!peer.ok)
|
|
45205
|
-
return peer.response;
|
|
45206
|
-
const originError = requirePrimaryOrigin(peer);
|
|
45207
|
-
if (originError)
|
|
45208
|
-
return originError;
|
|
45209
|
-
const body = await readBody(req);
|
|
45210
|
-
if (!body)
|
|
45211
|
-
return jsonError2("invalid JSON body", 400);
|
|
45212
|
-
const protocolError = checkProtocol(body);
|
|
45213
|
-
if (protocolError)
|
|
45214
|
-
return protocolError;
|
|
45215
|
-
const userId = str2(body, "userId");
|
|
45216
|
-
const hostTopicId = str2(body, "hostTopicId");
|
|
45217
|
-
const topicTitle = str2(body, "topicTitle");
|
|
45218
|
-
const execution = parseExecutionSpec(body.execution);
|
|
45219
|
-
if (!userId || !hostTopicId || !topicTitle || !execution) {
|
|
45220
|
-
return jsonError2("invalid peer provision request", 400);
|
|
45221
|
-
}
|
|
45222
|
-
const result = provisionMirrorTopic(peer.verified.fromCellId, {
|
|
45223
|
-
userId,
|
|
45224
|
-
hostTopicId,
|
|
45225
|
-
topicTitle,
|
|
45226
|
-
execution
|
|
45227
|
-
});
|
|
45228
|
-
if (!result.ok)
|
|
45229
|
-
return jsonError2(result.error, result.status);
|
|
45230
|
-
logger.info({ hostTopicId, localTopicId: result.localTopicId, fromNode: peer.verified.fromNodeName }, "otium: mirror room provisioned");
|
|
45231
|
-
return Response.json({ ok: true });
|
|
45232
|
-
}
|
|
45233
|
-
async function handleBind(req) {
|
|
45234
|
-
const peer = await requirePeer(req);
|
|
45235
|
-
if (!peer.ok)
|
|
45236
|
-
return peer.response;
|
|
45237
|
-
const originError = requirePrimaryOrigin(peer);
|
|
45238
|
-
if (originError)
|
|
45239
|
-
return originError;
|
|
45240
|
-
const body = await readBody(req);
|
|
45241
|
-
if (!body)
|
|
45242
|
-
return jsonError2("invalid JSON body", 400);
|
|
45243
|
-
const protocolError = checkProtocol(body);
|
|
45244
|
-
if (protocolError)
|
|
45245
|
-
return protocolError;
|
|
45246
|
-
const userId = str2(body, "userId");
|
|
45247
|
-
const hostTopicId = str2(body, "hostTopicId");
|
|
45248
|
-
const localTopicId = str2(body, "localTopicId");
|
|
45249
|
-
if (!userId || !hostTopicId || !localTopicId) {
|
|
45250
|
-
return jsonError2("invalid peer bind request", 400);
|
|
45251
|
-
}
|
|
45252
|
-
const result = bindOtiumTopic({
|
|
45253
|
-
hostNodeId: peer.verified.fromCellId,
|
|
45254
|
-
hostTopicId,
|
|
45255
|
-
localTopicId,
|
|
45256
|
-
userId
|
|
45257
|
-
});
|
|
45258
|
-
if (!result.ok)
|
|
45259
|
-
return jsonError2(result.error, result.status);
|
|
45260
|
-
return Response.json({ ok: true, localTopicId: result.localTopicId, replaced: result.replaced });
|
|
45261
|
-
}
|
|
45262
|
-
async function handleSharedTopicMessages(req) {
|
|
45263
|
-
const peer = await requirePeer(req);
|
|
45264
|
-
if (!peer.ok)
|
|
45265
|
-
return peer.response;
|
|
45266
|
-
const originError = requirePrimaryOrigin(peer);
|
|
45267
|
-
if (originError)
|
|
45268
|
-
return originError;
|
|
45269
|
-
const body = await readBody(req);
|
|
45270
|
-
if (!body)
|
|
45271
|
-
return jsonError2("invalid JSON body", 400);
|
|
45272
|
-
const protocolError = checkProtocol(body);
|
|
45273
|
-
if (protocolError)
|
|
45274
|
-
return protocolError;
|
|
45275
|
-
const localTopicId = str2(body, "localTopicId");
|
|
45276
|
-
const hostTopicId = str2(body, "hostTopicId");
|
|
45277
|
-
const messages = body.messages;
|
|
45278
|
-
if (!localTopicId || !hostTopicId || !Array.isArray(messages)) {
|
|
45279
|
-
return jsonError2("localTopicId, hostTopicId and messages are required", 400);
|
|
45280
|
-
}
|
|
45281
|
-
const session = getPeerSession(peer.verified.fromCellId, hostTopicId);
|
|
45282
|
-
if (!session || session.local_topic_id !== localTopicId) {
|
|
45283
|
-
return jsonError2("shared topic binding not found", 404);
|
|
45284
|
-
}
|
|
45285
|
-
const accepted = acceptSharedTopicMessages(messages, localTopicId, peer.verified.fromCellId);
|
|
45286
|
-
return Response.json({ ok: true, accepted });
|
|
45287
|
-
}
|
|
45288
|
-
async function handleSharedTopicsPrivate(req) {
|
|
45289
|
-
const peer = await requirePeer(req);
|
|
45290
|
-
if (!peer.ok)
|
|
45291
|
-
return peer.response;
|
|
45292
|
-
const originError = requirePrimaryOrigin(peer);
|
|
45293
|
-
if (originError)
|
|
45294
|
-
return originError;
|
|
45295
|
-
const body = await readBody(req);
|
|
45296
|
-
if (!body)
|
|
45297
|
-
return jsonError2("invalid JSON body", 400);
|
|
45298
|
-
const protocolError = checkProtocol(body);
|
|
45299
|
-
if (protocolError)
|
|
45300
|
-
return protocolError;
|
|
45301
|
-
if (body.reason !== "hub-removal")
|
|
45302
|
-
return jsonError2("reason must be hub-removal", 400);
|
|
45303
|
-
const updated = downgradeSharedTopicsForHub(peer.verified.fromCellId);
|
|
45304
|
-
return Response.json({ ok: true, updated });
|
|
45305
|
-
}
|
|
45306
|
-
async function handleUnbind(req) {
|
|
45307
|
-
const peer = await requirePeer(req);
|
|
45308
|
-
if (!peer.ok)
|
|
45309
|
-
return peer.response;
|
|
45310
|
-
const originError = requirePrimaryOrigin(peer);
|
|
45311
|
-
if (originError)
|
|
45312
|
-
return originError;
|
|
45313
|
-
const body = await readBody(req);
|
|
45314
|
-
if (!body)
|
|
45315
|
-
return jsonError2("invalid JSON body", 400);
|
|
45316
|
-
const protocolError = checkProtocol(body);
|
|
45317
|
-
if (protocolError)
|
|
45318
|
-
return protocolError;
|
|
45319
|
-
const hostTopicId = str2(body, "hostTopicId");
|
|
45320
|
-
if (!hostTopicId)
|
|
45321
|
-
return jsonError2("hostTopicId is required", 400);
|
|
45322
|
-
const removed = unbindOtiumTopic(peer.verified.fromCellId, hostTopicId);
|
|
45323
|
-
return Response.json({ ok: true, removed });
|
|
45324
|
-
}
|
|
45325
|
-
async function handleTurn(req) {
|
|
45326
|
-
const peer = await requirePeer(req);
|
|
45327
|
-
if (!peer.ok)
|
|
45328
|
-
return peer.response;
|
|
45329
|
-
const originError = requirePrimaryOrigin(peer);
|
|
45330
|
-
if (originError)
|
|
45331
|
-
return originError;
|
|
45332
|
-
const body = await readBody(req);
|
|
45333
|
-
if (!body)
|
|
45334
|
-
return jsonError2("invalid JSON body", 400);
|
|
45335
|
-
const protocolError = checkProtocol(body);
|
|
45336
|
-
if (protocolError)
|
|
45337
|
-
return protocolError;
|
|
45338
|
-
const requestId = str2(body, "requestId");
|
|
45339
|
-
const userId = str2(body, "userId");
|
|
45340
|
-
const hostTopicId = str2(body, "hostTopicId");
|
|
45341
|
-
const topicTitle = str2(body, "topicTitle");
|
|
45342
|
-
const execution = parseExecutionSpec(body.execution);
|
|
45343
|
-
if (body.execution !== undefined && !execution) {
|
|
45344
|
-
return jsonError2("invalid placed-topic execution spec", 400);
|
|
45345
|
-
}
|
|
45346
|
-
const agent = execution?.agent ?? str2(body, "agent");
|
|
45347
|
-
const message = str2(body, "message");
|
|
45348
|
-
if (!requestId || !userId || !hostTopicId || !topicTitle || !agent || !message) {
|
|
45349
|
-
return jsonError2("requestId, userId, hostTopicId, topicTitle, agent, message are required", 400);
|
|
45350
|
-
}
|
|
45351
|
-
if (message.length > MAX_PEER_MESSAGE_LENGTH)
|
|
45352
|
-
return jsonError2("message too long", 400);
|
|
45353
|
-
const hubNode = await resolvePeerNodeByCellId(peer.verified.fromCellId).catch(() => null);
|
|
45354
|
-
if (!hubNode)
|
|
45355
|
-
return jsonError2("calling node is not in this workspace", 403);
|
|
45356
|
-
const payload = {
|
|
45357
|
-
v: PEER_PROTOCOL_VERSION,
|
|
45358
|
-
requestId,
|
|
45359
|
-
userId,
|
|
45360
|
-
hostTopicId,
|
|
45361
|
-
topicTitle,
|
|
45362
|
-
...execution ? { execution } : {},
|
|
45363
|
-
...agent ? { agent } : {},
|
|
45364
|
-
...str2(body, "model") ? { model: str2(body, "model") } : {},
|
|
45365
|
-
...str2(body, "effort") ? { effort: str2(body, "effort") } : {},
|
|
45366
|
-
...Array.isArray(body.attachments) && body.attachments.every((entry) => typeof entry === "string") ? { attachments: body.attachments } : {},
|
|
45367
|
-
...str2(body, "sourceMessageId") ? { sourceMessageId: str2(body, "sourceMessageId") } : {},
|
|
45368
|
-
message
|
|
45369
|
-
};
|
|
45370
|
-
const result = runPeerTurn(hubNode, peer.verified.fromCellId, payload);
|
|
45371
|
-
if (!result.ok)
|
|
45372
|
-
return jsonError2(result.error, result.status);
|
|
45373
|
-
logger.info({ requestId, hostTopicId, fromNode: peer.verified.fromNodeName }, "otium: peer turn accepted");
|
|
45374
|
-
return Response.json({ ok: true });
|
|
45375
|
-
}
|
|
45376
44045
|
async function handleAbort(req) {
|
|
45377
44046
|
const peer = await requirePeer(req);
|
|
45378
44047
|
if (!peer.ok)
|
|
@@ -45386,20 +44055,12 @@ async function handleAbort(req) {
|
|
|
45386
44055
|
const protocolError = checkProtocol(body);
|
|
45387
44056
|
if (protocolError)
|
|
45388
44057
|
return protocolError;
|
|
45389
|
-
const userId =
|
|
45390
|
-
const toTopic =
|
|
44058
|
+
const userId = str(body, "userId");
|
|
44059
|
+
const toTopic = str(body, "toTopic");
|
|
45391
44060
|
if (!userId || !toTopic)
|
|
45392
44061
|
return jsonError2("userId and toTopic are required", 400);
|
|
45393
|
-
const requestId = str2(body, "requestId");
|
|
45394
|
-
if (requestId) {
|
|
45395
|
-
const aborted = abortHostedPeerTurn(peer.verified.fromCellId, requestId, userId, toTopic);
|
|
45396
|
-
if (!aborted)
|
|
45397
|
-
return jsonError2("turn not found or already completed", 404);
|
|
45398
|
-
logger.info({ fromNode: peer.verified.fromNodeName, toTopic, requestId }, "otium: exact peer turn abort accepted");
|
|
45399
|
-
return Response.json({ ok: true });
|
|
45400
|
-
}
|
|
45401
44062
|
const topic = getTopicByNameForUser(toTopic, userId);
|
|
45402
|
-
if (!topic || !
|
|
44063
|
+
if (!topic || !peerAddressable(topic)) {
|
|
45403
44064
|
return jsonError2(`shared topic "${toTopic}" not found on this node`, 404);
|
|
45404
44065
|
}
|
|
45405
44066
|
appendJsonlEntry(sessionInboxPath(userId, topic.id), {
|
|
@@ -45423,11 +44084,11 @@ async function handleTell(req) {
|
|
|
45423
44084
|
const protocolError = checkProtocol(body);
|
|
45424
44085
|
if (protocolError)
|
|
45425
44086
|
return protocolError;
|
|
45426
|
-
const requestId =
|
|
45427
|
-
const userId =
|
|
45428
|
-
const toTopic =
|
|
45429
|
-
const fromLabel =
|
|
45430
|
-
const message =
|
|
44087
|
+
const requestId = str(body, "requestId");
|
|
44088
|
+
const userId = str(body, "userId");
|
|
44089
|
+
const toTopic = str(body, "toTopic");
|
|
44090
|
+
const fromLabel = str(body, "fromLabel");
|
|
44091
|
+
const message = str(body, "message");
|
|
45431
44092
|
const depth = typeof body.depth === "number" ? body.depth : Number.NaN;
|
|
45432
44093
|
if (!requestId || !userId || !toTopic || !fromLabel || !message) {
|
|
45433
44094
|
return jsonError2("requestId, userId, toTopic, fromLabel, message are required", 400);
|
|
@@ -45441,7 +44102,7 @@ async function handleTell(req) {
|
|
|
45441
44102
|
return jsonError2(`tell depth limit exceeded (max ${MAX_TELL_DEPTH})`, 400);
|
|
45442
44103
|
}
|
|
45443
44104
|
const topic = getTopicByNameForUser(toTopic, userId);
|
|
45444
|
-
if (!topic || !
|
|
44105
|
+
if (!topic || !peerAddressable(topic)) {
|
|
45445
44106
|
return jsonError2(`shared topic "${toTopic}" not found on this node`, 404);
|
|
45446
44107
|
}
|
|
45447
44108
|
const claim = claimInboundPeerMessage({
|
|
@@ -45495,10 +44156,10 @@ async function handleSessions(req) {
|
|
|
45495
44156
|
const protocolError = checkProtocol(body);
|
|
45496
44157
|
if (protocolError)
|
|
45497
44158
|
return protocolError;
|
|
45498
|
-
const userId =
|
|
44159
|
+
const userId = str(body, "userId");
|
|
45499
44160
|
if (!userId)
|
|
45500
44161
|
return jsonError2("userId is required", 400);
|
|
45501
|
-
const topics = listTopics().filter((topic) => topic.kind !== "manager" && !topic.isSubagent &&
|
|
44162
|
+
const topics = listTopics().filter((topic) => topic.kind !== "manager" && !topic.isSubagent && peerAddressable(topic) && topic.participants.some((p) => p.userId === userId));
|
|
45502
44163
|
const titleCounts = new Map;
|
|
45503
44164
|
for (const topic of topics) {
|
|
45504
44165
|
const normalized = topic.title.toLowerCase();
|
|
@@ -45530,11 +44191,11 @@ async function handleAsk(req) {
|
|
|
45530
44191
|
const protocolError = checkProtocol(body);
|
|
45531
44192
|
if (protocolError)
|
|
45532
44193
|
return protocolError;
|
|
45533
|
-
const requestId =
|
|
45534
|
-
const userId =
|
|
45535
|
-
const toTopic =
|
|
45536
|
-
const fromLabel =
|
|
45537
|
-
const message =
|
|
44194
|
+
const requestId = str(body, "requestId");
|
|
44195
|
+
const userId = str(body, "userId");
|
|
44196
|
+
const toTopic = str(body, "toTopic");
|
|
44197
|
+
const fromLabel = str(body, "fromLabel");
|
|
44198
|
+
const message = str(body, "message");
|
|
45538
44199
|
const fromDepth = body.fromDepth === undefined ? 0 : typeof body.fromDepth === "number" ? body.fromDepth : Number.NaN;
|
|
45539
44200
|
const replyTo = body.replyTo;
|
|
45540
44201
|
const replyTopicId = typeof replyTo?.topicId === "string" ? replyTo.topicId : null;
|
|
@@ -45547,7 +44208,7 @@ async function handleAsk(req) {
|
|
|
45547
44208
|
return jsonError2("fromDepth must be a non-negative integer", 400);
|
|
45548
44209
|
}
|
|
45549
44210
|
const topic = getTopicByNameForUser(toTopic, userId);
|
|
45550
|
-
if (!topic || !
|
|
44211
|
+
if (!topic || !peerAddressable(topic)) {
|
|
45551
44212
|
return jsonError2(`shared topic "${toTopic}" not found on this node`, 404);
|
|
45552
44213
|
}
|
|
45553
44214
|
if (!topic.agent)
|
|
@@ -45600,10 +44261,10 @@ async function handleReply(req) {
|
|
|
45600
44261
|
const protocolError = checkProtocol(body);
|
|
45601
44262
|
if (protocolError)
|
|
45602
44263
|
return protocolError;
|
|
45603
|
-
const requestId =
|
|
45604
|
-
const userId =
|
|
44264
|
+
const requestId = str(body, "requestId");
|
|
44265
|
+
const userId = str(body, "userId");
|
|
45605
44266
|
const replyText = typeof body.replyText === "string" ? body.replyText : null;
|
|
45606
|
-
const fromLabel =
|
|
44267
|
+
const fromLabel = str(body, "fromLabel") ?? "peer";
|
|
45607
44268
|
const kind = body.kind === "error" ? "error" : "reply";
|
|
45608
44269
|
if (!requestId || !userId || replyText === null) {
|
|
45609
44270
|
return jsonError2("requestId, userId and replyText are required", 400);
|
|
@@ -45622,39 +44283,6 @@ async function handleReply(req) {
|
|
|
45622
44283
|
return jsonError2("ask callback delivery is retryable", 503);
|
|
45623
44284
|
return jsonError2("no pending ask for this requestId", 404);
|
|
45624
44285
|
}
|
|
45625
|
-
async function handleInputFile(req) {
|
|
45626
|
-
const peer = await requirePeer(req);
|
|
45627
|
-
if (!peer.ok)
|
|
45628
|
-
return peer.response;
|
|
45629
|
-
const originError = requirePrimaryOrigin(peer);
|
|
45630
|
-
if (originError)
|
|
45631
|
-
return originError;
|
|
45632
|
-
const contentLength = Number(req.headers.get("content-length"));
|
|
45633
|
-
if (Number.isFinite(contentLength) && contentLength > MAX_PEER_INPUT_REQUEST_BYTES) {
|
|
45634
|
-
return jsonError2("file too large", 413);
|
|
45635
|
-
}
|
|
45636
|
-
const form = await req.formData().catch(() => null);
|
|
45637
|
-
if (!form)
|
|
45638
|
-
return jsonError2("expected multipart/form-data", 400);
|
|
45639
|
-
const hostTopicId = form.get("hostTopicId");
|
|
45640
|
-
const userId = form.get("userId");
|
|
45641
|
-
const file = form.get("file");
|
|
45642
|
-
if (typeof hostTopicId !== "string" || typeof userId !== "string" || !(file instanceof File)) {
|
|
45643
|
-
return jsonError2("hostTopicId, userId, and file are required", 400);
|
|
45644
|
-
}
|
|
45645
|
-
if (file.size > MAX_PEER_INPUT_FILE_BYTES)
|
|
45646
|
-
return jsonError2("file too large", 413);
|
|
45647
|
-
const session = getPeerSession(peer.verified.fromCellId, hostTopicId);
|
|
45648
|
-
const topic = session ? getTopic(session.local_topic_id) : null;
|
|
45649
|
-
if (!session || !topic?.participants.some((participant) => participant.userId === userId)) {
|
|
45650
|
-
return jsonError2("provisioned peer room not found", 404);
|
|
45651
|
-
}
|
|
45652
|
-
const stored = await storePeerInputFile(file, {
|
|
45653
|
-
topicId: session.local_topic_id,
|
|
45654
|
-
ownerUserId: userId
|
|
45655
|
-
});
|
|
45656
|
-
return Response.json({ ok: true, fileId: stored.id });
|
|
45657
|
-
}
|
|
45658
44286
|
async function handleDeviceVault(req) {
|
|
45659
44287
|
const peer = await requirePeer(req);
|
|
45660
44288
|
if (!peer.ok)
|
|
@@ -45668,14 +44296,14 @@ async function handleDeviceVault(req) {
|
|
|
45668
44296
|
const protocolError = checkProtocol(body);
|
|
45669
44297
|
if (protocolError)
|
|
45670
44298
|
return protocolError;
|
|
45671
|
-
const userId =
|
|
45672
|
-
const operation =
|
|
44299
|
+
const userId = str(body, "userId");
|
|
44300
|
+
const operation = str(body, "operation");
|
|
45673
44301
|
if (!userId || !operation)
|
|
45674
44302
|
return jsonError2("userId and operation are required", 400);
|
|
45675
44303
|
if (operation === "list") {
|
|
45676
44304
|
return Response.json({ ok: true, entries: vaultList(userId) });
|
|
45677
44305
|
}
|
|
45678
|
-
const rawKey =
|
|
44306
|
+
const rawKey = str(body, "key");
|
|
45679
44307
|
if (!rawKey || !validateVaultKey(rawKey))
|
|
45680
44308
|
return jsonError2("invalid vault key", 400);
|
|
45681
44309
|
const key = normalizeVaultKey(rawKey);
|
|
@@ -45740,18 +44368,6 @@ async function handleOtiumPeerRequest(req) {
|
|
|
45740
44368
|
if (req.method !== "POST")
|
|
45741
44369
|
return jsonError2("not found", 404);
|
|
45742
44370
|
switch (path) {
|
|
45743
|
-
case "/api/v1/peer/provision":
|
|
45744
|
-
return handleProvision(req);
|
|
45745
|
-
case "/api/v1/peer/bind":
|
|
45746
|
-
return handleBind(req);
|
|
45747
|
-
case "/api/v1/peer/shared-topic/messages":
|
|
45748
|
-
return handleSharedTopicMessages(req);
|
|
45749
|
-
case "/api/v1/peer/shared-topics/private":
|
|
45750
|
-
return handleSharedTopicsPrivate(req);
|
|
45751
|
-
case "/api/v1/peer/unbind":
|
|
45752
|
-
return handleUnbind(req);
|
|
45753
|
-
case "/api/v1/peer/turn":
|
|
45754
|
-
return handleTurn(req);
|
|
45755
44371
|
case "/api/v1/peer/abort":
|
|
45756
44372
|
return handleAbort(req);
|
|
45757
44373
|
case "/api/v1/peer/tell":
|
|
@@ -45762,8 +44378,6 @@ async function handleOtiumPeerRequest(req) {
|
|
|
45762
44378
|
return handleSessions(req);
|
|
45763
44379
|
case "/api/v1/peer/reply":
|
|
45764
44380
|
return handleReply(req);
|
|
45765
|
-
case "/api/v1/peer/input-file":
|
|
45766
|
-
return handleInputFile(req);
|
|
45767
44381
|
case "/api/v1/peer/device-vault":
|
|
45768
44382
|
return handleDeviceVault(req);
|
|
45769
44383
|
default:
|
|
@@ -45773,15 +44387,11 @@ async function handleOtiumPeerRequest(req) {
|
|
|
45773
44387
|
var RUNTIME_VERSION;
|
|
45774
44388
|
var init_peer_server = __esm(async () => {
|
|
45775
44389
|
await init_src();
|
|
45776
|
-
await init_bindings();
|
|
45777
44390
|
await init_central();
|
|
45778
44391
|
await init_gateway_forward();
|
|
45779
|
-
await init_peer_files();
|
|
45780
44392
|
init_protocol();
|
|
45781
44393
|
await init_session_bridge();
|
|
45782
|
-
await init_shared_topic_sync();
|
|
45783
44394
|
await init_store2();
|
|
45784
|
-
await init_turn_bridge();
|
|
45785
44395
|
RUNTIME_VERSION = NEGOTIUM_VERSION;
|
|
45786
44396
|
});
|
|
45787
44397
|
|
|
@@ -45789,16 +44399,6 @@ var init_peer_server = __esm(async () => {
|
|
|
45789
44399
|
function startOtiumNodeRuntime(options) {
|
|
45790
44400
|
const { join: join43 } = options;
|
|
45791
44401
|
configureOtiumCentral(join43);
|
|
45792
|
-
const failed = failInterruptedPeerTurnRequestsOnStartup();
|
|
45793
|
-
if (failed > 0) {
|
|
45794
|
-
logger.warn({ failed }, "otium: failed interrupted peer turns from previous process");
|
|
45795
|
-
}
|
|
45796
|
-
const staleBindings = sweepStalePeerBindings((localTopicId) => getTopic(localTopicId) !== null);
|
|
45797
|
-
if (staleBindings.topicIds.length > 0) {
|
|
45798
|
-
logger.info({ topicIds: staleBindings.topicIds, ...staleBindings.removed }, "otium: removed peer state for local topics deleted while this node was offline");
|
|
45799
|
-
}
|
|
45800
|
-
const stopBackflow = startEventBackflow();
|
|
45801
|
-
const stopSharedTopicSync = startSharedTopicSync(join43);
|
|
45802
44402
|
const unregisterRuntimeBridge = registerPeerRuntimeBridge(otiumPeerRuntimeBridge);
|
|
45803
44403
|
const unregisterSessionBridge = registerPeerSessionBridge(otiumPeerSessionBridge);
|
|
45804
44404
|
const sessionBridgeIpc = startPeerSessionBridgeIpc(otiumPeerSessionBridge);
|
|
@@ -45814,7 +44414,7 @@ function startOtiumNodeRuntime(options) {
|
|
|
45814
44414
|
if (event.type !== "topic-deleted")
|
|
45815
44415
|
return;
|
|
45816
44416
|
const removed = cleanupPeerStateForLocalTopic(event.topicId);
|
|
45817
|
-
if (removed.
|
|
44417
|
+
if (removed.inboxRequests + removed.remoteAsks > 0) {
|
|
45818
44418
|
logger.info({ topicId: event.topicId, ...removed }, "otium: removed peer state for deleted local topic");
|
|
45819
44419
|
}
|
|
45820
44420
|
});
|
|
@@ -45841,8 +44441,6 @@ function startOtiumNodeRuntime(options) {
|
|
|
45841
44441
|
canonicalMcpBridge.stop();
|
|
45842
44442
|
stopPeerReplyOutbox();
|
|
45843
44443
|
uninstallFileHooks();
|
|
45844
|
-
stopBackflow();
|
|
45845
|
-
stopSharedTopicSync();
|
|
45846
44444
|
configureOtiumCentral(null);
|
|
45847
44445
|
}
|
|
45848
44446
|
};
|
|
@@ -45886,34 +44484,28 @@ var init_src8 = __esm(async () => {
|
|
|
45886
44484
|
await init_src();
|
|
45887
44485
|
await init_canonical_mcp_bridge();
|
|
45888
44486
|
await init_central();
|
|
45889
|
-
await init_event_backflow();
|
|
45890
44487
|
await init_join();
|
|
45891
44488
|
await init_peer_files();
|
|
45892
44489
|
await init_runtime_bridge();
|
|
45893
44490
|
await init_session_bridge();
|
|
45894
44491
|
init_session_bridge_ipc();
|
|
45895
|
-
await init_shared_topic_sync();
|
|
45896
44492
|
await init_store2();
|
|
45897
44493
|
init_tunnel_client();
|
|
45898
|
-
await init_bindings();
|
|
45899
44494
|
await init_central();
|
|
45900
44495
|
await init_enrollment();
|
|
45901
|
-
await init_event_backflow();
|
|
45902
44496
|
await init_join();
|
|
45903
44497
|
await init_peer_server();
|
|
45904
44498
|
init_protocol();
|
|
45905
44499
|
init_relay_protocol();
|
|
45906
44500
|
await init_runtime_bridge();
|
|
45907
|
-
await init_shared_topic_sync();
|
|
45908
44501
|
await init_store2();
|
|
45909
44502
|
init_tunnel_client();
|
|
45910
|
-
await init_turn_bridge();
|
|
45911
44503
|
otiumAdapter = defineNegotiumAdapter({
|
|
45912
44504
|
name: "otium",
|
|
45913
44505
|
capabilities: {
|
|
45914
44506
|
localUserInput: false,
|
|
45915
44507
|
topicManagement: false,
|
|
45916
|
-
externalPlacedTurn:
|
|
44508
|
+
externalPlacedTurn: false
|
|
45917
44509
|
},
|
|
45918
44510
|
projection: {
|
|
45919
44511
|
transcript: "full",
|
|
@@ -45931,7 +44523,7 @@ __export(exports_node_runtime, {
|
|
|
45931
44523
|
handleOtiumAdapterControlRequest: () => handleOtiumAdapterControlRequest,
|
|
45932
44524
|
OTIUM_ADAPTER_CONTROL_PREFIX: () => OTIUM_ADAPTER_CONTROL_PREFIX,
|
|
45933
44525
|
OTIUM_ADAPTER_CONTROL_HEADER: () => OTIUM_ADAPTER_CONTROL_HEADER,
|
|
45934
|
-
|
|
44526
|
+
MAX_PEER_REQUEST_BODY_BYTES: () => MAX_PEER_REQUEST_BODY_BYTES
|
|
45935
44527
|
});
|
|
45936
44528
|
async function handleOtiumAdapterControlRequest(req) {
|
|
45937
44529
|
const url = new URL(req.url);
|
|
@@ -46131,7 +44723,7 @@ async function runOtiumSidecar(options) {
|
|
|
46131
44723
|
port: options.port,
|
|
46132
44724
|
hostname: "127.0.0.1",
|
|
46133
44725
|
idleTimeout: 240,
|
|
46134
|
-
maxRequestBodySize:
|
|
44726
|
+
maxRequestBodySize: MAX_PEER_REQUEST_BODY_BYTES,
|
|
46135
44727
|
fetch: (req) => proxyOtiumPeerRequest(req)
|
|
46136
44728
|
});
|
|
46137
44729
|
} catch (error2) {
|
|
@@ -46223,27 +44815,6 @@ function parseOtiumServeRelayUrl(args) {
|
|
|
46223
44815
|
}
|
|
46224
44816
|
return raw.replace(/\/+$/, "");
|
|
46225
44817
|
}
|
|
46226
|
-
async function resolveHostNodeId(explicit) {
|
|
46227
|
-
if (explicit?.trim())
|
|
46228
|
-
return explicit.trim();
|
|
46229
|
-
const [{ configureOtiumCentral: configureOtiumCentral2, listPeerNodes: listPeerNodes2 }, { loadJoin: loadJoin2 }] = await Promise.all([
|
|
46230
|
-
init_central().then(() => exports_central),
|
|
46231
|
-
init_join().then(() => exports_join)
|
|
46232
|
-
]);
|
|
46233
|
-
const join43 = loadJoin2();
|
|
46234
|
-
if (!join43)
|
|
46235
|
-
throw new Error("not joined to an Otium workspace; pass --host-node or join first");
|
|
46236
|
-
configureOtiumCentral2(join43);
|
|
46237
|
-
try {
|
|
46238
|
-
const nodes = await listPeerNodes2({ fresh: true });
|
|
46239
|
-
const primary = nodes.find((node) => node.isPrimary && !node.self) ?? nodes.find((node) => node.isPrimary);
|
|
46240
|
-
if (!primary)
|
|
46241
|
-
throw new Error("workspace has no primary Otium node");
|
|
46242
|
-
return primary.cellId;
|
|
46243
|
-
} finally {
|
|
46244
|
-
configureOtiumCentral2(null);
|
|
46245
|
-
}
|
|
46246
|
-
}
|
|
46247
44818
|
async function spawnCanonicalNode2() {
|
|
46248
44819
|
const entry = process.argv[1];
|
|
46249
44820
|
if (!entry)
|
|
@@ -46275,11 +44846,11 @@ async function runCanonicalNodeChild2() {
|
|
|
46275
44846
|
let maxRequestBodySize;
|
|
46276
44847
|
if (hasConfiguredOtiumJoin2()) {
|
|
46277
44848
|
const { onShutdown: onShutdown2 } = await init_node_host().then(() => exports_node_host);
|
|
46278
|
-
const {
|
|
44849
|
+
const { MAX_PEER_REQUEST_BODY_BYTES: MAX_PEER_REQUEST_BODY_BYTES2, mountConfiguredOtiumNodeRuntime: mountConfiguredOtiumNodeRuntime2 } = await init_node_runtime().then(() => exports_node_runtime);
|
|
46279
44850
|
const runtime2 = mountConfiguredOtiumNodeRuntime2();
|
|
46280
44851
|
if (runtime2)
|
|
46281
44852
|
onShutdown2("otium-node-runtime", 125, () => runtime2.stop());
|
|
46282
|
-
maxRequestBodySize =
|
|
44853
|
+
maxRequestBodySize = MAX_PEER_REQUEST_BODY_BYTES2;
|
|
46283
44854
|
}
|
|
46284
44855
|
await runNodeDaemon2({ port: 0, ...maxRequestBodySize ? { maxRequestBodySize } : {} });
|
|
46285
44856
|
}
|
|
@@ -46301,20 +44872,29 @@ async function runOtiumCli(args = process.argv.slice(2)) {
|
|
|
46301
44872
|
if (process.env.OTIUM_CENTRAL_URL || process.env.OTIUM_CELL_ID || process.env.OTIUM_CELL_SECRET) {
|
|
46302
44873
|
throw new Error("Otium join is configured by environment; remove OTIUM_CENTRAL_URL, OTIUM_CELL_ID, and OTIUM_CELL_SECRET to disconnect");
|
|
46303
44874
|
}
|
|
46304
|
-
const { configureOtiumCentral: configureOtiumCentral2 } = await init_central().then(() => exports_central);
|
|
46305
44875
|
const { loadJoin: loadJoin2, removeJoin: removeJoin2 } = await init_join().then(() => exports_join);
|
|
46306
|
-
|
|
46307
|
-
const join43 = loadJoin2();
|
|
46308
|
-
if (!join43)
|
|
44876
|
+
if (!loadJoin2())
|
|
46309
44877
|
throw new Error("not joined to an Otium workspace");
|
|
46310
|
-
|
|
46311
|
-
|
|
46312
|
-
|
|
46313
|
-
|
|
46314
|
-
|
|
46315
|
-
|
|
44878
|
+
const { getVisibleTopics: getVisibleTopics2, isTopicShared: isTopicShared2, switchTopicAccessMode: switchTopicAccessMode2 } = await init_src().then(() => exports_src);
|
|
44879
|
+
let downgraded = 0;
|
|
44880
|
+
for (const topic of getVisibleTopics2()) {
|
|
44881
|
+
if (!isTopicShared2(topic))
|
|
44882
|
+
continue;
|
|
44883
|
+
const owner = topic.participants.find((participant) => participant.role === "owner");
|
|
44884
|
+
if (!owner)
|
|
44885
|
+
continue;
|
|
44886
|
+
if (topic.isSubagent)
|
|
44887
|
+
continue;
|
|
44888
|
+
const switched = switchTopicAccessMode2({
|
|
44889
|
+
topicId: topic.id,
|
|
44890
|
+
userId: owner.userId,
|
|
44891
|
+
accessMode: "private"
|
|
44892
|
+
});
|
|
44893
|
+
if (switched.ok)
|
|
44894
|
+
downgraded += switched.topicIds.length;
|
|
46316
44895
|
}
|
|
46317
|
-
|
|
44896
|
+
removeJoin2();
|
|
44897
|
+
console.log(`disconnected from Otium; workspace credentials removed` + (downgraded > 0 ? `; ${downgraded} topic(s) are now private` : ""));
|
|
46318
44898
|
break;
|
|
46319
44899
|
}
|
|
46320
44900
|
case "serve": {
|
|
@@ -46326,63 +44906,19 @@ async function runOtiumCli(args = process.argv.slice(2)) {
|
|
|
46326
44906
|
await runOtiumSidecar2({ port, relayUrl });
|
|
46327
44907
|
break;
|
|
46328
44908
|
}
|
|
46329
|
-
case "bindings": {
|
|
46330
|
-
const { listOtiumTopicBindings: listOtiumTopicBindings2 } = await init_bindings().then(() => exports_bindings);
|
|
46331
|
-
const bindings = listOtiumTopicBindings2();
|
|
46332
|
-
if (bindings.length === 0) {
|
|
46333
|
-
console.log("no Otium topic bindings");
|
|
46334
|
-
break;
|
|
46335
|
-
}
|
|
46336
|
-
for (const binding of bindings) {
|
|
46337
|
-
const local = binding.localTopicTitle ? `${binding.localTopicTitle} (${binding.localTopicId})` : `${binding.localTopicId} [missing]`;
|
|
46338
|
-
console.log(`${binding.transport.padEnd(16)} ${binding.hostNodeId}/${binding.hostTopicId} -> ${local}`);
|
|
46339
|
-
}
|
|
46340
|
-
break;
|
|
46341
|
-
}
|
|
46342
|
-
case "share": {
|
|
46343
|
-
const parsed = parseArgs(commandArgs);
|
|
46344
|
-
const [hostTopicId, localTopicId] = parsed.positional;
|
|
46345
|
-
const userId = parsed.options.get("user")?.trim();
|
|
46346
|
-
if (!hostTopicId || !localTopicId || !userId) {
|
|
46347
|
-
throw new Error("usage: negotium otium share <host-topic-id> <local-topic-id> --user <user-id> [--host-node <cell-id>]");
|
|
46348
|
-
}
|
|
46349
|
-
const hostNodeId = await resolveHostNodeId(parsed.options.get("host-node"));
|
|
46350
|
-
const { shareOtiumTopic: shareOtiumTopic2 } = await init_bindings().then(() => exports_bindings);
|
|
46351
|
-
const result = shareOtiumTopic2({ hostNodeId, hostTopicId, localTopicId, userId });
|
|
46352
|
-
if (!result.ok)
|
|
46353
|
-
throw new Error(result.error);
|
|
46354
|
-
console.log(`shared ${hostNodeId}/${hostTopicId} with local topic ${result.localTopicId}` + (result.replaced ? " (replaced previous binding)" : ""));
|
|
46355
|
-
break;
|
|
46356
|
-
}
|
|
46357
|
-
case "private": {
|
|
46358
|
-
const parsed = parseArgs(commandArgs);
|
|
46359
|
-
const [localTopicId] = parsed.positional;
|
|
46360
|
-
const userId = parsed.options.get("user")?.trim();
|
|
46361
|
-
if (!localTopicId || !userId) {
|
|
46362
|
-
throw new Error("usage: negotium otium private <local-topic-id> --user <user-id>");
|
|
46363
|
-
}
|
|
46364
|
-
const { setOtiumTopicPrivate: setOtiumTopicPrivate2 } = await init_bindings().then(() => exports_bindings);
|
|
46365
|
-
const result = setOtiumTopicPrivate2({ localTopicId, userId });
|
|
46366
|
-
if (!result.ok)
|
|
46367
|
-
throw new Error(result.error);
|
|
46368
|
-
console.log(`private mode selected for ${result.localTopicId}; removed ${result.removedBindings} Otium binding(s)`);
|
|
46369
|
-
break;
|
|
46370
|
-
}
|
|
46371
44909
|
default: {
|
|
46372
44910
|
console.log([
|
|
46373
44911
|
"negotium otium \u2014 attach a Negotium node to an Otium workspace",
|
|
46374
44912
|
"",
|
|
46375
|
-
"usage: negotium otium <join|leave|serve
|
|
44913
|
+
"usage: negotium otium <join|leave|serve> [args]",
|
|
46376
44914
|
"",
|
|
46377
44915
|
" join <code> store credentials from an Otium invite code",
|
|
46378
|
-
" leave
|
|
44916
|
+
" leave remove the stored workspace credentials",
|
|
46379
44917
|
" serve [--port <port>] [--relay <url>]",
|
|
46380
44918
|
" run peer routes and an outbound relay tunnel",
|
|
46381
|
-
"
|
|
46382
|
-
"
|
|
46383
|
-
"
|
|
46384
|
-
" private <local-topic> --user <id>",
|
|
46385
|
-
" remove all Otium bindings; keep Terminal/Telegram access"
|
|
44919
|
+
"",
|
|
44920
|
+
"Publish a topic to the workspace with /public in that topic; the hub",
|
|
44921
|
+
"discovers it over the Runtime Gateway. /private withdraws it."
|
|
46386
44922
|
].join(`
|
|
46387
44923
|
`));
|
|
46388
44924
|
if (command && command !== "help" && command !== "--help")
|
|
@@ -46898,8 +45434,8 @@ var CLI_COMMANDS = [
|
|
|
46898
45434
|
},
|
|
46899
45435
|
{
|
|
46900
45436
|
name: "otium",
|
|
46901
|
-
usage: "otium join|
|
|
46902
|
-
description: "manage the Otium workspace connection
|
|
45437
|
+
usage: "otium join|leave|serve",
|
|
45438
|
+
description: "manage the Otium workspace connection",
|
|
46903
45439
|
group: "Channels"
|
|
46904
45440
|
}
|
|
46905
45441
|
];
|
|
@@ -46939,11 +45475,11 @@ async function runCanonicalNode(port) {
|
|
|
46939
45475
|
let maxRequestBodySize;
|
|
46940
45476
|
if (hasConfiguredOtiumJoin2()) {
|
|
46941
45477
|
const { onShutdown: onShutdown2 } = await init_node_host().then(() => exports_node_host);
|
|
46942
|
-
const {
|
|
45478
|
+
const { MAX_PEER_REQUEST_BODY_BYTES: MAX_PEER_REQUEST_BODY_BYTES2, mountConfiguredOtiumNodeRuntime: mountConfiguredOtiumNodeRuntime2 } = await init_node_runtime().then(() => exports_node_runtime);
|
|
46943
45479
|
const otiumRuntime = mountConfiguredOtiumNodeRuntime2();
|
|
46944
45480
|
if (otiumRuntime)
|
|
46945
45481
|
onShutdown2("otium-node-runtime", 125, () => otiumRuntime.stop());
|
|
46946
|
-
maxRequestBodySize =
|
|
45482
|
+
maxRequestBodySize = MAX_PEER_REQUEST_BODY_BYTES2;
|
|
46947
45483
|
}
|
|
46948
45484
|
const node = await startDefaultNode2({
|
|
46949
45485
|
port,
|
|
@@ -47084,4 +45620,4 @@ switch (command) {
|
|
|
47084
45620
|
}
|
|
47085
45621
|
}
|
|
47086
45622
|
|
|
47087
|
-
//# debugId=
|
|
45623
|
+
//# debugId=B66F9D7F6CCE88E564756E2164756E21
|