negotium 0.1.13 → 0.1.15
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 +26 -6
- package/dist/cron.js.map +11 -10
- package/dist/hosted-agent.js +2 -2
- package/dist/hosted-agent.js.map +2 -2
- package/dist/main.js +1450 -635
- package/dist/main.js.map +37 -32
- package/dist/runtime/src/application/switch-topic-access-mode.ts +37 -0
- package/dist/runtime/src/index.ts +6 -0
- package/dist/runtime/src/query/active-rooms.ts +1 -0
- package/dist/runtime/src/runtime/turn-runner.ts +17 -2
- package/dist/runtime/src/storage/api-messages.ts +16 -2
- package/dist/runtime/src/storage/runtime-process-leases.ts +18 -0
- package/dist/runtime/src/topics/derive.ts +1 -0
- package/dist/runtime/src/types/api.ts +3 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js +9 -1
- package/dist/runtime-helpers.js.map +3 -3
- package/dist/storage.js +14 -4
- package/dist/storage.js.map +3 -3
- package/dist/types/packages/core/src/storage/api-messages.d.ts +2 -0
- package/dist/types/packages/core/src/types/api.d.ts +3 -0
- 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.15";
|
|
3328
3328
|
|
|
3329
3329
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
3330
3330
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -4520,6 +4520,8 @@ function initializeApiMessagesSchema() {
|
|
|
4520
4520
|
parent_id TEXT,
|
|
4521
4521
|
author_id TEXT NOT NULL,
|
|
4522
4522
|
source_adapter TEXT,
|
|
4523
|
+
source_node TEXT,
|
|
4524
|
+
source_message_id TEXT,
|
|
4523
4525
|
text TEXT NOT NULL,
|
|
4524
4526
|
query_id TEXT,
|
|
4525
4527
|
agent_type TEXT,
|
|
@@ -4539,6 +4541,12 @@ function initializeApiMessagesSchema() {
|
|
|
4539
4541
|
try {
|
|
4540
4542
|
db.exec("ALTER TABLE api_messages ADD COLUMN source_adapter TEXT");
|
|
4541
4543
|
} catch {}
|
|
4544
|
+
try {
|
|
4545
|
+
db.exec("ALTER TABLE api_messages ADD COLUMN source_node TEXT");
|
|
4546
|
+
} catch {}
|
|
4547
|
+
try {
|
|
4548
|
+
db.exec("ALTER TABLE api_messages ADD COLUMN source_message_id TEXT");
|
|
4549
|
+
} catch {}
|
|
4542
4550
|
try {
|
|
4543
4551
|
db.exec("ALTER TABLE api_messages ADD COLUMN deleted INTEGER NOT NULL DEFAULT 0");
|
|
4544
4552
|
} catch {}
|
|
@@ -4584,6 +4592,8 @@ function rowToDto(r) {
|
|
|
4584
4592
|
parentId: r.parent_id ?? undefined,
|
|
4585
4593
|
authorId: r.author_id,
|
|
4586
4594
|
sourceAdapter: r.source_adapter ?? undefined,
|
|
4595
|
+
sourceNode: r.source_node ?? undefined,
|
|
4596
|
+
sourceMessageId: r.source_message_id ?? undefined,
|
|
4587
4597
|
text: r.text,
|
|
4588
4598
|
queryId: r.query_id ?? undefined,
|
|
4589
4599
|
agentType: r.agent_type ?? undefined,
|
|
@@ -4617,9 +4627,9 @@ function appendApiMessage(msg, options = {}) {
|
|
|
4617
4627
|
let inserted = false;
|
|
4618
4628
|
db.transaction(() => {
|
|
4619
4629
|
const result = db.query(`INSERT INTO api_messages
|
|
4620
|
-
(id, topic_id, parent_id, author_id, source_adapter, text, query_id, agent_type, model, attachments, usage, deleted, edited_at, reactions, kind, ask_user_question, subagent_card, mentions, thread_root_id, created_at)
|
|
4621
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4622
|
-
ON CONFLICT(id) DO NOTHING`).run(msg.id, msg.topicId, msg.parentId ?? null, msg.authorId, msg.sourceAdapter ?? null, msg.text, msg.queryId ?? null, msg.agentType ?? null, msg.model ?? null, msg.attachments ? JSON.stringify(msg.attachments) : null, msg.usage ? JSON.stringify(msg.usage) : null, msg.deleted ? 1 : 0, msg.editedAt ?? null, msg.reactions?.length ? JSON.stringify(msg.reactions) : null, msg.kind ?? null, msg.askUserQuestion ? JSON.stringify(msg.askUserQuestion) : null, msg.subagentCard ? JSON.stringify(msg.subagentCard) : null, msg.mentions?.length ? JSON.stringify(msg.mentions) : null, msg.threadRootId ?? null, msg.createdAt);
|
|
4630
|
+
(id, topic_id, parent_id, author_id, source_adapter, source_node, source_message_id, text, query_id, agent_type, model, attachments, usage, deleted, edited_at, reactions, kind, ask_user_question, subagent_card, mentions, thread_root_id, created_at)
|
|
4631
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4632
|
+
ON CONFLICT(id) DO NOTHING`).run(msg.id, msg.topicId, msg.parentId ?? null, msg.authorId, msg.sourceAdapter ?? null, msg.sourceNode ?? null, msg.sourceMessageId ?? null, msg.text, msg.queryId ?? null, msg.agentType ?? null, msg.model ?? null, msg.attachments ? JSON.stringify(msg.attachments) : null, msg.usage ? JSON.stringify(msg.usage) : null, msg.deleted ? 1 : 0, msg.editedAt ?? null, msg.reactions?.length ? JSON.stringify(msg.reactions) : null, msg.kind ?? null, msg.askUserQuestion ? JSON.stringify(msg.askUserQuestion) : null, msg.subagentCard ? JSON.stringify(msg.subagentCard) : null, msg.mentions?.length ? JSON.stringify(msg.mentions) : null, msg.threadRootId ?? null, msg.createdAt);
|
|
4623
4633
|
inserted = Number(result.changes ?? 0) > 0;
|
|
4624
4634
|
if (inserted && updateTopicLastMessageAt && !msg.deleted) {
|
|
4625
4635
|
db.query(`UPDATE api_topics
|
|
@@ -7420,6 +7430,7 @@ function updateTopic(topicId, patch) {
|
|
|
7420
7430
|
return false;
|
|
7421
7431
|
Object.assign(topic, patch);
|
|
7422
7432
|
upsertTopic(topic);
|
|
7433
|
+
WsHub.get().broadcastTopicUpdated(topicId);
|
|
7423
7434
|
return true;
|
|
7424
7435
|
}
|
|
7425
7436
|
function isParticipant(topic, userId) {
|
|
@@ -11356,6 +11367,7 @@ async function streamAgentEvents(topicId, topicTitle, queryId, events, control,
|
|
|
11356
11367
|
queryId,
|
|
11357
11368
|
agentType,
|
|
11358
11369
|
model,
|
|
11370
|
+
sourceNode: execution?.sourceNode,
|
|
11359
11371
|
usage,
|
|
11360
11372
|
createdAt: new Date().toISOString()
|
|
11361
11373
|
};
|
|
@@ -11832,6 +11844,7 @@ function redispatchInject(inject) {
|
|
|
11832
11844
|
bridgeSessionFromHistory: inject.bridgeSessionFromHistory,
|
|
11833
11845
|
onSettled: inject.onSettled,
|
|
11834
11846
|
peerBridge: inject.peerBridge,
|
|
11847
|
+
sourceNode: inject.sourceNode,
|
|
11835
11848
|
askReplySources: inject.askReplySources,
|
|
11836
11849
|
_runtimeEpoch: inject.runtimeEpoch,
|
|
11837
11850
|
_sessionRetried: inject._sessionRetried,
|
|
@@ -12158,6 +12171,7 @@ function startAiTurn(params) {
|
|
|
12158
12171
|
let sessionId = sessionResolution.sessionId;
|
|
12159
12172
|
const deferredSessionId = params.sessionId === undefined && !sessionResolution.isolated ? undefined : sessionId;
|
|
12160
12173
|
const origin = params.origin ?? "user";
|
|
12174
|
+
const sourceNode = params.sourceNode;
|
|
12161
12175
|
const topicId = topic.id;
|
|
12162
12176
|
const requestId = params.requestId;
|
|
12163
12177
|
const depth = params.depth;
|
|
@@ -12207,6 +12221,7 @@ function startAiTurn(params) {
|
|
|
12207
12221
|
userId,
|
|
12208
12222
|
prompt,
|
|
12209
12223
|
origin,
|
|
12224
|
+
sourceNode,
|
|
12210
12225
|
requestId,
|
|
12211
12226
|
depth,
|
|
12212
12227
|
silent,
|
|
@@ -12598,7 +12613,7 @@ ${playwrightNote}`;
|
|
|
12598
12613
|
WsHub.get().broadcastTyping(topicId, "ai");
|
|
12599
12614
|
WsHub.get().broadcastAiActive(topicId, queryId);
|
|
12600
12615
|
}
|
|
12601
|
-
streamAgentEvents(topicId, topic.title, queryId, events, control, agentKind, resolvedModel, resolvedEffort, userId, !sessionRetried, onSessionId, { silent, peerBridge }).then(async (outcome) => {
|
|
12616
|
+
streamAgentEvents(topicId, topic.title, queryId, events, control, agentKind, resolvedModel, resolvedEffort, userId, !sessionRetried, onSessionId, { silent, peerBridge, sourceNode }).then(async (outcome) => {
|
|
12602
12617
|
if (outcome.kind === "session-expired") {
|
|
12603
12618
|
if (!silent)
|
|
12604
12619
|
WsHub.get().broadcastAborted(topicId, queryId, "stopped");
|
|
@@ -12743,6 +12758,8 @@ function triggerTopicAiTurn(topicId, userId, prompt, agentType, opts) {
|
|
|
12743
12758
|
topicId,
|
|
12744
12759
|
authorId: opts?.injectAuthorId ?? userId,
|
|
12745
12760
|
sourceAdapter: opts?.injectSourceAdapter,
|
|
12761
|
+
sourceNode: opts?.injectSourceNode,
|
|
12762
|
+
sourceMessageId: opts?.injectSourceMessageId,
|
|
12746
12763
|
authorName: execution.agent,
|
|
12747
12764
|
text: prompt,
|
|
12748
12765
|
agentType: execution.agent,
|
|
@@ -12782,6 +12799,7 @@ function triggerTopicAiTurn(topicId, userId, prompt, agentType, opts) {
|
|
|
12782
12799
|
bridgeSessionFromHistory: opts?.bridgeSessionFromHistory,
|
|
12783
12800
|
onSettled: opts?.onSettled,
|
|
12784
12801
|
peerBridge: opts?.peerBridge,
|
|
12802
|
+
sourceNode: opts?.injectSourceNode,
|
|
12785
12803
|
askReplySources: opts?.askReplySources,
|
|
12786
12804
|
from: opts?.from
|
|
12787
12805
|
});
|
|
@@ -13769,6 +13787,28 @@ var init_submit_user_message = __esm(async () => {
|
|
|
13769
13787
|
await init_api_messages();
|
|
13770
13788
|
});
|
|
13771
13789
|
|
|
13790
|
+
// ../../packages/core/src/application/switch-topic-access-mode.ts
|
|
13791
|
+
function switchTopicAccessMode(params) {
|
|
13792
|
+
const topic = getTopic(params.topicId);
|
|
13793
|
+
if (!topic)
|
|
13794
|
+
return { ok: false, error: "Topic not found" };
|
|
13795
|
+
const owner = topic.participants.some((participant) => participant.userId === params.userId && participant.role === "owner");
|
|
13796
|
+
if (!owner)
|
|
13797
|
+
return { ok: false, error: "Only topic owners can change privacy" };
|
|
13798
|
+
topic.accessMode = params.accessMode;
|
|
13799
|
+
upsertTopic(topic);
|
|
13800
|
+
WsHub.get().broadcastTopicUpdated(topic.id);
|
|
13801
|
+
return {
|
|
13802
|
+
ok: true,
|
|
13803
|
+
accessMode: params.accessMode,
|
|
13804
|
+
text: params.accessMode === "shared" ? `"${topic.title}" is public to the connected Otium Hub.` : `"${topic.title}" is private to this worker.`
|
|
13805
|
+
};
|
|
13806
|
+
}
|
|
13807
|
+
var init_switch_topic_access_mode = __esm(async () => {
|
|
13808
|
+
await init_bus();
|
|
13809
|
+
await init_api_topics();
|
|
13810
|
+
});
|
|
13811
|
+
|
|
13772
13812
|
// ../../packages/core/src/application/switch-topic-effort.ts
|
|
13773
13813
|
function switchTopicEffort(params) {
|
|
13774
13814
|
const topic = getTopic(params.topicId);
|
|
@@ -14799,6 +14839,10 @@ function getRuntimeProcessLease(role, now = Date.now(), staleMs = PROCESS_LEASE_
|
|
|
14799
14839
|
const lease = rowToLease2(row);
|
|
14800
14840
|
return now - lease.heartbeatAt <= staleMs ? lease : null;
|
|
14801
14841
|
}
|
|
14842
|
+
function listRuntimeProcessLeases(rolePrefix = "", now = Date.now(), staleMs = PROCESS_LEASE_STALE_MS) {
|
|
14843
|
+
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();
|
|
14844
|
+
return rows.map(rowToLease2).filter((lease) => now - lease.heartbeatAt <= staleMs);
|
|
14845
|
+
}
|
|
14802
14846
|
function heartbeatRuntimeProcessLease(role, ownerId, now = Date.now()) {
|
|
14803
14847
|
const result = db.query(`UPDATE runtime_process_leases
|
|
14804
14848
|
SET heartbeat_at = ?
|
|
@@ -15961,6 +16005,7 @@ __export(exports_src, {
|
|
|
15961
16005
|
textResult: () => textResult,
|
|
15962
16006
|
switchTopicModel: () => switchTopicModel,
|
|
15963
16007
|
switchTopicEffort: () => switchTopicEffort,
|
|
16008
|
+
switchTopicAccessMode: () => switchTopicAccessMode,
|
|
15964
16009
|
sweepStaleSubagentCards: () => sweepStaleSubagentCards,
|
|
15965
16010
|
submitUserMessage: () => submitUserMessage,
|
|
15966
16011
|
stripFileTags: () => stripFileTags,
|
|
@@ -16016,6 +16061,7 @@ __export(exports_src, {
|
|
|
16016
16061
|
listVaultEntries: () => listVaultEntries,
|
|
16017
16062
|
listTopics: () => listTopics,
|
|
16018
16063
|
listRuntimeTurnLeases: () => listRuntimeTurnLeases,
|
|
16064
|
+
listRuntimeProcessLeases: () => listRuntimeProcessLeases,
|
|
16019
16065
|
listRuntimeEventsAfter: () => listRuntimeEventsAfter,
|
|
16020
16066
|
listRunningTopicQueries: () => listRunningTopicQueries,
|
|
16021
16067
|
listRunningTopicIds: () => listRunningTopicIds,
|
|
@@ -16152,6 +16198,7 @@ var init_src = __esm(async () => {
|
|
|
16152
16198
|
await init_topic_cleanup();
|
|
16153
16199
|
await init_execute_external_user_turn();
|
|
16154
16200
|
await init_submit_user_message();
|
|
16201
|
+
await init_switch_topic_access_mode();
|
|
16155
16202
|
await init_switch_topic_effort();
|
|
16156
16203
|
await init_switch_topic_model();
|
|
16157
16204
|
await init_topic_service();
|
|
@@ -17852,14 +17899,17 @@ function createNodeControlHandler(options) {
|
|
|
17852
17899
|
const topic = topicForUser(topicId, userId);
|
|
17853
17900
|
if (!topic)
|
|
17854
17901
|
return jsonError(404, "Topic not found");
|
|
17855
|
-
const
|
|
17902
|
+
const sourceAdapter = body.sourceAdapter === "telegram" || body.sourceAdapter === "otium" ? body.sourceAdapter : "terminal";
|
|
17903
|
+
const { message, queryId } = submitUserMessage({
|
|
17856
17904
|
topic,
|
|
17857
17905
|
userId,
|
|
17858
17906
|
text: text2,
|
|
17859
|
-
sourceAdapter
|
|
17907
|
+
sourceAdapter,
|
|
17908
|
+
visualTools: body.visualTools === true,
|
|
17909
|
+
fileDeliveryTools: body.fileDeliveryTools === true,
|
|
17860
17910
|
startTurn: options.startTurn
|
|
17861
17911
|
});
|
|
17862
|
-
return Response.json({ ok: true, message }, { status: 201 });
|
|
17912
|
+
return Response.json({ ok: true, message, queryId }, { status: 201 });
|
|
17863
17913
|
}
|
|
17864
17914
|
const recentMatch = path.match(/^\/topics\/([^/]+)\/recent-events$/);
|
|
17865
17915
|
if (recentMatch && req.method === "GET") {
|
|
@@ -17883,6 +17933,24 @@ function createNodeControlHandler(options) {
|
|
|
17883
17933
|
return jsonError(400, result.error);
|
|
17884
17934
|
return Response.json({ ok: true, model: result.model, result: result.text });
|
|
17885
17935
|
}
|
|
17936
|
+
const accessModeMatch = path.match(/^\/topics\/([^/]+)\/access-mode$/);
|
|
17937
|
+
if (accessModeMatch && req.method === "POST") {
|
|
17938
|
+
const topicId = decodeURIComponent(accessModeMatch[1]);
|
|
17939
|
+
const body = await bodyRecord(req);
|
|
17940
|
+
const userId = requiredText(body.userId, "userId");
|
|
17941
|
+
const accessMode = requiredText(body.accessMode, "accessMode");
|
|
17942
|
+
if (accessMode !== "private" && accessMode !== "shared") {
|
|
17943
|
+
return jsonError(400, `Unknown access mode: ${accessMode}`);
|
|
17944
|
+
}
|
|
17945
|
+
const result = switchTopicAccessMode({ topicId, userId, accessMode });
|
|
17946
|
+
if (!result.ok)
|
|
17947
|
+
return jsonError(400, result.error);
|
|
17948
|
+
return Response.json({
|
|
17949
|
+
ok: true,
|
|
17950
|
+
accessMode: result.accessMode,
|
|
17951
|
+
result: result.text
|
|
17952
|
+
});
|
|
17953
|
+
}
|
|
17886
17954
|
const effortMatch = path.match(/^\/topics\/([^/]+)\/effort$/);
|
|
17887
17955
|
if (effortMatch && req.method === "POST") {
|
|
17888
17956
|
const topicId = decodeURIComponent(effortMatch[1]);
|
|
@@ -20013,6 +20081,7 @@ async function startDefaultNode(opts = {}) {
|
|
|
20013
20081
|
async function runNodeDaemon(opts = {}) {
|
|
20014
20082
|
const node = await startDefaultNode({
|
|
20015
20083
|
port: opts.port ?? 0,
|
|
20084
|
+
...opts.maxRequestBodySize ? { maxRequestBodySize: opts.maxRequestBodySize } : {},
|
|
20016
20085
|
advertise: true,
|
|
20017
20086
|
singleton: true
|
|
20018
20087
|
});
|
|
@@ -20199,6 +20268,16 @@ class EmbeddedNegotiumClient {
|
|
|
20199
20268
|
throw new Error(result.error);
|
|
20200
20269
|
return result.text;
|
|
20201
20270
|
}
|
|
20271
|
+
setAccessMode(topic, accessMode) {
|
|
20272
|
+
const result = switchTopicAccessMode({
|
|
20273
|
+
topicId: topic.id,
|
|
20274
|
+
userId: this.#userId,
|
|
20275
|
+
accessMode
|
|
20276
|
+
});
|
|
20277
|
+
if (!result.ok)
|
|
20278
|
+
throw new Error(result.error);
|
|
20279
|
+
return result.text;
|
|
20280
|
+
}
|
|
20202
20281
|
async deleteTopic(topic) {
|
|
20203
20282
|
await topicService.delete({ topicId: topic.id, userId: this.#userId });
|
|
20204
20283
|
}
|
|
@@ -20377,6 +20456,13 @@ class RemoteNegotiumClient {
|
|
|
20377
20456
|
});
|
|
20378
20457
|
return String(result.result ?? `Effort set to '${effort}'.`);
|
|
20379
20458
|
}
|
|
20459
|
+
async setAccessMode(topic, accessMode) {
|
|
20460
|
+
const result = await this.#request(`/topics/${encodeURIComponent(topic.id)}/access-mode`, {
|
|
20461
|
+
method: "POST",
|
|
20462
|
+
body: JSON.stringify({ userId: this.#userId, accessMode })
|
|
20463
|
+
});
|
|
20464
|
+
return String(result.result ?? `Access mode set to '${accessMode}'.`);
|
|
20465
|
+
}
|
|
20380
20466
|
async deleteTopic(topic) {
|
|
20381
20467
|
await this.#request(`/topics/${encodeURIComponent(topic.id)}?user=${encodeURIComponent(this.#userId)}`, { method: "DELETE" });
|
|
20382
20468
|
}
|
|
@@ -20629,7 +20715,7 @@ var MAX_CLIPBOARD_BYTES = 1e5;
|
|
|
20629
20715
|
|
|
20630
20716
|
// ../../adapters/terminal/src/clock.ts
|
|
20631
20717
|
function terminalNowMs() {
|
|
20632
|
-
return
|
|
20718
|
+
return Date.now();
|
|
20633
20719
|
}
|
|
20634
20720
|
|
|
20635
20721
|
// ../../adapters/terminal/src/commands.ts
|
|
@@ -20659,6 +20745,8 @@ var init_commands = __esm(() => {
|
|
|
20659
20745
|
{ name: "model", usage: "/model", description: "choose the model" },
|
|
20660
20746
|
{ name: "effort", usage: "/effort", description: "choose reasoning effort" },
|
|
20661
20747
|
{ name: "topics", usage: "/topics", description: "open topic picker" },
|
|
20748
|
+
{ name: "public", usage: "/public", description: "share this topic with Otium Hub" },
|
|
20749
|
+
{ name: "private", usage: "/private", description: "make this topic local-only" },
|
|
20662
20750
|
{
|
|
20663
20751
|
name: "fork",
|
|
20664
20752
|
usage: "/fork [name]",
|
|
@@ -20854,8 +20942,8 @@ function orderTopicsByParent(topics) {
|
|
|
20854
20942
|
}
|
|
20855
20943
|
function setBackgroundSessions(state, backgroundSessions) {
|
|
20856
20944
|
const orderedSessions = [
|
|
20857
|
-
...backgroundSessions.filter((session) => session.kind === "
|
|
20858
|
-
...backgroundSessions.filter((session) => session.kind === "
|
|
20945
|
+
...backgroundSessions.filter((session) => session.kind === "cron"),
|
|
20946
|
+
...backgroundSessions.filter((session) => session.kind === "memory")
|
|
20859
20947
|
];
|
|
20860
20948
|
const selectedStillExists = orderedSessions.some((session) => session.id === state.topicPickerBackgroundId);
|
|
20861
20949
|
return {
|
|
@@ -20872,8 +20960,11 @@ function pickedTopic(state) {
|
|
|
20872
20960
|
return state.topicPickerBackgroundId ? undefined : state.topics[state.topicPickerIndex];
|
|
20873
20961
|
}
|
|
20874
20962
|
function moveTopicPickerSelection(state, delta) {
|
|
20963
|
+
const indexedTopics = state.topics.map((topic, index) => ({ topic, index }));
|
|
20875
20964
|
const items = [
|
|
20876
|
-
...
|
|
20965
|
+
...indexedTopics.filter(({ topic }) => topic.kind === "manager").map(({ topic, index }) => ({ kind: "topic", id: topic.id, index })),
|
|
20966
|
+
...indexedTopics.filter(({ topic }) => topic.kind !== "manager" && topic.accessMode !== "shared").map(({ topic, index }) => ({ kind: "topic", id: topic.id, index })),
|
|
20967
|
+
...indexedTopics.filter(({ topic }) => topic.kind !== "manager" && topic.accessMode === "shared").map(({ topic, index }) => ({ kind: "topic", id: topic.id, index })),
|
|
20877
20968
|
...state.backgroundSessions.map((session) => ({
|
|
20878
20969
|
kind: "background",
|
|
20879
20970
|
id: session.id
|
|
@@ -21510,7 +21601,8 @@ function helpLines() {
|
|
|
21510
21601
|
line(" Esc/Ctrl-C stop active turn \xB7 Ctrl-C twice when idle to quit"),
|
|
21511
21602
|
line(""),
|
|
21512
21603
|
line(" Commands", { fg: theme.cyan, bold: true }),
|
|
21513
|
-
line(" /new /model /effort /topics /
|
|
21604
|
+
line(" /new /model /effort /topics /public /private"),
|
|
21605
|
+
line(" /fork /spawn /del /copy"),
|
|
21514
21606
|
line(" /abort /help /quit", { fg: theme.muted })
|
|
21515
21607
|
];
|
|
21516
21608
|
}
|
|
@@ -21561,23 +21653,27 @@ function plainTranscript(state) {
|
|
|
21561
21653
|
}
|
|
21562
21654
|
function topicOverlayLines(state, width, height, animationFrame = 0) {
|
|
21563
21655
|
const indexedTopics = state.topics.map((topic, topicIndex) => ({ topic, topicIndex }));
|
|
21564
|
-
const
|
|
21565
|
-
const
|
|
21656
|
+
const managerTopics = indexedTopics.filter(({ topic }) => topic.kind === "manager");
|
|
21657
|
+
const privateTopics = indexedTopics.filter(({ topic }) => topic.kind !== "manager" && topic.accessMode !== "shared");
|
|
21658
|
+
const publicTopics = indexedTopics.filter(({ topic }) => topic.kind !== "manager" && topic.accessMode === "shared");
|
|
21566
21659
|
const entries = [];
|
|
21567
|
-
|
|
21568
|
-
|
|
21569
|
-
|
|
21570
|
-
|
|
21571
|
-
|
|
21660
|
+
for (const [label, topics] of [
|
|
21661
|
+
["Manager", managerTopics],
|
|
21662
|
+
["Private", privateTopics],
|
|
21663
|
+
["Public", publicTopics]
|
|
21664
|
+
]) {
|
|
21665
|
+
if (topics.length === 0)
|
|
21666
|
+
continue;
|
|
21667
|
+
if (entries.length > 0)
|
|
21572
21668
|
entries.push({ kind: "separator" });
|
|
21573
|
-
entries.push({ kind: "heading", label
|
|
21574
|
-
entries.push(...
|
|
21669
|
+
entries.push({ kind: "heading", label });
|
|
21670
|
+
entries.push(...topics.map(({ topic, topicIndex }) => ({
|
|
21575
21671
|
kind: "topic",
|
|
21576
21672
|
topic,
|
|
21577
21673
|
topicIndex
|
|
21578
21674
|
})));
|
|
21579
21675
|
}
|
|
21580
|
-
for (const kind of ["
|
|
21676
|
+
for (const kind of ["cron", "memory"]) {
|
|
21581
21677
|
const sessions = state.backgroundSessions.filter((session) => session.kind === kind);
|
|
21582
21678
|
if (sessions.length === 0)
|
|
21583
21679
|
continue;
|
|
@@ -21594,18 +21690,12 @@ function topicOverlayLines(state, width, height, animationFrame = 0) {
|
|
|
21594
21690
|
}
|
|
21595
21691
|
const visibleCount = Math.max(1, height - 3);
|
|
21596
21692
|
const selectedEntryIndex = entries.findIndex((entry) => entry.kind === "topic" && !state.topicPickerBackgroundId && entry.topicIndex === state.topicPickerIndex || entry.kind === "background" && entry.sessionId === state.topicPickerBackgroundId);
|
|
21597
|
-
|
|
21598
|
-
if (start > 0 && entries[start - 1]?.kind === "manager" && selectedEntryIndex - (start - 1) < visibleCount) {
|
|
21599
|
-
start -= 1;
|
|
21600
|
-
}
|
|
21693
|
+
const start = Math.min(Math.max(0, entries.length - visibleCount), Math.max(0, selectedEntryIndex - visibleCount + 1));
|
|
21601
21694
|
return [
|
|
21602
21695
|
line(" Topics", { fg: theme.accent, bold: true }),
|
|
21603
21696
|
line(state.topicPickerRoot ? " \u2191\u2193 select \xB7 Enter open \xB7 N new \xB7 D/Del delete \xB7 Esc/Ctrl-C exit" : " \u2191\u2193 select \xB7 Enter open \xB7 N new \xB7 D/Del delete \xB7 Esc close \xB7 Ctrl-C exit; work continues", { fg: theme.muted }),
|
|
21604
21697
|
line(""),
|
|
21605
21698
|
...entries.length === 0 ? [line(" No topics yet \xB7 Press N to create one", { fg: theme.muted })] : entries.slice(start, start + visibleCount).map((entry) => {
|
|
21606
|
-
if (entry.kind === "manager") {
|
|
21607
|
-
return line(" Manager", { fg: theme.cyan, bold: true });
|
|
21608
|
-
}
|
|
21609
21699
|
if (entry.kind === "heading") {
|
|
21610
21700
|
return line(` ${entry.label}`, { fg: theme.cyan, bold: true });
|
|
21611
21701
|
}
|
|
@@ -21993,7 +22083,7 @@ function renderAppFrame(state, columns, rows, animationFrame = 0, nowMs = termin
|
|
|
21993
22083
|
const height = Math.max(14, rows);
|
|
21994
22084
|
const footer = footerLines(state, width);
|
|
21995
22085
|
const decision = decisionPane(state, width);
|
|
21996
|
-
const hideComposer = state.overlay === "background-session" || state.overlay === "vault" && (state.vaultMode === "list" || state.vaultMode === "confirm-delete");
|
|
22086
|
+
const hideComposer = state.overlay === "background-session" || state.overlay === "topics" || state.overlay === "vault" && (state.vaultMode === "list" || state.vaultMode === "confirm-delete");
|
|
21997
22087
|
const composer = hideComposer ? { lines: [], cursor: null } : composerPane(state, width);
|
|
21998
22088
|
const bodyHeight = Math.max(3, height - footer.length - decision.length - composer.lines.length);
|
|
21999
22089
|
const body = renderBody(conversationLines(state, width, bodyHeight, animationFrame, nowMs), width, bodyHeight);
|
|
@@ -23249,6 +23339,31 @@ class TerminalApp {
|
|
|
23249
23339
|
this.#queueRender();
|
|
23250
23340
|
return;
|
|
23251
23341
|
}
|
|
23342
|
+
if (command === "public" || command === "private") {
|
|
23343
|
+
if (args.length > 0) {
|
|
23344
|
+
this.#state = { ...this.#state, notice: `Usage: /${command}` };
|
|
23345
|
+
this.#queueRender();
|
|
23346
|
+
return;
|
|
23347
|
+
}
|
|
23348
|
+
const topic = activeTopic(this.#state);
|
|
23349
|
+
if (!topic) {
|
|
23350
|
+
this.#state = { ...this.#state, notice: "No topic selected" };
|
|
23351
|
+
this.#queueRender();
|
|
23352
|
+
return;
|
|
23353
|
+
}
|
|
23354
|
+
try {
|
|
23355
|
+
const notice = await this.#client.setAccessMode(topic, command === "public" ? "shared" : "private");
|
|
23356
|
+
await this.#refreshTopics(topic.title);
|
|
23357
|
+
this.#state = { ...this.#state, notice };
|
|
23358
|
+
} catch (error) {
|
|
23359
|
+
this.#state = {
|
|
23360
|
+
...this.#state,
|
|
23361
|
+
notice: error instanceof Error ? error.message : String(error)
|
|
23362
|
+
};
|
|
23363
|
+
}
|
|
23364
|
+
this.#queueRender();
|
|
23365
|
+
return;
|
|
23366
|
+
}
|
|
23252
23367
|
if (command === "compact") {
|
|
23253
23368
|
const topic = activeTopic(this.#state);
|
|
23254
23369
|
if (!topic) {
|
|
@@ -25688,13 +25803,25 @@ Warning: enable the bot administrator permission "Manage Topics" before creating
|
|
|
25688
25803
|
const rememberTarget = (queryId2) => {
|
|
25689
25804
|
targetByQueryId.set(queryId2, target);
|
|
25690
25805
|
};
|
|
25691
|
-
const
|
|
25806
|
+
const input = {
|
|
25692
25807
|
topic,
|
|
25693
25808
|
userId,
|
|
25694
25809
|
text: prompt,
|
|
25695
25810
|
sourceAdapter: "telegram",
|
|
25696
25811
|
visualTools: false,
|
|
25697
|
-
fileDeliveryTools: true
|
|
25812
|
+
fileDeliveryTools: true
|
|
25813
|
+
};
|
|
25814
|
+
if (opts.submitTurn) {
|
|
25815
|
+
opts.submitTurn(input).then(({ queryId: queryId2 }) => {
|
|
25816
|
+
if (queryId2)
|
|
25817
|
+
rememberTarget(queryId2);
|
|
25818
|
+
}).catch((error) => {
|
|
25819
|
+
logger.error({ err: error, topicId: topic.id }, "telegram adapter: remote turn failed");
|
|
25820
|
+
});
|
|
25821
|
+
return;
|
|
25822
|
+
}
|
|
25823
|
+
const { queryId } = submitUserMessage({
|
|
25824
|
+
...input,
|
|
25698
25825
|
onDispatched: rememberTarget,
|
|
25699
25826
|
startTurn: dispatchTurn
|
|
25700
25827
|
});
|
|
@@ -25883,7 +26010,8 @@ ${caption}` : voiceText : caption;
|
|
|
25883
26010
|
}
|
|
25884
26011
|
case "/abort": {
|
|
25885
26012
|
const mapping = byKey.get(mappingKey(chatId, threadId));
|
|
25886
|
-
|
|
26013
|
+
const aborted = mapping ? await (opts.abortTurn?.(mapping.topicId, userId) ?? topicService.abortTurn(mapping.topicId, userId)) : false;
|
|
26014
|
+
reply(chatId, threadId, aborted ? "aborted" : "nothing running");
|
|
25887
26015
|
return;
|
|
25888
26016
|
}
|
|
25889
26017
|
case "/agent": {
|
|
@@ -26260,7 +26388,7 @@ __export(exports_cli2, {
|
|
|
26260
26388
|
runTelegramCli: () => runTelegramCli
|
|
26261
26389
|
});
|
|
26262
26390
|
import TelegramBot from "node-telegram-bot-api";
|
|
26263
|
-
function startTelegramFromEnv() {
|
|
26391
|
+
function startTelegramFromEnv(options = {}) {
|
|
26264
26392
|
const token = process.env.TELEGRAM_BOT_TOKEN?.trim();
|
|
26265
26393
|
if (!token)
|
|
26266
26394
|
throw new Error("TELEGRAM_BOT_TOKEN is required");
|
|
@@ -26268,6 +26396,7 @@ function startTelegramFromEnv() {
|
|
|
26268
26396
|
const requestedAgent = process.env.TELEGRAM_DEFAULT_AGENT?.trim();
|
|
26269
26397
|
const forumChatId = Number.parseInt(process.env.TELEGRAM_FORUM_CHAT_ID ?? "", 10);
|
|
26270
26398
|
const adapter = startTelegramAdapter({
|
|
26399
|
+
...options,
|
|
26271
26400
|
client: bot,
|
|
26272
26401
|
allowedUsers: (process.env.TELEGRAM_ALLOWED_USERS ?? "").split(",").map((value) => value.trim()).filter(Boolean),
|
|
26273
26402
|
...process.env.TELEGRAM_VAULT_OWNER_USER_ID?.trim() ? { vaultOwnerTelegramUserId: process.env.TELEGRAM_VAULT_OWNER_USER_ID.trim() } : {},
|
|
@@ -26288,38 +26417,101 @@ function startTelegramFromEnv() {
|
|
|
26288
26417
|
}
|
|
26289
26418
|
};
|
|
26290
26419
|
}
|
|
26291
|
-
async function
|
|
26420
|
+
async function spawnCanonicalNode() {
|
|
26421
|
+
const entry = process.argv[1];
|
|
26422
|
+
if (!entry)
|
|
26423
|
+
throw new Error("cannot locate the Negotium CLI entrypoint");
|
|
26424
|
+
const child = Bun.spawn({
|
|
26425
|
+
cmd: [process.execPath, entry, "__node-daemon", "--port=0"],
|
|
26426
|
+
detached: true,
|
|
26427
|
+
env: { ...process.env, LOG_LEVEL: process.env.NEGOTIUM_NODE_LOG_LEVEL?.trim() || "info" },
|
|
26428
|
+
stdin: "ignore",
|
|
26429
|
+
stdout: "ignore",
|
|
26430
|
+
stderr: Bun.file(`${LOG_DIR}/node-daemon.log`)
|
|
26431
|
+
});
|
|
26432
|
+
child.unref();
|
|
26433
|
+
}
|
|
26434
|
+
async function ensureCanonicalNode() {
|
|
26435
|
+
const status = await inspectNodeDaemon();
|
|
26436
|
+
if (!status.running)
|
|
26437
|
+
await spawnCanonicalNode();
|
|
26438
|
+
return waitForNodeDaemon(15000);
|
|
26439
|
+
}
|
|
26440
|
+
async function runCanonicalNodeChild() {
|
|
26441
|
+
const { runNodeDaemon: runNodeDaemon2 } = await init_src5().then(() => exports_src3);
|
|
26442
|
+
await runNodeDaemon2({ port: 0 });
|
|
26443
|
+
}
|
|
26444
|
+
async function runTelegramCli(args = process.argv.slice(2)) {
|
|
26445
|
+
if (args[0] === "__node-daemon") {
|
|
26446
|
+
await runCanonicalNodeChild();
|
|
26447
|
+
return;
|
|
26448
|
+
}
|
|
26292
26449
|
if (!process.env.TELEGRAM_BOT_TOKEN?.trim()) {
|
|
26293
26450
|
throw new Error("TELEGRAM_BOT_TOKEN is required");
|
|
26294
26451
|
}
|
|
26295
|
-
|
|
26452
|
+
const initialNode = await ensureCanonicalNode();
|
|
26296
26453
|
const singleton = await waitForRequiredRuntimeProcessLease("adapter:telegram", {
|
|
26297
26454
|
workloadName: "Telegram adapter",
|
|
26298
26455
|
onLost: () => {
|
|
26299
26456
|
process.stderr.write(`negotium-telegram: singleton lease lost; shutting down
|
|
26300
26457
|
`);
|
|
26301
|
-
|
|
26458
|
+
runShutdown("test");
|
|
26302
26459
|
}
|
|
26303
26460
|
});
|
|
26304
|
-
try {
|
|
26305
|
-
node = await startDefaultNode({ port: 0 });
|
|
26306
|
-
} catch (error) {
|
|
26307
|
-
singleton.stop();
|
|
26308
|
-
throw error;
|
|
26309
|
-
}
|
|
26310
26461
|
let channel;
|
|
26311
26462
|
try {
|
|
26312
|
-
channel = startTelegramFromEnv(
|
|
26463
|
+
channel = startTelegramFromEnv({
|
|
26464
|
+
abortTurn: async (topicId, userId) => {
|
|
26465
|
+
const node = await waitForNodeDaemon(1500);
|
|
26466
|
+
const response = await fetch(`${node.baseUrl}${NODE_CONTROL_BASE_PATH}/topics/${encodeURIComponent(topicId)}/abort`, {
|
|
26467
|
+
method: "POST",
|
|
26468
|
+
headers: {
|
|
26469
|
+
authorization: `Bearer ${NODE_CONTROL_TOKEN}`,
|
|
26470
|
+
"content-type": "application/json"
|
|
26471
|
+
},
|
|
26472
|
+
body: JSON.stringify({ userId })
|
|
26473
|
+
});
|
|
26474
|
+
const body = await response.json();
|
|
26475
|
+
if (!response.ok)
|
|
26476
|
+
throw new Error(body.error ?? `node returned HTTP ${response.status}`);
|
|
26477
|
+
return body.aborted === true;
|
|
26478
|
+
},
|
|
26479
|
+
submitTurn: async (input) => {
|
|
26480
|
+
const node = await waitForNodeDaemon(1500);
|
|
26481
|
+
const response = await fetch(`${node.baseUrl}${NODE_CONTROL_BASE_PATH}/topics/${encodeURIComponent(input.topic.id)}/messages`, {
|
|
26482
|
+
method: "POST",
|
|
26483
|
+
headers: {
|
|
26484
|
+
authorization: `Bearer ${NODE_CONTROL_TOKEN}`,
|
|
26485
|
+
"content-type": "application/json"
|
|
26486
|
+
},
|
|
26487
|
+
body: JSON.stringify({
|
|
26488
|
+
userId: input.userId,
|
|
26489
|
+
text: input.text,
|
|
26490
|
+
sourceAdapter: input.sourceAdapter,
|
|
26491
|
+
visualTools: input.visualTools,
|
|
26492
|
+
fileDeliveryTools: input.fileDeliveryTools
|
|
26493
|
+
})
|
|
26494
|
+
});
|
|
26495
|
+
const body = await response.json();
|
|
26496
|
+
if (!response.ok)
|
|
26497
|
+
throw new Error(body.error ?? `node returned HTTP ${response.status}`);
|
|
26498
|
+
return { queryId: body.queryId };
|
|
26499
|
+
}
|
|
26500
|
+
});
|
|
26313
26501
|
} catch (error) {
|
|
26314
|
-
await node.stop();
|
|
26315
26502
|
singleton.stop();
|
|
26316
26503
|
throw error;
|
|
26317
26504
|
}
|
|
26318
26505
|
onShutdown("telegram-channel", 100, () => channel.stop());
|
|
26319
26506
|
onShutdown("telegram-singleton", 90, () => singleton.stop());
|
|
26320
|
-
|
|
26507
|
+
let resolveCompleted;
|
|
26508
|
+
const completed = new Promise((resolve13) => {
|
|
26509
|
+
resolveCompleted = resolve13;
|
|
26510
|
+
});
|
|
26511
|
+
onShutdown("telegram-completed", -100, resolveCompleted);
|
|
26512
|
+
process.stdout.write(`negotium Telegram adapter connected to canonical node pid ${initialNode.info?.pid ?? "unknown"}
|
|
26321
26513
|
`);
|
|
26322
|
-
await
|
|
26514
|
+
await completed;
|
|
26323
26515
|
}
|
|
26324
26516
|
var init_cli2 = __esm(async () => {
|
|
26325
26517
|
await init_src();
|
|
@@ -26328,37 +26520,6 @@ var init_cli2 = __esm(async () => {
|
|
|
26328
26520
|
if (false) {}
|
|
26329
26521
|
});
|
|
26330
26522
|
|
|
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
26523
|
// ../../adapters/otium/src/central.ts
|
|
26363
26524
|
var exports_central = {};
|
|
26364
26525
|
__export(exports_central, {
|
|
@@ -26518,6 +26679,7 @@ __export(exports_join, {
|
|
|
26518
26679
|
withJoinCredentialLock: () => withJoinCredentialLock,
|
|
26519
26680
|
saveJoinWhileLocked: () => saveJoinWhileLocked,
|
|
26520
26681
|
saveJoin: () => saveJoin,
|
|
26682
|
+
removeJoin: () => removeJoin,
|
|
26521
26683
|
parseInviteCode: () => parseInviteCode,
|
|
26522
26684
|
loadJoin: () => loadJoin,
|
|
26523
26685
|
joinFilePath: () => joinFilePath,
|
|
@@ -26778,6 +26940,24 @@ function saveJoinWhileLocked(join31, options = {}) {
|
|
|
26778
26940
|
function saveJoin(join31, options = {}) {
|
|
26779
26941
|
return withJoinCredentialLock(() => saveJoinWhileLocked(join31, options));
|
|
26780
26942
|
}
|
|
26943
|
+
function removeJoin() {
|
|
26944
|
+
return withJoinCredentialLock(() => {
|
|
26945
|
+
const path = joinFilePath();
|
|
26946
|
+
if (!existsSync25(path))
|
|
26947
|
+
return false;
|
|
26948
|
+
if (lstatSync(path).isSymbolicLink()) {
|
|
26949
|
+
throw new Error(`refusing to remove symlinked Otium join file at ${path}`);
|
|
26950
|
+
}
|
|
26951
|
+
unlinkSync18(path);
|
|
26952
|
+
const directoryFd = openSync3(dirname17(path), "r");
|
|
26953
|
+
try {
|
|
26954
|
+
fsyncSync2(directoryFd);
|
|
26955
|
+
} finally {
|
|
26956
|
+
closeSync3(directoryFd);
|
|
26957
|
+
}
|
|
26958
|
+
return true;
|
|
26959
|
+
});
|
|
26960
|
+
}
|
|
26781
26961
|
function loadJoin() {
|
|
26782
26962
|
const central = process.env.OTIUM_CENTRAL_URL?.trim();
|
|
26783
26963
|
const cellId = process.env.OTIUM_CELL_ID?.trim();
|
|
@@ -26812,363 +26992,56 @@ var init_join = __esm(async () => {
|
|
|
26812
26992
|
await init_src();
|
|
26813
26993
|
});
|
|
26814
26994
|
|
|
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}");
|
|
26995
|
+
// ../../adapters/otium/src/control-protocol.ts
|
|
26996
|
+
var OTIUM_ADAPTER_CONTROL_PREFIX = "/api/v1/adapter/otium", OTIUM_ADAPTER_CONTROL_HEADER = "x-negotium-adapter-token";
|
|
26997
|
+
|
|
26998
|
+
// ../../adapters/otium/src/protocol.ts
|
|
26999
|
+
function str(body, field) {
|
|
27000
|
+
const value = body[field];
|
|
27001
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
27002
|
+
}
|
|
27003
|
+
function parseExecutionSpec(value) {
|
|
27004
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
27005
|
+
return null;
|
|
27006
|
+
const raw = value;
|
|
27007
|
+
const agent = str(raw, "agent");
|
|
27008
|
+
const model = str(raw, "model");
|
|
27009
|
+
const effort = str(raw, "effort");
|
|
27010
|
+
const rawMcp = raw.mcp;
|
|
27011
|
+
if (!agent || !model || !effort || !Array.isArray(rawMcp) || !rawMcp.every((entry) => typeof entry === "string" && entry.trim().length > 0) || typeof raw.canSpawnSubagents !== "boolean") {
|
|
27012
|
+
return null;
|
|
26854
27013
|
}
|
|
26855
|
-
|
|
26856
|
-
|
|
27014
|
+
return {
|
|
27015
|
+
agent,
|
|
27016
|
+
model,
|
|
27017
|
+
effort,
|
|
27018
|
+
...str(raw, "description") ? { description: str(raw, "description") } : {},
|
|
27019
|
+
mcp: [...new Set(rawMcp.map((entry) => entry.trim()))],
|
|
27020
|
+
canSpawnSubagents: raw.canSpawnSubagents
|
|
27021
|
+
};
|
|
26857
27022
|
}
|
|
26858
|
-
|
|
26859
|
-
|
|
27023
|
+
var PEER_PROTOCOL_VERSION = 1, MAX_PEER_MESSAGE_LENGTH = 1e4, MAX_PEER_INPUT_FILE_BYTES, MAX_PEER_INPUT_REQUEST_BYTES;
|
|
27024
|
+
var init_protocol = __esm(() => {
|
|
27025
|
+
MAX_PEER_INPUT_FILE_BYTES = 2 * 1024 * 1024 * 1024;
|
|
27026
|
+
MAX_PEER_INPUT_REQUEST_BYTES = MAX_PEER_INPUT_FILE_BYTES + 8 * 1024 * 1024;
|
|
27027
|
+
});
|
|
27028
|
+
|
|
27029
|
+
// ../../adapters/otium/src/canonical-mcp-bridge.ts
|
|
27030
|
+
import { randomUUID as randomUUID24 } from "crypto";
|
|
27031
|
+
function readAuthorization(request) {
|
|
27032
|
+
const value = request.headers.get("authorization");
|
|
27033
|
+
return value?.startsWith("Bearer ") ? value.slice(7) : null;
|
|
26860
27034
|
}
|
|
26861
|
-
function
|
|
26862
|
-
const
|
|
26863
|
-
if (
|
|
26864
|
-
return
|
|
26865
|
-
|
|
26866
|
-
|
|
26867
|
-
return
|
|
26868
|
-
|
|
26869
|
-
|
|
26870
|
-
|
|
26871
|
-
}
|
|
26872
|
-
function loadOrCreatePending(invite, nodeName) {
|
|
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}`);
|
|
26879
|
-
}
|
|
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
|
-
try {
|
|
26892
|
-
fd = openSync4(temporaryPath, "wx", 384);
|
|
26893
|
-
writeFileSync19(fd, `${JSON.stringify(pending2, null, 2)}
|
|
26894
|
-
`, "utf8");
|
|
26895
|
-
fsyncSync3(fd);
|
|
26896
|
-
closeSync4(fd);
|
|
26897
|
-
fd = undefined;
|
|
26898
|
-
linkSync2(temporaryPath, path);
|
|
26899
|
-
unlinkSync19(temporaryPath);
|
|
26900
|
-
chmodSync6(path, 384);
|
|
26901
|
-
const directoryFd = openSync4(dirname18(path), "r");
|
|
26902
|
-
try {
|
|
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;
|
|
27035
|
+
async function readLimitedJson(request) {
|
|
27036
|
+
const declared = Number(request.headers.get("content-length") ?? "0");
|
|
27037
|
+
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES)
|
|
27038
|
+
return null;
|
|
27039
|
+
const reader = request.body?.getReader();
|
|
27040
|
+
if (!reader)
|
|
27041
|
+
return null;
|
|
27042
|
+
const chunks = [];
|
|
27043
|
+
let size = 0;
|
|
27044
|
+
const deadline = Date.now() + BODY_READ_TIMEOUT_MS;
|
|
27172
27045
|
try {
|
|
27173
27046
|
while (true) {
|
|
27174
27047
|
const remaining = deadline - Date.now();
|
|
@@ -27295,7 +27168,7 @@ function startCanonicalMcpBridge(options = {}) {
|
|
|
27295
27168
|
const url = `http://127.0.0.1:${server.port}/`;
|
|
27296
27169
|
const unregister = registerCanonicalMcpBridgeEnvProvider((scope) => {
|
|
27297
27170
|
sweep();
|
|
27298
|
-
const token = `${
|
|
27171
|
+
const token = `${randomUUID24()}${randomUUID24()}`;
|
|
27299
27172
|
capabilities.set(token, {
|
|
27300
27173
|
surface: scope.surface,
|
|
27301
27174
|
userId: scope.userId,
|
|
@@ -27348,6 +27221,43 @@ var init_canonical_mcp_bridge = __esm(async () => {
|
|
|
27348
27221
|
|
|
27349
27222
|
// ../../adapters/otium/src/store.ts
|
|
27350
27223
|
import { createHash as createHash7 } from "crypto";
|
|
27224
|
+
function isPeerDetached(hubNodeId) {
|
|
27225
|
+
const row = db.query("SELECT status FROM otium_peer_lifecycle WHERE hub_node_id = ?").get(hubNodeId);
|
|
27226
|
+
return row?.status === "detached";
|
|
27227
|
+
}
|
|
27228
|
+
function enqueueSharedMessage(args) {
|
|
27229
|
+
db.run(`INSERT OR REPLACE INTO otium_shared_message_outbox
|
|
27230
|
+
(local_topic_id, source_message_id, message_json, created_at)
|
|
27231
|
+
VALUES (?, ?, ?, ?)`, [
|
|
27232
|
+
args.localTopicId,
|
|
27233
|
+
args.sourceMessageId,
|
|
27234
|
+
JSON.stringify(args.message),
|
|
27235
|
+
new Date().toISOString()
|
|
27236
|
+
]);
|
|
27237
|
+
}
|
|
27238
|
+
function listSharedMessages(localTopicId) {
|
|
27239
|
+
return db.query("SELECT * FROM otium_shared_message_outbox WHERE local_topic_id = ? ORDER BY created_at").all(localTopicId);
|
|
27240
|
+
}
|
|
27241
|
+
function deleteSharedMessage(localTopicId, sourceMessageId) {
|
|
27242
|
+
return db.run("DELETE FROM otium_shared_message_outbox WHERE local_topic_id = ? AND source_message_id = ?", [localTopicId, sourceMessageId]).changes === 1;
|
|
27243
|
+
}
|
|
27244
|
+
function getSharedTopicState(localTopicId) {
|
|
27245
|
+
return db.query("SELECT * FROM otium_shared_topic_state WHERE local_topic_id = ?").get(localTopicId) ?? null;
|
|
27246
|
+
}
|
|
27247
|
+
function listSharedTopicStates() {
|
|
27248
|
+
return db.query("SELECT * FROM otium_shared_topic_state ORDER BY updated_at").all();
|
|
27249
|
+
}
|
|
27250
|
+
function setSharedTopicState(args) {
|
|
27251
|
+
db.run(`INSERT INTO otium_shared_topic_state (local_topic_id, host_topic_id, status, updated_at)
|
|
27252
|
+
VALUES (?, ?, ?, ?)
|
|
27253
|
+
ON CONFLICT(local_topic_id) DO UPDATE SET
|
|
27254
|
+
host_topic_id = excluded.host_topic_id,
|
|
27255
|
+
status = excluded.status,
|
|
27256
|
+
updated_at = excluded.updated_at`, [args.localTopicId, args.hostTopicId ?? null, args.status, new Date().toISOString()]);
|
|
27257
|
+
}
|
|
27258
|
+
function deleteSharedTopicState(localTopicId) {
|
|
27259
|
+
return db.run("DELETE FROM otium_shared_topic_state WHERE local_topic_id = ?", [localTopicId]).changes === 1;
|
|
27260
|
+
}
|
|
27351
27261
|
function getPeerSession(hostNodeId, hostTopicId) {
|
|
27352
27262
|
return db.query("SELECT * FROM otium_peer_sessions WHERE host_node_id = ? AND host_topic_id = ?").get(hostNodeId, hostTopicId) ?? null;
|
|
27353
27263
|
}
|
|
@@ -27388,6 +27298,19 @@ function unbindPeerSession(hostNodeId, hostTopicId) {
|
|
|
27388
27298
|
function unbindSharedPeerSessionsForLocalTopic(localTopicId) {
|
|
27389
27299
|
return db.run("DELETE FROM otium_peer_sessions WHERE local_topic_id = ? AND binding_mode = 'shared'", [localTopicId]).changes;
|
|
27390
27300
|
}
|
|
27301
|
+
function downgradeSharedTopicsLocally(hubNodeId) {
|
|
27302
|
+
return db.transaction(() => {
|
|
27303
|
+
const topics = db.query("SELECT id FROM api_topics WHERE access_mode = 'shared'").all().map((row) => row.id);
|
|
27304
|
+
db.run("UPDATE api_topics SET access_mode = 'private' WHERE access_mode = 'shared'");
|
|
27305
|
+
db.run("DELETE FROM otium_peer_sessions WHERE host_node_id = ?", [hubNodeId]);
|
|
27306
|
+
db.run("DELETE FROM otium_shared_message_outbox");
|
|
27307
|
+
db.run("DELETE FROM otium_shared_topic_state");
|
|
27308
|
+
db.run(`INSERT INTO otium_peer_lifecycle (hub_node_id, status, updated_at)
|
|
27309
|
+
VALUES (?, 'detached', ?)
|
|
27310
|
+
ON CONFLICT(hub_node_id) DO UPDATE SET status = 'detached', updated_at = excluded.updated_at`, [hubNodeId, new Date().toISOString()]);
|
|
27311
|
+
return topics;
|
|
27312
|
+
})();
|
|
27313
|
+
}
|
|
27391
27314
|
function listPeerSessions() {
|
|
27392
27315
|
return db.query("SELECT * FROM otium_peer_sessions").all();
|
|
27393
27316
|
}
|
|
@@ -27581,7 +27504,7 @@ var init_store2 = __esm(async () => {
|
|
|
27581
27504
|
db.exec("ALTER TABLE otium_peer_sessions ADD COLUMN binding_mode TEXT NOT NULL DEFAULT 'mirror'");
|
|
27582
27505
|
}
|
|
27583
27506
|
db.run(`UPDATE api_topics
|
|
27584
|
-
SET visibility = '
|
|
27507
|
+
SET visibility = 'visible', access_mode = 'shared', is_subagent = 0
|
|
27585
27508
|
WHERE id IN (
|
|
27586
27509
|
SELECT local_topic_id FROM otium_peer_sessions WHERE binding_mode = 'mirror'
|
|
27587
27510
|
)`);
|
|
@@ -27653,6 +27576,30 @@ var init_store2 = __esm(async () => {
|
|
|
27653
27576
|
updated_at INTEGER NOT NULL,
|
|
27654
27577
|
PRIMARY KEY (node_cell_id, request_id)
|
|
27655
27578
|
)
|
|
27579
|
+
`);
|
|
27580
|
+
db.exec(`
|
|
27581
|
+
CREATE TABLE IF NOT EXISTS otium_shared_topic_state (
|
|
27582
|
+
local_topic_id TEXT PRIMARY KEY,
|
|
27583
|
+
host_topic_id TEXT,
|
|
27584
|
+
status TEXT NOT NULL CHECK (status IN ('publishing', 'published', 'unpublishing')),
|
|
27585
|
+
updated_at TEXT NOT NULL
|
|
27586
|
+
)
|
|
27587
|
+
`);
|
|
27588
|
+
db.exec(`
|
|
27589
|
+
CREATE TABLE IF NOT EXISTS otium_shared_message_outbox (
|
|
27590
|
+
local_topic_id TEXT NOT NULL,
|
|
27591
|
+
source_message_id TEXT NOT NULL,
|
|
27592
|
+
message_json TEXT NOT NULL,
|
|
27593
|
+
created_at TEXT NOT NULL,
|
|
27594
|
+
PRIMARY KEY (local_topic_id, source_message_id)
|
|
27595
|
+
)
|
|
27596
|
+
`);
|
|
27597
|
+
db.exec(`
|
|
27598
|
+
CREATE TABLE IF NOT EXISTS otium_peer_lifecycle (
|
|
27599
|
+
hub_node_id TEXT PRIMARY KEY,
|
|
27600
|
+
status TEXT NOT NULL CHECK (status IN ('attached', 'detached')),
|
|
27601
|
+
updated_at TEXT NOT NULL
|
|
27602
|
+
)
|
|
27656
27603
|
`);
|
|
27657
27604
|
});
|
|
27658
27605
|
|
|
@@ -27844,7 +27791,7 @@ function createTurnForwarder(opts) {
|
|
|
27844
27791
|
logger.warn({ requestId, seq, type: event.type, attempt, error: result.error }, "otium: peer event delivery to hub failed");
|
|
27845
27792
|
if (attempt < PEER_EVENT_MAX_ATTEMPTS) {
|
|
27846
27793
|
const delayMs = retryBaseMs * 2 ** (attempt - 1);
|
|
27847
|
-
await new Promise((
|
|
27794
|
+
await new Promise((resolve14) => setTimeout(resolve14, delayMs));
|
|
27848
27795
|
}
|
|
27849
27796
|
}
|
|
27850
27797
|
forwarder.deliveryBlocked = true;
|
|
@@ -27983,8 +27930,8 @@ var init_event_backflow = __esm(async () => {
|
|
|
27983
27930
|
});
|
|
27984
27931
|
|
|
27985
27932
|
// ../../adapters/otium/src/peer-files.ts
|
|
27986
|
-
import { randomUUID as
|
|
27987
|
-
import { copyFileSync as copyFileSync3, mkdirSync as
|
|
27933
|
+
import { randomUUID as randomUUID25 } from "crypto";
|
|
27934
|
+
import { copyFileSync as copyFileSync3, mkdirSync as mkdirSync29, rmSync as rmSync6, statSync as statSync12 } from "fs";
|
|
27988
27935
|
import { basename as basename8, extname as extname5, join as join31 } from "path";
|
|
27989
27936
|
function fileType(mimeType) {
|
|
27990
27937
|
if (mimeType.startsWith("image/"))
|
|
@@ -28053,8 +28000,8 @@ function recordFile(args) {
|
|
|
28053
28000
|
});
|
|
28054
28001
|
}
|
|
28055
28002
|
function insertLocalFile(args) {
|
|
28056
|
-
const id =
|
|
28057
|
-
|
|
28003
|
+
const id = randomUUID25();
|
|
28004
|
+
mkdirSync29(PEER_FILES_DIR, { recursive: true });
|
|
28058
28005
|
const path = join31(PEER_FILES_DIR, `${id}-${safeFilename(args.filename)}`);
|
|
28059
28006
|
copyFileSync3(args.sourcePath, path);
|
|
28060
28007
|
return recordFile({ id, path, sizeBytes: statSync12(path).size, ...args });
|
|
@@ -28066,8 +28013,8 @@ function deletePeerFilesForTopic(topicId) {
|
|
|
28066
28013
|
rmSync6(row.path, { force: true });
|
|
28067
28014
|
}
|
|
28068
28015
|
async function storePeerInputFile(file, access) {
|
|
28069
|
-
const id =
|
|
28070
|
-
|
|
28016
|
+
const id = randomUUID25();
|
|
28017
|
+
mkdirSync29(PEER_FILES_DIR, { recursive: true });
|
|
28071
28018
|
const filename = file.name || "upload";
|
|
28072
28019
|
const path = join31(PEER_FILES_DIR, `${id}-${safeFilename(filename)}`);
|
|
28073
28020
|
const sizeBytes = await Bun.write(path, file);
|
|
@@ -28132,7 +28079,7 @@ var init_peer_files = __esm(async () => {
|
|
|
28132
28079
|
});
|
|
28133
28080
|
|
|
28134
28081
|
// ../../adapters/otium/src/runtime-bridge.ts
|
|
28135
|
-
import { randomUUID as
|
|
28082
|
+
import { randomUUID as randomUUID26 } from "crypto";
|
|
28136
28083
|
function isMcpToolResult(value) {
|
|
28137
28084
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
28138
28085
|
return false;
|
|
@@ -28202,7 +28149,7 @@ var init_runtime_bridge = __esm(async () => {
|
|
|
28202
28149
|
} catch (error) {
|
|
28203
28150
|
return errorResult(`Error: Failed to open hub question: ${error.message}`);
|
|
28204
28151
|
}
|
|
28205
|
-
const bridgeRequestId = `bridge-ask-${
|
|
28152
|
+
const bridgeRequestId = `bridge-ask-${randomUUID26()}`;
|
|
28206
28153
|
const baseUrl = hubNode.baseUrl.replace(/\/+$/, "");
|
|
28207
28154
|
const post = async (path, body) => {
|
|
28208
28155
|
const response = await fetch(`${baseUrl}${path}`, {
|
|
@@ -28762,6 +28709,287 @@ var init_session_bridge_ipc = __esm(() => {
|
|
|
28762
28709
|
MAX_BODY_BYTES2 = 1024 * 1024;
|
|
28763
28710
|
});
|
|
28764
28711
|
|
|
28712
|
+
// ../../adapters/otium/src/shared-topic-sync.ts
|
|
28713
|
+
var exports_shared_topic_sync = {};
|
|
28714
|
+
__export(exports_shared_topic_sync, {
|
|
28715
|
+
startSharedTopicSync: () => startSharedTopicSync,
|
|
28716
|
+
forwardSharedTopicMessage: () => forwardSharedTopicMessage,
|
|
28717
|
+
downgradeSharedTopicsForHub: () => downgradeSharedTopicsForHub,
|
|
28718
|
+
disconnectSharedTopics: () => disconnectSharedTopics,
|
|
28719
|
+
checkPeerAttachment: () => checkPeerAttachment,
|
|
28720
|
+
acceptSharedTopicMessages: () => acceptSharedTopicMessages
|
|
28721
|
+
});
|
|
28722
|
+
function isMirrorTopic(topicId) {
|
|
28723
|
+
return listPeerSessions().some((row) => row.local_topic_id === topicId && row.binding_mode === "mirror");
|
|
28724
|
+
}
|
|
28725
|
+
function metadata(topic) {
|
|
28726
|
+
if (!topic.agent)
|
|
28727
|
+
throw new Error(`topic ${topic.id} has no executable agent`);
|
|
28728
|
+
const registry = getRegistry(topic.agent);
|
|
28729
|
+
return {
|
|
28730
|
+
localTopicId: topic.id,
|
|
28731
|
+
title: topic.title,
|
|
28732
|
+
...topic.description ? { description: topic.description } : {},
|
|
28733
|
+
agent: topic.agent,
|
|
28734
|
+
model: topic.defaultModel || registry.defaultModel,
|
|
28735
|
+
effort: topic.defaultEffort ?? "medium"
|
|
28736
|
+
};
|
|
28737
|
+
}
|
|
28738
|
+
async function peerRequest(node, path, init) {
|
|
28739
|
+
const token = await mintPeerToken(node.cellId);
|
|
28740
|
+
const response = await fetch(`${node.baseUrl.replace(/\/+$/, "")}${path}`, {
|
|
28741
|
+
...init,
|
|
28742
|
+
headers: {
|
|
28743
|
+
authorization: `Bearer ${token}`,
|
|
28744
|
+
"content-type": "application/json",
|
|
28745
|
+
...init.headers ?? {}
|
|
28746
|
+
},
|
|
28747
|
+
signal: AbortSignal.timeout(15000)
|
|
28748
|
+
});
|
|
28749
|
+
const body = await response.json().catch(() => null);
|
|
28750
|
+
if (!response.ok || !body?.ok)
|
|
28751
|
+
throw new Error(String(body?.error ?? `peer request failed (${response.status})`));
|
|
28752
|
+
return body;
|
|
28753
|
+
}
|
|
28754
|
+
function messageEnvelope(message) {
|
|
28755
|
+
const author = message.agentType ? "ai" : message.kind === "system" || message.kind === "tool" ? "system" : "user";
|
|
28756
|
+
return {
|
|
28757
|
+
sourceMessageId: message.sourceMessageId ?? message.id,
|
|
28758
|
+
author,
|
|
28759
|
+
text: message.text,
|
|
28760
|
+
createdAt: message.createdAt,
|
|
28761
|
+
...message.agentType ? { agent: message.agentType } : {},
|
|
28762
|
+
...message.model ? { model: message.model } : {},
|
|
28763
|
+
...message.kind ? { kind: message.kind } : {}
|
|
28764
|
+
};
|
|
28765
|
+
}
|
|
28766
|
+
async function publishTopic(join32, topicId, node) {
|
|
28767
|
+
const topic = getTopic(topicId);
|
|
28768
|
+
if (!topic || topic.kind !== "agent" || !topic.agent || !isTopicShared(topic) || !topic.visibility || topic.visibility === "hidden" || isMirrorTopic(topicId))
|
|
28769
|
+
return;
|
|
28770
|
+
const state = getSharedTopicState(topicId);
|
|
28771
|
+
setSharedTopicState({
|
|
28772
|
+
localTopicId: topicId,
|
|
28773
|
+
hostTopicId: state?.host_topic_id,
|
|
28774
|
+
status: "publishing"
|
|
28775
|
+
});
|
|
28776
|
+
try {
|
|
28777
|
+
const body = await peerRequest(node, "/api/v1/peer/shared-topic", {
|
|
28778
|
+
method: "POST",
|
|
28779
|
+
body: JSON.stringify({
|
|
28780
|
+
v: PEER_PROTOCOL_VERSION,
|
|
28781
|
+
...metadata(topic)
|
|
28782
|
+
})
|
|
28783
|
+
});
|
|
28784
|
+
const hostTopicId = typeof body.hostTopicId === "string" ? body.hostTopicId : state?.host_topic_id;
|
|
28785
|
+
if (!hostTopicId)
|
|
28786
|
+
throw new Error("shared-topic response omitted hostTopicId");
|
|
28787
|
+
setSharedTopicState({ localTopicId: topicId, hostTopicId, status: "published" });
|
|
28788
|
+
const messages = listApiMessages(topicId, { limit: 200 }).page.filter((message) => !message.sourceNode).map(messageEnvelope);
|
|
28789
|
+
if (messages.length) {
|
|
28790
|
+
await peerRequest(node, "/api/v1/peer/shared-topic/messages", {
|
|
28791
|
+
method: "POST",
|
|
28792
|
+
body: JSON.stringify({
|
|
28793
|
+
v: PEER_PROTOCOL_VERSION,
|
|
28794
|
+
localTopicId: topicId,
|
|
28795
|
+
hostTopicId,
|
|
28796
|
+
messages
|
|
28797
|
+
})
|
|
28798
|
+
});
|
|
28799
|
+
}
|
|
28800
|
+
for (const row of listSharedMessages(topicId)) {
|
|
28801
|
+
await peerRequest(node, "/api/v1/peer/shared-topic/messages", {
|
|
28802
|
+
method: "POST",
|
|
28803
|
+
body: JSON.stringify({
|
|
28804
|
+
v: PEER_PROTOCOL_VERSION,
|
|
28805
|
+
localTopicId: topicId,
|
|
28806
|
+
hostTopicId,
|
|
28807
|
+
messages: [JSON.parse(row.message_json)]
|
|
28808
|
+
})
|
|
28809
|
+
});
|
|
28810
|
+
deleteSharedMessage(topicId, row.source_message_id);
|
|
28811
|
+
}
|
|
28812
|
+
} catch (error) {
|
|
28813
|
+
logger.warn({ error, topicId }, "otium: shared topic publish deferred");
|
|
28814
|
+
setTimeout(() => void reconcileTopic(join32, topicId), RETRY_MS).unref?.();
|
|
28815
|
+
}
|
|
28816
|
+
}
|
|
28817
|
+
async function unpublishTopic(join32, topicId, node) {
|
|
28818
|
+
const state = getSharedTopicState(topicId);
|
|
28819
|
+
if (!state)
|
|
28820
|
+
return;
|
|
28821
|
+
setSharedTopicState({
|
|
28822
|
+
localTopicId: topicId,
|
|
28823
|
+
hostTopicId: state.host_topic_id,
|
|
28824
|
+
status: "unpublishing"
|
|
28825
|
+
});
|
|
28826
|
+
try {
|
|
28827
|
+
await peerRequest(node, `/api/v1/peer/shared-topic/${encodeURIComponent(topicId)}`, {
|
|
28828
|
+
method: "DELETE"
|
|
28829
|
+
});
|
|
28830
|
+
deleteSharedTopicState(topicId);
|
|
28831
|
+
} catch (error) {
|
|
28832
|
+
logger.warn({ error, topicId }, "otium: shared topic unpublish deferred");
|
|
28833
|
+
setTimeout(() => void reconcileTopic(join32, topicId), RETRY_MS).unref?.();
|
|
28834
|
+
}
|
|
28835
|
+
}
|
|
28836
|
+
async function reconcileTopic(join32, topicId, explicit = false) {
|
|
28837
|
+
if (!otiumCentralConfig())
|
|
28838
|
+
return;
|
|
28839
|
+
if (!explicit && isPeerDetached(join32.cellId))
|
|
28840
|
+
return;
|
|
28841
|
+
const target = (await listPeerNodes()).find((node) => node.isPrimary) ?? null;
|
|
28842
|
+
if (!target)
|
|
28843
|
+
return;
|
|
28844
|
+
const topic = getTopic(topicId);
|
|
28845
|
+
if (topic?.kind === "agent" && topic.agent && isTopicShared(topic) && topic.visibility !== "hidden" && !isMirrorTopic(topicId))
|
|
28846
|
+
return publishTopic(join32, topicId, target);
|
|
28847
|
+
return unpublishTopic(join32, topicId, target);
|
|
28848
|
+
}
|
|
28849
|
+
function disconnectSharedTopics(join32) {
|
|
28850
|
+
const states = listSharedTopicStates();
|
|
28851
|
+
const hubPromise = listPeerNodes({ fresh: true }).then((nodes) => nodes.find((node) => node.isPrimary) ?? null, () => null);
|
|
28852
|
+
const updated = downgradeSharedTopicsLocally(join32.cellId);
|
|
28853
|
+
for (const topicId of updated)
|
|
28854
|
+
WsHub.get().broadcastTopicUpdated(topicId);
|
|
28855
|
+
const deletions = states.map(async (state) => {
|
|
28856
|
+
const hub = await hubPromise;
|
|
28857
|
+
if (!hub)
|
|
28858
|
+
return;
|
|
28859
|
+
try {
|
|
28860
|
+
await peerRequest(hub, `/api/v1/peer/shared-topic/${encodeURIComponent(state.local_topic_id)}`, {
|
|
28861
|
+
method: "DELETE"
|
|
28862
|
+
});
|
|
28863
|
+
} catch {}
|
|
28864
|
+
});
|
|
28865
|
+
return Promise.all(deletions).then(() => {
|
|
28866
|
+
return;
|
|
28867
|
+
});
|
|
28868
|
+
}
|
|
28869
|
+
function downgradeSharedTopicsForHub(hubNodeId) {
|
|
28870
|
+
const updated = downgradeSharedTopicsLocally(hubNodeId);
|
|
28871
|
+
for (const topicId of updated)
|
|
28872
|
+
WsHub.get().broadcastTopicUpdated(topicId);
|
|
28873
|
+
return updated.length;
|
|
28874
|
+
}
|
|
28875
|
+
async function checkPeerAttachment(join32) {
|
|
28876
|
+
const nodes = await listPeerNodes({ fresh: true });
|
|
28877
|
+
if (nodes.some((node) => node.cellId === join32.cellId))
|
|
28878
|
+
return true;
|
|
28879
|
+
downgradeSharedTopicsForHub(join32.cellId);
|
|
28880
|
+
return false;
|
|
28881
|
+
}
|
|
28882
|
+
function startSharedTopicSync(join32) {
|
|
28883
|
+
let stopped = false;
|
|
28884
|
+
let detached = isPeerDetached(join32.cellId);
|
|
28885
|
+
const queue = new Set;
|
|
28886
|
+
const schedule = (topicId, explicit = false) => {
|
|
28887
|
+
if (stopped || queue.has(topicId))
|
|
28888
|
+
return;
|
|
28889
|
+
queue.add(topicId);
|
|
28890
|
+
queueMicrotask(async () => {
|
|
28891
|
+
queue.delete(topicId);
|
|
28892
|
+
try {
|
|
28893
|
+
await reconcileTopic(join32, topicId, explicit);
|
|
28894
|
+
} catch (error) {
|
|
28895
|
+
logger.warn({ error, topicId }, "otium: shared topic reconciliation failed");
|
|
28896
|
+
}
|
|
28897
|
+
});
|
|
28898
|
+
};
|
|
28899
|
+
for (const topic of listTopics())
|
|
28900
|
+
schedule(topic.id, false);
|
|
28901
|
+
for (const state of listSharedTopicStates())
|
|
28902
|
+
schedule(state.local_topic_id);
|
|
28903
|
+
const attachmentCheck = setInterval(async () => {
|
|
28904
|
+
if (stopped || detached)
|
|
28905
|
+
return;
|
|
28906
|
+
try {
|
|
28907
|
+
if (!await checkPeerAttachment(join32))
|
|
28908
|
+
detached = true;
|
|
28909
|
+
} catch {}
|
|
28910
|
+
}, 30000);
|
|
28911
|
+
attachmentCheck.unref?.();
|
|
28912
|
+
const unsubscribe2 = runtimeBus().subscribe((event) => {
|
|
28913
|
+
if (event.type === "topic-created" || event.type === "topic-updated" || event.type === "topic-deleted")
|
|
28914
|
+
schedule(event.topicId, true);
|
|
28915
|
+
if (event.type === "message") {
|
|
28916
|
+
const message = event.payload;
|
|
28917
|
+
if (!message.sourceNode) {
|
|
28918
|
+
schedule(event.topicId);
|
|
28919
|
+
forwardSharedTopicMessage(join32, message).catch((error) => logger.warn({ error, topicId: event.topicId }, "otium: shared message deferred"));
|
|
28920
|
+
}
|
|
28921
|
+
}
|
|
28922
|
+
});
|
|
28923
|
+
return () => {
|
|
28924
|
+
stopped = true;
|
|
28925
|
+
clearInterval(attachmentCheck);
|
|
28926
|
+
unsubscribe2();
|
|
28927
|
+
};
|
|
28928
|
+
}
|
|
28929
|
+
async function forwardSharedTopicMessage(_join, message) {
|
|
28930
|
+
if (message.sourceNode || isMirrorTopic(message.topicId))
|
|
28931
|
+
return;
|
|
28932
|
+
if (getActiveForwarder(message.topicId))
|
|
28933
|
+
return;
|
|
28934
|
+
const state = getSharedTopicState(message.topicId);
|
|
28935
|
+
if (!state?.host_topic_id || state.status !== "published")
|
|
28936
|
+
return;
|
|
28937
|
+
const hub = (await listPeerNodes()).find((node) => node.isPrimary) ?? null;
|
|
28938
|
+
if (!hub)
|
|
28939
|
+
return;
|
|
28940
|
+
const envelope = messageEnvelope(message);
|
|
28941
|
+
try {
|
|
28942
|
+
await peerRequest(hub, "/api/v1/peer/shared-topic/messages", {
|
|
28943
|
+
method: "POST",
|
|
28944
|
+
body: JSON.stringify({
|
|
28945
|
+
v: PEER_PROTOCOL_VERSION,
|
|
28946
|
+
localTopicId: message.topicId,
|
|
28947
|
+
hostTopicId: state.host_topic_id,
|
|
28948
|
+
messages: [envelope]
|
|
28949
|
+
})
|
|
28950
|
+
});
|
|
28951
|
+
} catch (error) {
|
|
28952
|
+
enqueueSharedMessage({
|
|
28953
|
+
localTopicId: message.topicId,
|
|
28954
|
+
sourceMessageId: envelope.sourceMessageId,
|
|
28955
|
+
message: envelope
|
|
28956
|
+
});
|
|
28957
|
+
throw error;
|
|
28958
|
+
}
|
|
28959
|
+
}
|
|
28960
|
+
function acceptSharedTopicMessages(messages, localTopicId, sourceNode) {
|
|
28961
|
+
let inserted = 0;
|
|
28962
|
+
for (const incoming of messages) {
|
|
28963
|
+
if (!incoming.sourceMessageId || typeof incoming.text !== "string")
|
|
28964
|
+
continue;
|
|
28965
|
+
if (getApiMessage(localTopicId, incoming.sourceMessageId))
|
|
28966
|
+
continue;
|
|
28967
|
+
const message = {
|
|
28968
|
+
id: incoming.sourceMessageId,
|
|
28969
|
+
topicId: localTopicId,
|
|
28970
|
+
authorId: sourceNode,
|
|
28971
|
+
text: incoming.text,
|
|
28972
|
+
createdAt: incoming.createdAt,
|
|
28973
|
+
...incoming.agent ? { agentType: incoming.agent } : {},
|
|
28974
|
+
...incoming.model ? { model: incoming.model } : {},
|
|
28975
|
+
...incoming.kind ? { kind: incoming.kind } : {},
|
|
28976
|
+
sourceNode,
|
|
28977
|
+
sourceMessageId: incoming.sourceMessageId
|
|
28978
|
+
};
|
|
28979
|
+
appendApiMessage(message);
|
|
28980
|
+
inserted++;
|
|
28981
|
+
}
|
|
28982
|
+
return inserted;
|
|
28983
|
+
}
|
|
28984
|
+
var RETRY_MS = 1000;
|
|
28985
|
+
var init_shared_topic_sync = __esm(async () => {
|
|
28986
|
+
await init_src();
|
|
28987
|
+
await init_central();
|
|
28988
|
+
await init_event_backflow();
|
|
28989
|
+
init_protocol();
|
|
28990
|
+
await init_store2();
|
|
28991
|
+
});
|
|
28992
|
+
|
|
28765
28993
|
// ../../adapters/otium/src/relay-protocol.ts
|
|
28766
28994
|
function encodeFrame(frame) {
|
|
28767
28995
|
const encoded = JSON.stringify(frame);
|
|
@@ -29158,6 +29386,8 @@ class TunnelClient {
|
|
|
29158
29386
|
const pending2 = this.pendingHttp.get(frame.id);
|
|
29159
29387
|
try {
|
|
29160
29388
|
pending2?.bodyController?.enqueue(fromB64(frame.dataB64));
|
|
29389
|
+
if (pending2)
|
|
29390
|
+
this.startPendingHttp(frame.id, pending2);
|
|
29161
29391
|
} catch {}
|
|
29162
29392
|
break;
|
|
29163
29393
|
}
|
|
@@ -29165,6 +29395,8 @@ class TunnelClient {
|
|
|
29165
29395
|
const pending2 = this.pendingHttp.get(frame.id);
|
|
29166
29396
|
try {
|
|
29167
29397
|
pending2?.bodyController?.close();
|
|
29398
|
+
if (pending2)
|
|
29399
|
+
this.startPendingHttp(frame.id, pending2);
|
|
29168
29400
|
} catch {}
|
|
29169
29401
|
break;
|
|
29170
29402
|
}
|
|
@@ -29207,21 +29439,35 @@ class TunnelClient {
|
|
|
29207
29439
|
}
|
|
29208
29440
|
startHttpRequest(frame) {
|
|
29209
29441
|
const abort = new AbortController;
|
|
29210
|
-
const
|
|
29211
|
-
|
|
29212
|
-
|
|
29442
|
+
const headers = new Headers;
|
|
29443
|
+
for (const [name, value] of frame.headers)
|
|
29444
|
+
headers.append(name, value);
|
|
29445
|
+
const pending2 = {
|
|
29446
|
+
abort,
|
|
29447
|
+
bodyController: null,
|
|
29448
|
+
body: undefined,
|
|
29449
|
+
method: frame.method,
|
|
29450
|
+
path: frame.path,
|
|
29451
|
+
headers,
|
|
29452
|
+
started: false
|
|
29453
|
+
};
|
|
29213
29454
|
if (frame.hasBody) {
|
|
29214
|
-
body = new ReadableStream({
|
|
29455
|
+
pending2.body = new ReadableStream({
|
|
29215
29456
|
start: (controller) => {
|
|
29216
29457
|
pending2.bodyController = controller;
|
|
29217
29458
|
}
|
|
29218
29459
|
});
|
|
29219
29460
|
}
|
|
29220
|
-
|
|
29221
|
-
|
|
29222
|
-
|
|
29223
|
-
|
|
29224
|
-
|
|
29461
|
+
this.pendingHttp.set(frame.id, pending2);
|
|
29462
|
+
if (!frame.hasBody)
|
|
29463
|
+
this.startPendingHttp(frame.id, pending2);
|
|
29464
|
+
}
|
|
29465
|
+
startPendingHttp(id, pending2) {
|
|
29466
|
+
if (pending2.started || pending2.abort.signal.aborted)
|
|
29467
|
+
return;
|
|
29468
|
+
pending2.started = true;
|
|
29469
|
+
this.replayHttp(id, pending2.method, pending2.path, pending2.headers, pending2.body, pending2.abort).finally(() => {
|
|
29470
|
+
this.pendingHttp.delete(id);
|
|
29225
29471
|
});
|
|
29226
29472
|
}
|
|
29227
29473
|
async replayHttp(id, method, path, headers, body, abort) {
|
|
@@ -29365,57 +29611,301 @@ function bindOtiumTopic(options) {
|
|
|
29365
29611
|
if (!topic.participants.some((participant) => participant.userId === options.userId)) {
|
|
29366
29612
|
return { ok: false, error: "local topic is not visible to this user", status: 403 };
|
|
29367
29613
|
}
|
|
29368
|
-
if (!isTopicShared(topic)) {
|
|
29369
|
-
return { ok: false, error: "private topics must be shared explicitly first", status: 409 };
|
|
29614
|
+
if (!isTopicShared(topic)) {
|
|
29615
|
+
return { ok: false, error: "private topics must be shared explicitly first", status: 409 };
|
|
29616
|
+
}
|
|
29617
|
+
const previous = getPeerSession(options.hostNodeId, options.hostTopicId);
|
|
29618
|
+
bindPeerSession(options.hostNodeId, options.hostTopicId, options.localTopicId, "shared");
|
|
29619
|
+
return {
|
|
29620
|
+
ok: true,
|
|
29621
|
+
localTopicId: options.localTopicId,
|
|
29622
|
+
replaced: Boolean(previous && previous.local_topic_id !== options.localTopicId)
|
|
29623
|
+
};
|
|
29624
|
+
}
|
|
29625
|
+
function shareOtiumTopic(options) {
|
|
29626
|
+
const owned = ownsTopic(options.localTopicId, options.userId);
|
|
29627
|
+
if (!owned.ok)
|
|
29628
|
+
return owned;
|
|
29629
|
+
if (!isTopicShared(owned.topic)) {
|
|
29630
|
+
upsertTopic({ ...owned.topic, accessMode: "shared" });
|
|
29631
|
+
WsHub.get().broadcastTopicUpdated(owned.topic.id);
|
|
29632
|
+
}
|
|
29633
|
+
return bindOtiumTopic(options);
|
|
29634
|
+
}
|
|
29635
|
+
function setOtiumTopicPrivate(options) {
|
|
29636
|
+
const owned = ownsTopic(options.localTopicId, options.userId);
|
|
29637
|
+
if (!owned.ok)
|
|
29638
|
+
return owned;
|
|
29639
|
+
const removedBindings = unbindSharedPeerSessionsForLocalTopic(options.localTopicId);
|
|
29640
|
+
if (isTopicShared(owned.topic)) {
|
|
29641
|
+
upsertTopic({ ...owned.topic, accessMode: "private" });
|
|
29642
|
+
WsHub.get().broadcastTopicUpdated(owned.topic.id);
|
|
29643
|
+
}
|
|
29644
|
+
return { ok: true, localTopicId: options.localTopicId, removedBindings };
|
|
29645
|
+
}
|
|
29646
|
+
function unbindOtiumTopic(hostNodeId, hostTopicId) {
|
|
29647
|
+
return unbindPeerSession(hostNodeId, hostTopicId);
|
|
29648
|
+
}
|
|
29649
|
+
function listOtiumTopicBindings() {
|
|
29650
|
+
return listPeerSessions().map((row) => {
|
|
29651
|
+
const topic = getTopic(row.local_topic_id);
|
|
29652
|
+
return {
|
|
29653
|
+
hostNodeId: row.host_node_id,
|
|
29654
|
+
hostTopicId: row.host_topic_id,
|
|
29655
|
+
localTopicId: row.local_topic_id,
|
|
29656
|
+
transport: row.binding_mode === "shared" ? "shared-binding" : "internal-mirror",
|
|
29657
|
+
...topic ? { topicAccessMode: topic.accessMode ?? "private" } : {},
|
|
29658
|
+
...topic ? { localTopicTitle: topic.title } : {},
|
|
29659
|
+
localTopicExists: Boolean(topic),
|
|
29660
|
+
createdAt: row.created_at
|
|
29661
|
+
};
|
|
29662
|
+
});
|
|
29663
|
+
}
|
|
29664
|
+
var init_bindings = __esm(async () => {
|
|
29665
|
+
await init_src();
|
|
29666
|
+
await init_store2();
|
|
29667
|
+
});
|
|
29668
|
+
|
|
29669
|
+
// ../../adapters/otium/src/enrollment.ts
|
|
29670
|
+
import {
|
|
29671
|
+
createDecipheriv as createDecipheriv2,
|
|
29672
|
+
createPrivateKey,
|
|
29673
|
+
createPublicKey,
|
|
29674
|
+
diffieHellman,
|
|
29675
|
+
generateKeyPairSync,
|
|
29676
|
+
hkdfSync,
|
|
29677
|
+
randomUUID as randomUUID27
|
|
29678
|
+
} from "crypto";
|
|
29679
|
+
import {
|
|
29680
|
+
chmodSync as chmodSync6,
|
|
29681
|
+
closeSync as closeSync4,
|
|
29682
|
+
existsSync as existsSync26,
|
|
29683
|
+
fsyncSync as fsyncSync3,
|
|
29684
|
+
linkSync as linkSync2,
|
|
29685
|
+
mkdirSync as mkdirSync30,
|
|
29686
|
+
openSync as openSync4,
|
|
29687
|
+
readFileSync as readFileSync21,
|
|
29688
|
+
renameSync as renameSync14,
|
|
29689
|
+
unlinkSync as unlinkSync19,
|
|
29690
|
+
writeFileSync as writeFileSync19
|
|
29691
|
+
} from "fs";
|
|
29692
|
+
import { dirname as dirname18, resolve as resolve14 } from "path";
|
|
29693
|
+
function parseEnrollmentInvite(code) {
|
|
29694
|
+
let parsed;
|
|
29695
|
+
try {
|
|
29696
|
+
parsed = JSON.parse(Buffer.from(code.trim(), "base64url").toString("utf8"));
|
|
29697
|
+
} catch {
|
|
29698
|
+
throw new Error("production invite must be base64url JSON");
|
|
29699
|
+
}
|
|
29700
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
29701
|
+
throw new Error("production invite must decode to an object");
|
|
29702
|
+
}
|
|
29703
|
+
const raw = parsed;
|
|
29704
|
+
const central = typeof raw.central === "string" ? raw.central.trim().replace(/\/+$/, "") : "";
|
|
29705
|
+
const token = typeof raw.token === "string" ? raw.token.trim() : "";
|
|
29706
|
+
if (raw.v !== 2 || !central || !token.startsWith("nei_")) {
|
|
29707
|
+
throw new Error("production invite requires {v:2, central:http(s), token:nei_\u2026}");
|
|
29708
|
+
}
|
|
29709
|
+
assertSecureCentralUrl(central);
|
|
29710
|
+
return { v: 2, central, token };
|
|
29711
|
+
}
|
|
29712
|
+
function pendingEnrollmentPath() {
|
|
29713
|
+
return resolve14(DATA_DIR, "otium-enrollment-pending.json");
|
|
29714
|
+
}
|
|
29715
|
+
function isEnrollmentPending(invite) {
|
|
29716
|
+
const path = pendingEnrollmentPath();
|
|
29717
|
+
if (!existsSync26(path))
|
|
29718
|
+
return false;
|
|
29719
|
+
try {
|
|
29720
|
+
const saved = JSON.parse(readFileSync21(path, "utf8"));
|
|
29721
|
+
return saved.central === invite.central && saved.token === invite.token;
|
|
29722
|
+
} catch {
|
|
29723
|
+
return false;
|
|
29724
|
+
}
|
|
29725
|
+
}
|
|
29726
|
+
function loadOrCreatePending(invite, nodeName) {
|
|
29727
|
+
const path = pendingEnrollmentPath();
|
|
29728
|
+
if (existsSync26(path)) {
|
|
29729
|
+
const saved = JSON.parse(readFileSync21(path, "utf8"));
|
|
29730
|
+
if (saved.central === invite.central && saved.token === invite.token)
|
|
29731
|
+
return saved;
|
|
29732
|
+
throw new Error(`another Otium enrollment is pending at ${path}`);
|
|
29370
29733
|
}
|
|
29371
|
-
const
|
|
29372
|
-
|
|
29373
|
-
|
|
29374
|
-
|
|
29375
|
-
|
|
29376
|
-
|
|
29734
|
+
const pair = generateKeyPairSync("x25519");
|
|
29735
|
+
const pending2 = {
|
|
29736
|
+
...invite,
|
|
29737
|
+
idempotencyKey: randomUUID27(),
|
|
29738
|
+
privateKey: pair.privateKey.export({ format: "der", type: "pkcs8" }).toString("base64url"),
|
|
29739
|
+
publicKey: pair.publicKey.export({ format: "der", type: "spki" }).toString("base64url"),
|
|
29740
|
+
...nodeName ? { nodeName } : {}
|
|
29377
29741
|
};
|
|
29742
|
+
mkdirSync30(dirname18(path), { recursive: true });
|
|
29743
|
+
const temporaryPath = resolve14(dirname18(path), `.otium-enrollment-pending.json.${process.pid}.${randomUUID27()}.tmp`);
|
|
29744
|
+
let fd;
|
|
29745
|
+
try {
|
|
29746
|
+
fd = openSync4(temporaryPath, "wx", 384);
|
|
29747
|
+
writeFileSync19(fd, `${JSON.stringify(pending2, null, 2)}
|
|
29748
|
+
`, "utf8");
|
|
29749
|
+
fsyncSync3(fd);
|
|
29750
|
+
closeSync4(fd);
|
|
29751
|
+
fd = undefined;
|
|
29752
|
+
linkSync2(temporaryPath, path);
|
|
29753
|
+
unlinkSync19(temporaryPath);
|
|
29754
|
+
chmodSync6(path, 384);
|
|
29755
|
+
const directoryFd = openSync4(dirname18(path), "r");
|
|
29756
|
+
try {
|
|
29757
|
+
fsyncSync3(directoryFd);
|
|
29758
|
+
} finally {
|
|
29759
|
+
closeSync4(directoryFd);
|
|
29760
|
+
}
|
|
29761
|
+
} catch (error) {
|
|
29762
|
+
if (fd !== undefined)
|
|
29763
|
+
closeSync4(fd);
|
|
29764
|
+
if (existsSync26(temporaryPath))
|
|
29765
|
+
unlinkSync19(temporaryPath);
|
|
29766
|
+
if (error.code === "EEXIST" && existsSync26(path)) {
|
|
29767
|
+
const saved = JSON.parse(readFileSync21(path, "utf8"));
|
|
29768
|
+
if (saved.central === invite.central && saved.token === invite.token)
|
|
29769
|
+
return saved;
|
|
29770
|
+
}
|
|
29771
|
+
throw error;
|
|
29772
|
+
}
|
|
29773
|
+
return pending2;
|
|
29378
29774
|
}
|
|
29379
|
-
function
|
|
29380
|
-
const
|
|
29381
|
-
|
|
29382
|
-
|
|
29383
|
-
|
|
29384
|
-
|
|
29775
|
+
function replacePendingEnrollment(pending2) {
|
|
29776
|
+
const path = pendingEnrollmentPath();
|
|
29777
|
+
const directory = dirname18(path);
|
|
29778
|
+
const temporaryPath = resolve14(directory, `.otium-enrollment-pending.json.${process.pid}.${randomUUID27()}.tmp`);
|
|
29779
|
+
let fd;
|
|
29780
|
+
try {
|
|
29781
|
+
fd = openSync4(temporaryPath, "wx", 384);
|
|
29782
|
+
writeFileSync19(fd, `${JSON.stringify(pending2, null, 2)}
|
|
29783
|
+
`, "utf8");
|
|
29784
|
+
fsyncSync3(fd);
|
|
29785
|
+
closeSync4(fd);
|
|
29786
|
+
fd = undefined;
|
|
29787
|
+
renameSync14(temporaryPath, path);
|
|
29788
|
+
const directoryFd = openSync4(directory, "r");
|
|
29789
|
+
try {
|
|
29790
|
+
fsyncSync3(directoryFd);
|
|
29791
|
+
} finally {
|
|
29792
|
+
closeSync4(directoryFd);
|
|
29793
|
+
}
|
|
29794
|
+
} catch (error) {
|
|
29795
|
+
if (fd !== undefined)
|
|
29796
|
+
closeSync4(fd);
|
|
29797
|
+
if (existsSync26(temporaryPath))
|
|
29798
|
+
unlinkSync19(temporaryPath);
|
|
29799
|
+
throw error;
|
|
29385
29800
|
}
|
|
29386
|
-
return bindOtiumTopic(options);
|
|
29387
29801
|
}
|
|
29388
|
-
function
|
|
29389
|
-
|
|
29390
|
-
|
|
29391
|
-
|
|
29392
|
-
|
|
29393
|
-
|
|
29394
|
-
|
|
29802
|
+
function recordClaimedCredential(pending2, join32) {
|
|
29803
|
+
withJoinCredentialLock(() => {
|
|
29804
|
+
const current3 = JSON.parse(readFileSync21(pendingEnrollmentPath(), "utf8"));
|
|
29805
|
+
if (current3.central !== pending2.central || current3.token !== pending2.token || current3.idempotencyKey !== pending2.idempotencyKey || current3.publicKey !== pending2.publicKey) {
|
|
29806
|
+
throw new Error("pending Otium enrollment changed while its claim was in flight");
|
|
29807
|
+
}
|
|
29808
|
+
const claimed = { digest: joinCredentialDigest(join32), cellId: join32.cellId };
|
|
29809
|
+
if (current3.claimed && (current3.claimed.digest !== claimed.digest || current3.claimed.cellId !== claimed.cellId)) {
|
|
29810
|
+
throw new Error("central returned different credentials for an idempotent enrollment claim");
|
|
29811
|
+
}
|
|
29812
|
+
replacePendingEnrollment({ ...current3, claimed });
|
|
29813
|
+
});
|
|
29814
|
+
}
|
|
29815
|
+
function openCredential(envelope, privateKeyDer) {
|
|
29816
|
+
if (envelope.v !== 1 || envelope.algorithm !== "X25519-HKDF-SHA256-AES-256-GCM") {
|
|
29817
|
+
throw new Error("unsupported enrollment credential envelope");
|
|
29395
29818
|
}
|
|
29396
|
-
|
|
29819
|
+
const privateKey = createPrivateKey({
|
|
29820
|
+
key: Buffer.from(privateKeyDer, "base64url"),
|
|
29821
|
+
format: "der",
|
|
29822
|
+
type: "pkcs8"
|
|
29823
|
+
});
|
|
29824
|
+
const ephemeral = createPublicKey({
|
|
29825
|
+
key: Buffer.from(envelope.ephemeralPublicKey, "base64url"),
|
|
29826
|
+
format: "der",
|
|
29827
|
+
type: "spki"
|
|
29828
|
+
});
|
|
29829
|
+
const shared = diffieHellman({ privateKey, publicKey: ephemeral });
|
|
29830
|
+
const key = Buffer.from(hkdfSync("sha256", shared, Buffer.from(envelope.salt, "base64url"), INFO, 32));
|
|
29831
|
+
const decipher = createDecipheriv2("aes-256-gcm", key, Buffer.from(envelope.nonce, "base64url"));
|
|
29832
|
+
decipher.setAAD(INFO);
|
|
29833
|
+
decipher.setAuthTag(Buffer.from(envelope.tag, "base64url"));
|
|
29834
|
+
return Buffer.concat([
|
|
29835
|
+
decipher.update(Buffer.from(envelope.ciphertext, "base64url")),
|
|
29836
|
+
decipher.final()
|
|
29837
|
+
]).toString("utf8");
|
|
29397
29838
|
}
|
|
29398
|
-
function
|
|
29399
|
-
|
|
29839
|
+
async function postJson(url, body, headers) {
|
|
29840
|
+
const response = await fetch(url, {
|
|
29841
|
+
method: "POST",
|
|
29842
|
+
headers: { "content-type": "application/json", ...headers },
|
|
29843
|
+
body: JSON.stringify(body)
|
|
29844
|
+
});
|
|
29845
|
+
const result = await response.json();
|
|
29846
|
+
if (!response.ok)
|
|
29847
|
+
throw new Error(String(result.error || `request failed (${response.status})`));
|
|
29848
|
+
return result;
|
|
29400
29849
|
}
|
|
29401
|
-
function
|
|
29402
|
-
return
|
|
29403
|
-
|
|
29404
|
-
return {
|
|
29405
|
-
hostNodeId: row.host_node_id,
|
|
29406
|
-
hostTopicId: row.host_topic_id,
|
|
29407
|
-
localTopicId: row.local_topic_id,
|
|
29408
|
-
transport: row.binding_mode === "shared" ? "shared-binding" : "internal-mirror",
|
|
29409
|
-
...topic ? { topicAccessMode: topic.accessMode ?? "private" } : {},
|
|
29410
|
-
...topic ? { localTopicTitle: topic.title } : {},
|
|
29411
|
-
localTopicExists: Boolean(topic),
|
|
29412
|
-
createdAt: row.created_at
|
|
29413
|
-
};
|
|
29850
|
+
async function previewEnrollment(invite) {
|
|
29851
|
+
return postJson(`${invite.central}/api/v1/node-enrollments/preview`, {
|
|
29852
|
+
token: invite.token
|
|
29414
29853
|
});
|
|
29415
29854
|
}
|
|
29416
|
-
|
|
29855
|
+
async function claimEnrollment(invite, nodeName) {
|
|
29856
|
+
const pending2 = loadOrCreatePending(invite, nodeName);
|
|
29857
|
+
const response = await postJson(`${invite.central}/api/v1/node-enrollments/claim`, {
|
|
29858
|
+
token: invite.token,
|
|
29859
|
+
credentialPublicKey: pending2.publicKey,
|
|
29860
|
+
...pending2.nodeName ? { nodeName: pending2.nodeName } : {}
|
|
29861
|
+
}, { "idempotency-key": pending2.idempotencyKey });
|
|
29862
|
+
const credential = response.credential;
|
|
29863
|
+
const secret = openCredential(credential, pending2.privateKey);
|
|
29864
|
+
if (!secret.startsWith("rcs_"))
|
|
29865
|
+
throw new Error("central returned an invalid runtime credential");
|
|
29866
|
+
const join32 = {
|
|
29867
|
+
v: 2,
|
|
29868
|
+
central: invite.central,
|
|
29869
|
+
relay: String(response.relayUrl),
|
|
29870
|
+
cellId: String(response.cell?.id),
|
|
29871
|
+
secret
|
|
29872
|
+
};
|
|
29873
|
+
if (!join32.relay || !join32.cellId || join32.cellId === "undefined") {
|
|
29874
|
+
throw new Error("central returned an incomplete enrollment response");
|
|
29875
|
+
}
|
|
29876
|
+
assertSecureRelayUrl(join32.relay);
|
|
29877
|
+
recordClaimedCredential(pending2, join32);
|
|
29878
|
+
return join32;
|
|
29879
|
+
}
|
|
29880
|
+
function commitEnrollment(join32, options = {}) {
|
|
29881
|
+
return withJoinCredentialLock(() => {
|
|
29882
|
+
const pendingPath = pendingEnrollmentPath();
|
|
29883
|
+
const pending2 = existsSync26(pendingPath) ? JSON.parse(readFileSync21(pendingPath, "utf8")) : null;
|
|
29884
|
+
const digest = joinCredentialDigest(join32);
|
|
29885
|
+
if (pending2 && (!pending2.claimed || pending2.claimed.digest !== digest || pending2.claimed.cellId !== join32.cellId)) {
|
|
29886
|
+
throw new Error(`pending Otium enrollment at ${pendingPath} does not match these credentials`);
|
|
29887
|
+
}
|
|
29888
|
+
const path = saveJoinWhileLocked(join32, options);
|
|
29889
|
+
if (!isJoinPersisted(join32)) {
|
|
29890
|
+
throw new Error("Otium join credentials were not durably persisted");
|
|
29891
|
+
}
|
|
29892
|
+
if (!pending2)
|
|
29893
|
+
return path;
|
|
29894
|
+
unlinkSync19(pendingPath);
|
|
29895
|
+
const directoryFd = openSync4(dirname18(pendingPath), "r");
|
|
29896
|
+
try {
|
|
29897
|
+
fsyncSync3(directoryFd);
|
|
29898
|
+
} finally {
|
|
29899
|
+
closeSync4(directoryFd);
|
|
29900
|
+
}
|
|
29901
|
+
return path;
|
|
29902
|
+
});
|
|
29903
|
+
}
|
|
29904
|
+
var INFO;
|
|
29905
|
+
var init_enrollment = __esm(async () => {
|
|
29417
29906
|
await init_src();
|
|
29418
|
-
await
|
|
29907
|
+
await init_join();
|
|
29908
|
+
INFO = Buffer.from("otium-node-enrollment-v1", "utf8");
|
|
29419
29909
|
});
|
|
29420
29910
|
|
|
29421
29911
|
// ../../adapters/otium/src/turn-bridge.ts
|
|
@@ -29471,8 +29961,8 @@ function provisionMirrorTopic(hostCellId, payload) {
|
|
|
29471
29961
|
defaultModel: nextModel,
|
|
29472
29962
|
defaultEffort: EFFORT_LEVELS.includes(execution.effort) ? execution.effort : current3?.defaultEffort ?? "medium",
|
|
29473
29963
|
participants: [{ userId: payload.userId, role: "owner" }],
|
|
29474
|
-
isSubagent:
|
|
29475
|
-
visibility: "
|
|
29964
|
+
isSubagent: undefined,
|
|
29965
|
+
visibility: "visible",
|
|
29476
29966
|
accessMode: "shared",
|
|
29477
29967
|
...execution.description ? { description: execution.description } : {},
|
|
29478
29968
|
createdAt: current3?.createdAt ?? now,
|
|
@@ -29550,6 +30040,8 @@ function runPeerTurn(hubNode, hostCellId, payload, opts = {}) {
|
|
|
29550
30040
|
origin: "user",
|
|
29551
30041
|
requestId: payload.requestId,
|
|
29552
30042
|
injectAuthorId: payload.userId,
|
|
30043
|
+
injectSourceNode: hostCellId,
|
|
30044
|
+
...payload.sourceMessageId ? { injectSourceMessageId: payload.sourceMessageId } : {},
|
|
29553
30045
|
attachments: payload.attachments,
|
|
29554
30046
|
visualTools: true,
|
|
29555
30047
|
fileDeliveryTools: true,
|
|
@@ -29736,7 +30228,7 @@ async function handleProvision(req) {
|
|
|
29736
30228
|
});
|
|
29737
30229
|
if (!result.ok)
|
|
29738
30230
|
return jsonError2(result.error, result.status);
|
|
29739
|
-
logger.info({ hostTopicId, localTopicId: result.localTopicId, fromNode: peer.verified.fromNodeName }, "otium:
|
|
30231
|
+
logger.info({ hostTopicId, localTopicId: result.localTopicId, fromNode: peer.verified.fromNodeName }, "otium: mirror room provisioned");
|
|
29740
30232
|
return Response.json({ ok: true });
|
|
29741
30233
|
}
|
|
29742
30234
|
async function handleBind(req) {
|
|
@@ -29768,6 +30260,50 @@ async function handleBind(req) {
|
|
|
29768
30260
|
return jsonError2(result.error, result.status);
|
|
29769
30261
|
return Response.json({ ok: true, localTopicId: result.localTopicId, replaced: result.replaced });
|
|
29770
30262
|
}
|
|
30263
|
+
async function handleSharedTopicMessages(req) {
|
|
30264
|
+
const peer = await requirePeer(req);
|
|
30265
|
+
if (!peer.ok)
|
|
30266
|
+
return peer.response;
|
|
30267
|
+
const originError = requirePrimaryOrigin(peer);
|
|
30268
|
+
if (originError)
|
|
30269
|
+
return originError;
|
|
30270
|
+
const body = await readBody(req);
|
|
30271
|
+
if (!body)
|
|
30272
|
+
return jsonError2("invalid JSON body", 400);
|
|
30273
|
+
const protocolError = checkProtocol(body);
|
|
30274
|
+
if (protocolError)
|
|
30275
|
+
return protocolError;
|
|
30276
|
+
const localTopicId = str2(body, "localTopicId");
|
|
30277
|
+
const hostTopicId = str2(body, "hostTopicId");
|
|
30278
|
+
const messages = body.messages;
|
|
30279
|
+
if (!localTopicId || !hostTopicId || !Array.isArray(messages)) {
|
|
30280
|
+
return jsonError2("localTopicId, hostTopicId and messages are required", 400);
|
|
30281
|
+
}
|
|
30282
|
+
const session = getPeerSession(peer.verified.fromCellId, hostTopicId);
|
|
30283
|
+
if (!session || session.local_topic_id !== localTopicId) {
|
|
30284
|
+
return jsonError2("shared topic binding not found", 404);
|
|
30285
|
+
}
|
|
30286
|
+
const accepted = acceptSharedTopicMessages(messages, localTopicId, peer.verified.fromCellId);
|
|
30287
|
+
return Response.json({ ok: true, accepted });
|
|
30288
|
+
}
|
|
30289
|
+
async function handleSharedTopicsPrivate(req) {
|
|
30290
|
+
const peer = await requirePeer(req);
|
|
30291
|
+
if (!peer.ok)
|
|
30292
|
+
return peer.response;
|
|
30293
|
+
const originError = requirePrimaryOrigin(peer);
|
|
30294
|
+
if (originError)
|
|
30295
|
+
return originError;
|
|
30296
|
+
const body = await readBody(req);
|
|
30297
|
+
if (!body)
|
|
30298
|
+
return jsonError2("invalid JSON body", 400);
|
|
30299
|
+
const protocolError = checkProtocol(body);
|
|
30300
|
+
if (protocolError)
|
|
30301
|
+
return protocolError;
|
|
30302
|
+
if (body.reason !== "hub-removal")
|
|
30303
|
+
return jsonError2("reason must be hub-removal", 400);
|
|
30304
|
+
const updated = downgradeSharedTopicsForHub(peer.verified.fromCellId);
|
|
30305
|
+
return Response.json({ ok: true, updated });
|
|
30306
|
+
}
|
|
29771
30307
|
async function handleUnbind(req) {
|
|
29772
30308
|
const peer = await requirePeer(req);
|
|
29773
30309
|
if (!peer.ok)
|
|
@@ -29829,6 +30365,7 @@ async function handleTurn(req) {
|
|
|
29829
30365
|
...str2(body, "model") ? { model: str2(body, "model") } : {},
|
|
29830
30366
|
...str2(body, "effort") ? { effort: str2(body, "effort") } : {},
|
|
29831
30367
|
...Array.isArray(body.attachments) && body.attachments.every((entry) => typeof entry === "string") ? { attachments: body.attachments } : {},
|
|
30368
|
+
...str2(body, "sourceMessageId") ? { sourceMessageId: str2(body, "sourceMessageId") } : {},
|
|
29832
30369
|
message
|
|
29833
30370
|
};
|
|
29834
30371
|
const result = runPeerTurn(hubNode, peer.verified.fromCellId, payload);
|
|
@@ -30182,6 +30719,10 @@ async function handleOtiumPeerRequest(req) {
|
|
|
30182
30719
|
return handleProvision(req);
|
|
30183
30720
|
case "/api/v1/peer/bind":
|
|
30184
30721
|
return handleBind(req);
|
|
30722
|
+
case "/api/v1/peer/shared-topic/messages":
|
|
30723
|
+
return handleSharedTopicMessages(req);
|
|
30724
|
+
case "/api/v1/peer/shared-topics/private":
|
|
30725
|
+
return handleSharedTopicsPrivate(req);
|
|
30185
30726
|
case "/api/v1/peer/unbind":
|
|
30186
30727
|
return handleUnbind(req);
|
|
30187
30728
|
case "/api/v1/peer/turn":
|
|
@@ -30210,62 +30751,16 @@ var init_peer_server = __esm(async () => {
|
|
|
30210
30751
|
await init_bindings();
|
|
30211
30752
|
await init_central();
|
|
30212
30753
|
await init_peer_files();
|
|
30213
|
-
init_protocol();
|
|
30214
|
-
await init_session_bridge();
|
|
30215
|
-
await
|
|
30216
|
-
await
|
|
30217
|
-
|
|
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
|
|
30754
|
+
init_protocol();
|
|
30755
|
+
await init_session_bridge();
|
|
30756
|
+
await init_shared_topic_sync();
|
|
30757
|
+
await init_store2();
|
|
30758
|
+
await init_turn_bridge();
|
|
30759
|
+
RUNTIME_VERSION = NEGOTIUM_VERSION;
|
|
30267
30760
|
});
|
|
30268
|
-
|
|
30761
|
+
|
|
30762
|
+
// ../../adapters/otium/src/index.ts
|
|
30763
|
+
function startOtiumNodeRuntime(options) {
|
|
30269
30764
|
const { join: join32 } = options;
|
|
30270
30765
|
configureOtiumCentral(join32);
|
|
30271
30766
|
const failed = failInterruptedPeerTurnRequestsOnStartup();
|
|
@@ -30273,6 +30768,7 @@ function startOtiumAdapter(options) {
|
|
|
30273
30768
|
logger.warn({ failed }, "otium: failed interrupted peer turns from previous process");
|
|
30274
30769
|
}
|
|
30275
30770
|
const stopBackflow = startEventBackflow();
|
|
30771
|
+
const stopSharedTopicSync = startSharedTopicSync(join32);
|
|
30276
30772
|
const unregisterRuntimeBridge = registerPeerRuntimeBridge(otiumPeerRuntimeBridge);
|
|
30277
30773
|
const unregisterSessionBridge = registerPeerSessionBridge(otiumPeerSessionBridge);
|
|
30278
30774
|
const sessionBridgeIpc = startPeerSessionBridgeIpc(otiumPeerSessionBridge);
|
|
@@ -30293,7 +30789,6 @@ function startOtiumAdapter(options) {
|
|
|
30293
30789
|
}
|
|
30294
30790
|
});
|
|
30295
30791
|
let stopped = false;
|
|
30296
|
-
let tunnel = null;
|
|
30297
30792
|
logger.info({ central: join32.central, cellId: join32.cellId }, "otium: worker mode enabled");
|
|
30298
30793
|
selfPeerNode().then((self) => {
|
|
30299
30794
|
if (self) {
|
|
@@ -30305,17 +30800,41 @@ function startOtiumAdapter(options) {
|
|
|
30305
30800
|
return {
|
|
30306
30801
|
name: "otium",
|
|
30307
30802
|
join: join32,
|
|
30803
|
+
stop: () => {
|
|
30804
|
+
if (stopped)
|
|
30805
|
+
return;
|
|
30806
|
+
stopped = true;
|
|
30807
|
+
unsubscribeTopicCleanup();
|
|
30808
|
+
unregisterRuntimeBridge();
|
|
30809
|
+
unregisterSessionBridge();
|
|
30810
|
+
sessionBridgeIpc.stop();
|
|
30811
|
+
canonicalMcpBridge.stop();
|
|
30812
|
+
stopPeerReplyOutbox();
|
|
30813
|
+
uninstallFileHooks();
|
|
30814
|
+
stopBackflow();
|
|
30815
|
+
stopSharedTopicSync();
|
|
30816
|
+
configureOtiumCentral(null);
|
|
30817
|
+
}
|
|
30818
|
+
};
|
|
30819
|
+
}
|
|
30820
|
+
function startOtiumAdapter(options) {
|
|
30821
|
+
const runtime = startOtiumNodeRuntime(options);
|
|
30822
|
+
let tunnel = null;
|
|
30823
|
+
let stopped = false;
|
|
30824
|
+
return {
|
|
30825
|
+
name: "otium",
|
|
30826
|
+
join: runtime.join,
|
|
30308
30827
|
startTunnel: ({ targetOrigin, relayUrl }) => {
|
|
30309
30828
|
if (stopped || tunnel)
|
|
30310
30829
|
return;
|
|
30311
|
-
const selectedRelay = relayUrl?.trim() ||
|
|
30830
|
+
const selectedRelay = relayUrl?.trim() || runtime.join.relay || process.env.OTIUM_RELAY_URL?.trim();
|
|
30312
30831
|
if (!selectedRelay) {
|
|
30313
30832
|
logger.info({}, "otium: relay tunnel disabled (no relay URL configured)");
|
|
30314
30833
|
return;
|
|
30315
30834
|
}
|
|
30316
30835
|
tunnel = new TunnelClient({
|
|
30317
30836
|
relayUrl: selectedRelay,
|
|
30318
|
-
token:
|
|
30837
|
+
token: runtime.join.secret,
|
|
30319
30838
|
targetOrigin,
|
|
30320
30839
|
nodeVersion: `negotium@${NEGOTIUM_VERSION}`,
|
|
30321
30840
|
logger
|
|
@@ -30328,24 +30847,10 @@ function startOtiumAdapter(options) {
|
|
|
30328
30847
|
stopped = true;
|
|
30329
30848
|
tunnel?.stop();
|
|
30330
30849
|
tunnel = null;
|
|
30331
|
-
|
|
30332
|
-
unregisterRuntimeBridge();
|
|
30333
|
-
unregisterSessionBridge();
|
|
30334
|
-
sessionBridgeIpc.stop();
|
|
30335
|
-
canonicalMcpBridge.stop();
|
|
30336
|
-
stopPeerReplyOutbox();
|
|
30337
|
-
uninstallFileHooks();
|
|
30338
|
-
stopBackflow();
|
|
30339
|
-
configureOtiumCentral(null);
|
|
30850
|
+
runtime.stop();
|
|
30340
30851
|
}
|
|
30341
30852
|
};
|
|
30342
30853
|
}
|
|
30343
|
-
function startOtiumWorker() {
|
|
30344
|
-
const join32 = loadJoin();
|
|
30345
|
-
if (!join32)
|
|
30346
|
-
return null;
|
|
30347
|
-
return startOtiumAdapter({ join: join32 });
|
|
30348
|
-
}
|
|
30349
30854
|
var otiumAdapter;
|
|
30350
30855
|
var init_src8 = __esm(async () => {
|
|
30351
30856
|
await init_src();
|
|
@@ -30357,6 +30862,7 @@ var init_src8 = __esm(async () => {
|
|
|
30357
30862
|
await init_runtime_bridge();
|
|
30358
30863
|
await init_session_bridge();
|
|
30359
30864
|
init_session_bridge_ipc();
|
|
30865
|
+
await init_shared_topic_sync();
|
|
30360
30866
|
await init_store2();
|
|
30361
30867
|
init_tunnel_client();
|
|
30362
30868
|
await init_bindings();
|
|
@@ -30368,6 +30874,7 @@ var init_src8 = __esm(async () => {
|
|
|
30368
30874
|
init_protocol();
|
|
30369
30875
|
init_relay_protocol();
|
|
30370
30876
|
await init_runtime_bridge();
|
|
30877
|
+
await init_shared_topic_sync();
|
|
30371
30878
|
await init_store2();
|
|
30372
30879
|
init_tunnel_client();
|
|
30373
30880
|
await init_turn_bridge();
|
|
@@ -30387,6 +30894,245 @@ var init_src8 = __esm(async () => {
|
|
|
30387
30894
|
});
|
|
30388
30895
|
});
|
|
30389
30896
|
|
|
30897
|
+
// ../../adapters/otium/src/node-runtime.ts
|
|
30898
|
+
var exports_node_runtime = {};
|
|
30899
|
+
__export(exports_node_runtime, {
|
|
30900
|
+
mountConfiguredOtiumNodeRuntime: () => mountConfiguredOtiumNodeRuntime,
|
|
30901
|
+
handleOtiumAdapterControlRequest: () => handleOtiumAdapterControlRequest,
|
|
30902
|
+
OTIUM_ADAPTER_CONTROL_PREFIX: () => OTIUM_ADAPTER_CONTROL_PREFIX,
|
|
30903
|
+
OTIUM_ADAPTER_CONTROL_HEADER: () => OTIUM_ADAPTER_CONTROL_HEADER,
|
|
30904
|
+
MAX_PEER_INPUT_REQUEST_BYTES: () => MAX_PEER_INPUT_REQUEST_BYTES
|
|
30905
|
+
});
|
|
30906
|
+
async function handleOtiumAdapterControlRequest(req) {
|
|
30907
|
+
const url = new URL(req.url);
|
|
30908
|
+
if (!url.pathname.startsWith(`${OTIUM_ADAPTER_CONTROL_PREFIX}/`))
|
|
30909
|
+
return null;
|
|
30910
|
+
if (req.headers.get(OTIUM_ADAPTER_CONTROL_HEADER) !== NODE_CONTROL_TOKEN) {
|
|
30911
|
+
return Response.json({ ok: false, error: "Unauthorized" }, { status: 401 });
|
|
30912
|
+
}
|
|
30913
|
+
const peerPath = url.pathname.slice(OTIUM_ADAPTER_CONTROL_PREFIX.length) || "/";
|
|
30914
|
+
const peerUrl = new URL(req.url);
|
|
30915
|
+
peerUrl.pathname = peerPath;
|
|
30916
|
+
const headers = new Headers(req.headers);
|
|
30917
|
+
headers.delete(OTIUM_ADAPTER_CONTROL_HEADER);
|
|
30918
|
+
const body = req.method === "GET" || req.method === "HEAD" ? undefined : await req.arrayBuffer();
|
|
30919
|
+
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 });
|
|
30920
|
+
}
|
|
30921
|
+
function mountConfiguredOtiumNodeRuntime() {
|
|
30922
|
+
const join32 = loadJoin();
|
|
30923
|
+
if (!join32)
|
|
30924
|
+
return null;
|
|
30925
|
+
const runtime = startOtiumNodeRuntime({ join: join32 });
|
|
30926
|
+
registerNodeRequestHandler("otium-adapter-control", handleOtiumAdapterControlRequest);
|
|
30927
|
+
let stopped = false;
|
|
30928
|
+
return {
|
|
30929
|
+
...runtime,
|
|
30930
|
+
stop() {
|
|
30931
|
+
if (stopped)
|
|
30932
|
+
return;
|
|
30933
|
+
stopped = true;
|
|
30934
|
+
unregisterNodeRequestHandler("otium-adapter-control");
|
|
30935
|
+
runtime.stop();
|
|
30936
|
+
}
|
|
30937
|
+
};
|
|
30938
|
+
}
|
|
30939
|
+
var init_node_runtime = __esm(async () => {
|
|
30940
|
+
await init_src();
|
|
30941
|
+
await init_src8();
|
|
30942
|
+
await init_join();
|
|
30943
|
+
await init_peer_server();
|
|
30944
|
+
init_protocol();
|
|
30945
|
+
});
|
|
30946
|
+
|
|
30947
|
+
// ../../adapters/otium/src/join-cli.ts
|
|
30948
|
+
var exports_join_cli = {};
|
|
30949
|
+
__export(exports_join_cli, {
|
|
30950
|
+
joinCommand: () => joinCommand
|
|
30951
|
+
});
|
|
30952
|
+
function option2(args, name) {
|
|
30953
|
+
const index = args.indexOf(`--${name}`);
|
|
30954
|
+
return index >= 0 ? args[index + 1]?.trim() : undefined;
|
|
30955
|
+
}
|
|
30956
|
+
async function confirmEnrollment(message) {
|
|
30957
|
+
if (!process.stdin.isTTY)
|
|
30958
|
+
return false;
|
|
30959
|
+
const { createInterface } = await import("readline/promises");
|
|
30960
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
30961
|
+
try {
|
|
30962
|
+
return /^(?:y|yes)$/i.test((await prompt.question(`${message} [y/N] `)).trim());
|
|
30963
|
+
} finally {
|
|
30964
|
+
prompt.close();
|
|
30965
|
+
}
|
|
30966
|
+
}
|
|
30967
|
+
async function joinCommand(args) {
|
|
30968
|
+
const code = args[0]?.trim();
|
|
30969
|
+
if (!code) {
|
|
30970
|
+
console.error("usage: negotium-otium join <invite-code> [--yes] [--name <node-name>] [--legacy] [--replace]");
|
|
30971
|
+
process.exitCode = 1;
|
|
30972
|
+
return;
|
|
30973
|
+
}
|
|
30974
|
+
let join32;
|
|
30975
|
+
let productionEnrollment = false;
|
|
30976
|
+
if (args.includes("--legacy")) {
|
|
30977
|
+
try {
|
|
30978
|
+
join32 = parseInviteCode(code);
|
|
30979
|
+
} catch (err2) {
|
|
30980
|
+
console.error(`invalid legacy invite code: ${err2 instanceof Error ? err2.message : err2}`);
|
|
30981
|
+
process.exitCode = 1;
|
|
30982
|
+
return;
|
|
30983
|
+
}
|
|
30984
|
+
} else {
|
|
30985
|
+
try {
|
|
30986
|
+
const invite = parseEnrollmentInvite(code);
|
|
30987
|
+
const resuming = isEnrollmentPending(invite);
|
|
30988
|
+
let nodeName = option2(args, "name");
|
|
30989
|
+
if (resuming) {
|
|
30990
|
+
console.log(`Resuming interrupted Otium enrollment with ${invite.central}`);
|
|
30991
|
+
} else {
|
|
30992
|
+
const preview = await previewEnrollment(invite);
|
|
30993
|
+
const workspace = preview.preview?.workspace;
|
|
30994
|
+
nodeName ||= preview.preview?.suggestedNodeName || undefined;
|
|
30995
|
+
console.log(`Otium workspace: ${workspace?.name ?? workspace?.slug ?? workspace?.id}`);
|
|
30996
|
+
console.log(` central: ${invite.central}`);
|
|
30997
|
+
console.log(` transport: ${preview.preview?.transport ?? "relay"}`);
|
|
30998
|
+
console.log(` access: ${preview.preview?.topics ?? "explicitly shared topics only"}`);
|
|
30999
|
+
const accepted = args.includes("--yes") || await confirmEnrollment("Join this workspace?");
|
|
31000
|
+
if (!accepted) {
|
|
31001
|
+
console.log("enrollment cancelled");
|
|
31002
|
+
return;
|
|
31003
|
+
}
|
|
31004
|
+
}
|
|
31005
|
+
join32 = await claimEnrollment(invite, nodeName);
|
|
31006
|
+
productionEnrollment = true;
|
|
31007
|
+
} catch (err2) {
|
|
31008
|
+
console.error(`enrollment failed: ${err2 instanceof Error ? err2.message : err2}`);
|
|
31009
|
+
process.exitCode = 1;
|
|
31010
|
+
return;
|
|
31011
|
+
}
|
|
31012
|
+
}
|
|
31013
|
+
let path;
|
|
31014
|
+
try {
|
|
31015
|
+
const saveOptions = { replaceExisting: args.includes("--replace") };
|
|
31016
|
+
path = productionEnrollment ? commitEnrollment(join32, saveOptions) : saveJoin(join32, saveOptions);
|
|
31017
|
+
} catch (err2) {
|
|
31018
|
+
console.error(`could not save join credentials: ${err2 instanceof Error ? err2.message : err2}`);
|
|
31019
|
+
process.exitCode = 1;
|
|
31020
|
+
return;
|
|
31021
|
+
}
|
|
31022
|
+
console.log(`otium join credentials saved to ${path}`);
|
|
31023
|
+
console.log(` central: ${join32.central}`);
|
|
31024
|
+
console.log(` cellId: ${join32.cellId}`);
|
|
31025
|
+
configureOtiumCentral(join32);
|
|
31026
|
+
try {
|
|
31027
|
+
const self = await selfPeerNode();
|
|
31028
|
+
if (self) {
|
|
31029
|
+
console.log(`attached to workspace as "${self.nodeName ?? self.cellId}" (baseUrl ${self.baseUrl})`);
|
|
31030
|
+
} else {
|
|
31031
|
+
console.warn("warning: central answered but this cell has no visible assignment yet \u2014 check the workspace assignment");
|
|
31032
|
+
}
|
|
31033
|
+
} catch (err2) {
|
|
31034
|
+
console.warn(`warning: could not verify against central (${err2 instanceof Error ? err2.message : err2}) \u2014 credentials saved anyway`);
|
|
31035
|
+
} finally {
|
|
31036
|
+
configureOtiumCentral(null);
|
|
31037
|
+
}
|
|
31038
|
+
console.log("\nnext: `negotium-otium serve` (mounts the otium peer routes automatically)");
|
|
31039
|
+
}
|
|
31040
|
+
var init_join_cli = __esm(async () => {
|
|
31041
|
+
await init_central();
|
|
31042
|
+
await init_enrollment();
|
|
31043
|
+
await init_join();
|
|
31044
|
+
});
|
|
31045
|
+
|
|
31046
|
+
// ../../adapters/otium/src/sidecar.ts
|
|
31047
|
+
var exports_sidecar = {};
|
|
31048
|
+
__export(exports_sidecar, {
|
|
31049
|
+
runOtiumSidecar: () => runOtiumSidecar,
|
|
31050
|
+
proxyOtiumPeerRequest: () => proxyOtiumPeerRequest
|
|
31051
|
+
});
|
|
31052
|
+
async function proxyOtiumPeerRequest(req, dependencies = {}) {
|
|
31053
|
+
const inspectNode = dependencies.inspectNode ?? inspectNodeDaemon;
|
|
31054
|
+
const fetchRequest = dependencies.fetch ?? fetch;
|
|
31055
|
+
const status = await inspectNode();
|
|
31056
|
+
if (!status.running || !status.info) {
|
|
31057
|
+
return Response.json({ ok: false, error: "canonical Negotium node is unavailable" }, { status: 503 });
|
|
31058
|
+
}
|
|
31059
|
+
const source = new URL(req.url);
|
|
31060
|
+
const target = new URL(`http://127.0.0.1:${status.info.port}`);
|
|
31061
|
+
target.pathname = `${OTIUM_ADAPTER_CONTROL_PREFIX}${source.pathname}`;
|
|
31062
|
+
target.search = source.search;
|
|
31063
|
+
const headers = new Headers(req.headers);
|
|
31064
|
+
headers.set(OTIUM_ADAPTER_CONTROL_HEADER, NODE_CONTROL_TOKEN);
|
|
31065
|
+
try {
|
|
31066
|
+
const body = req.method === "GET" || req.method === "HEAD" ? undefined : await req.arrayBuffer();
|
|
31067
|
+
return await fetchRequest(new Request(target.toString(), { method: req.method, headers, body, signal: req.signal }));
|
|
31068
|
+
} catch (error) {
|
|
31069
|
+
logger.warn({ err: error }, "otium sidecar: canonical node request failed");
|
|
31070
|
+
return Response.json({ ok: false, error: "canonical Negotium node connection failed" }, { status: 503 });
|
|
31071
|
+
}
|
|
31072
|
+
}
|
|
31073
|
+
async function runOtiumSidecar(options) {
|
|
31074
|
+
const join32 = loadJoin();
|
|
31075
|
+
if (!join32) {
|
|
31076
|
+
throw new Error("not joined to an Otium workspace \u2014 run `negotium otium join <code>` first");
|
|
31077
|
+
}
|
|
31078
|
+
const initialNode = await inspectNodeDaemon();
|
|
31079
|
+
if (!initialNode.running) {
|
|
31080
|
+
throw new Error("canonical Negotium node is not running");
|
|
31081
|
+
}
|
|
31082
|
+
const ready = await proxyOtiumPeerRequest(new Request("http://127.0.0.1/ready"));
|
|
31083
|
+
if (!ready.ok) {
|
|
31084
|
+
throw new Error("canonical Negotium node has no Otium runtime; restart it after joining the workspace");
|
|
31085
|
+
}
|
|
31086
|
+
let server;
|
|
31087
|
+
const lease = await waitForRequiredRuntimeProcessLease("adapter:otium", {
|
|
31088
|
+
workloadName: "Otium adapter",
|
|
31089
|
+
onLost: () => {
|
|
31090
|
+
process.stderr.write(`negotium otium: singleton lease lost; shutting down
|
|
31091
|
+
`);
|
|
31092
|
+
runShutdown("test");
|
|
31093
|
+
}
|
|
31094
|
+
});
|
|
31095
|
+
try {
|
|
31096
|
+
server = Bun.serve({
|
|
31097
|
+
port: options.port,
|
|
31098
|
+
hostname: "127.0.0.1",
|
|
31099
|
+
idleTimeout: 240,
|
|
31100
|
+
maxRequestBodySize: MAX_PEER_INPUT_REQUEST_BYTES,
|
|
31101
|
+
fetch: (req) => proxyOtiumPeerRequest(req)
|
|
31102
|
+
});
|
|
31103
|
+
} catch (error) {
|
|
31104
|
+
lease.stop();
|
|
31105
|
+
throw error;
|
|
31106
|
+
}
|
|
31107
|
+
const selectedRelay = options.relayUrl?.trim() || join32.relay || process.env.OTIUM_RELAY_URL?.trim();
|
|
31108
|
+
const tunnel = selectedRelay ? new TunnelClient({
|
|
31109
|
+
relayUrl: selectedRelay,
|
|
31110
|
+
token: join32.secret,
|
|
31111
|
+
targetOrigin: `http://127.0.0.1:${server.port}`,
|
|
31112
|
+
nodeVersion: `negotium@${NEGOTIUM_VERSION}`,
|
|
31113
|
+
logger
|
|
31114
|
+
}) : null;
|
|
31115
|
+
tunnel?.start();
|
|
31116
|
+
let resolveCompleted;
|
|
31117
|
+
const completed = new Promise((resolve15) => {
|
|
31118
|
+
resolveCompleted = resolve15;
|
|
31119
|
+
});
|
|
31120
|
+
onShutdown("otium-sidecar-server", 130, () => server?.stop(true));
|
|
31121
|
+
onShutdown("otium-sidecar-tunnel", 120, () => tunnel?.stop());
|
|
31122
|
+
onShutdown("otium-sidecar-lease", 110, () => lease.stop());
|
|
31123
|
+
onShutdown("otium-sidecar-completed", -100, resolveCompleted);
|
|
31124
|
+
process.stdout.write(`negotium Otium adapter listening on 127.0.0.1:${server.port} (canonical node pid ${initialNode.info?.pid})
|
|
31125
|
+
`);
|
|
31126
|
+
await completed;
|
|
31127
|
+
}
|
|
31128
|
+
var init_sidecar = __esm(async () => {
|
|
31129
|
+
await init_src();
|
|
31130
|
+
await init_src5();
|
|
31131
|
+
await init_join();
|
|
31132
|
+
init_protocol();
|
|
31133
|
+
init_tunnel_client();
|
|
31134
|
+
});
|
|
31135
|
+
|
|
30390
31136
|
// ../../adapters/otium/src/cli.ts
|
|
30391
31137
|
var exports_cli3 = {};
|
|
30392
31138
|
__export(exports_cli3, {
|
|
@@ -30464,66 +31210,80 @@ async function resolveHostNodeId(explicit) {
|
|
|
30464
31210
|
configureOtiumCentral2(null);
|
|
30465
31211
|
}
|
|
30466
31212
|
}
|
|
31213
|
+
async function spawnCanonicalNode2() {
|
|
31214
|
+
const entry = process.argv[1];
|
|
31215
|
+
if (!entry)
|
|
31216
|
+
throw new Error("cannot locate the Negotium CLI entrypoint");
|
|
31217
|
+
const { LOG_DIR: LOG_DIR2 } = await init_src().then(() => exports_src);
|
|
31218
|
+
const child = Bun.spawn({
|
|
31219
|
+
cmd: [process.execPath, entry, "__node-daemon", "--port=0"],
|
|
31220
|
+
detached: true,
|
|
31221
|
+
env: { ...process.env, LOG_LEVEL: process.env.NEGOTIUM_NODE_LOG_LEVEL?.trim() || "info" },
|
|
31222
|
+
stdin: "ignore",
|
|
31223
|
+
stdout: "ignore",
|
|
31224
|
+
stderr: Bun.file(`${LOG_DIR2}/node-daemon.log`)
|
|
31225
|
+
});
|
|
31226
|
+
child.unref();
|
|
31227
|
+
}
|
|
31228
|
+
async function ensureCanonicalNode2() {
|
|
31229
|
+
const { inspectNodeDaemon: inspectNodeDaemon2, waitForNodeDaemon: waitForNodeDaemon2 } = await init_src5().then(() => exports_src3);
|
|
31230
|
+
const status = await inspectNodeDaemon2();
|
|
31231
|
+
if (status.running)
|
|
31232
|
+
return;
|
|
31233
|
+
await spawnCanonicalNode2();
|
|
31234
|
+
await waitForNodeDaemon2(15000);
|
|
31235
|
+
}
|
|
31236
|
+
async function runCanonicalNodeChild2() {
|
|
31237
|
+
const { onShutdown: onShutdown2 } = await init_src().then(() => exports_src);
|
|
31238
|
+
const { MAX_PEER_INPUT_REQUEST_BYTES: MAX_PEER_INPUT_REQUEST_BYTES2, mountConfiguredOtiumNodeRuntime: mountConfiguredOtiumNodeRuntime2 } = await init_node_runtime().then(() => exports_node_runtime);
|
|
31239
|
+
const { runNodeDaemon: runNodeDaemon2 } = await init_src5().then(() => exports_src3);
|
|
31240
|
+
const runtime = mountConfiguredOtiumNodeRuntime2();
|
|
31241
|
+
if (runtime)
|
|
31242
|
+
onShutdown2("otium-node-runtime", 125, () => runtime.stop());
|
|
31243
|
+
await runNodeDaemon2({ port: 0, maxRequestBodySize: MAX_PEER_INPUT_REQUEST_BYTES2 });
|
|
31244
|
+
}
|
|
30467
31245
|
async function runOtiumCli(args = process.argv.slice(2)) {
|
|
30468
31246
|
const [command, ...commandArgs] = args;
|
|
30469
31247
|
switch (command) {
|
|
31248
|
+
case "__node-daemon": {
|
|
31249
|
+
await runCanonicalNodeChild2();
|
|
31250
|
+
break;
|
|
31251
|
+
}
|
|
30470
31252
|
case "join": {
|
|
30471
31253
|
const { joinCommand: joinCommand2 } = await init_join_cli().then(() => exports_join_cli);
|
|
30472
31254
|
await joinCommand2(commandArgs);
|
|
30473
31255
|
break;
|
|
30474
31256
|
}
|
|
31257
|
+
case "leave":
|
|
31258
|
+
case "disconnect": {
|
|
31259
|
+
if (commandArgs.length > 0)
|
|
31260
|
+
throw new Error(`usage: negotium otium ${command}`);
|
|
31261
|
+
if (process.env.OTIUM_CENTRAL_URL || process.env.OTIUM_CELL_ID || process.env.OTIUM_CELL_SECRET) {
|
|
31262
|
+
throw new Error("Otium join is configured by environment; remove OTIUM_CENTRAL_URL, OTIUM_CELL_ID, and OTIUM_CELL_SECRET to disconnect");
|
|
31263
|
+
}
|
|
31264
|
+
const { configureOtiumCentral: configureOtiumCentral2 } = await init_central().then(() => exports_central);
|
|
31265
|
+
const { loadJoin: loadJoin2, removeJoin: removeJoin2 } = await init_join().then(() => exports_join);
|
|
31266
|
+
const { disconnectSharedTopics: disconnectSharedTopics2 } = await init_shared_topic_sync().then(() => exports_shared_topic_sync);
|
|
31267
|
+
const join32 = loadJoin2();
|
|
31268
|
+
if (!join32)
|
|
31269
|
+
throw new Error("not joined to an Otium workspace");
|
|
31270
|
+
configureOtiumCentral2(join32);
|
|
31271
|
+
try {
|
|
31272
|
+
await disconnectSharedTopics2(join32);
|
|
31273
|
+
removeJoin2();
|
|
31274
|
+
} finally {
|
|
31275
|
+
configureOtiumCentral2(null);
|
|
31276
|
+
}
|
|
31277
|
+
console.log("disconnected from Otium; local shared topics are now private");
|
|
31278
|
+
break;
|
|
31279
|
+
}
|
|
30475
31280
|
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
|
-
]);
|
|
31281
|
+
const { NEGOTIUM_PORT: NEGOTIUM_PORT2 } = await init_src().then(() => exports_src);
|
|
31282
|
+
const { runOtiumSidecar: runOtiumSidecar2 } = await init_sidecar().then(() => exports_sidecar);
|
|
30487
31283
|
const port = parseOtiumServePort(commandArgs, NEGOTIUM_PORT2);
|
|
30488
31284
|
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;
|
|
31285
|
+
await ensureCanonicalNode2();
|
|
31286
|
+
await runOtiumSidecar2({ port, relayUrl });
|
|
30527
31287
|
break;
|
|
30528
31288
|
}
|
|
30529
31289
|
case "bindings": {
|
|
@@ -30572,9 +31332,10 @@ async function runOtiumCli(args = process.argv.slice(2)) {
|
|
|
30572
31332
|
console.log([
|
|
30573
31333
|
"negotium otium \u2014 attach a Negotium node to an Otium workspace",
|
|
30574
31334
|
"",
|
|
30575
|
-
"usage: negotium otium <join|serve|bindings|share|private> [args]",
|
|
31335
|
+
"usage: negotium otium <join|leave|serve|bindings|share|private> [args]",
|
|
30576
31336
|
"",
|
|
30577
31337
|
" join <code> store credentials from an Otium invite code",
|
|
31338
|
+
" leave delete Hub copies, make local topics private, and remove credentials",
|
|
30578
31339
|
" serve [--port <port>] [--relay <url>]",
|
|
30579
31340
|
" run peer routes and an outbound relay tunnel",
|
|
30580
31341
|
" bindings list internal mirrors and shared topic bindings",
|
|
@@ -30590,7 +31351,6 @@ async function runOtiumCli(args = process.argv.slice(2)) {
|
|
|
30590
31351
|
}
|
|
30591
31352
|
}
|
|
30592
31353
|
var init_cli3 = __esm(() => {
|
|
30593
|
-
init_protocol();
|
|
30594
31354
|
if (false) {}
|
|
30595
31355
|
});
|
|
30596
31356
|
|
|
@@ -31304,6 +32064,36 @@ function numericOption(values, name, fallback) {
|
|
|
31304
32064
|
const parsed = Number.parseInt(values.find((value) => value.startsWith(prefix))?.slice(prefix.length) ?? "", 10);
|
|
31305
32065
|
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
31306
32066
|
}
|
|
32067
|
+
async function runCanonicalNode(port) {
|
|
32068
|
+
const { onShutdown: onShutdown2 } = await init_src().then(() => exports_src);
|
|
32069
|
+
const { MAX_PEER_INPUT_REQUEST_BYTES: MAX_PEER_INPUT_REQUEST_BYTES2, mountConfiguredOtiumNodeRuntime: mountConfiguredOtiumNodeRuntime2 } = await init_node_runtime().then(() => exports_node_runtime);
|
|
32070
|
+
const { startDefaultNode: startDefaultNode2 } = await init_src5().then(() => exports_src3);
|
|
32071
|
+
const otiumRuntime = mountConfiguredOtiumNodeRuntime2();
|
|
32072
|
+
if (otiumRuntime)
|
|
32073
|
+
onShutdown2("otium-node-runtime", 125, () => otiumRuntime.stop());
|
|
32074
|
+
const node = await startDefaultNode2({
|
|
32075
|
+
port,
|
|
32076
|
+
advertise: true,
|
|
32077
|
+
singleton: true,
|
|
32078
|
+
maxRequestBodySize: MAX_PEER_INPUT_REQUEST_BYTES2
|
|
32079
|
+
});
|
|
32080
|
+
console.log(`negotium node listening on 127.0.0.1:${node.port} (ctrl-c to stop)`);
|
|
32081
|
+
await node.completed;
|
|
32082
|
+
}
|
|
32083
|
+
async function stopAdapter(name) {
|
|
32084
|
+
const { getRuntimeProcessLease: getRuntimeProcessLease2 } = await init_src().then(() => exports_src);
|
|
32085
|
+
const lease = getRuntimeProcessLease2(`adapter:${name}`);
|
|
32086
|
+
if (!lease)
|
|
32087
|
+
return false;
|
|
32088
|
+
try {
|
|
32089
|
+
process.kill(lease.pid, "SIGTERM");
|
|
32090
|
+
return true;
|
|
32091
|
+
} catch (error) {
|
|
32092
|
+
if (error.code === "ESRCH")
|
|
32093
|
+
return false;
|
|
32094
|
+
throw error;
|
|
32095
|
+
}
|
|
32096
|
+
}
|
|
31307
32097
|
switch (command) {
|
|
31308
32098
|
case "init": {
|
|
31309
32099
|
const { initCommand: initCommand2 } = await init_init().then(() => exports_init);
|
|
@@ -31316,22 +32106,20 @@ switch (command) {
|
|
|
31316
32106
|
break;
|
|
31317
32107
|
}
|
|
31318
32108
|
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;
|
|
32109
|
+
if (args[0] === "otium") {
|
|
32110
|
+
const { runOtiumCli: runOtiumCli2 } = await loadOtiumCli();
|
|
32111
|
+
await runOtiumCli2(["serve", ...args.slice(1)]);
|
|
32112
|
+
break;
|
|
32113
|
+
}
|
|
32114
|
+
await runCanonicalNode(numericOption(args, "port", 7777));
|
|
31327
32115
|
break;
|
|
31328
32116
|
}
|
|
31329
32117
|
case "__node-daemon": {
|
|
31330
|
-
|
|
31331
|
-
await runNodeDaemon2({ port: numericOption(args, "port", 0) });
|
|
32118
|
+
await runCanonicalNode(numericOption(args, "port", 0));
|
|
31332
32119
|
break;
|
|
31333
32120
|
}
|
|
31334
32121
|
case "status": {
|
|
32122
|
+
const { listRuntimeProcessLeases: listRuntimeProcessLeases2 } = await init_src().then(() => exports_src);
|
|
31335
32123
|
const { inspectNodeDaemon: inspectNodeDaemon2 } = await init_src5().then(() => exports_src3);
|
|
31336
32124
|
const status = await inspectNodeDaemon2();
|
|
31337
32125
|
if (status.running && status.info) {
|
|
@@ -31342,10 +32130,33 @@ switch (command) {
|
|
|
31342
32130
|
} else {
|
|
31343
32131
|
console.log("negotium node is stopped");
|
|
31344
32132
|
}
|
|
32133
|
+
const adapters = listRuntimeProcessLeases2("adapter:");
|
|
32134
|
+
if (adapters.length === 0)
|
|
32135
|
+
console.log("adapters: none");
|
|
32136
|
+
else {
|
|
32137
|
+
for (const adapter of adapters) {
|
|
32138
|
+
console.log(`adapter ${adapter.role.slice("adapter:".length)} running (pid ${adapter.pid}, since ${new Date(adapter.startedAt).toISOString()})`);
|
|
32139
|
+
}
|
|
32140
|
+
}
|
|
31345
32141
|
break;
|
|
31346
32142
|
}
|
|
31347
32143
|
case "stop": {
|
|
31348
32144
|
const { stopNodeDaemon: stopNodeDaemon2 } = await init_src5().then(() => exports_src3);
|
|
32145
|
+
const target = args[0];
|
|
32146
|
+
if (target === "otium" || target === "telegram") {
|
|
32147
|
+
const stopped2 = await stopAdapter(target);
|
|
32148
|
+
console.log(stopped2 ? `${target} adapter shutdown requested` : `${target} adapter is not running`);
|
|
32149
|
+
break;
|
|
32150
|
+
}
|
|
32151
|
+
if (target && target !== "--all") {
|
|
32152
|
+
throw new Error("usage: negotium stop [otium|telegram|--all]");
|
|
32153
|
+
}
|
|
32154
|
+
if (target === "--all") {
|
|
32155
|
+
for (const name of ["otium", "telegram"]) {
|
|
32156
|
+
if (await stopAdapter(name))
|
|
32157
|
+
console.log(`${name} adapter shutdown requested`);
|
|
32158
|
+
}
|
|
32159
|
+
}
|
|
31349
32160
|
const stopped = await stopNodeDaemon2();
|
|
31350
32161
|
console.log(stopped ? "negotium node shutdown requested" : "negotium node is not running");
|
|
31351
32162
|
break;
|
|
@@ -31377,11 +32188,14 @@ switch (command) {
|
|
|
31377
32188
|
}
|
|
31378
32189
|
case "telegram": {
|
|
31379
32190
|
const { runTelegramCli: runTelegramCli2 } = await loadTelegramCli();
|
|
31380
|
-
await runTelegramCli2();
|
|
32191
|
+
await runTelegramCli2(args);
|
|
31381
32192
|
break;
|
|
31382
32193
|
}
|
|
31383
32194
|
case "otium": {
|
|
31384
32195
|
const { runOtiumCli: runOtiumCli2 } = await loadOtiumCli();
|
|
32196
|
+
if (args[0] === "serve") {
|
|
32197
|
+
process.stderr.write("warning: `negotium otium serve` is deprecated; use `negotium serve otium`\n");
|
|
32198
|
+
}
|
|
31385
32199
|
await runOtiumCli2(args);
|
|
31386
32200
|
break;
|
|
31387
32201
|
}
|
|
@@ -31400,9 +32214,10 @@ switch (command) {
|
|
|
31400
32214
|
" init bootstrap ~/.negotium and check agent auth",
|
|
31401
32215
|
" chat [topic] interactive chat (creates the topic if missing)",
|
|
31402
32216
|
" options: --agent=claude|codex|maestro",
|
|
31403
|
-
" serve foreground
|
|
31404
|
-
"
|
|
31405
|
-
"
|
|
32217
|
+
" serve foreground canonical node (default port 7777)",
|
|
32218
|
+
" serve otium ensure the canonical node and run the Otium sidecar",
|
|
32219
|
+
" status show the canonical node and adapter processes",
|
|
32220
|
+
" stop [otium|telegram|--all] stop the node, one adapter, or everything",
|
|
31406
32221
|
" topics list topics on this node",
|
|
31407
32222
|
" mcp list|add|remove|enable|disable manage node MCP manifest",
|
|
31408
32223
|
" vault list|set|get|del node secret store (encrypted at rest)",
|
|
@@ -31420,4 +32235,4 @@ switch (command) {
|
|
|
31420
32235
|
}
|
|
31421
32236
|
}
|
|
31422
32237
|
|
|
31423
|
-
//# debugId=
|
|
32238
|
+
//# debugId=17956C13E9ACAD6764756E2164756E21
|