negotium 0.1.13 → 0.1.14
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/cron.js +2 -2
- package/dist/cron.js.map +5 -5
- package/dist/hosted-agent.js +2 -2
- package/dist/hosted-agent.js.map +2 -2
- package/dist/main.js +831 -596
- package/dist/main.js.map +20 -17
- package/dist/runtime/src/index.ts +1 -0
- package/dist/runtime/src/storage/runtime-process-leases.ts +18 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -3324,7 +3324,7 @@ var init_claude_provider = __esm(async () => {
|
|
|
3324
3324
|
});
|
|
3325
3325
|
|
|
3326
3326
|
// ../../packages/core/src/version.ts
|
|
3327
|
-
var NEGOTIUM_VERSION = "0.1.
|
|
3327
|
+
var NEGOTIUM_VERSION = "0.1.14";
|
|
3328
3328
|
|
|
3329
3329
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
3330
3330
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -14799,6 +14799,10 @@ function getRuntimeProcessLease(role, now = Date.now(), staleMs = PROCESS_LEASE_
|
|
|
14799
14799
|
const lease = rowToLease2(row);
|
|
14800
14800
|
return now - lease.heartbeatAt <= staleMs ? lease : null;
|
|
14801
14801
|
}
|
|
14802
|
+
function listRuntimeProcessLeases(rolePrefix = "", now = Date.now(), staleMs = PROCESS_LEASE_STALE_MS) {
|
|
14803
|
+
const rows = rolePrefix ? db.query("SELECT * FROM runtime_process_leases WHERE role LIKE ? ORDER BY role").all(`${rolePrefix}%`) : db.query("SELECT * FROM runtime_process_leases ORDER BY role").all();
|
|
14804
|
+
return rows.map(rowToLease2).filter((lease) => now - lease.heartbeatAt <= staleMs);
|
|
14805
|
+
}
|
|
14802
14806
|
function heartbeatRuntimeProcessLease(role, ownerId, now = Date.now()) {
|
|
14803
14807
|
const result = db.query(`UPDATE runtime_process_leases
|
|
14804
14808
|
SET heartbeat_at = ?
|
|
@@ -16016,6 +16020,7 @@ __export(exports_src, {
|
|
|
16016
16020
|
listVaultEntries: () => listVaultEntries,
|
|
16017
16021
|
listTopics: () => listTopics,
|
|
16018
16022
|
listRuntimeTurnLeases: () => listRuntimeTurnLeases,
|
|
16023
|
+
listRuntimeProcessLeases: () => listRuntimeProcessLeases,
|
|
16019
16024
|
listRuntimeEventsAfter: () => listRuntimeEventsAfter,
|
|
16020
16025
|
listRunningTopicQueries: () => listRunningTopicQueries,
|
|
16021
16026
|
listRunningTopicIds: () => listRunningTopicIds,
|
|
@@ -17852,14 +17857,17 @@ function createNodeControlHandler(options) {
|
|
|
17852
17857
|
const topic = topicForUser(topicId, userId);
|
|
17853
17858
|
if (!topic)
|
|
17854
17859
|
return jsonError(404, "Topic not found");
|
|
17855
|
-
const
|
|
17860
|
+
const sourceAdapter = body.sourceAdapter === "telegram" || body.sourceAdapter === "otium" ? body.sourceAdapter : "terminal";
|
|
17861
|
+
const { message, queryId } = submitUserMessage({
|
|
17856
17862
|
topic,
|
|
17857
17863
|
userId,
|
|
17858
17864
|
text: text2,
|
|
17859
|
-
sourceAdapter
|
|
17865
|
+
sourceAdapter,
|
|
17866
|
+
visualTools: body.visualTools === true,
|
|
17867
|
+
fileDeliveryTools: body.fileDeliveryTools === true,
|
|
17860
17868
|
startTurn: options.startTurn
|
|
17861
17869
|
});
|
|
17862
|
-
return Response.json({ ok: true, message }, { status: 201 });
|
|
17870
|
+
return Response.json({ ok: true, message, queryId }, { status: 201 });
|
|
17863
17871
|
}
|
|
17864
17872
|
const recentMatch = path.match(/^\/topics\/([^/]+)\/recent-events$/);
|
|
17865
17873
|
if (recentMatch && req.method === "GET") {
|
|
@@ -20013,6 +20021,7 @@ async function startDefaultNode(opts = {}) {
|
|
|
20013
20021
|
async function runNodeDaemon(opts = {}) {
|
|
20014
20022
|
const node = await startDefaultNode({
|
|
20015
20023
|
port: opts.port ?? 0,
|
|
20024
|
+
...opts.maxRequestBodySize ? { maxRequestBodySize: opts.maxRequestBodySize } : {},
|
|
20016
20025
|
advertise: true,
|
|
20017
20026
|
singleton: true
|
|
20018
20027
|
});
|
|
@@ -20629,7 +20638,7 @@ var MAX_CLIPBOARD_BYTES = 1e5;
|
|
|
20629
20638
|
|
|
20630
20639
|
// ../../adapters/terminal/src/clock.ts
|
|
20631
20640
|
function terminalNowMs() {
|
|
20632
|
-
return
|
|
20641
|
+
return Date.now();
|
|
20633
20642
|
}
|
|
20634
20643
|
|
|
20635
20644
|
// ../../adapters/terminal/src/commands.ts
|
|
@@ -25688,13 +25697,25 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
|
|
|
25688
25697
|
const rememberTarget = (queryId2) => {
|
|
25689
25698
|
targetByQueryId.set(queryId2, target);
|
|
25690
25699
|
};
|
|
25691
|
-
const
|
|
25700
|
+
const input = {
|
|
25692
25701
|
topic,
|
|
25693
25702
|
userId,
|
|
25694
25703
|
text: prompt,
|
|
25695
25704
|
sourceAdapter: "telegram",
|
|
25696
25705
|
visualTools: false,
|
|
25697
|
-
fileDeliveryTools: true
|
|
25706
|
+
fileDeliveryTools: true
|
|
25707
|
+
};
|
|
25708
|
+
if (opts.submitTurn) {
|
|
25709
|
+
opts.submitTurn(input).then(({ queryId: queryId2 }) => {
|
|
25710
|
+
if (queryId2)
|
|
25711
|
+
rememberTarget(queryId2);
|
|
25712
|
+
}).catch((error) => {
|
|
25713
|
+
logger.error({ err: error, topicId: topic.id }, "telegram adapter: remote turn failed");
|
|
25714
|
+
});
|
|
25715
|
+
return;
|
|
25716
|
+
}
|
|
25717
|
+
const { queryId } = submitUserMessage({
|
|
25718
|
+
...input,
|
|
25698
25719
|
onDispatched: rememberTarget,
|
|
25699
25720
|
startTurn: dispatchTurn
|
|
25700
25721
|
});
|
|
@@ -25883,7 +25904,8 @@ ${caption}` : voiceText : caption;
|
|
|
25883
25904
|
}
|
|
25884
25905
|
case "/abort": {
|
|
25885
25906
|
const mapping = byKey.get(mappingKey(chatId, threadId));
|
|
25886
|
-
|
|
25907
|
+
const aborted = mapping ? await (opts.abortTurn?.(mapping.topicId, userId) ?? topicService.abortTurn(mapping.topicId, userId)) : false;
|
|
25908
|
+
reply(chatId, threadId, aborted ? "aborted" : "nothing running");
|
|
25887
25909
|
return;
|
|
25888
25910
|
}
|
|
25889
25911
|
case "/agent": {
|
|
@@ -26260,7 +26282,7 @@ __export(exports_cli2, {
|
|
|
26260
26282
|
runTelegramCli: () => runTelegramCli
|
|
26261
26283
|
});
|
|
26262
26284
|
import TelegramBot from "node-telegram-bot-api";
|
|
26263
|
-
function startTelegramFromEnv() {
|
|
26285
|
+
function startTelegramFromEnv(options = {}) {
|
|
26264
26286
|
const token = process.env.TELEGRAM_BOT_TOKEN?.trim();
|
|
26265
26287
|
if (!token)
|
|
26266
26288
|
throw new Error("TELEGRAM_BOT_TOKEN is required");
|
|
@@ -26268,6 +26290,7 @@ function startTelegramFromEnv() {
|
|
|
26268
26290
|
const requestedAgent = process.env.TELEGRAM_DEFAULT_AGENT?.trim();
|
|
26269
26291
|
const forumChatId = Number.parseInt(process.env.TELEGRAM_FORUM_CHAT_ID ?? "", 10);
|
|
26270
26292
|
const adapter = startTelegramAdapter({
|
|
26293
|
+
...options,
|
|
26271
26294
|
client: bot,
|
|
26272
26295
|
allowedUsers: (process.env.TELEGRAM_ALLOWED_USERS ?? "").split(",").map((value) => value.trim()).filter(Boolean),
|
|
26273
26296
|
...process.env.TELEGRAM_VAULT_OWNER_USER_ID?.trim() ? { vaultOwnerTelegramUserId: process.env.TELEGRAM_VAULT_OWNER_USER_ID.trim() } : {},
|
|
@@ -26288,38 +26311,101 @@ function startTelegramFromEnv() {
|
|
|
26288
26311
|
}
|
|
26289
26312
|
};
|
|
26290
26313
|
}
|
|
26291
|
-
async function
|
|
26314
|
+
async function spawnCanonicalNode() {
|
|
26315
|
+
const entry = process.argv[1];
|
|
26316
|
+
if (!entry)
|
|
26317
|
+
throw new Error("cannot locate the Negotium CLI entrypoint");
|
|
26318
|
+
const child = Bun.spawn({
|
|
26319
|
+
cmd: [process.execPath, entry, "__node-daemon", "--port=0"],
|
|
26320
|
+
detached: true,
|
|
26321
|
+
env: { ...process.env, LOG_LEVEL: process.env.NEGOTIUM_NODE_LOG_LEVEL?.trim() || "info" },
|
|
26322
|
+
stdin: "ignore",
|
|
26323
|
+
stdout: "ignore",
|
|
26324
|
+
stderr: Bun.file(`${LOG_DIR}/node-daemon.log`)
|
|
26325
|
+
});
|
|
26326
|
+
child.unref();
|
|
26327
|
+
}
|
|
26328
|
+
async function ensureCanonicalNode() {
|
|
26329
|
+
const status = await inspectNodeDaemon();
|
|
26330
|
+
if (!status.running)
|
|
26331
|
+
await spawnCanonicalNode();
|
|
26332
|
+
return waitForNodeDaemon(15000);
|
|
26333
|
+
}
|
|
26334
|
+
async function runCanonicalNodeChild() {
|
|
26335
|
+
const { runNodeDaemon: runNodeDaemon2 } = await init_src5().then(() => exports_src3);
|
|
26336
|
+
await runNodeDaemon2({ port: 0 });
|
|
26337
|
+
}
|
|
26338
|
+
async function runTelegramCli(args = process.argv.slice(2)) {
|
|
26339
|
+
if (args[0] === "__node-daemon") {
|
|
26340
|
+
await runCanonicalNodeChild();
|
|
26341
|
+
return;
|
|
26342
|
+
}
|
|
26292
26343
|
if (!process.env.TELEGRAM_BOT_TOKEN?.trim()) {
|
|
26293
26344
|
throw new Error("TELEGRAM_BOT_TOKEN is required");
|
|
26294
26345
|
}
|
|
26295
|
-
|
|
26346
|
+
const initialNode = await ensureCanonicalNode();
|
|
26296
26347
|
const singleton = await waitForRequiredRuntimeProcessLease("adapter:telegram", {
|
|
26297
26348
|
workloadName: "Telegram adapter",
|
|
26298
26349
|
onLost: () => {
|
|
26299
26350
|
process.stderr.write(`negotium-telegram: singleton lease lost; shutting down
|
|
26300
26351
|
`);
|
|
26301
|
-
|
|
26352
|
+
runShutdown("test");
|
|
26302
26353
|
}
|
|
26303
26354
|
});
|
|
26304
|
-
try {
|
|
26305
|
-
node = await startDefaultNode({ port: 0 });
|
|
26306
|
-
} catch (error) {
|
|
26307
|
-
singleton.stop();
|
|
26308
|
-
throw error;
|
|
26309
|
-
}
|
|
26310
26355
|
let channel;
|
|
26311
26356
|
try {
|
|
26312
|
-
channel = startTelegramFromEnv(
|
|
26357
|
+
channel = startTelegramFromEnv({
|
|
26358
|
+
abortTurn: async (topicId, userId) => {
|
|
26359
|
+
const node = await waitForNodeDaemon(1500);
|
|
26360
|
+
const response = await fetch(`${node.baseUrl}${NODE_CONTROL_BASE_PATH}/topics/${encodeURIComponent(topicId)}/abort`, {
|
|
26361
|
+
method: "POST",
|
|
26362
|
+
headers: {
|
|
26363
|
+
authorization: `Bearer ${NODE_CONTROL_TOKEN}`,
|
|
26364
|
+
"content-type": "application/json"
|
|
26365
|
+
},
|
|
26366
|
+
body: JSON.stringify({ userId })
|
|
26367
|
+
});
|
|
26368
|
+
const body = await response.json();
|
|
26369
|
+
if (!response.ok)
|
|
26370
|
+
throw new Error(body.error ?? `node returned HTTP ${response.status}`);
|
|
26371
|
+
return body.aborted === true;
|
|
26372
|
+
},
|
|
26373
|
+
submitTurn: async (input) => {
|
|
26374
|
+
const node = await waitForNodeDaemon(1500);
|
|
26375
|
+
const response = await fetch(`${node.baseUrl}${NODE_CONTROL_BASE_PATH}/topics/${encodeURIComponent(input.topic.id)}/messages`, {
|
|
26376
|
+
method: "POST",
|
|
26377
|
+
headers: {
|
|
26378
|
+
authorization: `Bearer ${NODE_CONTROL_TOKEN}`,
|
|
26379
|
+
"content-type": "application/json"
|
|
26380
|
+
},
|
|
26381
|
+
body: JSON.stringify({
|
|
26382
|
+
userId: input.userId,
|
|
26383
|
+
text: input.text,
|
|
26384
|
+
sourceAdapter: input.sourceAdapter,
|
|
26385
|
+
visualTools: input.visualTools,
|
|
26386
|
+
fileDeliveryTools: input.fileDeliveryTools
|
|
26387
|
+
})
|
|
26388
|
+
});
|
|
26389
|
+
const body = await response.json();
|
|
26390
|
+
if (!response.ok)
|
|
26391
|
+
throw new Error(body.error ?? `node returned HTTP ${response.status}`);
|
|
26392
|
+
return { queryId: body.queryId };
|
|
26393
|
+
}
|
|
26394
|
+
});
|
|
26313
26395
|
} catch (error) {
|
|
26314
|
-
await node.stop();
|
|
26315
26396
|
singleton.stop();
|
|
26316
26397
|
throw error;
|
|
26317
26398
|
}
|
|
26318
26399
|
onShutdown("telegram-channel", 100, () => channel.stop());
|
|
26319
26400
|
onShutdown("telegram-singleton", 90, () => singleton.stop());
|
|
26320
|
-
|
|
26401
|
+
let resolveCompleted;
|
|
26402
|
+
const completed = new Promise((resolve13) => {
|
|
26403
|
+
resolveCompleted = resolve13;
|
|
26404
|
+
});
|
|
26405
|
+
onShutdown("telegram-completed", -100, resolveCompleted);
|
|
26406
|
+
process.stdout.write(`negotium Telegram adapter connected to canonical node pid ${initialNode.info?.pid ?? "unknown"}
|
|
26321
26407
|
`);
|
|
26322
|
-
await
|
|
26408
|
+
await completed;
|
|
26323
26409
|
}
|
|
26324
26410
|
var init_cli2 = __esm(async () => {
|
|
26325
26411
|
await init_src();
|
|
@@ -26328,37 +26414,6 @@ var init_cli2 = __esm(async () => {
|
|
|
26328
26414
|
if (false) {}
|
|
26329
26415
|
});
|
|
26330
26416
|
|
|
26331
|
-
// ../../adapters/otium/src/protocol.ts
|
|
26332
|
-
function str(body, field) {
|
|
26333
|
-
const value = body[field];
|
|
26334
|
-
return typeof value === "string" && value.trim() ? value : null;
|
|
26335
|
-
}
|
|
26336
|
-
function parseExecutionSpec(value) {
|
|
26337
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
26338
|
-
return null;
|
|
26339
|
-
const raw = value;
|
|
26340
|
-
const agent = str(raw, "agent");
|
|
26341
|
-
const model = str(raw, "model");
|
|
26342
|
-
const effort = str(raw, "effort");
|
|
26343
|
-
const rawMcp = raw.mcp;
|
|
26344
|
-
if (!agent || !model || !effort || !Array.isArray(rawMcp) || !rawMcp.every((entry) => typeof entry === "string" && entry.trim().length > 0) || typeof raw.canSpawnSubagents !== "boolean") {
|
|
26345
|
-
return null;
|
|
26346
|
-
}
|
|
26347
|
-
return {
|
|
26348
|
-
agent,
|
|
26349
|
-
model,
|
|
26350
|
-
effort,
|
|
26351
|
-
...str(raw, "description") ? { description: str(raw, "description") } : {},
|
|
26352
|
-
mcp: [...new Set(rawMcp.map((entry) => entry.trim()))],
|
|
26353
|
-
canSpawnSubagents: raw.canSpawnSubagents
|
|
26354
|
-
};
|
|
26355
|
-
}
|
|
26356
|
-
var PEER_PROTOCOL_VERSION = 1, MAX_PEER_MESSAGE_LENGTH = 1e4, MAX_PEER_INPUT_FILE_BYTES, MAX_PEER_INPUT_REQUEST_BYTES;
|
|
26357
|
-
var init_protocol = __esm(() => {
|
|
26358
|
-
MAX_PEER_INPUT_FILE_BYTES = 2 * 1024 * 1024 * 1024;
|
|
26359
|
-
MAX_PEER_INPUT_REQUEST_BYTES = MAX_PEER_INPUT_FILE_BYTES + 8 * 1024 * 1024;
|
|
26360
|
-
});
|
|
26361
|
-
|
|
26362
26417
|
// ../../adapters/otium/src/central.ts
|
|
26363
26418
|
var exports_central = {};
|
|
26364
26419
|
__export(exports_central, {
|
|
@@ -26812,403 +26867,96 @@ var init_join = __esm(async () => {
|
|
|
26812
26867
|
await init_src();
|
|
26813
26868
|
});
|
|
26814
26869
|
|
|
26815
|
-
// ../../adapters/otium/src/
|
|
26816
|
-
|
|
26817
|
-
|
|
26818
|
-
|
|
26819
|
-
|
|
26820
|
-
|
|
26821
|
-
|
|
26822
|
-
|
|
26823
|
-
|
|
26824
|
-
|
|
26825
|
-
|
|
26826
|
-
|
|
26827
|
-
|
|
26828
|
-
|
|
26829
|
-
|
|
26830
|
-
|
|
26831
|
-
|
|
26832
|
-
|
|
26833
|
-
readFileSync as readFileSync21,
|
|
26834
|
-
renameSync as renameSync14,
|
|
26835
|
-
unlinkSync as unlinkSync19,
|
|
26836
|
-
writeFileSync as writeFileSync19
|
|
26837
|
-
} from "fs";
|
|
26838
|
-
import { dirname as dirname18, resolve as resolve14 } from "path";
|
|
26839
|
-
function parseEnrollmentInvite(code) {
|
|
26840
|
-
let parsed;
|
|
26841
|
-
try {
|
|
26842
|
-
parsed = JSON.parse(Buffer.from(code.trim(), "base64url").toString("utf8"));
|
|
26843
|
-
} catch {
|
|
26844
|
-
throw new Error("production invite must be base64url JSON");
|
|
26845
|
-
}
|
|
26846
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
26847
|
-
throw new Error("production invite must decode to an object");
|
|
26848
|
-
}
|
|
26849
|
-
const raw = parsed;
|
|
26850
|
-
const central = typeof raw.central === "string" ? raw.central.trim().replace(/\/+$/, "") : "";
|
|
26851
|
-
const token = typeof raw.token === "string" ? raw.token.trim() : "";
|
|
26852
|
-
if (raw.v !== 2 || !central || !token.startsWith("nei_")) {
|
|
26853
|
-
throw new Error("production invite requires {v:2, central:http(s), token:nei_\u2026}");
|
|
26870
|
+
// ../../adapters/otium/src/control-protocol.ts
|
|
26871
|
+
var OTIUM_ADAPTER_CONTROL_PREFIX = "/api/v1/adapter/otium", OTIUM_ADAPTER_CONTROL_HEADER = "x-negotium-adapter-token";
|
|
26872
|
+
|
|
26873
|
+
// ../../adapters/otium/src/protocol.ts
|
|
26874
|
+
function str(body, field) {
|
|
26875
|
+
const value = body[field];
|
|
26876
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
26877
|
+
}
|
|
26878
|
+
function parseExecutionSpec(value) {
|
|
26879
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
26880
|
+
return null;
|
|
26881
|
+
const raw = value;
|
|
26882
|
+
const agent = str(raw, "agent");
|
|
26883
|
+
const model = str(raw, "model");
|
|
26884
|
+
const effort = str(raw, "effort");
|
|
26885
|
+
const rawMcp = raw.mcp;
|
|
26886
|
+
if (!agent || !model || !effort || !Array.isArray(rawMcp) || !rawMcp.every((entry) => typeof entry === "string" && entry.trim().length > 0) || typeof raw.canSpawnSubagents !== "boolean") {
|
|
26887
|
+
return null;
|
|
26854
26888
|
}
|
|
26855
|
-
|
|
26856
|
-
|
|
26889
|
+
return {
|
|
26890
|
+
agent,
|
|
26891
|
+
model,
|
|
26892
|
+
effort,
|
|
26893
|
+
...str(raw, "description") ? { description: str(raw, "description") } : {},
|
|
26894
|
+
mcp: [...new Set(rawMcp.map((entry) => entry.trim()))],
|
|
26895
|
+
canSpawnSubagents: raw.canSpawnSubagents
|
|
26896
|
+
};
|
|
26857
26897
|
}
|
|
26858
|
-
|
|
26859
|
-
|
|
26898
|
+
var PEER_PROTOCOL_VERSION = 1, MAX_PEER_MESSAGE_LENGTH = 1e4, MAX_PEER_INPUT_FILE_BYTES, MAX_PEER_INPUT_REQUEST_BYTES;
|
|
26899
|
+
var init_protocol = __esm(() => {
|
|
26900
|
+
MAX_PEER_INPUT_FILE_BYTES = 2 * 1024 * 1024 * 1024;
|
|
26901
|
+
MAX_PEER_INPUT_REQUEST_BYTES = MAX_PEER_INPUT_FILE_BYTES + 8 * 1024 * 1024;
|
|
26902
|
+
});
|
|
26903
|
+
|
|
26904
|
+
// ../../adapters/otium/src/canonical-mcp-bridge.ts
|
|
26905
|
+
import { randomUUID as randomUUID24 } from "crypto";
|
|
26906
|
+
function readAuthorization(request) {
|
|
26907
|
+
const value = request.headers.get("authorization");
|
|
26908
|
+
return value?.startsWith("Bearer ") ? value.slice(7) : null;
|
|
26860
26909
|
}
|
|
26861
|
-
function
|
|
26862
|
-
const
|
|
26863
|
-
if (
|
|
26864
|
-
return
|
|
26910
|
+
async function readLimitedJson(request) {
|
|
26911
|
+
const declared = Number(request.headers.get("content-length") ?? "0");
|
|
26912
|
+
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES)
|
|
26913
|
+
return null;
|
|
26914
|
+
const reader = request.body?.getReader();
|
|
26915
|
+
if (!reader)
|
|
26916
|
+
return null;
|
|
26917
|
+
const chunks = [];
|
|
26918
|
+
let size = 0;
|
|
26919
|
+
const deadline = Date.now() + BODY_READ_TIMEOUT_MS;
|
|
26865
26920
|
try {
|
|
26866
|
-
|
|
26867
|
-
|
|
26921
|
+
while (true) {
|
|
26922
|
+
const remaining = deadline - Date.now();
|
|
26923
|
+
if (remaining <= 0)
|
|
26924
|
+
throw new Error("body read timeout");
|
|
26925
|
+
let timer;
|
|
26926
|
+
const { done, value } = await Promise.race([
|
|
26927
|
+
reader.read(),
|
|
26928
|
+
new Promise((_, reject) => {
|
|
26929
|
+
timer = setTimeout(() => reject(new Error("body read timeout")), remaining);
|
|
26930
|
+
})
|
|
26931
|
+
]).finally(() => {
|
|
26932
|
+
if (timer)
|
|
26933
|
+
clearTimeout(timer);
|
|
26934
|
+
});
|
|
26935
|
+
if (done)
|
|
26936
|
+
break;
|
|
26937
|
+
size += value.byteLength;
|
|
26938
|
+
if (size > MAX_BODY_BYTES)
|
|
26939
|
+
throw new Error("body too large");
|
|
26940
|
+
chunks.push(value);
|
|
26941
|
+
}
|
|
26868
26942
|
} catch {
|
|
26869
|
-
|
|
26870
|
-
|
|
26871
|
-
}
|
|
26872
|
-
|
|
26873
|
-
const path = pendingEnrollmentPath();
|
|
26874
|
-
if (existsSync26(path)) {
|
|
26875
|
-
const saved = JSON.parse(readFileSync21(path, "utf8"));
|
|
26876
|
-
if (saved.central === invite.central && saved.token === invite.token)
|
|
26877
|
-
return saved;
|
|
26878
|
-
throw new Error(`another Otium enrollment is pending at ${path}`);
|
|
26943
|
+
await reader.cancel().catch(() => {
|
|
26944
|
+
return;
|
|
26945
|
+
});
|
|
26946
|
+
return null;
|
|
26879
26947
|
}
|
|
26880
|
-
const pair = generateKeyPairSync("x25519");
|
|
26881
|
-
const pending2 = {
|
|
26882
|
-
...invite,
|
|
26883
|
-
idempotencyKey: randomUUID24(),
|
|
26884
|
-
privateKey: pair.privateKey.export({ format: "der", type: "pkcs8" }).toString("base64url"),
|
|
26885
|
-
publicKey: pair.publicKey.export({ format: "der", type: "spki" }).toString("base64url"),
|
|
26886
|
-
...nodeName ? { nodeName } : {}
|
|
26887
|
-
};
|
|
26888
|
-
mkdirSync29(dirname18(path), { recursive: true });
|
|
26889
|
-
const temporaryPath = resolve14(dirname18(path), `.otium-enrollment-pending.json.${process.pid}.${randomUUID24()}.tmp`);
|
|
26890
|
-
let fd;
|
|
26891
26948
|
try {
|
|
26892
|
-
|
|
26893
|
-
|
|
26894
|
-
|
|
26895
|
-
|
|
26896
|
-
|
|
26897
|
-
|
|
26898
|
-
|
|
26899
|
-
|
|
26900
|
-
|
|
26901
|
-
|
|
26902
|
-
|
|
26903
|
-
fsyncSync3(directoryFd);
|
|
26904
|
-
} finally {
|
|
26905
|
-
closeSync4(directoryFd);
|
|
26906
|
-
}
|
|
26907
|
-
} catch (error) {
|
|
26908
|
-
if (fd !== undefined)
|
|
26909
|
-
closeSync4(fd);
|
|
26910
|
-
if (existsSync26(temporaryPath))
|
|
26911
|
-
unlinkSync19(temporaryPath);
|
|
26912
|
-
if (error.code === "EEXIST" && existsSync26(path)) {
|
|
26913
|
-
const saved = JSON.parse(readFileSync21(path, "utf8"));
|
|
26914
|
-
if (saved.central === invite.central && saved.token === invite.token)
|
|
26915
|
-
return saved;
|
|
26916
|
-
}
|
|
26917
|
-
throw error;
|
|
26918
|
-
}
|
|
26919
|
-
return pending2;
|
|
26920
|
-
}
|
|
26921
|
-
function replacePendingEnrollment(pending2) {
|
|
26922
|
-
const path = pendingEnrollmentPath();
|
|
26923
|
-
const directory = dirname18(path);
|
|
26924
|
-
const temporaryPath = resolve14(directory, `.otium-enrollment-pending.json.${process.pid}.${randomUUID24()}.tmp`);
|
|
26925
|
-
let fd;
|
|
26926
|
-
try {
|
|
26927
|
-
fd = openSync4(temporaryPath, "wx", 384);
|
|
26928
|
-
writeFileSync19(fd, `${JSON.stringify(pending2, null, 2)}
|
|
26929
|
-
`, "utf8");
|
|
26930
|
-
fsyncSync3(fd);
|
|
26931
|
-
closeSync4(fd);
|
|
26932
|
-
fd = undefined;
|
|
26933
|
-
renameSync14(temporaryPath, path);
|
|
26934
|
-
const directoryFd = openSync4(directory, "r");
|
|
26935
|
-
try {
|
|
26936
|
-
fsyncSync3(directoryFd);
|
|
26937
|
-
} finally {
|
|
26938
|
-
closeSync4(directoryFd);
|
|
26939
|
-
}
|
|
26940
|
-
} catch (error) {
|
|
26941
|
-
if (fd !== undefined)
|
|
26942
|
-
closeSync4(fd);
|
|
26943
|
-
if (existsSync26(temporaryPath))
|
|
26944
|
-
unlinkSync19(temporaryPath);
|
|
26945
|
-
throw error;
|
|
26946
|
-
}
|
|
26947
|
-
}
|
|
26948
|
-
function recordClaimedCredential(pending2, join31) {
|
|
26949
|
-
withJoinCredentialLock(() => {
|
|
26950
|
-
const current3 = JSON.parse(readFileSync21(pendingEnrollmentPath(), "utf8"));
|
|
26951
|
-
if (current3.central !== pending2.central || current3.token !== pending2.token || current3.idempotencyKey !== pending2.idempotencyKey || current3.publicKey !== pending2.publicKey) {
|
|
26952
|
-
throw new Error("pending Otium enrollment changed while its claim was in flight");
|
|
26953
|
-
}
|
|
26954
|
-
const claimed = { digest: joinCredentialDigest(join31), cellId: join31.cellId };
|
|
26955
|
-
if (current3.claimed && (current3.claimed.digest !== claimed.digest || current3.claimed.cellId !== claimed.cellId)) {
|
|
26956
|
-
throw new Error("central returned different credentials for an idempotent enrollment claim");
|
|
26957
|
-
}
|
|
26958
|
-
replacePendingEnrollment({ ...current3, claimed });
|
|
26959
|
-
});
|
|
26960
|
-
}
|
|
26961
|
-
function openCredential(envelope, privateKeyDer) {
|
|
26962
|
-
if (envelope.v !== 1 || envelope.algorithm !== "X25519-HKDF-SHA256-AES-256-GCM") {
|
|
26963
|
-
throw new Error("unsupported enrollment credential envelope");
|
|
26964
|
-
}
|
|
26965
|
-
const privateKey = createPrivateKey({
|
|
26966
|
-
key: Buffer.from(privateKeyDer, "base64url"),
|
|
26967
|
-
format: "der",
|
|
26968
|
-
type: "pkcs8"
|
|
26969
|
-
});
|
|
26970
|
-
const ephemeral = createPublicKey({
|
|
26971
|
-
key: Buffer.from(envelope.ephemeralPublicKey, "base64url"),
|
|
26972
|
-
format: "der",
|
|
26973
|
-
type: "spki"
|
|
26974
|
-
});
|
|
26975
|
-
const shared = diffieHellman({ privateKey, publicKey: ephemeral });
|
|
26976
|
-
const key = Buffer.from(hkdfSync("sha256", shared, Buffer.from(envelope.salt, "base64url"), INFO, 32));
|
|
26977
|
-
const decipher = createDecipheriv2("aes-256-gcm", key, Buffer.from(envelope.nonce, "base64url"));
|
|
26978
|
-
decipher.setAAD(INFO);
|
|
26979
|
-
decipher.setAuthTag(Buffer.from(envelope.tag, "base64url"));
|
|
26980
|
-
return Buffer.concat([
|
|
26981
|
-
decipher.update(Buffer.from(envelope.ciphertext, "base64url")),
|
|
26982
|
-
decipher.final()
|
|
26983
|
-
]).toString("utf8");
|
|
26984
|
-
}
|
|
26985
|
-
async function postJson(url, body, headers) {
|
|
26986
|
-
const response = await fetch(url, {
|
|
26987
|
-
method: "POST",
|
|
26988
|
-
headers: { "content-type": "application/json", ...headers },
|
|
26989
|
-
body: JSON.stringify(body)
|
|
26990
|
-
});
|
|
26991
|
-
const result = await response.json();
|
|
26992
|
-
if (!response.ok)
|
|
26993
|
-
throw new Error(String(result.error || `request failed (${response.status})`));
|
|
26994
|
-
return result;
|
|
26995
|
-
}
|
|
26996
|
-
async function previewEnrollment(invite) {
|
|
26997
|
-
return postJson(`${invite.central}/api/v1/node-enrollments/preview`, {
|
|
26998
|
-
token: invite.token
|
|
26999
|
-
});
|
|
27000
|
-
}
|
|
27001
|
-
async function claimEnrollment(invite, nodeName) {
|
|
27002
|
-
const pending2 = loadOrCreatePending(invite, nodeName);
|
|
27003
|
-
const response = await postJson(`${invite.central}/api/v1/node-enrollments/claim`, {
|
|
27004
|
-
token: invite.token,
|
|
27005
|
-
credentialPublicKey: pending2.publicKey,
|
|
27006
|
-
...pending2.nodeName ? { nodeName: pending2.nodeName } : {}
|
|
27007
|
-
}, { "idempotency-key": pending2.idempotencyKey });
|
|
27008
|
-
const credential = response.credential;
|
|
27009
|
-
const secret = openCredential(credential, pending2.privateKey);
|
|
27010
|
-
if (!secret.startsWith("rcs_"))
|
|
27011
|
-
throw new Error("central returned an invalid runtime credential");
|
|
27012
|
-
const join31 = {
|
|
27013
|
-
v: 2,
|
|
27014
|
-
central: invite.central,
|
|
27015
|
-
relay: String(response.relayUrl),
|
|
27016
|
-
cellId: String(response.cell?.id),
|
|
27017
|
-
secret
|
|
27018
|
-
};
|
|
27019
|
-
if (!join31.relay || !join31.cellId || join31.cellId === "undefined") {
|
|
27020
|
-
throw new Error("central returned an incomplete enrollment response");
|
|
27021
|
-
}
|
|
27022
|
-
assertSecureRelayUrl(join31.relay);
|
|
27023
|
-
recordClaimedCredential(pending2, join31);
|
|
27024
|
-
return join31;
|
|
27025
|
-
}
|
|
27026
|
-
function commitEnrollment(join31, options = {}) {
|
|
27027
|
-
return withJoinCredentialLock(() => {
|
|
27028
|
-
const pendingPath = pendingEnrollmentPath();
|
|
27029
|
-
const pending2 = existsSync26(pendingPath) ? JSON.parse(readFileSync21(pendingPath, "utf8")) : null;
|
|
27030
|
-
const digest = joinCredentialDigest(join31);
|
|
27031
|
-
if (pending2 && (!pending2.claimed || pending2.claimed.digest !== digest || pending2.claimed.cellId !== join31.cellId)) {
|
|
27032
|
-
throw new Error(`pending Otium enrollment at ${pendingPath} does not match these credentials`);
|
|
27033
|
-
}
|
|
27034
|
-
const path = saveJoinWhileLocked(join31, options);
|
|
27035
|
-
if (!isJoinPersisted(join31)) {
|
|
27036
|
-
throw new Error("Otium join credentials were not durably persisted");
|
|
27037
|
-
}
|
|
27038
|
-
if (!pending2)
|
|
27039
|
-
return path;
|
|
27040
|
-
unlinkSync19(pendingPath);
|
|
27041
|
-
const directoryFd = openSync4(dirname18(pendingPath), "r");
|
|
27042
|
-
try {
|
|
27043
|
-
fsyncSync3(directoryFd);
|
|
27044
|
-
} finally {
|
|
27045
|
-
closeSync4(directoryFd);
|
|
27046
|
-
}
|
|
27047
|
-
return path;
|
|
27048
|
-
});
|
|
27049
|
-
}
|
|
27050
|
-
var INFO;
|
|
27051
|
-
var init_enrollment = __esm(async () => {
|
|
27052
|
-
await init_src();
|
|
27053
|
-
await init_join();
|
|
27054
|
-
INFO = Buffer.from("otium-node-enrollment-v1", "utf8");
|
|
27055
|
-
});
|
|
27056
|
-
|
|
27057
|
-
// ../../adapters/otium/src/join-cli.ts
|
|
27058
|
-
var exports_join_cli = {};
|
|
27059
|
-
__export(exports_join_cli, {
|
|
27060
|
-
joinCommand: () => joinCommand
|
|
27061
|
-
});
|
|
27062
|
-
function option2(args, name) {
|
|
27063
|
-
const index = args.indexOf(`--${name}`);
|
|
27064
|
-
return index >= 0 ? args[index + 1]?.trim() : undefined;
|
|
27065
|
-
}
|
|
27066
|
-
async function confirmEnrollment(message) {
|
|
27067
|
-
if (!process.stdin.isTTY)
|
|
27068
|
-
return false;
|
|
27069
|
-
const { createInterface } = await import("readline/promises");
|
|
27070
|
-
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
27071
|
-
try {
|
|
27072
|
-
return /^(?:y|yes)$/i.test((await prompt.question(`${message} [y/N] `)).trim());
|
|
27073
|
-
} finally {
|
|
27074
|
-
prompt.close();
|
|
27075
|
-
}
|
|
27076
|
-
}
|
|
27077
|
-
async function joinCommand(args) {
|
|
27078
|
-
const code = args[0]?.trim();
|
|
27079
|
-
if (!code) {
|
|
27080
|
-
console.error("usage: negotium-otium join <invite-code> [--yes] [--name <node-name>] [--legacy] [--replace]");
|
|
27081
|
-
process.exitCode = 1;
|
|
27082
|
-
return;
|
|
27083
|
-
}
|
|
27084
|
-
let join31;
|
|
27085
|
-
let productionEnrollment = false;
|
|
27086
|
-
if (args.includes("--legacy")) {
|
|
27087
|
-
try {
|
|
27088
|
-
join31 = parseInviteCode(code);
|
|
27089
|
-
} catch (err2) {
|
|
27090
|
-
console.error(`invalid legacy invite code: ${err2 instanceof Error ? err2.message : err2}`);
|
|
27091
|
-
process.exitCode = 1;
|
|
27092
|
-
return;
|
|
27093
|
-
}
|
|
27094
|
-
} else {
|
|
27095
|
-
try {
|
|
27096
|
-
const invite = parseEnrollmentInvite(code);
|
|
27097
|
-
const resuming = isEnrollmentPending(invite);
|
|
27098
|
-
let nodeName = option2(args, "name");
|
|
27099
|
-
if (resuming) {
|
|
27100
|
-
console.log(`Resuming interrupted Otium enrollment with ${invite.central}`);
|
|
27101
|
-
} else {
|
|
27102
|
-
const preview = await previewEnrollment(invite);
|
|
27103
|
-
const workspace = preview.preview?.workspace;
|
|
27104
|
-
nodeName ||= preview.preview?.suggestedNodeName || undefined;
|
|
27105
|
-
console.log(`Otium workspace: ${workspace?.name ?? workspace?.slug ?? workspace?.id}`);
|
|
27106
|
-
console.log(` central: ${invite.central}`);
|
|
27107
|
-
console.log(` transport: ${preview.preview?.transport ?? "relay"}`);
|
|
27108
|
-
console.log(` access: ${preview.preview?.topics ?? "explicitly shared topics only"}`);
|
|
27109
|
-
const accepted = args.includes("--yes") || await confirmEnrollment("Join this workspace?");
|
|
27110
|
-
if (!accepted) {
|
|
27111
|
-
console.log("enrollment cancelled");
|
|
27112
|
-
return;
|
|
27113
|
-
}
|
|
27114
|
-
}
|
|
27115
|
-
join31 = await claimEnrollment(invite, nodeName);
|
|
27116
|
-
productionEnrollment = true;
|
|
27117
|
-
} catch (err2) {
|
|
27118
|
-
console.error(`enrollment failed: ${err2 instanceof Error ? err2.message : err2}`);
|
|
27119
|
-
process.exitCode = 1;
|
|
27120
|
-
return;
|
|
27121
|
-
}
|
|
27122
|
-
}
|
|
27123
|
-
let path;
|
|
27124
|
-
try {
|
|
27125
|
-
const saveOptions = { replaceExisting: args.includes("--replace") };
|
|
27126
|
-
path = productionEnrollment ? commitEnrollment(join31, saveOptions) : saveJoin(join31, saveOptions);
|
|
27127
|
-
} catch (err2) {
|
|
27128
|
-
console.error(`could not save join credentials: ${err2 instanceof Error ? err2.message : err2}`);
|
|
27129
|
-
process.exitCode = 1;
|
|
27130
|
-
return;
|
|
27131
|
-
}
|
|
27132
|
-
console.log(`otium join credentials saved to ${path}`);
|
|
27133
|
-
console.log(` central: ${join31.central}`);
|
|
27134
|
-
console.log(` cellId: ${join31.cellId}`);
|
|
27135
|
-
configureOtiumCentral(join31);
|
|
27136
|
-
try {
|
|
27137
|
-
const self = await selfPeerNode();
|
|
27138
|
-
if (self) {
|
|
27139
|
-
console.log(`attached to workspace as "${self.nodeName ?? self.cellId}" (baseUrl ${self.baseUrl})`);
|
|
27140
|
-
} else {
|
|
27141
|
-
console.warn("warning: central answered but this cell has no visible assignment yet \u2014 check the workspace assignment");
|
|
27142
|
-
}
|
|
27143
|
-
} catch (err2) {
|
|
27144
|
-
console.warn(`warning: could not verify against central (${err2 instanceof Error ? err2.message : err2}) \u2014 credentials saved anyway`);
|
|
27145
|
-
} finally {
|
|
27146
|
-
configureOtiumCentral(null);
|
|
27147
|
-
}
|
|
27148
|
-
console.log("\nnext: `negotium-otium serve` (mounts the otium peer routes automatically)");
|
|
27149
|
-
}
|
|
27150
|
-
var init_join_cli = __esm(async () => {
|
|
27151
|
-
await init_central();
|
|
27152
|
-
await init_enrollment();
|
|
27153
|
-
await init_join();
|
|
27154
|
-
});
|
|
27155
|
-
|
|
27156
|
-
// ../../adapters/otium/src/canonical-mcp-bridge.ts
|
|
27157
|
-
import { randomUUID as randomUUID25 } from "crypto";
|
|
27158
|
-
function readAuthorization(request) {
|
|
27159
|
-
const value = request.headers.get("authorization");
|
|
27160
|
-
return value?.startsWith("Bearer ") ? value.slice(7) : null;
|
|
27161
|
-
}
|
|
27162
|
-
async function readLimitedJson(request) {
|
|
27163
|
-
const declared = Number(request.headers.get("content-length") ?? "0");
|
|
27164
|
-
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES)
|
|
27165
|
-
return null;
|
|
27166
|
-
const reader = request.body?.getReader();
|
|
27167
|
-
if (!reader)
|
|
27168
|
-
return null;
|
|
27169
|
-
const chunks = [];
|
|
27170
|
-
let size = 0;
|
|
27171
|
-
const deadline = Date.now() + BODY_READ_TIMEOUT_MS;
|
|
27172
|
-
try {
|
|
27173
|
-
while (true) {
|
|
27174
|
-
const remaining = deadline - Date.now();
|
|
27175
|
-
if (remaining <= 0)
|
|
27176
|
-
throw new Error("body read timeout");
|
|
27177
|
-
let timer;
|
|
27178
|
-
const { done, value } = await Promise.race([
|
|
27179
|
-
reader.read(),
|
|
27180
|
-
new Promise((_, reject) => {
|
|
27181
|
-
timer = setTimeout(() => reject(new Error("body read timeout")), remaining);
|
|
27182
|
-
})
|
|
27183
|
-
]).finally(() => {
|
|
27184
|
-
if (timer)
|
|
27185
|
-
clearTimeout(timer);
|
|
27186
|
-
});
|
|
27187
|
-
if (done)
|
|
27188
|
-
break;
|
|
27189
|
-
size += value.byteLength;
|
|
27190
|
-
if (size > MAX_BODY_BYTES)
|
|
27191
|
-
throw new Error("body too large");
|
|
27192
|
-
chunks.push(value);
|
|
27193
|
-
}
|
|
27194
|
-
} catch {
|
|
27195
|
-
await reader.cancel().catch(() => {
|
|
27196
|
-
return;
|
|
27197
|
-
});
|
|
27198
|
-
return null;
|
|
27199
|
-
}
|
|
27200
|
-
try {
|
|
27201
|
-
const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
27202
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
27203
|
-
return null;
|
|
27204
|
-
const body = parsed;
|
|
27205
|
-
if (typeof body.tool !== "string" || body.tool.length > 128)
|
|
27206
|
-
return null;
|
|
27207
|
-
if (!body.input || typeof body.input !== "object" || Array.isArray(body.input))
|
|
27208
|
-
return null;
|
|
27209
|
-
return { tool: body.tool, input: body.input };
|
|
27210
|
-
} catch {
|
|
27211
|
-
return null;
|
|
26949
|
+
const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
26950
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
26951
|
+
return null;
|
|
26952
|
+
const body = parsed;
|
|
26953
|
+
if (typeof body.tool !== "string" || body.tool.length > 128)
|
|
26954
|
+
return null;
|
|
26955
|
+
if (!body.input || typeof body.input !== "object" || Array.isArray(body.input))
|
|
26956
|
+
return null;
|
|
26957
|
+
return { tool: body.tool, input: body.input };
|
|
26958
|
+
} catch {
|
|
26959
|
+
return null;
|
|
27212
26960
|
}
|
|
27213
26961
|
}
|
|
27214
26962
|
async function forwardCanonicalTool(capability, request) {
|
|
@@ -27295,7 +27043,7 @@ function startCanonicalMcpBridge(options = {}) {
|
|
|
27295
27043
|
const url = `http://127.0.0.1:${server.port}/`;
|
|
27296
27044
|
const unregister = registerCanonicalMcpBridgeEnvProvider((scope) => {
|
|
27297
27045
|
sweep();
|
|
27298
|
-
const token = `${
|
|
27046
|
+
const token = `${randomUUID24()}${randomUUID24()}`;
|
|
27299
27047
|
capabilities.set(token, {
|
|
27300
27048
|
surface: scope.surface,
|
|
27301
27049
|
userId: scope.userId,
|
|
@@ -27844,7 +27592,7 @@ function createTurnForwarder(opts) {
|
|
|
27844
27592
|
logger.warn({ requestId, seq, type: event.type, attempt, error: result.error }, "otium: peer event delivery to hub failed");
|
|
27845
27593
|
if (attempt < PEER_EVENT_MAX_ATTEMPTS) {
|
|
27846
27594
|
const delayMs = retryBaseMs * 2 ** (attempt - 1);
|
|
27847
|
-
await new Promise((
|
|
27595
|
+
await new Promise((resolve14) => setTimeout(resolve14, delayMs));
|
|
27848
27596
|
}
|
|
27849
27597
|
}
|
|
27850
27598
|
forwarder.deliveryBlocked = true;
|
|
@@ -27983,8 +27731,8 @@ var init_event_backflow = __esm(async () => {
|
|
|
27983
27731
|
});
|
|
27984
27732
|
|
|
27985
27733
|
// ../../adapters/otium/src/peer-files.ts
|
|
27986
|
-
import { randomUUID as
|
|
27987
|
-
import { copyFileSync as copyFileSync3, mkdirSync as
|
|
27734
|
+
import { randomUUID as randomUUID25 } from "crypto";
|
|
27735
|
+
import { copyFileSync as copyFileSync3, mkdirSync as mkdirSync29, rmSync as rmSync6, statSync as statSync12 } from "fs";
|
|
27988
27736
|
import { basename as basename8, extname as extname5, join as join31 } from "path";
|
|
27989
27737
|
function fileType(mimeType) {
|
|
27990
27738
|
if (mimeType.startsWith("image/"))
|
|
@@ -28053,8 +27801,8 @@ function recordFile(args) {
|
|
|
28053
27801
|
});
|
|
28054
27802
|
}
|
|
28055
27803
|
function insertLocalFile(args) {
|
|
28056
|
-
const id =
|
|
28057
|
-
|
|
27804
|
+
const id = randomUUID25();
|
|
27805
|
+
mkdirSync29(PEER_FILES_DIR, { recursive: true });
|
|
28058
27806
|
const path = join31(PEER_FILES_DIR, `${id}-${safeFilename(args.filename)}`);
|
|
28059
27807
|
copyFileSync3(args.sourcePath, path);
|
|
28060
27808
|
return recordFile({ id, path, sizeBytes: statSync12(path).size, ...args });
|
|
@@ -28066,8 +27814,8 @@ function deletePeerFilesForTopic(topicId) {
|
|
|
28066
27814
|
rmSync6(row.path, { force: true });
|
|
28067
27815
|
}
|
|
28068
27816
|
async function storePeerInputFile(file, access) {
|
|
28069
|
-
const id =
|
|
28070
|
-
|
|
27817
|
+
const id = randomUUID25();
|
|
27818
|
+
mkdirSync29(PEER_FILES_DIR, { recursive: true });
|
|
28071
27819
|
const filename = file.name || "upload";
|
|
28072
27820
|
const path = join31(PEER_FILES_DIR, `${id}-${safeFilename(filename)}`);
|
|
28073
27821
|
const sizeBytes = await Bun.write(path, file);
|
|
@@ -28132,7 +27880,7 @@ var init_peer_files = __esm(async () => {
|
|
|
28132
27880
|
});
|
|
28133
27881
|
|
|
28134
27882
|
// ../../adapters/otium/src/runtime-bridge.ts
|
|
28135
|
-
import { randomUUID as
|
|
27883
|
+
import { randomUUID as randomUUID26 } from "crypto";
|
|
28136
27884
|
function isMcpToolResult(value) {
|
|
28137
27885
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
28138
27886
|
return false;
|
|
@@ -28202,7 +27950,7 @@ var init_runtime_bridge = __esm(async () => {
|
|
|
28202
27950
|
} catch (error) {
|
|
28203
27951
|
return errorResult(`Error: Failed to open hub question: ${error.message}`);
|
|
28204
27952
|
}
|
|
28205
|
-
const bridgeRequestId = `bridge-ask-${
|
|
27953
|
+
const bridgeRequestId = `bridge-ask-${randomUUID26()}`;
|
|
28206
27954
|
const baseUrl = hubNode.baseUrl.replace(/\/+$/, "");
|
|
28207
27955
|
const post = async (path, body) => {
|
|
28208
27956
|
const response = await fetch(`${baseUrl}${path}`, {
|
|
@@ -29418,6 +29166,248 @@ var init_bindings = __esm(async () => {
|
|
|
29418
29166
|
await init_store2();
|
|
29419
29167
|
});
|
|
29420
29168
|
|
|
29169
|
+
// ../../adapters/otium/src/enrollment.ts
|
|
29170
|
+
import {
|
|
29171
|
+
createDecipheriv as createDecipheriv2,
|
|
29172
|
+
createPrivateKey,
|
|
29173
|
+
createPublicKey,
|
|
29174
|
+
diffieHellman,
|
|
29175
|
+
generateKeyPairSync,
|
|
29176
|
+
hkdfSync,
|
|
29177
|
+
randomUUID as randomUUID27
|
|
29178
|
+
} from "crypto";
|
|
29179
|
+
import {
|
|
29180
|
+
chmodSync as chmodSync6,
|
|
29181
|
+
closeSync as closeSync4,
|
|
29182
|
+
existsSync as existsSync26,
|
|
29183
|
+
fsyncSync as fsyncSync3,
|
|
29184
|
+
linkSync as linkSync2,
|
|
29185
|
+
mkdirSync as mkdirSync30,
|
|
29186
|
+
openSync as openSync4,
|
|
29187
|
+
readFileSync as readFileSync21,
|
|
29188
|
+
renameSync as renameSync14,
|
|
29189
|
+
unlinkSync as unlinkSync19,
|
|
29190
|
+
writeFileSync as writeFileSync19
|
|
29191
|
+
} from "fs";
|
|
29192
|
+
import { dirname as dirname18, resolve as resolve14 } from "path";
|
|
29193
|
+
function parseEnrollmentInvite(code) {
|
|
29194
|
+
let parsed;
|
|
29195
|
+
try {
|
|
29196
|
+
parsed = JSON.parse(Buffer.from(code.trim(), "base64url").toString("utf8"));
|
|
29197
|
+
} catch {
|
|
29198
|
+
throw new Error("production invite must be base64url JSON");
|
|
29199
|
+
}
|
|
29200
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
29201
|
+
throw new Error("production invite must decode to an object");
|
|
29202
|
+
}
|
|
29203
|
+
const raw = parsed;
|
|
29204
|
+
const central = typeof raw.central === "string" ? raw.central.trim().replace(/\/+$/, "") : "";
|
|
29205
|
+
const token = typeof raw.token === "string" ? raw.token.trim() : "";
|
|
29206
|
+
if (raw.v !== 2 || !central || !token.startsWith("nei_")) {
|
|
29207
|
+
throw new Error("production invite requires {v:2, central:http(s), token:nei_\u2026}");
|
|
29208
|
+
}
|
|
29209
|
+
assertSecureCentralUrl(central);
|
|
29210
|
+
return { v: 2, central, token };
|
|
29211
|
+
}
|
|
29212
|
+
function pendingEnrollmentPath() {
|
|
29213
|
+
return resolve14(DATA_DIR, "otium-enrollment-pending.json");
|
|
29214
|
+
}
|
|
29215
|
+
function isEnrollmentPending(invite) {
|
|
29216
|
+
const path = pendingEnrollmentPath();
|
|
29217
|
+
if (!existsSync26(path))
|
|
29218
|
+
return false;
|
|
29219
|
+
try {
|
|
29220
|
+
const saved = JSON.parse(readFileSync21(path, "utf8"));
|
|
29221
|
+
return saved.central === invite.central && saved.token === invite.token;
|
|
29222
|
+
} catch {
|
|
29223
|
+
return false;
|
|
29224
|
+
}
|
|
29225
|
+
}
|
|
29226
|
+
function loadOrCreatePending(invite, nodeName) {
|
|
29227
|
+
const path = pendingEnrollmentPath();
|
|
29228
|
+
if (existsSync26(path)) {
|
|
29229
|
+
const saved = JSON.parse(readFileSync21(path, "utf8"));
|
|
29230
|
+
if (saved.central === invite.central && saved.token === invite.token)
|
|
29231
|
+
return saved;
|
|
29232
|
+
throw new Error(`another Otium enrollment is pending at ${path}`);
|
|
29233
|
+
}
|
|
29234
|
+
const pair = generateKeyPairSync("x25519");
|
|
29235
|
+
const pending2 = {
|
|
29236
|
+
...invite,
|
|
29237
|
+
idempotencyKey: randomUUID27(),
|
|
29238
|
+
privateKey: pair.privateKey.export({ format: "der", type: "pkcs8" }).toString("base64url"),
|
|
29239
|
+
publicKey: pair.publicKey.export({ format: "der", type: "spki" }).toString("base64url"),
|
|
29240
|
+
...nodeName ? { nodeName } : {}
|
|
29241
|
+
};
|
|
29242
|
+
mkdirSync30(dirname18(path), { recursive: true });
|
|
29243
|
+
const temporaryPath = resolve14(dirname18(path), `.otium-enrollment-pending.json.${process.pid}.${randomUUID27()}.tmp`);
|
|
29244
|
+
let fd;
|
|
29245
|
+
try {
|
|
29246
|
+
fd = openSync4(temporaryPath, "wx", 384);
|
|
29247
|
+
writeFileSync19(fd, `${JSON.stringify(pending2, null, 2)}
|
|
29248
|
+
`, "utf8");
|
|
29249
|
+
fsyncSync3(fd);
|
|
29250
|
+
closeSync4(fd);
|
|
29251
|
+
fd = undefined;
|
|
29252
|
+
linkSync2(temporaryPath, path);
|
|
29253
|
+
unlinkSync19(temporaryPath);
|
|
29254
|
+
chmodSync6(path, 384);
|
|
29255
|
+
const directoryFd = openSync4(dirname18(path), "r");
|
|
29256
|
+
try {
|
|
29257
|
+
fsyncSync3(directoryFd);
|
|
29258
|
+
} finally {
|
|
29259
|
+
closeSync4(directoryFd);
|
|
29260
|
+
}
|
|
29261
|
+
} catch (error) {
|
|
29262
|
+
if (fd !== undefined)
|
|
29263
|
+
closeSync4(fd);
|
|
29264
|
+
if (existsSync26(temporaryPath))
|
|
29265
|
+
unlinkSync19(temporaryPath);
|
|
29266
|
+
if (error.code === "EEXIST" && existsSync26(path)) {
|
|
29267
|
+
const saved = JSON.parse(readFileSync21(path, "utf8"));
|
|
29268
|
+
if (saved.central === invite.central && saved.token === invite.token)
|
|
29269
|
+
return saved;
|
|
29270
|
+
}
|
|
29271
|
+
throw error;
|
|
29272
|
+
}
|
|
29273
|
+
return pending2;
|
|
29274
|
+
}
|
|
29275
|
+
function replacePendingEnrollment(pending2) {
|
|
29276
|
+
const path = pendingEnrollmentPath();
|
|
29277
|
+
const directory = dirname18(path);
|
|
29278
|
+
const temporaryPath = resolve14(directory, `.otium-enrollment-pending.json.${process.pid}.${randomUUID27()}.tmp`);
|
|
29279
|
+
let fd;
|
|
29280
|
+
try {
|
|
29281
|
+
fd = openSync4(temporaryPath, "wx", 384);
|
|
29282
|
+
writeFileSync19(fd, `${JSON.stringify(pending2, null, 2)}
|
|
29283
|
+
`, "utf8");
|
|
29284
|
+
fsyncSync3(fd);
|
|
29285
|
+
closeSync4(fd);
|
|
29286
|
+
fd = undefined;
|
|
29287
|
+
renameSync14(temporaryPath, path);
|
|
29288
|
+
const directoryFd = openSync4(directory, "r");
|
|
29289
|
+
try {
|
|
29290
|
+
fsyncSync3(directoryFd);
|
|
29291
|
+
} finally {
|
|
29292
|
+
closeSync4(directoryFd);
|
|
29293
|
+
}
|
|
29294
|
+
} catch (error) {
|
|
29295
|
+
if (fd !== undefined)
|
|
29296
|
+
closeSync4(fd);
|
|
29297
|
+
if (existsSync26(temporaryPath))
|
|
29298
|
+
unlinkSync19(temporaryPath);
|
|
29299
|
+
throw error;
|
|
29300
|
+
}
|
|
29301
|
+
}
|
|
29302
|
+
function recordClaimedCredential(pending2, join32) {
|
|
29303
|
+
withJoinCredentialLock(() => {
|
|
29304
|
+
const current3 = JSON.parse(readFileSync21(pendingEnrollmentPath(), "utf8"));
|
|
29305
|
+
if (current3.central !== pending2.central || current3.token !== pending2.token || current3.idempotencyKey !== pending2.idempotencyKey || current3.publicKey !== pending2.publicKey) {
|
|
29306
|
+
throw new Error("pending Otium enrollment changed while its claim was in flight");
|
|
29307
|
+
}
|
|
29308
|
+
const claimed = { digest: joinCredentialDigest(join32), cellId: join32.cellId };
|
|
29309
|
+
if (current3.claimed && (current3.claimed.digest !== claimed.digest || current3.claimed.cellId !== claimed.cellId)) {
|
|
29310
|
+
throw new Error("central returned different credentials for an idempotent enrollment claim");
|
|
29311
|
+
}
|
|
29312
|
+
replacePendingEnrollment({ ...current3, claimed });
|
|
29313
|
+
});
|
|
29314
|
+
}
|
|
29315
|
+
function openCredential(envelope, privateKeyDer) {
|
|
29316
|
+
if (envelope.v !== 1 || envelope.algorithm !== "X25519-HKDF-SHA256-AES-256-GCM") {
|
|
29317
|
+
throw new Error("unsupported enrollment credential envelope");
|
|
29318
|
+
}
|
|
29319
|
+
const privateKey = createPrivateKey({
|
|
29320
|
+
key: Buffer.from(privateKeyDer, "base64url"),
|
|
29321
|
+
format: "der",
|
|
29322
|
+
type: "pkcs8"
|
|
29323
|
+
});
|
|
29324
|
+
const ephemeral = createPublicKey({
|
|
29325
|
+
key: Buffer.from(envelope.ephemeralPublicKey, "base64url"),
|
|
29326
|
+
format: "der",
|
|
29327
|
+
type: "spki"
|
|
29328
|
+
});
|
|
29329
|
+
const shared = diffieHellman({ privateKey, publicKey: ephemeral });
|
|
29330
|
+
const key = Buffer.from(hkdfSync("sha256", shared, Buffer.from(envelope.salt, "base64url"), INFO, 32));
|
|
29331
|
+
const decipher = createDecipheriv2("aes-256-gcm", key, Buffer.from(envelope.nonce, "base64url"));
|
|
29332
|
+
decipher.setAAD(INFO);
|
|
29333
|
+
decipher.setAuthTag(Buffer.from(envelope.tag, "base64url"));
|
|
29334
|
+
return Buffer.concat([
|
|
29335
|
+
decipher.update(Buffer.from(envelope.ciphertext, "base64url")),
|
|
29336
|
+
decipher.final()
|
|
29337
|
+
]).toString("utf8");
|
|
29338
|
+
}
|
|
29339
|
+
async function postJson(url, body, headers) {
|
|
29340
|
+
const response = await fetch(url, {
|
|
29341
|
+
method: "POST",
|
|
29342
|
+
headers: { "content-type": "application/json", ...headers },
|
|
29343
|
+
body: JSON.stringify(body)
|
|
29344
|
+
});
|
|
29345
|
+
const result = await response.json();
|
|
29346
|
+
if (!response.ok)
|
|
29347
|
+
throw new Error(String(result.error || `request failed (${response.status})`));
|
|
29348
|
+
return result;
|
|
29349
|
+
}
|
|
29350
|
+
async function previewEnrollment(invite) {
|
|
29351
|
+
return postJson(`${invite.central}/api/v1/node-enrollments/preview`, {
|
|
29352
|
+
token: invite.token
|
|
29353
|
+
});
|
|
29354
|
+
}
|
|
29355
|
+
async function claimEnrollment(invite, nodeName) {
|
|
29356
|
+
const pending2 = loadOrCreatePending(invite, nodeName);
|
|
29357
|
+
const response = await postJson(`${invite.central}/api/v1/node-enrollments/claim`, {
|
|
29358
|
+
token: invite.token,
|
|
29359
|
+
credentialPublicKey: pending2.publicKey,
|
|
29360
|
+
...pending2.nodeName ? { nodeName: pending2.nodeName } : {}
|
|
29361
|
+
}, { "idempotency-key": pending2.idempotencyKey });
|
|
29362
|
+
const credential = response.credential;
|
|
29363
|
+
const secret = openCredential(credential, pending2.privateKey);
|
|
29364
|
+
if (!secret.startsWith("rcs_"))
|
|
29365
|
+
throw new Error("central returned an invalid runtime credential");
|
|
29366
|
+
const join32 = {
|
|
29367
|
+
v: 2,
|
|
29368
|
+
central: invite.central,
|
|
29369
|
+
relay: String(response.relayUrl),
|
|
29370
|
+
cellId: String(response.cell?.id),
|
|
29371
|
+
secret
|
|
29372
|
+
};
|
|
29373
|
+
if (!join32.relay || !join32.cellId || join32.cellId === "undefined") {
|
|
29374
|
+
throw new Error("central returned an incomplete enrollment response");
|
|
29375
|
+
}
|
|
29376
|
+
assertSecureRelayUrl(join32.relay);
|
|
29377
|
+
recordClaimedCredential(pending2, join32);
|
|
29378
|
+
return join32;
|
|
29379
|
+
}
|
|
29380
|
+
function commitEnrollment(join32, options = {}) {
|
|
29381
|
+
return withJoinCredentialLock(() => {
|
|
29382
|
+
const pendingPath = pendingEnrollmentPath();
|
|
29383
|
+
const pending2 = existsSync26(pendingPath) ? JSON.parse(readFileSync21(pendingPath, "utf8")) : null;
|
|
29384
|
+
const digest = joinCredentialDigest(join32);
|
|
29385
|
+
if (pending2 && (!pending2.claimed || pending2.claimed.digest !== digest || pending2.claimed.cellId !== join32.cellId)) {
|
|
29386
|
+
throw new Error(`pending Otium enrollment at ${pendingPath} does not match these credentials`);
|
|
29387
|
+
}
|
|
29388
|
+
const path = saveJoinWhileLocked(join32, options);
|
|
29389
|
+
if (!isJoinPersisted(join32)) {
|
|
29390
|
+
throw new Error("Otium join credentials were not durably persisted");
|
|
29391
|
+
}
|
|
29392
|
+
if (!pending2)
|
|
29393
|
+
return path;
|
|
29394
|
+
unlinkSync19(pendingPath);
|
|
29395
|
+
const directoryFd = openSync4(dirname18(pendingPath), "r");
|
|
29396
|
+
try {
|
|
29397
|
+
fsyncSync3(directoryFd);
|
|
29398
|
+
} finally {
|
|
29399
|
+
closeSync4(directoryFd);
|
|
29400
|
+
}
|
|
29401
|
+
return path;
|
|
29402
|
+
});
|
|
29403
|
+
}
|
|
29404
|
+
var INFO;
|
|
29405
|
+
var init_enrollment = __esm(async () => {
|
|
29406
|
+
await init_src();
|
|
29407
|
+
await init_join();
|
|
29408
|
+
INFO = Buffer.from("otium-node-enrollment-v1", "utf8");
|
|
29409
|
+
});
|
|
29410
|
+
|
|
29421
29411
|
// ../../adapters/otium/src/turn-bridge.ts
|
|
29422
29412
|
import { randomUUID as randomUUID28 } from "crypto";
|
|
29423
29413
|
function executionFor(payload) {
|
|
@@ -30205,67 +30195,20 @@ async function handleOtiumPeerRequest(req) {
|
|
|
30205
30195
|
}
|
|
30206
30196
|
}
|
|
30207
30197
|
var RUNTIME_VERSION;
|
|
30208
|
-
var init_peer_server = __esm(async () => {
|
|
30209
|
-
await init_src();
|
|
30210
|
-
await init_bindings();
|
|
30211
|
-
await init_central();
|
|
30212
|
-
await init_peer_files();
|
|
30213
|
-
init_protocol();
|
|
30214
|
-
await init_session_bridge();
|
|
30215
|
-
await init_store2();
|
|
30216
|
-
await init_turn_bridge();
|
|
30217
|
-
RUNTIME_VERSION = NEGOTIUM_VERSION;
|
|
30218
|
-
});
|
|
30219
|
-
|
|
30220
|
-
// ../../adapters/otium/src/index.ts
|
|
30221
|
-
var exports_src5 = {};
|
|
30222
|
-
__export(exports_src5, {
|
|
30223
|
-
verifyPeerToken: () => verifyPeerToken,
|
|
30224
|
-
unbindOtiumTopic: () => unbindOtiumTopic,
|
|
30225
|
-
translateBusEvent: () => translateBusEvent,
|
|
30226
|
-
stopEventBackflow: () => stopEventBackflow,
|
|
30227
|
-
startOtiumWorker: () => startOtiumWorker,
|
|
30228
|
-
startOtiumAdapter: () => startOtiumAdapter,
|
|
30229
|
-
startEventBackflow: () => startEventBackflow,
|
|
30230
|
-
shareOtiumTopic: () => shareOtiumTopic,
|
|
30231
|
-
setOtiumTopicPrivate: () => setOtiumTopicPrivate,
|
|
30232
|
-
selfPeerNode: () => selfPeerNode,
|
|
30233
|
-
saveJoin: () => saveJoin,
|
|
30234
|
-
runPeerTurn: () => runPeerTurn,
|
|
30235
|
-
resolvePeerNodeByCellId: () => resolvePeerNodeByCellId,
|
|
30236
|
-
resetPeerCentralCaches: () => resetPeerCentralCaches,
|
|
30237
|
-
registerTurnForwarder: () => registerTurnForwarder,
|
|
30238
|
-
provisionMirrorTopic: () => provisionMirrorTopic,
|
|
30239
|
-
previewEnrollment: () => previewEnrollment,
|
|
30240
|
-
pendingEnrollmentPath: () => pendingEnrollmentPath,
|
|
30241
|
-
parseInviteCode: () => parseInviteCode,
|
|
30242
|
-
parseEnrollmentInvite: () => parseEnrollmentInvite,
|
|
30243
|
-
otiumPeerRuntimeBridge: () => otiumPeerRuntimeBridge,
|
|
30244
|
-
otiumCentralConfig: () => otiumCentralConfig,
|
|
30245
|
-
otiumAdapter: () => otiumAdapter,
|
|
30246
|
-
mintPeerToken: () => mintPeerToken,
|
|
30247
|
-
loadJoin: () => loadJoin,
|
|
30248
|
-
listPeerNodes: () => listPeerNodes,
|
|
30249
|
-
listOtiumTopicBindings: () => listOtiumTopicBindings,
|
|
30250
|
-
joinFilePath: () => joinFilePath,
|
|
30251
|
-
isJoinPersisted: () => isJoinPersisted,
|
|
30252
|
-
isEnrollmentPending: () => isEnrollmentPending,
|
|
30253
|
-
hubEventSender: () => hubEventSender,
|
|
30254
|
-
handleOtiumPeerRequest: () => handleOtiumPeerRequest,
|
|
30255
|
-
getActiveForwarder: () => getActiveForwarder,
|
|
30256
|
-
failInterruptedPeerTurnRequestsOnStartup: () => failInterruptedPeerTurnRequestsOnStartup,
|
|
30257
|
-
createTurnForwarder: () => createTurnForwarder,
|
|
30258
|
-
configureOtiumCentral: () => configureOtiumCentral,
|
|
30259
|
-
commitEnrollment: () => commitEnrollment,
|
|
30260
|
-
cleanupPeerStateForLocalTopic: () => cleanupPeerStateForLocalTopic,
|
|
30261
|
-
claimEnrollment: () => claimEnrollment,
|
|
30262
|
-
bindOtiumTopic: () => bindOtiumTopic,
|
|
30263
|
-
abortHostedPeerTurn: () => abortHostedPeerTurn,
|
|
30264
|
-
TunnelClient: () => TunnelClient,
|
|
30265
|
-
RELAY_PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
30266
|
-
PEER_PROTOCOL_VERSION: () => PEER_PROTOCOL_VERSION
|
|
30198
|
+
var init_peer_server = __esm(async () => {
|
|
30199
|
+
await init_src();
|
|
30200
|
+
await init_bindings();
|
|
30201
|
+
await init_central();
|
|
30202
|
+
await init_peer_files();
|
|
30203
|
+
init_protocol();
|
|
30204
|
+
await init_session_bridge();
|
|
30205
|
+
await init_store2();
|
|
30206
|
+
await init_turn_bridge();
|
|
30207
|
+
RUNTIME_VERSION = NEGOTIUM_VERSION;
|
|
30267
30208
|
});
|
|
30268
|
-
|
|
30209
|
+
|
|
30210
|
+
// ../../adapters/otium/src/index.ts
|
|
30211
|
+
function startOtiumNodeRuntime(options) {
|
|
30269
30212
|
const { join: join32 } = options;
|
|
30270
30213
|
configureOtiumCentral(join32);
|
|
30271
30214
|
const failed = failInterruptedPeerTurnRequestsOnStartup();
|
|
@@ -30293,7 +30236,6 @@ function startOtiumAdapter(options) {
|
|
|
30293
30236
|
}
|
|
30294
30237
|
});
|
|
30295
30238
|
let stopped = false;
|
|
30296
|
-
let tunnel = null;
|
|
30297
30239
|
logger.info({ central: join32.central, cellId: join32.cellId }, "otium: worker mode enabled");
|
|
30298
30240
|
selfPeerNode().then((self) => {
|
|
30299
30241
|
if (self) {
|
|
@@ -30305,17 +30247,40 @@ function startOtiumAdapter(options) {
|
|
|
30305
30247
|
return {
|
|
30306
30248
|
name: "otium",
|
|
30307
30249
|
join: join32,
|
|
30250
|
+
stop: () => {
|
|
30251
|
+
if (stopped)
|
|
30252
|
+
return;
|
|
30253
|
+
stopped = true;
|
|
30254
|
+
unsubscribeTopicCleanup();
|
|
30255
|
+
unregisterRuntimeBridge();
|
|
30256
|
+
unregisterSessionBridge();
|
|
30257
|
+
sessionBridgeIpc.stop();
|
|
30258
|
+
canonicalMcpBridge.stop();
|
|
30259
|
+
stopPeerReplyOutbox();
|
|
30260
|
+
uninstallFileHooks();
|
|
30261
|
+
stopBackflow();
|
|
30262
|
+
configureOtiumCentral(null);
|
|
30263
|
+
}
|
|
30264
|
+
};
|
|
30265
|
+
}
|
|
30266
|
+
function startOtiumAdapter(options) {
|
|
30267
|
+
const runtime = startOtiumNodeRuntime(options);
|
|
30268
|
+
let tunnel = null;
|
|
30269
|
+
let stopped = false;
|
|
30270
|
+
return {
|
|
30271
|
+
name: "otium",
|
|
30272
|
+
join: runtime.join,
|
|
30308
30273
|
startTunnel: ({ targetOrigin, relayUrl }) => {
|
|
30309
30274
|
if (stopped || tunnel)
|
|
30310
30275
|
return;
|
|
30311
|
-
const selectedRelay = relayUrl?.trim() ||
|
|
30276
|
+
const selectedRelay = relayUrl?.trim() || runtime.join.relay || process.env.OTIUM_RELAY_URL?.trim();
|
|
30312
30277
|
if (!selectedRelay) {
|
|
30313
30278
|
logger.info({}, "otium: relay tunnel disabled (no relay URL configured)");
|
|
30314
30279
|
return;
|
|
30315
30280
|
}
|
|
30316
30281
|
tunnel = new TunnelClient({
|
|
30317
30282
|
relayUrl: selectedRelay,
|
|
30318
|
-
token:
|
|
30283
|
+
token: runtime.join.secret,
|
|
30319
30284
|
targetOrigin,
|
|
30320
30285
|
nodeVersion: `negotium@${NEGOTIUM_VERSION}`,
|
|
30321
30286
|
logger
|
|
@@ -30328,24 +30293,10 @@ function startOtiumAdapter(options) {
|
|
|
30328
30293
|
stopped = true;
|
|
30329
30294
|
tunnel?.stop();
|
|
30330
30295
|
tunnel = null;
|
|
30331
|
-
|
|
30332
|
-
unregisterRuntimeBridge();
|
|
30333
|
-
unregisterSessionBridge();
|
|
30334
|
-
sessionBridgeIpc.stop();
|
|
30335
|
-
canonicalMcpBridge.stop();
|
|
30336
|
-
stopPeerReplyOutbox();
|
|
30337
|
-
uninstallFileHooks();
|
|
30338
|
-
stopBackflow();
|
|
30339
|
-
configureOtiumCentral(null);
|
|
30296
|
+
runtime.stop();
|
|
30340
30297
|
}
|
|
30341
30298
|
};
|
|
30342
30299
|
}
|
|
30343
|
-
function startOtiumWorker() {
|
|
30344
|
-
const join32 = loadJoin();
|
|
30345
|
-
if (!join32)
|
|
30346
|
-
return null;
|
|
30347
|
-
return startOtiumAdapter({ join: join32 });
|
|
30348
|
-
}
|
|
30349
30300
|
var otiumAdapter;
|
|
30350
30301
|
var init_src8 = __esm(async () => {
|
|
30351
30302
|
await init_src();
|
|
@@ -30387,6 +30338,245 @@ var init_src8 = __esm(async () => {
|
|
|
30387
30338
|
});
|
|
30388
30339
|
});
|
|
30389
30340
|
|
|
30341
|
+
// ../../adapters/otium/src/node-runtime.ts
|
|
30342
|
+
var exports_node_runtime = {};
|
|
30343
|
+
__export(exports_node_runtime, {
|
|
30344
|
+
mountConfiguredOtiumNodeRuntime: () => mountConfiguredOtiumNodeRuntime,
|
|
30345
|
+
handleOtiumAdapterControlRequest: () => handleOtiumAdapterControlRequest,
|
|
30346
|
+
OTIUM_ADAPTER_CONTROL_PREFIX: () => OTIUM_ADAPTER_CONTROL_PREFIX,
|
|
30347
|
+
OTIUM_ADAPTER_CONTROL_HEADER: () => OTIUM_ADAPTER_CONTROL_HEADER,
|
|
30348
|
+
MAX_PEER_INPUT_REQUEST_BYTES: () => MAX_PEER_INPUT_REQUEST_BYTES
|
|
30349
|
+
});
|
|
30350
|
+
async function handleOtiumAdapterControlRequest(req) {
|
|
30351
|
+
const url = new URL(req.url);
|
|
30352
|
+
if (!url.pathname.startsWith(`${OTIUM_ADAPTER_CONTROL_PREFIX}/`))
|
|
30353
|
+
return null;
|
|
30354
|
+
if (req.headers.get(OTIUM_ADAPTER_CONTROL_HEADER) !== NODE_CONTROL_TOKEN) {
|
|
30355
|
+
return Response.json({ ok: false, error: "Unauthorized" }, { status: 401 });
|
|
30356
|
+
}
|
|
30357
|
+
const peerPath = url.pathname.slice(OTIUM_ADAPTER_CONTROL_PREFIX.length) || "/";
|
|
30358
|
+
const peerUrl = new URL(req.url);
|
|
30359
|
+
peerUrl.pathname = peerPath;
|
|
30360
|
+
const headers = new Headers(req.headers);
|
|
30361
|
+
headers.delete(OTIUM_ADAPTER_CONTROL_HEADER);
|
|
30362
|
+
const body = req.method === "GET" || req.method === "HEAD" ? undefined : await req.arrayBuffer();
|
|
30363
|
+
return await handleOtiumPeerRequest(new Request(peerUrl.toString(), { method: req.method, headers, body, signal: req.signal })) ?? Response.json({ ok: false, error: "Otium route not found" }, { status: 404 });
|
|
30364
|
+
}
|
|
30365
|
+
function mountConfiguredOtiumNodeRuntime() {
|
|
30366
|
+
const join32 = loadJoin();
|
|
30367
|
+
if (!join32)
|
|
30368
|
+
return null;
|
|
30369
|
+
const runtime = startOtiumNodeRuntime({ join: join32 });
|
|
30370
|
+
registerNodeRequestHandler("otium-adapter-control", handleOtiumAdapterControlRequest);
|
|
30371
|
+
let stopped = false;
|
|
30372
|
+
return {
|
|
30373
|
+
...runtime,
|
|
30374
|
+
stop() {
|
|
30375
|
+
if (stopped)
|
|
30376
|
+
return;
|
|
30377
|
+
stopped = true;
|
|
30378
|
+
unregisterNodeRequestHandler("otium-adapter-control");
|
|
30379
|
+
runtime.stop();
|
|
30380
|
+
}
|
|
30381
|
+
};
|
|
30382
|
+
}
|
|
30383
|
+
var init_node_runtime = __esm(async () => {
|
|
30384
|
+
await init_src();
|
|
30385
|
+
await init_src8();
|
|
30386
|
+
await init_join();
|
|
30387
|
+
await init_peer_server();
|
|
30388
|
+
init_protocol();
|
|
30389
|
+
});
|
|
30390
|
+
|
|
30391
|
+
// ../../adapters/otium/src/join-cli.ts
|
|
30392
|
+
var exports_join_cli = {};
|
|
30393
|
+
__export(exports_join_cli, {
|
|
30394
|
+
joinCommand: () => joinCommand
|
|
30395
|
+
});
|
|
30396
|
+
function option2(args, name) {
|
|
30397
|
+
const index = args.indexOf(`--${name}`);
|
|
30398
|
+
return index >= 0 ? args[index + 1]?.trim() : undefined;
|
|
30399
|
+
}
|
|
30400
|
+
async function confirmEnrollment(message) {
|
|
30401
|
+
if (!process.stdin.isTTY)
|
|
30402
|
+
return false;
|
|
30403
|
+
const { createInterface } = await import("readline/promises");
|
|
30404
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
30405
|
+
try {
|
|
30406
|
+
return /^(?:y|yes)$/i.test((await prompt.question(`${message} [y/N] `)).trim());
|
|
30407
|
+
} finally {
|
|
30408
|
+
prompt.close();
|
|
30409
|
+
}
|
|
30410
|
+
}
|
|
30411
|
+
async function joinCommand(args) {
|
|
30412
|
+
const code = args[0]?.trim();
|
|
30413
|
+
if (!code) {
|
|
30414
|
+
console.error("usage: negotium-otium join <invite-code> [--yes] [--name <node-name>] [--legacy] [--replace]");
|
|
30415
|
+
process.exitCode = 1;
|
|
30416
|
+
return;
|
|
30417
|
+
}
|
|
30418
|
+
let join32;
|
|
30419
|
+
let productionEnrollment = false;
|
|
30420
|
+
if (args.includes("--legacy")) {
|
|
30421
|
+
try {
|
|
30422
|
+
join32 = parseInviteCode(code);
|
|
30423
|
+
} catch (err2) {
|
|
30424
|
+
console.error(`invalid legacy invite code: ${err2 instanceof Error ? err2.message : err2}`);
|
|
30425
|
+
process.exitCode = 1;
|
|
30426
|
+
return;
|
|
30427
|
+
}
|
|
30428
|
+
} else {
|
|
30429
|
+
try {
|
|
30430
|
+
const invite = parseEnrollmentInvite(code);
|
|
30431
|
+
const resuming = isEnrollmentPending(invite);
|
|
30432
|
+
let nodeName = option2(args, "name");
|
|
30433
|
+
if (resuming) {
|
|
30434
|
+
console.log(`Resuming interrupted Otium enrollment with ${invite.central}`);
|
|
30435
|
+
} else {
|
|
30436
|
+
const preview = await previewEnrollment(invite);
|
|
30437
|
+
const workspace = preview.preview?.workspace;
|
|
30438
|
+
nodeName ||= preview.preview?.suggestedNodeName || undefined;
|
|
30439
|
+
console.log(`Otium workspace: ${workspace?.name ?? workspace?.slug ?? workspace?.id}`);
|
|
30440
|
+
console.log(` central: ${invite.central}`);
|
|
30441
|
+
console.log(` transport: ${preview.preview?.transport ?? "relay"}`);
|
|
30442
|
+
console.log(` access: ${preview.preview?.topics ?? "explicitly shared topics only"}`);
|
|
30443
|
+
const accepted = args.includes("--yes") || await confirmEnrollment("Join this workspace?");
|
|
30444
|
+
if (!accepted) {
|
|
30445
|
+
console.log("enrollment cancelled");
|
|
30446
|
+
return;
|
|
30447
|
+
}
|
|
30448
|
+
}
|
|
30449
|
+
join32 = await claimEnrollment(invite, nodeName);
|
|
30450
|
+
productionEnrollment = true;
|
|
30451
|
+
} catch (err2) {
|
|
30452
|
+
console.error(`enrollment failed: ${err2 instanceof Error ? err2.message : err2}`);
|
|
30453
|
+
process.exitCode = 1;
|
|
30454
|
+
return;
|
|
30455
|
+
}
|
|
30456
|
+
}
|
|
30457
|
+
let path;
|
|
30458
|
+
try {
|
|
30459
|
+
const saveOptions = { replaceExisting: args.includes("--replace") };
|
|
30460
|
+
path = productionEnrollment ? commitEnrollment(join32, saveOptions) : saveJoin(join32, saveOptions);
|
|
30461
|
+
} catch (err2) {
|
|
30462
|
+
console.error(`could not save join credentials: ${err2 instanceof Error ? err2.message : err2}`);
|
|
30463
|
+
process.exitCode = 1;
|
|
30464
|
+
return;
|
|
30465
|
+
}
|
|
30466
|
+
console.log(`otium join credentials saved to ${path}`);
|
|
30467
|
+
console.log(` central: ${join32.central}`);
|
|
30468
|
+
console.log(` cellId: ${join32.cellId}`);
|
|
30469
|
+
configureOtiumCentral(join32);
|
|
30470
|
+
try {
|
|
30471
|
+
const self = await selfPeerNode();
|
|
30472
|
+
if (self) {
|
|
30473
|
+
console.log(`attached to workspace as "${self.nodeName ?? self.cellId}" (baseUrl ${self.baseUrl})`);
|
|
30474
|
+
} else {
|
|
30475
|
+
console.warn("warning: central answered but this cell has no visible assignment yet \u2014 check the workspace assignment");
|
|
30476
|
+
}
|
|
30477
|
+
} catch (err2) {
|
|
30478
|
+
console.warn(`warning: could not verify against central (${err2 instanceof Error ? err2.message : err2}) \u2014 credentials saved anyway`);
|
|
30479
|
+
} finally {
|
|
30480
|
+
configureOtiumCentral(null);
|
|
30481
|
+
}
|
|
30482
|
+
console.log("\nnext: `negotium-otium serve` (mounts the otium peer routes automatically)");
|
|
30483
|
+
}
|
|
30484
|
+
var init_join_cli = __esm(async () => {
|
|
30485
|
+
await init_central();
|
|
30486
|
+
await init_enrollment();
|
|
30487
|
+
await init_join();
|
|
30488
|
+
});
|
|
30489
|
+
|
|
30490
|
+
// ../../adapters/otium/src/sidecar.ts
|
|
30491
|
+
var exports_sidecar = {};
|
|
30492
|
+
__export(exports_sidecar, {
|
|
30493
|
+
runOtiumSidecar: () => runOtiumSidecar,
|
|
30494
|
+
proxyOtiumPeerRequest: () => proxyOtiumPeerRequest
|
|
30495
|
+
});
|
|
30496
|
+
async function proxyOtiumPeerRequest(req, dependencies = {}) {
|
|
30497
|
+
const inspectNode = dependencies.inspectNode ?? inspectNodeDaemon;
|
|
30498
|
+
const fetchRequest = dependencies.fetch ?? fetch;
|
|
30499
|
+
const status = await inspectNode();
|
|
30500
|
+
if (!status.running || !status.info) {
|
|
30501
|
+
return Response.json({ ok: false, error: "canonical Negotium node is unavailable" }, { status: 503 });
|
|
30502
|
+
}
|
|
30503
|
+
const source = new URL(req.url);
|
|
30504
|
+
const target = new URL(`http://127.0.0.1:${status.info.port}`);
|
|
30505
|
+
target.pathname = `${OTIUM_ADAPTER_CONTROL_PREFIX}${source.pathname}`;
|
|
30506
|
+
target.search = source.search;
|
|
30507
|
+
const headers = new Headers(req.headers);
|
|
30508
|
+
headers.set(OTIUM_ADAPTER_CONTROL_HEADER, NODE_CONTROL_TOKEN);
|
|
30509
|
+
try {
|
|
30510
|
+
const body = req.method === "GET" || req.method === "HEAD" ? undefined : await req.arrayBuffer();
|
|
30511
|
+
return await fetchRequest(new Request(target.toString(), { method: req.method, headers, body, signal: req.signal }));
|
|
30512
|
+
} catch (error) {
|
|
30513
|
+
logger.warn({ err: error }, "otium sidecar: canonical node request failed");
|
|
30514
|
+
return Response.json({ ok: false, error: "canonical Negotium node connection failed" }, { status: 503 });
|
|
30515
|
+
}
|
|
30516
|
+
}
|
|
30517
|
+
async function runOtiumSidecar(options) {
|
|
30518
|
+
const join32 = loadJoin();
|
|
30519
|
+
if (!join32) {
|
|
30520
|
+
throw new Error("not joined to an Otium workspace \u2014 run `negotium otium join <code>` first");
|
|
30521
|
+
}
|
|
30522
|
+
const initialNode = await inspectNodeDaemon();
|
|
30523
|
+
if (!initialNode.running) {
|
|
30524
|
+
throw new Error("canonical Negotium node is not running");
|
|
30525
|
+
}
|
|
30526
|
+
const ready = await proxyOtiumPeerRequest(new Request("http://127.0.0.1/ready"));
|
|
30527
|
+
if (!ready.ok) {
|
|
30528
|
+
throw new Error("canonical Negotium node has no Otium runtime; restart it after joining the workspace");
|
|
30529
|
+
}
|
|
30530
|
+
let server;
|
|
30531
|
+
const lease = await waitForRequiredRuntimeProcessLease("adapter:otium", {
|
|
30532
|
+
workloadName: "Otium adapter",
|
|
30533
|
+
onLost: () => {
|
|
30534
|
+
process.stderr.write(`negotium otium: singleton lease lost; shutting down
|
|
30535
|
+
`);
|
|
30536
|
+
runShutdown("test");
|
|
30537
|
+
}
|
|
30538
|
+
});
|
|
30539
|
+
try {
|
|
30540
|
+
server = Bun.serve({
|
|
30541
|
+
port: options.port,
|
|
30542
|
+
hostname: "127.0.0.1",
|
|
30543
|
+
idleTimeout: 240,
|
|
30544
|
+
maxRequestBodySize: MAX_PEER_INPUT_REQUEST_BYTES,
|
|
30545
|
+
fetch: (req) => proxyOtiumPeerRequest(req)
|
|
30546
|
+
});
|
|
30547
|
+
} catch (error) {
|
|
30548
|
+
lease.stop();
|
|
30549
|
+
throw error;
|
|
30550
|
+
}
|
|
30551
|
+
const selectedRelay = options.relayUrl?.trim() || join32.relay || process.env.OTIUM_RELAY_URL?.trim();
|
|
30552
|
+
const tunnel = selectedRelay ? new TunnelClient({
|
|
30553
|
+
relayUrl: selectedRelay,
|
|
30554
|
+
token: join32.secret,
|
|
30555
|
+
targetOrigin: `http://127.0.0.1:${server.port}`,
|
|
30556
|
+
nodeVersion: `negotium@${NEGOTIUM_VERSION}`,
|
|
30557
|
+
logger
|
|
30558
|
+
}) : null;
|
|
30559
|
+
tunnel?.start();
|
|
30560
|
+
let resolveCompleted;
|
|
30561
|
+
const completed = new Promise((resolve15) => {
|
|
30562
|
+
resolveCompleted = resolve15;
|
|
30563
|
+
});
|
|
30564
|
+
onShutdown("otium-sidecar-server", 130, () => server?.stop(true));
|
|
30565
|
+
onShutdown("otium-sidecar-tunnel", 120, () => tunnel?.stop());
|
|
30566
|
+
onShutdown("otium-sidecar-lease", 110, () => lease.stop());
|
|
30567
|
+
onShutdown("otium-sidecar-completed", -100, resolveCompleted);
|
|
30568
|
+
process.stdout.write(`negotium Otium adapter listening on 127.0.0.1:${server.port} (canonical node pid ${initialNode.info?.pid})
|
|
30569
|
+
`);
|
|
30570
|
+
await completed;
|
|
30571
|
+
}
|
|
30572
|
+
var init_sidecar = __esm(async () => {
|
|
30573
|
+
await init_src();
|
|
30574
|
+
await init_src5();
|
|
30575
|
+
await init_join();
|
|
30576
|
+
init_protocol();
|
|
30577
|
+
init_tunnel_client();
|
|
30578
|
+
});
|
|
30579
|
+
|
|
30390
30580
|
// ../../adapters/otium/src/cli.ts
|
|
30391
30581
|
var exports_cli3 = {};
|
|
30392
30582
|
__export(exports_cli3, {
|
|
@@ -30464,66 +30654,57 @@ async function resolveHostNodeId(explicit) {
|
|
|
30464
30654
|
configureOtiumCentral2(null);
|
|
30465
30655
|
}
|
|
30466
30656
|
}
|
|
30657
|
+
async function spawnCanonicalNode2() {
|
|
30658
|
+
const entry = process.argv[1];
|
|
30659
|
+
if (!entry)
|
|
30660
|
+
throw new Error("cannot locate the Negotium CLI entrypoint");
|
|
30661
|
+
const { LOG_DIR: LOG_DIR2 } = await init_src().then(() => exports_src);
|
|
30662
|
+
const child = Bun.spawn({
|
|
30663
|
+
cmd: [process.execPath, entry, "__node-daemon", "--port=0"],
|
|
30664
|
+
detached: true,
|
|
30665
|
+
env: { ...process.env, LOG_LEVEL: process.env.NEGOTIUM_NODE_LOG_LEVEL?.trim() || "info" },
|
|
30666
|
+
stdin: "ignore",
|
|
30667
|
+
stdout: "ignore",
|
|
30668
|
+
stderr: Bun.file(`${LOG_DIR2}/node-daemon.log`)
|
|
30669
|
+
});
|
|
30670
|
+
child.unref();
|
|
30671
|
+
}
|
|
30672
|
+
async function ensureCanonicalNode2() {
|
|
30673
|
+
const { inspectNodeDaemon: inspectNodeDaemon2, waitForNodeDaemon: waitForNodeDaemon2 } = await init_src5().then(() => exports_src3);
|
|
30674
|
+
const status = await inspectNodeDaemon2();
|
|
30675
|
+
if (status.running)
|
|
30676
|
+
return;
|
|
30677
|
+
await spawnCanonicalNode2();
|
|
30678
|
+
await waitForNodeDaemon2(15000);
|
|
30679
|
+
}
|
|
30680
|
+
async function runCanonicalNodeChild2() {
|
|
30681
|
+
const { onShutdown: onShutdown2 } = await init_src().then(() => exports_src);
|
|
30682
|
+
const { MAX_PEER_INPUT_REQUEST_BYTES: MAX_PEER_INPUT_REQUEST_BYTES2, mountConfiguredOtiumNodeRuntime: mountConfiguredOtiumNodeRuntime2 } = await init_node_runtime().then(() => exports_node_runtime);
|
|
30683
|
+
const { runNodeDaemon: runNodeDaemon2 } = await init_src5().then(() => exports_src3);
|
|
30684
|
+
const runtime = mountConfiguredOtiumNodeRuntime2();
|
|
30685
|
+
if (runtime)
|
|
30686
|
+
onShutdown2("otium-node-runtime", 125, () => runtime.stop());
|
|
30687
|
+
await runNodeDaemon2({ port: 0, maxRequestBodySize: MAX_PEER_INPUT_REQUEST_BYTES2 });
|
|
30688
|
+
}
|
|
30467
30689
|
async function runOtiumCli(args = process.argv.slice(2)) {
|
|
30468
30690
|
const [command, ...commandArgs] = args;
|
|
30469
30691
|
switch (command) {
|
|
30692
|
+
case "__node-daemon": {
|
|
30693
|
+
await runCanonicalNodeChild2();
|
|
30694
|
+
break;
|
|
30695
|
+
}
|
|
30470
30696
|
case "join": {
|
|
30471
30697
|
const { joinCommand: joinCommand2 } = await init_join_cli().then(() => exports_join_cli);
|
|
30472
30698
|
await joinCommand2(commandArgs);
|
|
30473
30699
|
break;
|
|
30474
30700
|
}
|
|
30475
30701
|
case "serve": {
|
|
30476
|
-
const {
|
|
30477
|
-
|
|
30478
|
-
onShutdown: onShutdown2,
|
|
30479
|
-
registerNodeRequestHandler: registerNodeRequestHandler2,
|
|
30480
|
-
unregisterNodeRequestHandler: unregisterNodeRequestHandler2,
|
|
30481
|
-
waitForRequiredRuntimeProcessLease: waitForRequiredRuntimeProcessLease2
|
|
30482
|
-
} = await init_src().then(() => exports_src);
|
|
30483
|
-
const [{ startDefaultNode: startDefaultNode2 }, otium] = await Promise.all([
|
|
30484
|
-
init_src5().then(() => exports_src3),
|
|
30485
|
-
init_src8().then(() => exports_src5)
|
|
30486
|
-
]);
|
|
30702
|
+
const { NEGOTIUM_PORT: NEGOTIUM_PORT2 } = await init_src().then(() => exports_src);
|
|
30703
|
+
const { runOtiumSidecar: runOtiumSidecar2 } = await init_sidecar().then(() => exports_sidecar);
|
|
30487
30704
|
const port = parseOtiumServePort(commandArgs, NEGOTIUM_PORT2);
|
|
30488
30705
|
const relayUrl = parseOtiumServeRelayUrl(commandArgs);
|
|
30489
|
-
|
|
30490
|
-
|
|
30491
|
-
workloadName: "Otium adapter",
|
|
30492
|
-
onLost: () => {
|
|
30493
|
-
console.error("negotium otium: singleton lease lost; shutting down");
|
|
30494
|
-
node?.stop();
|
|
30495
|
-
}
|
|
30496
|
-
});
|
|
30497
|
-
const { handleOtiumPeerRequest: handleOtiumPeerRequest2, startOtiumWorker: startOtiumWorker2 } = otium;
|
|
30498
|
-
const worker = startOtiumWorker2();
|
|
30499
|
-
if (!worker) {
|
|
30500
|
-
singleton.stop();
|
|
30501
|
-
throw new Error("not joined to an otium workspace \u2014 run `negotium otium join <code>` first");
|
|
30502
|
-
}
|
|
30503
|
-
registerNodeRequestHandler2("otium-peer", handleOtiumPeerRequest2);
|
|
30504
|
-
let workerStopped = false;
|
|
30505
|
-
const stopWorker = () => {
|
|
30506
|
-
if (workerStopped)
|
|
30507
|
-
return;
|
|
30508
|
-
workerStopped = true;
|
|
30509
|
-
unregisterNodeRequestHandler2("otium-peer");
|
|
30510
|
-
worker.stop();
|
|
30511
|
-
};
|
|
30512
|
-
onShutdown2("otium-worker", 100, stopWorker);
|
|
30513
|
-
onShutdown2("otium-singleton", 90, () => singleton.stop());
|
|
30514
|
-
try {
|
|
30515
|
-
node = await startDefaultNode2({
|
|
30516
|
-
port,
|
|
30517
|
-
maxRequestBodySize: MAX_PEER_INPUT_REQUEST_BYTES
|
|
30518
|
-
});
|
|
30519
|
-
} catch (error) {
|
|
30520
|
-
stopWorker();
|
|
30521
|
-
singleton.stop();
|
|
30522
|
-
throw error;
|
|
30523
|
-
}
|
|
30524
|
-
console.log(`negotium node (otium worker) listening on 127.0.0.1:${node.port} (ctrl-c to stop)`);
|
|
30525
|
-
worker.startTunnel({ targetOrigin: `http://127.0.0.1:${node.port}`, relayUrl });
|
|
30526
|
-
await node.completed;
|
|
30706
|
+
await ensureCanonicalNode2();
|
|
30707
|
+
await runOtiumSidecar2({ port, relayUrl });
|
|
30527
30708
|
break;
|
|
30528
30709
|
}
|
|
30529
30710
|
case "bindings": {
|
|
@@ -30590,7 +30771,6 @@ async function runOtiumCli(args = process.argv.slice(2)) {
|
|
|
30590
30771
|
}
|
|
30591
30772
|
}
|
|
30592
30773
|
var init_cli3 = __esm(() => {
|
|
30593
|
-
init_protocol();
|
|
30594
30774
|
if (false) {}
|
|
30595
30775
|
});
|
|
30596
30776
|
|
|
@@ -31304,6 +31484,36 @@ function numericOption(values, name, fallback) {
|
|
|
31304
31484
|
const parsed = Number.parseInt(values.find((value) => value.startsWith(prefix))?.slice(prefix.length) ?? "", 10);
|
|
31305
31485
|
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
31306
31486
|
}
|
|
31487
|
+
async function runCanonicalNode(port) {
|
|
31488
|
+
const { onShutdown: onShutdown2 } = await init_src().then(() => exports_src);
|
|
31489
|
+
const { MAX_PEER_INPUT_REQUEST_BYTES: MAX_PEER_INPUT_REQUEST_BYTES2, mountConfiguredOtiumNodeRuntime: mountConfiguredOtiumNodeRuntime2 } = await init_node_runtime().then(() => exports_node_runtime);
|
|
31490
|
+
const { startDefaultNode: startDefaultNode2 } = await init_src5().then(() => exports_src3);
|
|
31491
|
+
const otiumRuntime = mountConfiguredOtiumNodeRuntime2();
|
|
31492
|
+
if (otiumRuntime)
|
|
31493
|
+
onShutdown2("otium-node-runtime", 125, () => otiumRuntime.stop());
|
|
31494
|
+
const node = await startDefaultNode2({
|
|
31495
|
+
port,
|
|
31496
|
+
advertise: true,
|
|
31497
|
+
singleton: true,
|
|
31498
|
+
maxRequestBodySize: MAX_PEER_INPUT_REQUEST_BYTES2
|
|
31499
|
+
});
|
|
31500
|
+
console.log(`negotium node listening on 127.0.0.1:${node.port} (ctrl-c to stop)`);
|
|
31501
|
+
await node.completed;
|
|
31502
|
+
}
|
|
31503
|
+
async function stopAdapter(name) {
|
|
31504
|
+
const { getRuntimeProcessLease: getRuntimeProcessLease2 } = await init_src().then(() => exports_src);
|
|
31505
|
+
const lease = getRuntimeProcessLease2(`adapter:${name}`);
|
|
31506
|
+
if (!lease)
|
|
31507
|
+
return false;
|
|
31508
|
+
try {
|
|
31509
|
+
process.kill(lease.pid, "SIGTERM");
|
|
31510
|
+
return true;
|
|
31511
|
+
} catch (error) {
|
|
31512
|
+
if (error.code === "ESRCH")
|
|
31513
|
+
return false;
|
|
31514
|
+
throw error;
|
|
31515
|
+
}
|
|
31516
|
+
}
|
|
31307
31517
|
switch (command) {
|
|
31308
31518
|
case "init": {
|
|
31309
31519
|
const { initCommand: initCommand2 } = await init_init().then(() => exports_init);
|
|
@@ -31316,22 +31526,20 @@ switch (command) {
|
|
|
31316
31526
|
break;
|
|
31317
31527
|
}
|
|
31318
31528
|
case "serve": {
|
|
31319
|
-
|
|
31320
|
-
|
|
31321
|
-
|
|
31322
|
-
|
|
31323
|
-
|
|
31324
|
-
|
|
31325
|
-
console.log(`negotium node listening on 127.0.0.1:${node.port} (ctrl-c to stop)`);
|
|
31326
|
-
await node.completed;
|
|
31529
|
+
if (args[0] === "otium") {
|
|
31530
|
+
const { runOtiumCli: runOtiumCli2 } = await loadOtiumCli();
|
|
31531
|
+
await runOtiumCli2(["serve", ...args.slice(1)]);
|
|
31532
|
+
break;
|
|
31533
|
+
}
|
|
31534
|
+
await runCanonicalNode(numericOption(args, "port", 7777));
|
|
31327
31535
|
break;
|
|
31328
31536
|
}
|
|
31329
31537
|
case "__node-daemon": {
|
|
31330
|
-
|
|
31331
|
-
await runNodeDaemon2({ port: numericOption(args, "port", 0) });
|
|
31538
|
+
await runCanonicalNode(numericOption(args, "port", 0));
|
|
31332
31539
|
break;
|
|
31333
31540
|
}
|
|
31334
31541
|
case "status": {
|
|
31542
|
+
const { listRuntimeProcessLeases: listRuntimeProcessLeases2 } = await init_src().then(() => exports_src);
|
|
31335
31543
|
const { inspectNodeDaemon: inspectNodeDaemon2 } = await init_src5().then(() => exports_src3);
|
|
31336
31544
|
const status = await inspectNodeDaemon2();
|
|
31337
31545
|
if (status.running && status.info) {
|
|
@@ -31342,10 +31550,33 @@ switch (command) {
|
|
|
31342
31550
|
} else {
|
|
31343
31551
|
console.log("negotium node is stopped");
|
|
31344
31552
|
}
|
|
31553
|
+
const adapters = listRuntimeProcessLeases2("adapter:");
|
|
31554
|
+
if (adapters.length === 0)
|
|
31555
|
+
console.log("adapters: none");
|
|
31556
|
+
else {
|
|
31557
|
+
for (const adapter of adapters) {
|
|
31558
|
+
console.log(`adapter ${adapter.role.slice("adapter:".length)} running (pid ${adapter.pid}, since ${new Date(adapter.startedAt).toISOString()})`);
|
|
31559
|
+
}
|
|
31560
|
+
}
|
|
31345
31561
|
break;
|
|
31346
31562
|
}
|
|
31347
31563
|
case "stop": {
|
|
31348
31564
|
const { stopNodeDaemon: stopNodeDaemon2 } = await init_src5().then(() => exports_src3);
|
|
31565
|
+
const target = args[0];
|
|
31566
|
+
if (target === "otium" || target === "telegram") {
|
|
31567
|
+
const stopped2 = await stopAdapter(target);
|
|
31568
|
+
console.log(stopped2 ? `${target} adapter shutdown requested` : `${target} adapter is not running`);
|
|
31569
|
+
break;
|
|
31570
|
+
}
|
|
31571
|
+
if (target && target !== "--all") {
|
|
31572
|
+
throw new Error("usage: negotium stop [otium|telegram|--all]");
|
|
31573
|
+
}
|
|
31574
|
+
if (target === "--all") {
|
|
31575
|
+
for (const name of ["otium", "telegram"]) {
|
|
31576
|
+
if (await stopAdapter(name))
|
|
31577
|
+
console.log(`${name} adapter shutdown requested`);
|
|
31578
|
+
}
|
|
31579
|
+
}
|
|
31349
31580
|
const stopped = await stopNodeDaemon2();
|
|
31350
31581
|
console.log(stopped ? "negotium node shutdown requested" : "negotium node is not running");
|
|
31351
31582
|
break;
|
|
@@ -31377,11 +31608,14 @@ switch (command) {
|
|
|
31377
31608
|
}
|
|
31378
31609
|
case "telegram": {
|
|
31379
31610
|
const { runTelegramCli: runTelegramCli2 } = await loadTelegramCli();
|
|
31380
|
-
await runTelegramCli2();
|
|
31611
|
+
await runTelegramCli2(args);
|
|
31381
31612
|
break;
|
|
31382
31613
|
}
|
|
31383
31614
|
case "otium": {
|
|
31384
31615
|
const { runOtiumCli: runOtiumCli2 } = await loadOtiumCli();
|
|
31616
|
+
if (args[0] === "serve") {
|
|
31617
|
+
process.stderr.write("warning: `negotium otium serve` is deprecated; use `negotium serve otium`\n");
|
|
31618
|
+
}
|
|
31385
31619
|
await runOtiumCli2(args);
|
|
31386
31620
|
break;
|
|
31387
31621
|
}
|
|
@@ -31400,9 +31634,10 @@ switch (command) {
|
|
|
31400
31634
|
" init bootstrap ~/.negotium and check agent auth",
|
|
31401
31635
|
" chat [topic] interactive chat (creates the topic if missing)",
|
|
31402
31636
|
" options: --agent=claude|codex|maestro",
|
|
31403
|
-
" serve foreground
|
|
31404
|
-
"
|
|
31405
|
-
"
|
|
31637
|
+
" serve foreground canonical node (default port 7777)",
|
|
31638
|
+
" serve otium ensure the canonical node and run the Otium sidecar",
|
|
31639
|
+
" status show the canonical node and adapter processes",
|
|
31640
|
+
" stop [otium|telegram|--all] stop the node, one adapter, or everything",
|
|
31406
31641
|
" topics list topics on this node",
|
|
31407
31642
|
" mcp list|add|remove|enable|disable manage node MCP manifest",
|
|
31408
31643
|
" vault list|set|get|del node secret store (encrypted at rest)",
|
|
@@ -31420,4 +31655,4 @@ switch (command) {
|
|
|
31420
31655
|
}
|
|
31421
31656
|
}
|
|
31422
31657
|
|
|
31423
|
-
//# debugId=
|
|
31658
|
+
//# debugId=F87B7DCB5931FEC964756E2164756E21
|