negotium 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-helpers.js +21 -20
- package/dist/agent-helpers.js.map +6 -6
- package/dist/background-bash.js +2 -1
- package/dist/background-bash.js.map +3 -3
- package/dist/browser-runtime.js +2 -1
- package/dist/browser-runtime.js.map +3 -3
- package/dist/{chunk-knv045mn.js → chunk-dcfmsrnb.js} +2 -1
- package/dist/{chunk-knv045mn.js.map → chunk-dcfmsrnb.js.map} +3 -3
- package/dist/hosted-agent.js +3 -2
- package/dist/hosted-agent.js.map +4 -4
- package/dist/main.js +522 -269
- package/dist/main.js.map +11 -9
- package/dist/mcp-factories.js +21 -20
- package/dist/mcp-factories.js.map +6 -6
- package/dist/prompts.js +2 -1
- package/dist/prompts.js.map +3 -3
- package/dist/query-runtime.js +2 -1
- package/dist/query-runtime.js.map +3 -3
- package/dist/registry.js +9 -5
- package/dist/registry.js.map +4 -4
- package/dist/rollout.js +1 -1
- package/dist/runtime/src/agents/maestro-registry.ts +32 -2
- package/dist/runtime/src/agents/mcp-tools/inline-assets.ts +193 -0
- package/dist/runtime/src/agents/mcp-tools/publish-html.ts +183 -0
- package/dist/runtime/src/mcp-runtime-host.ts +1 -0
- package/dist/runtime/src/platform/config.ts +12 -0
- package/dist/runtime/src/runtime/turn-runner.ts +34 -11
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js +2 -1
- package/dist/runtime-helpers.js.map +3 -3
- package/dist/types/packages/core/src/platform/config.d.ts +7 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/dist/vault.js +2 -1
- package/dist/vault.js.map +3 -3
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -253,7 +253,7 @@ function resolveDefaultModel(agent, registryDefaultModel) {
|
|
|
253
253
|
function codexAuthFilePath() {
|
|
254
254
|
return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(process.env.CODEX_HOME || join(homedir(), ".codex"), "auth.json");
|
|
255
255
|
}
|
|
256
|
-
var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CRON_WORKSPACE_DIR, BROWSER_DIR, BROWSER_PROFILES_DIR, BINARIES_DIR, SECRETS_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", BROWSER_RS_VERSION = "v0.1.17", BROWSER_RS_MIN_SECURE_VERSION = "0.1.15", BROWSER_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, BROWSER_MCP_SSE_PROXY_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, BACKGROUND_BASH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, UPLOADS_DIR, VAULT_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5", MODEL_FABLE = "claude-fable-5", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, _envMaxTellDepth, MAX_TELL_DEPTH;
|
|
256
|
+
var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CRON_WORKSPACE_DIR, BROWSER_DIR, BROWSER_PROFILES_DIR, BINARIES_DIR, SECRETS_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", BROWSER_RS_VERSION = "v0.1.17", BROWSER_RS_MIN_SECURE_VERSION = "0.1.15", SNIPPETS_API_URL, BROWSER_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, BROWSER_MCP_SSE_PROXY_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, BACKGROUND_BASH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, UPLOADS_DIR, VAULT_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5", MODEL_FABLE = "claude-fable-5", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, _envMaxTellDepth, MAX_TELL_DEPTH;
|
|
257
257
|
var init_config = __esm(() => {
|
|
258
258
|
init_config_helpers();
|
|
259
259
|
init_logger();
|
|
@@ -274,6 +274,7 @@ var init_config = __esm(() => {
|
|
|
274
274
|
SESSION_WORKSPACE_DIR = resolve(STATE_DIR, "data", "sessions");
|
|
275
275
|
CLAUDE_EXECUTABLE_ENV = envText("NEGOTIUM_CLAUDE_EXECUTABLE");
|
|
276
276
|
CLAUDE_EXECUTABLE = CLAUDE_EXECUTABLE_ENV ? resolve(CLAUDE_EXECUTABLE_ENV) : undefined;
|
|
277
|
+
SNIPPETS_API_URL = (envText("NEGOTIUM_SNIPPETS_API_URL") ?? envText("SNIPPETS_API_URL") ?? "").replace(/\/+$/, "");
|
|
277
278
|
BROWSER_RS_BIN = resolveBrowserRsBin(envText("NEGOTIUM_BROWSER_RS_BIN"));
|
|
278
279
|
PLAYWRIGHT_MCP_BIN = resolveBrowserMcpBin(envText("NEGOTIUM_BROWSER_MCP_BIN"));
|
|
279
280
|
TSX_BIN = resolveDependencyBin("tsx");
|
|
@@ -1560,7 +1561,7 @@ var exports_version = {};
|
|
|
1560
1561
|
__export(exports_version, {
|
|
1561
1562
|
NEGOTIUM_VERSION: () => NEGOTIUM_VERSION
|
|
1562
1563
|
});
|
|
1563
|
-
var NEGOTIUM_VERSION = "0.2.
|
|
1564
|
+
var NEGOTIUM_VERSION = "0.2.10";
|
|
1564
1565
|
|
|
1565
1566
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
1566
1567
|
import { spawn } from "child_process";
|
|
@@ -3633,6 +3634,9 @@ function existingCreatedAt(path) {
|
|
|
3633
3634
|
return;
|
|
3634
3635
|
}
|
|
3635
3636
|
}
|
|
3637
|
+
function nonEmptyTurnText(text) {
|
|
3638
|
+
return text.trim().length > 0 ? text : BLANK_TURN_PLACEHOLDER;
|
|
3639
|
+
}
|
|
3636
3640
|
function writeRollout(options) {
|
|
3637
3641
|
const sessionId = options.reuseSessionId ?? randomUUID3();
|
|
3638
3642
|
assertUuidLike("sessionId", sessionId);
|
|
@@ -3640,8 +3644,8 @@ function writeRollout(options) {
|
|
|
3640
3644
|
const path = maestroSessionPath(sessionId);
|
|
3641
3645
|
mkdirSync6(maestroSessionsDir(), { recursive: true });
|
|
3642
3646
|
const messages = extractChatPairs(options.entries).flatMap((pair) => [
|
|
3643
|
-
{ role: "user", content: pair.userText },
|
|
3644
|
-
{ role: "assistant", content: pair.assistantText }
|
|
3647
|
+
{ role: "user", content: nonEmptyTurnText(pair.userText) },
|
|
3648
|
+
{ role: "assistant", content: nonEmptyTurnText(pair.assistantText) }
|
|
3645
3649
|
]);
|
|
3646
3650
|
const lines = [
|
|
3647
3651
|
{
|
|
@@ -3661,7 +3665,7 @@ function writeRollout(options) {
|
|
|
3661
3665
|
});
|
|
3662
3666
|
return { sessionId, rolloutPath: path };
|
|
3663
3667
|
}
|
|
3664
|
-
var ALIAS_MAP2, VALID_MODELS, VALID_EFFORTS3, maestroRegistry, maestroRegistryOperations;
|
|
3668
|
+
var ALIAS_MAP2, VALID_MODELS, VALID_EFFORTS3, maestroRegistry, BLANK_TURN_PLACEHOLDER = "(no text content in this turn)", maestroRegistryOperations;
|
|
3665
3669
|
var init_maestro_registry = __esm(() => {
|
|
3666
3670
|
init_shared();
|
|
3667
3671
|
init_logger();
|
|
@@ -18530,10 +18534,9 @@ function startAiTurn(params) {
|
|
|
18530
18534
|
].join(`
|
|
18531
18535
|
`);
|
|
18532
18536
|
}
|
|
18537
|
+
const turnReminders = [];
|
|
18533
18538
|
if (params.from === FROM_AUTO_CONTINUE) {
|
|
18534
|
-
|
|
18535
|
-
|
|
18536
|
-
<system-reminder>\uC774 \uD134\uC740 \uC124\uC815 \uC790\uB3D9 \uC870\uC815(effort/model/agent) \uD6C4 \uC790\uB3D9 \uC7AC\uAC1C\uB41C \uD134\uC774\uB2E4. effort/model/agent \uB09C\uC774\uB3C4 \uC7AC\uD3C9\uAC00 \uBC0F \uC124\uC815 \uBCC0\uACBD \uC5C6\uC774 \uC989\uC2DC \uC791\uC5C5\uC744 \uC2DC\uC791\uD560 \uAC83.</system-reminder>`;
|
|
18539
|
+
turnReminders.push("<system-reminder>\uC774 \uD134\uC740 \uC124\uC815 \uC790\uB3D9 \uC870\uC815(effort/model/agent) \uD6C4 \uC790\uB3D9 \uC7AC\uAC1C\uB41C \uD134\uC774\uB2E4. effort/model/agent \uB09C\uC774\uB3C4 \uC7AC\uD3C9\uAC00 \uBC0F \uC124\uC815 \uBCC0\uACBD \uC5C6\uC774 \uC989\uC2DC \uC791\uC5C5\uC744 \uC2DC\uC791\uD560 \uAC83.</system-reminder>");
|
|
18537
18540
|
}
|
|
18538
18541
|
if (!silent && !sessionRetried) {
|
|
18539
18542
|
const promptsToRecord = userConversationPromptsToRecord(renderedUserPrompts, loggedUserMessageCount, agentPrompt);
|
|
@@ -18616,26 +18619,24 @@ function startAiTurn(params) {
|
|
|
18616
18619
|
logger.warn({ topicId, err: err2 }, "ai: ensureBgBash failed \u2014 proceeding without background bash tools");
|
|
18617
18620
|
}
|
|
18618
18621
|
}
|
|
18619
|
-
let effectiveSystemPrompt = systemPrompt;
|
|
18620
18622
|
if (playwrightRequested && !browserProfileOwner) {
|
|
18621
|
-
|
|
18622
|
-
effectiveSystemPrompt = `${effectiveSystemPrompt}
|
|
18623
|
-
|
|
18624
|
-
${ownerOnlyNote}`;
|
|
18623
|
+
turnReminders.push("<system-reminder>Browser tools are unavailable in this turn because browser profiles are private to the topic owner. Do not attempt browser calls or ask to reuse the owner's login state.</system-reminder>");
|
|
18625
18624
|
}
|
|
18626
18625
|
if (consumePlaywrightUnavailable(userId, topic.title)) {
|
|
18627
|
-
|
|
18628
|
-
effectiveSystemPrompt = `${effectiveSystemPrompt}
|
|
18629
|
-
|
|
18630
|
-
${playwrightNote}`;
|
|
18626
|
+
turnReminders.push("<system-reminder>Playwright browser tools are UNAVAILABLE this turn. The `mcp__playwright__*` tools have been removed from this turn's catalog because the long-lived browser MCP could not be prepared. Do not attempt to call browser tools. If browser interaction is required, ask the user to retry shortly or use a non-browser alternative.</system-reminder>");
|
|
18631
18627
|
}
|
|
18628
|
+
const effectivePrompt = turnReminders.length > 0 ? `${agentPrompt}
|
|
18629
|
+
|
|
18630
|
+
${turnReminders.join(`
|
|
18631
|
+
|
|
18632
|
+
`)}` : agentPrompt;
|
|
18632
18633
|
try {
|
|
18633
18634
|
yield* runAgent({
|
|
18634
18635
|
agent: agentKind,
|
|
18635
|
-
prompt:
|
|
18636
|
+
prompt: effectivePrompt,
|
|
18636
18637
|
attachments: promptAttachments,
|
|
18637
18638
|
cwd: workspaceCwd,
|
|
18638
|
-
systemPrompt
|
|
18639
|
+
systemPrompt,
|
|
18639
18640
|
sessionId,
|
|
18640
18641
|
userId,
|
|
18641
18642
|
vaultUserId,
|
|
@@ -22366,6 +22367,253 @@ var init_node_host = __esm(async () => {
|
|
|
22366
22367
|
]);
|
|
22367
22368
|
});
|
|
22368
22369
|
|
|
22370
|
+
// ../../packages/core/src/agents/mcp-tools/inline-assets.ts
|
|
22371
|
+
import { existsSync as existsSync24, readFileSync as readFileSync20, statSync as statSync13 } from "fs";
|
|
22372
|
+
import { extname as extname2, isAbsolute as isAbsolute6, resolve as resolve16 } from "path";
|
|
22373
|
+
function isNonLocal(ref) {
|
|
22374
|
+
return /^(?:[a-z][a-z0-9+.-]*:|\/\/|#)/i.test(ref.trim());
|
|
22375
|
+
}
|
|
22376
|
+
function stripSuffix(ref) {
|
|
22377
|
+
return ref.replace(/[?#].*$/, "");
|
|
22378
|
+
}
|
|
22379
|
+
function inlineLocalAssets(html, options) {
|
|
22380
|
+
const { baseDir, maxAssetBytes, maxTotalBytes } = options;
|
|
22381
|
+
const inlined = [];
|
|
22382
|
+
const unresolved = [];
|
|
22383
|
+
const skipped = [];
|
|
22384
|
+
const cache = new Map;
|
|
22385
|
+
let budget = maxTotalBytes - Buffer.byteLength(html, "utf-8");
|
|
22386
|
+
function toDataUri(rawRef) {
|
|
22387
|
+
const ref = rawRef.trim();
|
|
22388
|
+
if (!ref || isNonLocal(ref))
|
|
22389
|
+
return null;
|
|
22390
|
+
const cached = cache.get(ref);
|
|
22391
|
+
if (cached !== undefined)
|
|
22392
|
+
return cached;
|
|
22393
|
+
const relPath = stripSuffix(ref);
|
|
22394
|
+
if (!relPath)
|
|
22395
|
+
return null;
|
|
22396
|
+
const abs = isAbsolute6(relPath) ? relPath : resolve16(baseDir, relPath);
|
|
22397
|
+
let bytes;
|
|
22398
|
+
try {
|
|
22399
|
+
if (!existsSync24(abs) || !statSync13(abs).isFile()) {
|
|
22400
|
+
unresolved.push(ref);
|
|
22401
|
+
cache.set(ref, null);
|
|
22402
|
+
return null;
|
|
22403
|
+
}
|
|
22404
|
+
bytes = statSync13(abs).size;
|
|
22405
|
+
} catch {
|
|
22406
|
+
unresolved.push(ref);
|
|
22407
|
+
cache.set(ref, null);
|
|
22408
|
+
return null;
|
|
22409
|
+
}
|
|
22410
|
+
if (bytes > maxAssetBytes) {
|
|
22411
|
+
skipped.push({ ref, bytes, reason: "asset-too-large" });
|
|
22412
|
+
cache.set(ref, null);
|
|
22413
|
+
return null;
|
|
22414
|
+
}
|
|
22415
|
+
const encodedCost = Math.ceil(bytes / 3) * 4;
|
|
22416
|
+
if (encodedCost > budget) {
|
|
22417
|
+
skipped.push({ ref, bytes, reason: "document-too-large" });
|
|
22418
|
+
cache.set(ref, null);
|
|
22419
|
+
return null;
|
|
22420
|
+
}
|
|
22421
|
+
let uri;
|
|
22422
|
+
try {
|
|
22423
|
+
const mime = MIME_BY_EXT[extname2(abs).toLowerCase()] ?? "application/octet-stream";
|
|
22424
|
+
uri = `data:${mime};base64,${readFileSync20(abs).toString("base64")}`;
|
|
22425
|
+
} catch {
|
|
22426
|
+
unresolved.push(ref);
|
|
22427
|
+
cache.set(ref, null);
|
|
22428
|
+
return null;
|
|
22429
|
+
}
|
|
22430
|
+
budget -= uri.length;
|
|
22431
|
+
inlined.push({ ref, bytes });
|
|
22432
|
+
cache.set(ref, uri);
|
|
22433
|
+
return uri;
|
|
22434
|
+
}
|
|
22435
|
+
function rewriteSrcset(value) {
|
|
22436
|
+
return value.split(",").map((candidate) => {
|
|
22437
|
+
const trimmed = candidate.trim();
|
|
22438
|
+
if (!trimmed)
|
|
22439
|
+
return candidate;
|
|
22440
|
+
const [url, ...descriptor] = trimmed.split(/\s+/);
|
|
22441
|
+
const uri = url ? toDataUri(url) : null;
|
|
22442
|
+
if (!uri)
|
|
22443
|
+
return candidate;
|
|
22444
|
+
return [uri, ...descriptor].join(" ");
|
|
22445
|
+
}).join(", ");
|
|
22446
|
+
}
|
|
22447
|
+
let out = html;
|
|
22448
|
+
out = out.replace(/(\b(?:src|poster)\s*=\s*)(["'])([^"']*)\2/gi, (match, prefix, quote, ref) => {
|
|
22449
|
+
const uri = toDataUri(ref);
|
|
22450
|
+
return uri ? `${prefix}${quote}${uri}${quote}` : match;
|
|
22451
|
+
});
|
|
22452
|
+
out = out.replace(/(\bsrcset\s*=\s*)(["'])([^"']*)\2/gi, (match, prefix, quote, value) => {
|
|
22453
|
+
const rewritten = rewriteSrcset(value);
|
|
22454
|
+
return rewritten === value ? match : `${prefix}${quote}${rewritten}${quote}`;
|
|
22455
|
+
});
|
|
22456
|
+
out = out.replace(/<link\b[^>]*>/gi, (tag) => tag.replace(/(\bhref\s*=\s*)(["'])([^"']*)\2/i, (match, prefix, quote, ref) => {
|
|
22457
|
+
const uri = toDataUri(ref);
|
|
22458
|
+
return uri ? `${prefix}${quote}${uri}${quote}` : match;
|
|
22459
|
+
}));
|
|
22460
|
+
out = out.replace(/url\(\s*(["']?)([^"')]+)\1\s*\)/gi, (match, quote, ref) => {
|
|
22461
|
+
const uri = toDataUri(ref);
|
|
22462
|
+
return uri ? `url(${quote}${uri}${quote})` : match;
|
|
22463
|
+
});
|
|
22464
|
+
return { html: out, inlined, unresolved, skipped };
|
|
22465
|
+
}
|
|
22466
|
+
var MIME_BY_EXT;
|
|
22467
|
+
var init_inline_assets = __esm(() => {
|
|
22468
|
+
MIME_BY_EXT = {
|
|
22469
|
+
".apng": "image/apng",
|
|
22470
|
+
".avif": "image/avif",
|
|
22471
|
+
".bmp": "image/bmp",
|
|
22472
|
+
".css": "text/css",
|
|
22473
|
+
".gif": "image/gif",
|
|
22474
|
+
".ico": "image/x-icon",
|
|
22475
|
+
".jpeg": "image/jpeg",
|
|
22476
|
+
".jpg": "image/jpeg",
|
|
22477
|
+
".mp3": "audio/mpeg",
|
|
22478
|
+
".mp4": "video/mp4",
|
|
22479
|
+
".otf": "font/otf",
|
|
22480
|
+
".png": "image/png",
|
|
22481
|
+
".svg": "image/svg+xml",
|
|
22482
|
+
".ttf": "font/ttf",
|
|
22483
|
+
".wav": "audio/wav",
|
|
22484
|
+
".webm": "video/webm",
|
|
22485
|
+
".webp": "image/webp",
|
|
22486
|
+
".woff": "font/woff",
|
|
22487
|
+
".woff2": "font/woff2"
|
|
22488
|
+
};
|
|
22489
|
+
});
|
|
22490
|
+
|
|
22491
|
+
// ../../packages/core/src/agents/mcp-tools/publish-html.ts
|
|
22492
|
+
import { z as z5 } from "zod";
|
|
22493
|
+
function formatPublishBytes(bytes) {
|
|
22494
|
+
if (bytes < 1024)
|
|
22495
|
+
return `${bytes}B`;
|
|
22496
|
+
if (bytes < 1024 * 1024)
|
|
22497
|
+
return `${(bytes / 1024).toFixed(0)}KB`;
|
|
22498
|
+
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
22499
|
+
}
|
|
22500
|
+
function parseSnippetId(input) {
|
|
22501
|
+
const trimmed = input.trim();
|
|
22502
|
+
const fromUrl = trimmed.match(/\/snippets\/([A-Za-z0-9-]+)/)?.[1];
|
|
22503
|
+
const id = fromUrl ?? trimmed;
|
|
22504
|
+
return /^[A-Za-z0-9-]+$/.test(id) ? id : null;
|
|
22505
|
+
}
|
|
22506
|
+
async function snippetRequest(baseUrl, path, init = {}) {
|
|
22507
|
+
try {
|
|
22508
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
22509
|
+
...init,
|
|
22510
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
22511
|
+
});
|
|
22512
|
+
const body = await response.json().catch(() => null);
|
|
22513
|
+
if (body)
|
|
22514
|
+
return body;
|
|
22515
|
+
return { ok: false, error: `snippet service returned ${response.status}` };
|
|
22516
|
+
} catch (error) {
|
|
22517
|
+
const message = error instanceof Error ? error.message : "snippet service unreachable";
|
|
22518
|
+
return { ok: false, error: message };
|
|
22519
|
+
}
|
|
22520
|
+
}
|
|
22521
|
+
function createPublishHtmlToolDefinitions(ctx, options = {}) {
|
|
22522
|
+
const apiUrl = (options.apiUrl ?? SNIPPETS_API_URL)?.replace(/\/+$/, "");
|
|
22523
|
+
if (!apiUrl)
|
|
22524
|
+
return [];
|
|
22525
|
+
const getCtx = typeof ctx === "function" ? ctx : () => ctx;
|
|
22526
|
+
return [
|
|
22527
|
+
{
|
|
22528
|
+
name: "publish_html",
|
|
22529
|
+
description: [
|
|
22530
|
+
"Publish an HTML document and return a public, shareable https link that",
|
|
22531
|
+
"stays live for 72 hours. Use this when the user should be able to open,",
|
|
22532
|
+
"keep, or send the result \u2014 a report, dashboard, or styled table \u2014 rather",
|
|
22533
|
+
"than only glance at it in the side panel.",
|
|
22534
|
+
"Pass a complete standalone document and inline the CSS and JS.",
|
|
22535
|
+
"For charts and diagrams prefer inline <svg>: it stays sharp at any zoom",
|
|
22536
|
+
"and is far smaller than an image file.",
|
|
22537
|
+
"Images may be remote URLs, data: URIs, or paths to local files you",
|
|
22538
|
+
"created \u2014 local files are embedded automatically, so the page keeps",
|
|
22539
|
+
"working after your workspace is gone."
|
|
22540
|
+
].join(" "),
|
|
22541
|
+
schema: {
|
|
22542
|
+
html: z5.string().describe("Complete standalone HTML document to publish. Inline the CSS and JS."),
|
|
22543
|
+
base_dir: z5.string().optional().describe("Directory that relative image paths resolve against. Defaults to the working directory.")
|
|
22544
|
+
},
|
|
22545
|
+
async handler(input) {
|
|
22546
|
+
const html = input?.html ?? "";
|
|
22547
|
+
if (!html.trim())
|
|
22548
|
+
return errorResult("html is empty \u2014 provide an HTML document.");
|
|
22549
|
+
const baseDir = input.base_dir?.trim() || getCtx().cwd || process.cwd();
|
|
22550
|
+
const report = inlineLocalAssets(html, {
|
|
22551
|
+
baseDir,
|
|
22552
|
+
maxAssetBytes: MAX_PUBLISHED_ASSET_BYTES,
|
|
22553
|
+
maxTotalBytes: MAX_PUBLISHED_BYTES
|
|
22554
|
+
});
|
|
22555
|
+
const bytes = Buffer.byteLength(report.html, "utf-8");
|
|
22556
|
+
if (bytes > MAX_PUBLISHED_BYTES) {
|
|
22557
|
+
return errorResult(`Document is ${formatPublishBytes(bytes)} after embedding images, over the ${formatPublishBytes(MAX_PUBLISHED_BYTES)} limit. Use fewer or smaller images, or switch charts to inline <svg>.`);
|
|
22558
|
+
}
|
|
22559
|
+
const result = await snippetRequest(apiUrl, "/snippets", {
|
|
22560
|
+
method: "POST",
|
|
22561
|
+
headers: { "content-type": "text/html; charset=utf-8" },
|
|
22562
|
+
body: report.html
|
|
22563
|
+
});
|
|
22564
|
+
if (!result.ok || !result.url) {
|
|
22565
|
+
return errorResult(result.error ?? "Failed to publish.");
|
|
22566
|
+
}
|
|
22567
|
+
const notes = [];
|
|
22568
|
+
if (report.inlined.length > 0) {
|
|
22569
|
+
const total = report.inlined.reduce((sum, a) => sum + a.bytes, 0);
|
|
22570
|
+
notes.push(`Embedded ${report.inlined.length} local file(s), ${formatPublishBytes(total)}.`);
|
|
22571
|
+
}
|
|
22572
|
+
if (report.unresolved.length > 0) {
|
|
22573
|
+
notes.push(`WARNING \u2014 these look like local files but were not found, so they will render broken: ${report.unresolved.join(", ")}. Check the paths (relative to ${baseDir}) and republish.`);
|
|
22574
|
+
}
|
|
22575
|
+
for (const s of report.skipped) {
|
|
22576
|
+
notes.push(s.reason === "asset-too-large" ? `WARNING \u2014 ${s.ref} is ${formatPublishBytes(s.bytes)}, over the ${formatPublishBytes(MAX_PUBLISHED_ASSET_BYTES)} per-file limit, and was left as a broken link.` : `WARNING \u2014 ${s.ref} was left out because the document hit the ${formatPublishBytes(MAX_PUBLISHED_BYTES)} limit.`);
|
|
22577
|
+
}
|
|
22578
|
+
const payload = {
|
|
22579
|
+
url: result.url,
|
|
22580
|
+
snippetId: result.snippetId,
|
|
22581
|
+
expiresAt: result.expiresAt,
|
|
22582
|
+
sizeBytes: bytes,
|
|
22583
|
+
...report.inlined.length > 0 ? { embeddedFiles: report.inlined.length } : {}
|
|
22584
|
+
};
|
|
22585
|
+
return textResult([JSON.stringify(payload, null, 2), ...notes].join(`
|
|
22586
|
+
|
|
22587
|
+
`));
|
|
22588
|
+
}
|
|
22589
|
+
},
|
|
22590
|
+
{
|
|
22591
|
+
name: "unpublish_html",
|
|
22592
|
+
description: "Delete a previously published HTML document before its 72-hour expiry. Accepts the URL returned by publish_html, or its id.",
|
|
22593
|
+
schema: {
|
|
22594
|
+
snippet: z5.string().describe("URL returned by publish_html, or the bare id.")
|
|
22595
|
+
},
|
|
22596
|
+
async handler(input) {
|
|
22597
|
+
const id = parseSnippetId(input?.snippet ?? "");
|
|
22598
|
+
if (!id)
|
|
22599
|
+
return errorResult("Could not read a published document id from that value.");
|
|
22600
|
+
const result = await snippetRequest(apiUrl, `/snippets/${id}`, { method: "DELETE" });
|
|
22601
|
+
if (!result.ok)
|
|
22602
|
+
return errorResult(result.error ?? "Failed to delete.");
|
|
22603
|
+
return textResult(`Published document ${id} deleted.`);
|
|
22604
|
+
}
|
|
22605
|
+
}
|
|
22606
|
+
];
|
|
22607
|
+
}
|
|
22608
|
+
var REQUEST_TIMEOUT_MS = 30000, MAX_PUBLISHED_BYTES, MAX_PUBLISHED_ASSET_BYTES;
|
|
22609
|
+
var init_publish_html = __esm(() => {
|
|
22610
|
+
init_common();
|
|
22611
|
+
init_inline_assets();
|
|
22612
|
+
init_config();
|
|
22613
|
+
MAX_PUBLISHED_BYTES = 10 * 1024 * 1024;
|
|
22614
|
+
MAX_PUBLISHED_ASSET_BYTES = 5 * 1024 * 1024;
|
|
22615
|
+
});
|
|
22616
|
+
|
|
22369
22617
|
// ../../packages/core/src/mcp-runtime-host.ts
|
|
22370
22618
|
var exports_mcp_runtime_host = {};
|
|
22371
22619
|
__export(exports_mcp_runtime_host, {
|
|
@@ -22397,6 +22645,7 @@ __export(exports_mcp_runtime_host, {
|
|
|
22397
22645
|
createSubagentManagementToolDefinitions: () => createSubagentManagementToolDefinitions,
|
|
22398
22646
|
createSpawnSubagentToolDefinition: () => createSpawnSubagentToolDefinition,
|
|
22399
22647
|
createSelfConfigToolDefinitions: () => createSelfConfigToolDefinitions,
|
|
22648
|
+
createPublishHtmlToolDefinitions: () => createPublishHtmlToolDefinitions,
|
|
22400
22649
|
createPrepareSubagentToolDefinition: () => createPrepareSubagentToolDefinition,
|
|
22401
22650
|
createAskUserToolDefinition: () => createAskUserToolDefinition,
|
|
22402
22651
|
canSpawnSubagentsFromTopic: () => canSpawnSubagentsFromTopic,
|
|
@@ -22420,6 +22669,7 @@ __export(exports_mcp_runtime_host, {
|
|
|
22420
22669
|
});
|
|
22421
22670
|
var init_mcp_runtime_host = __esm(async () => {
|
|
22422
22671
|
init_common();
|
|
22672
|
+
init_publish_html();
|
|
22423
22673
|
init_visuals2();
|
|
22424
22674
|
init_runtime_spec();
|
|
22425
22675
|
init_config();
|
|
@@ -22449,7 +22699,7 @@ var init_mcp_runtime_host = __esm(async () => {
|
|
|
22449
22699
|
});
|
|
22450
22700
|
|
|
22451
22701
|
// ../../packages/mcp/src/node-tools.ts
|
|
22452
|
-
import { z as
|
|
22702
|
+
import { z as z6 } from "zod";
|
|
22453
22703
|
function resolveTopicForUser(ctx, ref) {
|
|
22454
22704
|
const trimmed = ref.trim();
|
|
22455
22705
|
if (!trimmed)
|
|
@@ -22473,11 +22723,11 @@ function describeTopic(topic) {
|
|
|
22473
22723
|
}
|
|
22474
22724
|
function registerNodeTools(server, ctx) {
|
|
22475
22725
|
server.tool("register_topic", "Create a new topic (agent room) on this negotium node, owned by the calling user. " + "Returns the new topic's id, title, agent, and model. Use the session-comm tell_session " + "tool to hand the new topic work.", {
|
|
22476
|
-
title:
|
|
22477
|
-
agent:
|
|
22478
|
-
model:
|
|
22479
|
-
effort:
|
|
22480
|
-
description:
|
|
22726
|
+
title: z6.string().describe("Unique title for the new topic."),
|
|
22727
|
+
agent: z6.enum(["claude", "codex", "maestro"]).optional().describe("AI backend for the room. Defaults to maestro."),
|
|
22728
|
+
model: z6.string().optional().describe("Model override, must be valid for the agent."),
|
|
22729
|
+
effort: z6.enum(EFFORT_VALUES).optional().describe("Reasoning effort override for the room."),
|
|
22730
|
+
description: z6.string().optional().describe("Short description of the topic's purpose.")
|
|
22481
22731
|
}, async ({ title, agent, model, effort, description }) => {
|
|
22482
22732
|
try {
|
|
22483
22733
|
const topic = registerTopic({
|
|
@@ -22512,7 +22762,7 @@ function registerNodeTools(server, ctx) {
|
|
|
22512
22762
|
`));
|
|
22513
22763
|
});
|
|
22514
22764
|
server.tool("abort_topic", "Abort the running turn in another topic on this node. Fire-and-forget: also queues an abort " + "signal in the topic's inbox so a turn that has not started yet is cancelled too. Returns whether an active turn was aborted.", {
|
|
22515
|
-
topic:
|
|
22765
|
+
topic: z6.string().describe("Target topic title or id.")
|
|
22516
22766
|
}, async ({ topic }) => {
|
|
22517
22767
|
const resolved = resolveTopicForUser(ctx, topic);
|
|
22518
22768
|
if ("error" in resolved)
|
|
@@ -22537,7 +22787,7 @@ function registerNodeTools(server, ctx) {
|
|
|
22537
22787
|
return textResult(queued ? `No active turn in "${target.title}"; abort signal queued in its inbox.` : `No active turn in "${target.title}", and the abort signal could NOT be queued ` + `(its inbox is locked). The topic will run its queued work; retry shortly.`);
|
|
22538
22788
|
});
|
|
22539
22789
|
server.tool("restart_topic", "Reset another topic's AI context while preserving the topic and its visible message history. " + "The next message starts a fresh provider session. Only the topic owner can restart it.", {
|
|
22540
|
-
topic:
|
|
22790
|
+
topic: z6.string().describe("Target topic title or id.")
|
|
22541
22791
|
}, async ({ topic }) => {
|
|
22542
22792
|
const resolved = resolveTopicForUser(ctx, topic);
|
|
22543
22793
|
if ("error" in resolved)
|
|
@@ -22553,8 +22803,8 @@ function registerNodeTools(server, ctx) {
|
|
|
22553
22803
|
return result.isError ? errorResult(`Error: ${result.text}`) : textResult(result.text);
|
|
22554
22804
|
});
|
|
22555
22805
|
server.tool("delete_topic", "Delete a topic owned by the calling user after archiving its conversation history. Deletion is blocked " + "when the archive cannot be written; pass force: true only as an explicit escape hatch that accepts losing history.", {
|
|
22556
|
-
topic:
|
|
22557
|
-
force:
|
|
22806
|
+
topic: z6.string().describe("Target topic title or id."),
|
|
22807
|
+
force: z6.boolean().optional().describe("Delete even if archiving the conversation history fails. Default false.")
|
|
22558
22808
|
}, async ({ topic, force }) => {
|
|
22559
22809
|
const resolved = resolveTopicForUser(ctx, topic);
|
|
22560
22810
|
if ("error" in resolved)
|
|
@@ -22600,7 +22850,7 @@ __export(exports_task, {
|
|
|
22600
22850
|
createTaskMcpServer: () => createTaskMcpServer
|
|
22601
22851
|
});
|
|
22602
22852
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
22603
|
-
import { z as
|
|
22853
|
+
import { z as z7 } from "zod";
|
|
22604
22854
|
function createTaskMcpServer(context, host2 = defaultTaskMcpHost) {
|
|
22605
22855
|
const scopeKey = context.topic ? taskScopeKey({ topicId: context.topicId, session: context.topic }) : "";
|
|
22606
22856
|
const server = new McpServer({ name: "task", version: "1.0.0" });
|
|
@@ -22610,13 +22860,13 @@ function createTaskMcpServer(context, host2 = defaultTaskMcpHost) {
|
|
|
22610
22860
|
}
|
|
22611
22861
|
return null;
|
|
22612
22862
|
};
|
|
22613
|
-
const statusEnum =
|
|
22863
|
+
const statusEnum = z7.enum(TASK_STATUS_VALUES);
|
|
22614
22864
|
server.tool("task_create", "Add one or more tasks to this topic's shared Otium task list. Use this instead of provider-native todo/task tools.", {
|
|
22615
|
-
tasks:
|
|
22616
|
-
subject:
|
|
22617
|
-
active_form:
|
|
22618
|
-
blocked_by:
|
|
22619
|
-
owner:
|
|
22865
|
+
tasks: z7.array(z7.object({
|
|
22866
|
+
subject: z7.string().min(1).describe("Short imperative task description"),
|
|
22867
|
+
active_form: z7.string().optional().describe("Label to show while the task is in progress"),
|
|
22868
|
+
blocked_by: z7.array(z7.string()).optional().describe("Task ids blocking this task"),
|
|
22869
|
+
owner: z7.string().optional().describe("Owner/agent label for multi-agent work")
|
|
22620
22870
|
})).min(1)
|
|
22621
22871
|
}, async ({ tasks: inputs }) => {
|
|
22622
22872
|
const guard = requireContext();
|
|
@@ -22640,13 +22890,13 @@ ${renderTaskList(tasks2)}`);
|
|
|
22640
22890
|
}
|
|
22641
22891
|
});
|
|
22642
22892
|
server.tool("task_update", "Update task status/content in this topic's shared Otium task list. Batch related status changes in one call.", {
|
|
22643
|
-
updates:
|
|
22644
|
-
id:
|
|
22893
|
+
updates: z7.array(z7.object({
|
|
22894
|
+
id: z7.string().describe("Target task id"),
|
|
22645
22895
|
status: statusEnum.optional().describe("New status"),
|
|
22646
|
-
subject:
|
|
22647
|
-
active_form:
|
|
22648
|
-
blocked_by:
|
|
22649
|
-
owner:
|
|
22896
|
+
subject: z7.string().min(1).optional().describe("Replacement task description"),
|
|
22897
|
+
active_form: z7.string().optional().describe("Replacement in-progress label; empty string clears it"),
|
|
22898
|
+
blocked_by: z7.array(z7.string()).optional().describe("Replacement blocker id list"),
|
|
22899
|
+
owner: z7.string().optional().describe("Replacement owner; empty string clears it")
|
|
22650
22900
|
})).min(1)
|
|
22651
22901
|
}, async ({ updates }) => {
|
|
22652
22902
|
const guard = requireContext();
|
|
@@ -22680,7 +22930,7 @@ Missing ids ignored: ${missing.join(", ")}` : "";
|
|
|
22680
22930
|
return mcpError(`task_list failed: ${errMsg(error)}`);
|
|
22681
22931
|
}
|
|
22682
22932
|
});
|
|
22683
|
-
server.tool("task_get", "Read one task from this topic's shared Otium task list as JSON.", { id:
|
|
22933
|
+
server.tool("task_get", "Read one task from this topic's shared Otium task list as JSON.", { id: z7.string().describe("Task id") }, async ({ id }) => {
|
|
22684
22934
|
const guard = requireContext();
|
|
22685
22935
|
if (guard)
|
|
22686
22936
|
return guard;
|
|
@@ -22694,8 +22944,8 @@ Missing ids ignored: ${missing.join(", ")}` : "";
|
|
|
22694
22944
|
}
|
|
22695
22945
|
});
|
|
22696
22946
|
server.tool("task_delete", "Delete tasks from this topic's shared Otium task list. Use all=true only when starting a new plan.", {
|
|
22697
|
-
ids:
|
|
22698
|
-
all:
|
|
22947
|
+
ids: z7.array(z7.string()).optional().describe("Task ids to delete"),
|
|
22948
|
+
all: z7.boolean().optional().describe("Delete the whole list")
|
|
22699
22949
|
}, async ({ ids, all }) => {
|
|
22700
22950
|
const guard = requireContext();
|
|
22701
22951
|
if (guard)
|
|
@@ -22732,7 +22982,7 @@ __export(exports_token_stats2, {
|
|
|
22732
22982
|
createTokenStatsMcpServer: () => createTokenStatsMcpServer
|
|
22733
22983
|
});
|
|
22734
22984
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
22735
|
-
import { z as
|
|
22985
|
+
import { z as z8 } from "zod";
|
|
22736
22986
|
function createTokenStatsMcpServer(context, host2 = defaultTokenStatsMcpHost) {
|
|
22737
22987
|
if (!context.userId)
|
|
22738
22988
|
throw new Error("token-stats MCP requires userId");
|
|
@@ -22748,9 +22998,9 @@ function createTokenStatsMcpServer(context, host2 = defaultTokenStatsMcpHost) {
|
|
|
22748
22998
|
"groupBy: 'session'\uC73C\uB85C \uC124\uC815\uD558\uBA74 \uC138\uC158\uBCC4 \uD1A0\uD070 \uC0AC\uC6A9\uB7C9 \uB7AD\uD0B9\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4."
|
|
22749
22999
|
].join(`
|
|
22750
23000
|
`), {
|
|
22751
|
-
from:
|
|
22752
|
-
to:
|
|
22753
|
-
groupBy:
|
|
23001
|
+
from: z8.string().optional().describe("\uC2DC\uC791 \uC2DC\uAC01 ISO 8601. \uC608: '2026-03-28T09:00:00Z'"),
|
|
23002
|
+
to: z8.string().optional().describe("\uC885\uB8CC \uC2DC\uAC01 ISO 8601. \uC0DD\uB7B5 \uC2DC \uD604\uC7AC"),
|
|
23003
|
+
groupBy: z8.enum(["hour", "session"]).optional().describe("\uC9D1\uACC4 \uAE30\uC900. \uAE30\uBCF8\uAC12: hour")
|
|
22754
23004
|
}, async ({ from, to, groupBy = "hour" }) => {
|
|
22755
23005
|
const { total, byHour, bySession, estimatedCostUsd } = host2.getStats(context.userId, from, to);
|
|
22756
23006
|
const lines = [
|
|
@@ -22957,10 +23207,10 @@ __export(exports_agent_health, {
|
|
|
22957
23207
|
createAgentHealthMcpServer: () => createAgentHealthMcpServer
|
|
22958
23208
|
});
|
|
22959
23209
|
import { spawn as spawn6 } from "child_process";
|
|
22960
|
-
import { existsSync as
|
|
23210
|
+
import { existsSync as existsSync25, readdirSync as readdirSync8 } from "fs";
|
|
22961
23211
|
import { join as join33 } from "path";
|
|
22962
23212
|
import { McpServer as McpServer4 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
22963
|
-
import { z as
|
|
23213
|
+
import { z as z9 } from "zod";
|
|
22964
23214
|
function signalProcessTree2(child, signal) {
|
|
22965
23215
|
try {
|
|
22966
23216
|
if (child.pid && process.platform !== "win32")
|
|
@@ -22974,7 +23224,7 @@ function signalProcessTree2(child, signal) {
|
|
|
22974
23224
|
}
|
|
22975
23225
|
}
|
|
22976
23226
|
function spawnCapture(cmd, args, timeoutMs, signal, env) {
|
|
22977
|
-
return new Promise((
|
|
23227
|
+
return new Promise((resolve17, reject) => {
|
|
22978
23228
|
const child = spawn6(cmd, args, {
|
|
22979
23229
|
env: env ?? process.env,
|
|
22980
23230
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -23032,7 +23282,7 @@ function spawnCapture(cmd, args, timeoutMs, signal, env) {
|
|
|
23032
23282
|
if (killTimer)
|
|
23033
23283
|
clearTimeout(killTimer);
|
|
23034
23284
|
if (code === 0)
|
|
23035
|
-
|
|
23285
|
+
resolve17({ stdout, stderr });
|
|
23036
23286
|
else
|
|
23037
23287
|
reject(new Error(stderr.trim() || `${cmd} exited with ${code ?? exitSignal}`));
|
|
23038
23288
|
});
|
|
@@ -23110,11 +23360,11 @@ async function checkMaestro(model, timeoutMs, signal) {
|
|
|
23110
23360
|
const remaining = deadline - Date.now();
|
|
23111
23361
|
if (remaining <= 0)
|
|
23112
23362
|
throw new Error("timeout");
|
|
23113
|
-
const eventResult = await new Promise((
|
|
23363
|
+
const eventResult = await new Promise((resolve17, reject) => {
|
|
23114
23364
|
const timeout = setTimeout(() => reject(new Error("timeout")), remaining);
|
|
23115
23365
|
const onAbort = () => reject(new Error("aborted"));
|
|
23116
23366
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
23117
|
-
activeIterator.next().then(
|
|
23367
|
+
activeIterator.next().then(resolve17, reject).finally(() => {
|
|
23118
23368
|
clearTimeout(timeout);
|
|
23119
23369
|
signal.removeEventListener("abort", onAbort);
|
|
23120
23370
|
});
|
|
@@ -23144,7 +23394,7 @@ function getDefaultModel(registry) {
|
|
|
23144
23394
|
}
|
|
23145
23395
|
async function withCheckSlot(operation) {
|
|
23146
23396
|
if (activeChecks >= MAX_CONCURRENT_CHECKS) {
|
|
23147
|
-
await new Promise((
|
|
23397
|
+
await new Promise((resolve17) => checkWaiters.push(resolve17));
|
|
23148
23398
|
}
|
|
23149
23399
|
activeChecks += 1;
|
|
23150
23400
|
try {
|
|
@@ -23163,9 +23413,9 @@ function createAgentHealthMcpServer(context) {
|
|
|
23163
23413
|
await originalClose();
|
|
23164
23414
|
};
|
|
23165
23415
|
server.tool("check_agent", "\uC2E4\uC81C API \uD638\uCD9C\uB85C \uD2B9\uC815 agent+model \uC870\uD569\uC774 \uC815\uC0C1 \uB3D9\uC791\uD558\uB294\uC9C0 \uCCB4\uD06C\uD569\uB2C8\uB2E4.", {
|
|
23166
|
-
agent:
|
|
23167
|
-
model:
|
|
23168
|
-
timeoutMs:
|
|
23416
|
+
agent: z9.enum(["claude", "codex", "maestro"]).describe("\uCCB4\uD06C\uD560 agent"),
|
|
23417
|
+
model: z9.string().optional().describe("\uCCB4\uD06C\uD560 \uBAA8\uB378 (\uC5C6\uC73C\uBA74 agent\uBCC4 default \uC0AC\uC6A9)"),
|
|
23418
|
+
timeoutMs: z9.number().default(30000).describe("\uD0C0\uC784\uC544\uC6C3 (ms), \uAE30\uBCF8 30\uCD08")
|
|
23169
23419
|
}, async ({ agent, model, timeoutMs }) => {
|
|
23170
23420
|
const entry = AGENTS.find((candidate) => candidate.kind === agent);
|
|
23171
23421
|
if (!entry)
|
|
@@ -23181,7 +23431,7 @@ function createAgentHealthMcpServer(context) {
|
|
|
23181
23431
|
latency: ${result.latency}ms
|
|
23182
23432
|
error: ${result.error}`);
|
|
23183
23433
|
});
|
|
23184
|
-
server.tool("check_all", "\uBAA8\uB4E0 agent (claude, codex, maestro)\uB97C \uAE30\uBCF8 \uBAA8\uB378\uB85C \uB3D9\uC2DC\uC5D0 \uCCB4\uD06C\uD569\uB2C8\uB2E4.", { timeoutMs:
|
|
23434
|
+
server.tool("check_all", "\uBAA8\uB4E0 agent (claude, codex, maestro)\uB97C \uAE30\uBCF8 \uBAA8\uB378\uB85C \uB3D9\uC2DC\uC5D0 \uCCB4\uD06C\uD569\uB2C8\uB2E4.", { timeoutMs: z9.number().default(30000).describe("agent\uBCC4 \uD0C0\uC784\uC544\uC6C3 (ms), \uAE30\uBCF8 30\uCD08") }, async ({ timeoutMs }) => {
|
|
23185
23435
|
const userKey = context.userId ?? "unscoped";
|
|
23186
23436
|
if (activeCheckAllUsers.has(userKey)) {
|
|
23187
23437
|
return mcpOk("FAIL check_all is already running for this user");
|
|
@@ -23203,7 +23453,7 @@ ${results.every((result) => result.ok) ? "All agents healthy" : "Some agents fai
|
|
|
23203
23453
|
if (!context.userId)
|
|
23204
23454
|
return mcpOk("\uC2E4\uD589 \uC911\uC778 \uCFFC\uB9AC \uC870\uD68C \uBD88\uAC00 (user context \uC5C6\uC74C)");
|
|
23205
23455
|
const stateDir = join33(USERS_LOG_DIR, context.userId, "active-queries");
|
|
23206
|
-
if (!
|
|
23456
|
+
if (!existsSync25(stateDir))
|
|
23207
23457
|
return mcpOk("\uC2E4\uD589 \uC911\uC778 \uCFFC\uB9AC \uC5C6\uC74C");
|
|
23208
23458
|
let files;
|
|
23209
23459
|
try {
|
|
@@ -23294,7 +23544,7 @@ __export(exports_session_comm, {
|
|
|
23294
23544
|
createSessionCommMcpServer: () => createSessionCommMcpServer
|
|
23295
23545
|
});
|
|
23296
23546
|
import { McpServer as McpServer5 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
23297
|
-
import { z as
|
|
23547
|
+
import { z as z10 } from "zod";
|
|
23298
23548
|
function createSessionCommMcpServer(context, host2, options = {}) {
|
|
23299
23549
|
const server = new McpServer5({ name: "session-comm", version: "2.0.0" });
|
|
23300
23550
|
const required = options.requiredMcpServers?.join(", ") || "none";
|
|
@@ -23302,28 +23552,28 @@ function createSessionCommMcpServer(context, host2, options = {}) {
|
|
|
23302
23552
|
const subagentIdentity = resolveSubagentTellIdentity(context.currentTopicId, context.subagentParentTopicId);
|
|
23303
23553
|
server.tool("list_sessions", "List available sessions and remote peer sessions for inter-session communication.", {}, async () => host2.listSessions(context));
|
|
23304
23554
|
server.tool("configure_mcp", `Configure optional MCP servers for the current topic. Required: ${required}. Optional: ${optional}.`, {
|
|
23305
|
-
enabled:
|
|
23555
|
+
enabled: z10.array(z10.string()).nullable().optional()
|
|
23306
23556
|
}, async ({ enabled }) => host2.configureMcp(context, enabled));
|
|
23307
23557
|
server.tool("get_mcp_config", "Get the current topic MCP configuration.", {}, async () => host2.getMcpConfig(context));
|
|
23308
23558
|
if (host2.getBrowserProfile) {
|
|
23309
23559
|
server.tool("get_browser_profile", "Get this topic's browser profile and the profiles owned by the same user.", {}, async () => host2.getBrowserProfile(context));
|
|
23310
23560
|
}
|
|
23311
23561
|
if (host2.setBrowserProfile) {
|
|
23312
|
-
server.tool("set_browser_profile", "Assign this topic to a named shared browser profile. Takes effect next turn.", { profile:
|
|
23562
|
+
server.tool("set_browser_profile", "Assign this topic to a named shared browser profile. Takes effect next turn.", { profile: z10.string() }, async ({ profile }) => host2.setBrowserProfile(context, profile));
|
|
23313
23563
|
}
|
|
23314
23564
|
if (host2.deleteBrowserProfile) {
|
|
23315
|
-
server.tool("delete_browser_profile", "Delete an unused named browser profile and its on-disk data. The default or an assigned profile cannot be deleted.", { profile:
|
|
23565
|
+
server.tool("delete_browser_profile", "Delete an unused named browser profile and its on-disk data. The default or an assigned profile cannot be deleted.", { profile: z10.string() }, async ({ profile }) => host2.deleteBrowserProfile(context, profile));
|
|
23316
23566
|
}
|
|
23317
23567
|
server.tool("peek_session", "Inspect running and idle sessions, including pending ask_session calls.", {}, async () => host2.peekSession(context));
|
|
23318
|
-
server.tool("set_description", "Set the current session description used as a routing hint.", { description:
|
|
23568
|
+
server.tool("set_description", "Set the current session description used as a routing hint.", { description: z10.string() }, async ({ description }) => host2.setDescription(context, description));
|
|
23319
23569
|
if (!context.replyOnly) {
|
|
23320
23570
|
if (!subagentIdentity.restricted) {
|
|
23321
|
-
server.tool("ask_session", "Ask another local or remote session a question and wait for its answer.", { to:
|
|
23571
|
+
server.tool("ask_session", "Ask another local or remote session a question and wait for its answer.", { to: z10.string(), message: z10.string() }, async (input) => host2.askSession(context, input));
|
|
23322
23572
|
}
|
|
23323
23573
|
if (!subagentIdentity.restricted) {
|
|
23324
|
-
server.tool("abort_session", "Abort the active query in another session.", { to:
|
|
23574
|
+
server.tool("abort_session", "Abort the active query in another session.", { to: z10.string() }, async ({ to }) => host2.abortSession(context, to));
|
|
23325
23575
|
}
|
|
23326
|
-
server.tool("tell_session", "Send a one-way message to another local or remote session.", { to:
|
|
23576
|
+
server.tool("tell_session", "Send a one-way message to another local or remote session.", { to: z10.string(), message: z10.string() }, async (input) => {
|
|
23327
23577
|
if (subagentIdentity.restricted && !canSubagentTellTargetByName(subagentIdentity, input.to)) {
|
|
23328
23578
|
return {
|
|
23329
23579
|
content: [
|
|
@@ -23998,7 +24248,7 @@ async function executeVaultRun(userId, request, host2) {
|
|
|
23998
24248
|
const stdoutState = { bytes: 0, truncated: false };
|
|
23999
24249
|
const stderrState = { bytes: 0, truncated: false };
|
|
24000
24250
|
const startedAt = Date.now();
|
|
24001
|
-
return await new Promise((
|
|
24251
|
+
return await new Promise((resolve17) => {
|
|
24002
24252
|
const child = spawn7(process.env.SHELL || "/bin/sh", ["-s"], {
|
|
24003
24253
|
cwd: request.cwd,
|
|
24004
24254
|
detached: true,
|
|
@@ -24031,7 +24281,7 @@ async function executeVaultRun(userId, request, host2) {
|
|
|
24031
24281
|
durationMs: Date.now() - startedAt,
|
|
24032
24282
|
timedOut
|
|
24033
24283
|
}, "vault credential command used");
|
|
24034
|
-
|
|
24284
|
+
resolve17({ ...result, usedKeys: substitution.usedKeys });
|
|
24035
24285
|
};
|
|
24036
24286
|
child.stdout.on("data", (chunk) => appendBounded(stdoutChunks, chunk, stdoutState, maxOutputBytes));
|
|
24037
24287
|
child.stderr.on("data", (chunk) => appendBounded(stderrChunks, chunk, stderrState, maxOutputBytes));
|
|
@@ -24075,7 +24325,7 @@ __export(exports_vault, {
|
|
|
24075
24325
|
createVaultMcpServer: () => createVaultMcpServer
|
|
24076
24326
|
});
|
|
24077
24327
|
import { McpServer as McpServer6 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24078
|
-
import { z as
|
|
24328
|
+
import { z as z11 } from "zod";
|
|
24079
24329
|
function createVaultMcpServer(context, host2, executors = {}) {
|
|
24080
24330
|
const server = new McpServer6({ name: "vault", version: "1.0.0" });
|
|
24081
24331
|
const run = executors.run ?? executeVaultRun;
|
|
@@ -24093,9 +24343,9 @@ ${lines.join(`
|
|
|
24093
24343
|
});
|
|
24094
24344
|
if (!context.listOnly && !context.httpOnly) {
|
|
24095
24345
|
server.tool("vault_run", "Run a shell command containing {{KEY}} references inside Otium's credential broker. Expanded command input never reaches the model/provider, and stdout/stderr are redacted before return. Prefer vault_http_request for HTTP APIs.", {
|
|
24096
|
-
command:
|
|
24097
|
-
timeout_ms:
|
|
24098
|
-
max_output_bytes:
|
|
24346
|
+
command: z11.string().min(1).max(64 * 1024),
|
|
24347
|
+
timeout_ms: z11.number().int().min(1000).max(600000).optional(),
|
|
24348
|
+
max_output_bytes: z11.number().int().min(1024).max(2 * 1024 * 1024).optional()
|
|
24099
24349
|
}, async ({ command, timeout_ms, max_output_bytes }) => {
|
|
24100
24350
|
if (!context.userId)
|
|
24101
24351
|
return mcpError("Vault unavailable: no user context");
|
|
@@ -24110,12 +24360,12 @@ ${lines.join(`
|
|
|
24110
24360
|
}
|
|
24111
24361
|
if (!context.listOnly)
|
|
24112
24362
|
server.tool("vault_http_request", "Make an HTTPS request with {{KEY}} references resolved inside Otium. Put secrets in headers or body, never in the URL. The expanded request is not returned; the response is redacted before the model sees it.", {
|
|
24113
|
-
method:
|
|
24114
|
-
url:
|
|
24115
|
-
headers:
|
|
24116
|
-
body:
|
|
24117
|
-
timeout_ms:
|
|
24118
|
-
max_response_bytes:
|
|
24363
|
+
method: z11.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).default("GET"),
|
|
24364
|
+
url: z11.string().url().describe("Absolute HTTPS URL without Vault placeholders"),
|
|
24365
|
+
headers: z11.record(z11.string(), z11.string()).optional(),
|
|
24366
|
+
body: z11.string().optional(),
|
|
24367
|
+
timeout_ms: z11.number().int().min(1000).max(120000).optional(),
|
|
24368
|
+
max_response_bytes: z11.number().int().min(1024).max(1024 * 1024).optional()
|
|
24119
24369
|
}, async ({ method, url, headers, body, timeout_ms, max_response_bytes }) => {
|
|
24120
24370
|
if (!context.userId)
|
|
24121
24371
|
return mcpError("Vault unavailable: no user context");
|
|
@@ -24147,17 +24397,17 @@ import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
|
|
|
24147
24397
|
import { randomUUID as randomUUID21 } from "crypto";
|
|
24148
24398
|
import {
|
|
24149
24399
|
closeSync as closeSync3,
|
|
24150
|
-
existsSync as
|
|
24400
|
+
existsSync as existsSync26,
|
|
24151
24401
|
mkdirSync as mkdirSync24,
|
|
24152
24402
|
openSync as openSync3,
|
|
24153
24403
|
readdirSync as readdirSync9,
|
|
24154
|
-
readFileSync as
|
|
24404
|
+
readFileSync as readFileSync21,
|
|
24155
24405
|
renameSync as renameSync10,
|
|
24156
|
-
statSync as
|
|
24406
|
+
statSync as statSync14,
|
|
24157
24407
|
unlinkSync as unlinkSync18,
|
|
24158
24408
|
writeFileSync as writeFileSync17
|
|
24159
24409
|
} from "fs";
|
|
24160
|
-
import { basename as basename7, dirname as dirname16, join as join35, resolve as
|
|
24410
|
+
import { basename as basename7, dirname as dirname16, join as join35, resolve as resolve17 } from "path";
|
|
24161
24411
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
24162
24412
|
import { StdioServerTransport as StdioServerTransport2 } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24163
24413
|
import {
|
|
@@ -24204,7 +24454,7 @@ function acquireRecoveryGuard(lockPath) {
|
|
|
24204
24454
|
return () => {
|
|
24205
24455
|
closeSync3(fd);
|
|
24206
24456
|
try {
|
|
24207
|
-
if (
|
|
24457
|
+
if (readFileSync21(guardPath, "utf-8") === ownerToken)
|
|
24208
24458
|
unlinkSync18(guardPath);
|
|
24209
24459
|
} catch {}
|
|
24210
24460
|
};
|
|
@@ -24238,7 +24488,7 @@ function acquireFileLock(path) {
|
|
|
24238
24488
|
closeSync3(fd);
|
|
24239
24489
|
const releaseGuard = acquireRecoveryGuard(lockPath);
|
|
24240
24490
|
try {
|
|
24241
|
-
if (
|
|
24491
|
+
if (readFileSync21(lockPath, "utf-8") === ownerToken)
|
|
24242
24492
|
unlinkSync18(lockPath);
|
|
24243
24493
|
} catch {} finally {
|
|
24244
24494
|
releaseGuard();
|
|
@@ -24250,8 +24500,8 @@ function acquireFileLock(path) {
|
|
|
24250
24500
|
const releaseGuard = acquireRecoveryGuard(lockPath);
|
|
24251
24501
|
try {
|
|
24252
24502
|
try {
|
|
24253
|
-
const staleToken =
|
|
24254
|
-
if (Date.now() -
|
|
24503
|
+
const staleToken = readFileSync21(lockPath, "utf-8");
|
|
24504
|
+
if (Date.now() - statSync14(lockPath).mtimeMs > FILE_LOCK_STALE_MS && readFileSync21(lockPath, "utf-8") === staleToken) {
|
|
24255
24505
|
unlinkSync18(lockPath);
|
|
24256
24506
|
continue;
|
|
24257
24507
|
}
|
|
@@ -24314,7 +24564,7 @@ function wikiQuery(args) {
|
|
|
24314
24564
|
continue;
|
|
24315
24565
|
const fp = join35(sub, f.name);
|
|
24316
24566
|
try {
|
|
24317
|
-
const text2 =
|
|
24567
|
+
const text2 = readFileSync21(fp, "utf-8");
|
|
24318
24568
|
scored.push({
|
|
24319
24569
|
score: scoreMatch(text2),
|
|
24320
24570
|
path: `${label}/${entry.name}/${f.name}`,
|
|
@@ -24327,7 +24577,7 @@ function wikiQuery(args) {
|
|
|
24327
24577
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
24328
24578
|
const fp = join35(dir, entry.name);
|
|
24329
24579
|
try {
|
|
24330
|
-
const text2 =
|
|
24580
|
+
const text2 = readFileSync21(fp, "utf-8");
|
|
24331
24581
|
scored.push({
|
|
24332
24582
|
score: scoreMatch(text2),
|
|
24333
24583
|
path: `${label}/${entry.name}`,
|
|
@@ -24433,20 +24683,20 @@ function wikiLastConversation(args) {
|
|
|
24433
24683
|
return { content: [{ type: "text", text: "No topic specified." }] };
|
|
24434
24684
|
}
|
|
24435
24685
|
const name = slugify(topicNameFrom(rawTopic));
|
|
24436
|
-
const archiveDir =
|
|
24686
|
+
const archiveDir = resolve17(runtime().archiveDir, name);
|
|
24437
24687
|
try {
|
|
24438
|
-
let files =
|
|
24688
|
+
let files = existsSync26(archiveDir) ? readdirSync9(archiveDir).filter((f) => f.endsWith(".jsonl")).sort().reverse() : [];
|
|
24439
24689
|
if (files.length === 0) {
|
|
24440
|
-
files =
|
|
24690
|
+
files = existsSync26(runtime().archiveDir) ? readdirSync9(runtime().archiveDir).filter((f) => f.startsWith(name) && f.endsWith(".jsonl")).sort().reverse() : [];
|
|
24441
24691
|
}
|
|
24442
24692
|
if (files.length === 0) {
|
|
24443
24693
|
return {
|
|
24444
24694
|
content: [{ type: "text", text: `No archive found for topic: ${rawTopic}` }]
|
|
24445
24695
|
};
|
|
24446
24696
|
}
|
|
24447
|
-
const actualPath = files[0].includes("/") ? files[0] :
|
|
24697
|
+
const actualPath = files[0].includes("/") ? files[0] : resolve17(existsSync26(archiveDir) && readdirSync9(archiveDir).includes(files[0]) ? archiveDir : runtime().archiveDir, files[0]);
|
|
24448
24698
|
try {
|
|
24449
|
-
const raw =
|
|
24699
|
+
const raw = readFileSync21(actualPath, "utf-8");
|
|
24450
24700
|
const lines = raw.trim().split(`
|
|
24451
24701
|
`).filter(Boolean);
|
|
24452
24702
|
const turns2 = [];
|
|
@@ -24517,9 +24767,9 @@ function skillQuery(args) {
|
|
|
24517
24767
|
})) {
|
|
24518
24768
|
if (!entry.isDirectory())
|
|
24519
24769
|
continue;
|
|
24520
|
-
const skillFile =
|
|
24770
|
+
const skillFile = resolve17(runtime().skillsDir, entry.name, "skill.md");
|
|
24521
24771
|
try {
|
|
24522
|
-
const text2 =
|
|
24772
|
+
const text2 = readFileSync21(skillFile, "utf-8");
|
|
24523
24773
|
const lower = text2.toLowerCase();
|
|
24524
24774
|
let score = 0;
|
|
24525
24775
|
for (const w of query2.split(/\s+/).filter(Boolean)) {
|
|
@@ -24585,12 +24835,12 @@ ${fresh.join(`
|
|
|
24585
24835
|
`)}
|
|
24586
24836
|
`;
|
|
24587
24837
|
}
|
|
24588
|
-
const skillDir =
|
|
24838
|
+
const skillDir = resolve17(runtime().skillsDir, name);
|
|
24589
24839
|
ensureDir(skillDir);
|
|
24590
|
-
const skillPath =
|
|
24840
|
+
const skillPath = resolve17(skillDir, "skill.md");
|
|
24591
24841
|
let finalContent = content;
|
|
24592
24842
|
try {
|
|
24593
|
-
const existing =
|
|
24843
|
+
const existing = readFileSync21(skillPath, "utf-8");
|
|
24594
24844
|
finalContent = mergeGotchas(content, extractGotchas(existing));
|
|
24595
24845
|
} catch {}
|
|
24596
24846
|
writeFileSync17(skillPath, finalContent, "utf-8");
|
|
@@ -24620,7 +24870,7 @@ function saveWikiEntry(args) {
|
|
|
24620
24870
|
let counter = 1;
|
|
24621
24871
|
while (true) {
|
|
24622
24872
|
try {
|
|
24623
|
-
writeFileSync17(
|
|
24873
|
+
writeFileSync17(resolve17(runtime().summariesDir, summaryName), content, {
|
|
24624
24874
|
encoding: "utf-8",
|
|
24625
24875
|
flag: "wx"
|
|
24626
24876
|
});
|
|
@@ -24637,8 +24887,8 @@ function saveWikiEntry(args) {
|
|
|
24637
24887
|
let sqliteUpdated = false;
|
|
24638
24888
|
if (topicId && setTopicBrief2) {
|
|
24639
24889
|
try {
|
|
24640
|
-
const briefPath =
|
|
24641
|
-
let briefMd =
|
|
24890
|
+
const briefPath = resolve17(runtime().topicsDir, `${fileSlug}.md`);
|
|
24891
|
+
let briefMd = existsSync26(briefPath) ? readFileSync21(briefPath, "utf-8") : undefined;
|
|
24642
24892
|
if (briefMd === undefined && getTopicBrief2 && !getTopicBrief2(fileSlug)?.briefMd) {
|
|
24643
24893
|
const legacy = getTopicBrief2(topicId)?.briefMd;
|
|
24644
24894
|
if (legacy)
|
|
@@ -24671,9 +24921,9 @@ function saveTopicBrief(args) {
|
|
|
24671
24921
|
return { content: [{ type: "text", text: "Missing content." }] };
|
|
24672
24922
|
const topicId = runtime().topicId;
|
|
24673
24923
|
const fileSlug = wikiBriefStorageKey(rawTopic, topicId);
|
|
24674
|
-
const briefPath =
|
|
24924
|
+
const briefPath = resolve17(runtime().topicsDir, `${fileSlug}.md`);
|
|
24675
24925
|
ensureDir(dirname16(briefPath));
|
|
24676
|
-
const existed =
|
|
24926
|
+
const existed = existsSync26(briefPath);
|
|
24677
24927
|
writeFileSync17(briefPath, content, "utf-8");
|
|
24678
24928
|
const setTopicBrief2 = runtime().host.setTopicBrief;
|
|
24679
24929
|
let sqliteUpdated = false;
|
|
@@ -24705,17 +24955,17 @@ function indexUpsert(args) {
|
|
|
24705
24955
|
const dateStr = date ?? today;
|
|
24706
24956
|
const indexPath = (() => {
|
|
24707
24957
|
if (kind === "topic")
|
|
24708
|
-
return
|
|
24958
|
+
return resolve17(runtime().wikiDir, "topic-index.md");
|
|
24709
24959
|
if (kind === "skill")
|
|
24710
|
-
return
|
|
24711
|
-
return
|
|
24960
|
+
return resolve17(runtime().wikiDir, "skill-index.md");
|
|
24961
|
+
return resolve17(runtime().wikiDir, "article-index.md");
|
|
24712
24962
|
})();
|
|
24713
24963
|
ensureDir(dirname16(indexPath));
|
|
24714
24964
|
const releaseLock = acquireFileLock(indexPath);
|
|
24715
24965
|
try {
|
|
24716
24966
|
let index;
|
|
24717
24967
|
try {
|
|
24718
|
-
index =
|
|
24968
|
+
index = readFileSync21(indexPath, "utf-8");
|
|
24719
24969
|
} catch {
|
|
24720
24970
|
index = "";
|
|
24721
24971
|
}
|
|
@@ -24789,18 +25039,18 @@ function indexUpsert(args) {
|
|
|
24789
25039
|
}
|
|
24790
25040
|
function createWikiMcpServer(context, host2) {
|
|
24791
25041
|
const surface = context.surface ?? "all";
|
|
24792
|
-
const wikiDir =
|
|
25042
|
+
const wikiDir = resolve17(host2.wikiRoot);
|
|
24793
25043
|
const current3 = {
|
|
24794
25044
|
userId: context.userId,
|
|
24795
25045
|
topicId: context.topicId,
|
|
24796
25046
|
surface,
|
|
24797
25047
|
host: host2,
|
|
24798
25048
|
wikiDir,
|
|
24799
|
-
skillsDir:
|
|
24800
|
-
topicsDir:
|
|
24801
|
-
summariesDir:
|
|
24802
|
-
articlesDir:
|
|
24803
|
-
archiveDir:
|
|
25049
|
+
skillsDir: resolve17(wikiDir, "skills"),
|
|
25050
|
+
topicsDir: resolve17(wikiDir, "topic"),
|
|
25051
|
+
summariesDir: resolve17(wikiDir, "summaries"),
|
|
25052
|
+
articlesDir: resolve17(wikiDir, "articles"),
|
|
25053
|
+
archiveDir: resolve17(wikiDir, "archive")
|
|
24804
25054
|
};
|
|
24805
25055
|
const tools = surface === "wiki" ? WIKI_TOOLS : surface === "skills" ? SKILL_TOOLS : [...WIKI_TOOLS, ...SKILL_TOOLS];
|
|
24806
25056
|
for (const directory of [
|
|
@@ -25000,11 +25250,11 @@ __export(exports_activity_log, {
|
|
|
25000
25250
|
readSentFilesForTopic: () => readSentFilesForTopic
|
|
25001
25251
|
});
|
|
25002
25252
|
import {
|
|
25003
|
-
existsSync as
|
|
25253
|
+
existsSync as existsSync27,
|
|
25004
25254
|
mkdirSync as mkdirSync25,
|
|
25005
25255
|
readdirSync as readdirSync10,
|
|
25006
25256
|
renameSync as renameSync11,
|
|
25007
|
-
statSync as
|
|
25257
|
+
statSync as statSync15,
|
|
25008
25258
|
unlinkSync as unlinkSync19,
|
|
25009
25259
|
writeFileSync as writeFileSync18
|
|
25010
25260
|
} from "fs";
|
|
@@ -25024,7 +25274,7 @@ function writeLog(entry) {
|
|
|
25024
25274
|
const sidShort = entry.sessionId ? entry.sessionId.slice(0, 8) : "new";
|
|
25025
25275
|
const file = join36(logDir, `${entry.userId}_${safeSession}_${sidShort}.jsonl`);
|
|
25026
25276
|
try {
|
|
25027
|
-
const stat =
|
|
25277
|
+
const stat = statSync15(file);
|
|
25028
25278
|
if (stat.size >= MAX_FILE_SIZE) {
|
|
25029
25279
|
const rotated = file.replace(/\.jsonl$/, `.${Date.now()}.jsonl`);
|
|
25030
25280
|
renameSync11(file, rotated);
|
|
@@ -25042,7 +25292,7 @@ function rotateOldLogs() {
|
|
|
25042
25292
|
try {
|
|
25043
25293
|
const files = readdirSync10(logDir).filter((f) => f.endsWith(".jsonl")).map((f) => {
|
|
25044
25294
|
try {
|
|
25045
|
-
const stat =
|
|
25295
|
+
const stat = statSync15(join36(logDir, f));
|
|
25046
25296
|
return { name: f, size: stat.size, mtimeMs: stat.mtimeMs };
|
|
25047
25297
|
} catch {
|
|
25048
25298
|
return null;
|
|
@@ -25084,7 +25334,7 @@ function entryMatchesTopic(e, topicName) {
|
|
|
25084
25334
|
}
|
|
25085
25335
|
function readSentFilesForTopic(userId, topicName) {
|
|
25086
25336
|
const file = join36(resolveStorageUsersLogDir(), String(userId), "sent-files.jsonl");
|
|
25087
|
-
if (!
|
|
25337
|
+
if (!existsSync27(file))
|
|
25088
25338
|
return [];
|
|
25089
25339
|
try {
|
|
25090
25340
|
return readJsonlLines(file).map((line) => JSON.parse(line)).filter((e) => entryMatchesTopic(e, topicName));
|
|
@@ -25095,7 +25345,7 @@ function readSentFilesForTopic(userId, topicName) {
|
|
|
25095
25345
|
}
|
|
25096
25346
|
function removeSentFilesForTopic(userId, topicName) {
|
|
25097
25347
|
const file = join36(resolveStorageUsersLogDir(), String(userId), "sent-files.jsonl");
|
|
25098
|
-
if (!
|
|
25348
|
+
if (!existsSync27(file))
|
|
25099
25349
|
return;
|
|
25100
25350
|
try {
|
|
25101
25351
|
const remaining = readJsonlLines(file).filter((line) => {
|
|
@@ -25839,7 +26089,7 @@ var init_storage_public = __esm(async () => {
|
|
|
25839
26089
|
});
|
|
25840
26090
|
|
|
25841
26091
|
// ../../packages/mcp/src/hosted-surfaces.ts
|
|
25842
|
-
import { resolve as
|
|
26092
|
+
import { resolve as resolve18 } from "path";
|
|
25843
26093
|
function isActiveHostedMcpSurface(surface) {
|
|
25844
26094
|
return ACTIVE_HOSTED_MCP_SURFACES.includes(surface);
|
|
25845
26095
|
}
|
|
@@ -25925,7 +26175,7 @@ async function buildHostedSurfaceServer(surface, context) {
|
|
|
25925
26175
|
...topicId ? { topicId } : {},
|
|
25926
26176
|
surface
|
|
25927
26177
|
}, {
|
|
25928
|
-
wikiRoot:
|
|
26178
|
+
wikiRoot: resolve18(WORKSPACE_DIR2, "wiki"),
|
|
25929
26179
|
getTopicBrief: storage.getTopicBrief,
|
|
25930
26180
|
resolveTopicBrief: accessibleTopicBrief,
|
|
25931
26181
|
setTopicBrief: storage.setTopicBrief
|
|
@@ -26054,11 +26304,11 @@ var init_sse_transport = () => {};
|
|
|
26054
26304
|
|
|
26055
26305
|
// ../../packages/mcp/src/server.ts
|
|
26056
26306
|
import { createHash as createHash9, randomUUID as randomUUID23 } from "crypto";
|
|
26057
|
-
import { realpathSync as realpathSync6, statSync as
|
|
26058
|
-
import { basename as basename9, extname as
|
|
26307
|
+
import { realpathSync as realpathSync6, statSync as statSync16 } from "fs";
|
|
26308
|
+
import { basename as basename9, extname as extname3, resolve as resolve19 } from "path";
|
|
26059
26309
|
import { McpServer as McpServer7 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
26060
26310
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
26061
|
-
import { z as
|
|
26311
|
+
import { z as z12 } from "zod";
|
|
26062
26312
|
function requireTopicAccess(ctx) {
|
|
26063
26313
|
const topic = getTopic(ctx.topicId);
|
|
26064
26314
|
if (!topic)
|
|
@@ -26069,8 +26319,8 @@ function requireTopicAccess(ctx) {
|
|
|
26069
26319
|
return { topic };
|
|
26070
26320
|
}
|
|
26071
26321
|
function isPathInside2(baseDir, filePath) {
|
|
26072
|
-
const cwd =
|
|
26073
|
-
const normalized =
|
|
26322
|
+
const cwd = resolve19(baseDir);
|
|
26323
|
+
const normalized = resolve19(filePath);
|
|
26074
26324
|
try {
|
|
26075
26325
|
const realCwd = realpathSync6(cwd);
|
|
26076
26326
|
const real = realpathSync6(normalized);
|
|
@@ -26086,18 +26336,18 @@ function localFileInfo(ctx, filePath) {
|
|
|
26086
26336
|
if (!isPathInside2(ctx.cwd, filePath)) {
|
|
26087
26337
|
return { error: "Access denied. File must be within the topic workspace." };
|
|
26088
26338
|
}
|
|
26089
|
-
const normalizedPath =
|
|
26339
|
+
const normalizedPath = resolve19(filePath);
|
|
26090
26340
|
if (isSensitivePath(normalizedPath)) {
|
|
26091
26341
|
return { error: "Access denied. Path matches the sensitive-file blacklist." };
|
|
26092
26342
|
}
|
|
26093
26343
|
try {
|
|
26094
|
-
const stats =
|
|
26344
|
+
const stats = statSync16(normalizedPath);
|
|
26095
26345
|
if (!stats.isFile())
|
|
26096
26346
|
return { error: `${filePath} is not a file` };
|
|
26097
26347
|
return {
|
|
26098
26348
|
normalizedPath,
|
|
26099
26349
|
name: basename9(filePath),
|
|
26100
|
-
ext:
|
|
26350
|
+
ext: extname3(filePath).toLowerCase(),
|
|
26101
26351
|
sizeBytes: stats.size,
|
|
26102
26352
|
sizeMB: (stats.size / (1024 * 1024)).toFixed(2)
|
|
26103
26353
|
};
|
|
@@ -26183,6 +26433,9 @@ function buildNegotiumMcpServer(ctx) {
|
|
|
26183
26433
|
const handler = ctx.peerBridge && (def.name === "show_html" || def.name === "show_mermaid") ? async () => textResult("Visual queued for ordered display on the canonical hub.") : def.handler;
|
|
26184
26434
|
server.tool(def.name, def.description, def.schema, handler);
|
|
26185
26435
|
}
|
|
26436
|
+
for (const def of createPublishHtmlToolDefinitions({ cwd: ctx.cwd })) {
|
|
26437
|
+
server.tool(def.name, def.description, def.schema, def.handler);
|
|
26438
|
+
}
|
|
26186
26439
|
}
|
|
26187
26440
|
const selfConfigCtx = {
|
|
26188
26441
|
topicId: ctx.topicId,
|
|
@@ -26275,8 +26528,8 @@ function buildNegotiumMcpServer(ctx) {
|
|
|
26275
26528
|
}
|
|
26276
26529
|
}
|
|
26277
26530
|
if (ctx.fileDeliveryTools === true) {
|
|
26278
|
-
server.tool("send_file", "Send a local file to the user in the chat. Use this when you want to share a file (image, document, PDF, code, etc.) with the user. The file will appear as a downloadable item in the chat.", { file_path:
|
|
26279
|
-
server.tool("send_files", "Send multiple local files to the user in the chat at once.", { file_paths:
|
|
26531
|
+
server.tool("send_file", "Send a local file to the user in the chat. Use this when you want to share a file (image, document, PDF, code, etc.) with the user. The file will appear as a downloadable item in the chat.", { file_path: z12.string().describe("Absolute path to the file to send") }, async ({ file_path }) => deliverFile(ctx, file_path));
|
|
26532
|
+
server.tool("send_files", "Send multiple local files to the user in the chat at once.", { file_paths: z12.array(z12.string()).describe("Array of absolute file paths to send") }, async ({ file_paths }) => {
|
|
26280
26533
|
const results = await Promise.all(file_paths.map((filePath) => deliverFile(ctx, filePath)));
|
|
26281
26534
|
const hasError = results.some((result) => ("isError" in result) && result.isError);
|
|
26282
26535
|
return {
|
|
@@ -26564,37 +26817,37 @@ var init_src2 = __esm(async () => {
|
|
|
26564
26817
|
|
|
26565
26818
|
// ../../packages/mcp-host/src/paths.ts
|
|
26566
26819
|
import { homedir as homedir9 } from "os";
|
|
26567
|
-
import { resolve as
|
|
26820
|
+
import { resolve as resolve20 } from "path";
|
|
26568
26821
|
function stateDir() {
|
|
26569
26822
|
const env = process.env.NEGOTIUM_STATE_DIR?.trim();
|
|
26570
|
-
return env ?
|
|
26823
|
+
return env ? resolve20(env) : resolve20(homedir9(), ".negotium");
|
|
26571
26824
|
}
|
|
26572
26825
|
function defaultPortsDir() {
|
|
26573
|
-
return
|
|
26826
|
+
return resolve20(stateDir(), "run", "mcp-ports");
|
|
26574
26827
|
}
|
|
26575
26828
|
function defaultManifestFile() {
|
|
26576
|
-
return
|
|
26829
|
+
return resolve20(stateDir(), "data", "mcp-manifest.json");
|
|
26577
26830
|
}
|
|
26578
26831
|
var init_paths = () => {};
|
|
26579
26832
|
|
|
26580
26833
|
// ../../packages/mcp-host/src/spec.ts
|
|
26581
|
-
import { z as
|
|
26834
|
+
import { z as z13 } from "zod";
|
|
26582
26835
|
var KEY_PATTERN, portSchema, mcpServerSpecSchema;
|
|
26583
26836
|
var init_spec = __esm(() => {
|
|
26584
26837
|
KEY_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
26585
|
-
portSchema =
|
|
26586
|
-
mcpServerSpecSchema =
|
|
26587
|
-
key:
|
|
26588
|
-
transport:
|
|
26589
|
-
command:
|
|
26590
|
-
args:
|
|
26591
|
-
env:
|
|
26592
|
-
cwd:
|
|
26593
|
-
portRange:
|
|
26594
|
-
scope:
|
|
26595
|
-
readyTimeoutMs:
|
|
26596
|
-
idleEvictMs:
|
|
26597
|
-
healthIntervalMs:
|
|
26838
|
+
portSchema = z13.number().int().min(1).max(65535);
|
|
26839
|
+
mcpServerSpecSchema = z13.object({
|
|
26840
|
+
key: z13.string().min(1).regex(KEY_PATTERN, "key may only contain [a-zA-Z0-9._-]").refine((k) => k !== "." && k !== "..", "key may not be a dot path"),
|
|
26841
|
+
transport: z13.enum(["stdio", "http"]),
|
|
26842
|
+
command: z13.string().min(1),
|
|
26843
|
+
args: z13.array(z13.string()).optional(),
|
|
26844
|
+
env: z13.record(z13.string(), z13.string()).optional(),
|
|
26845
|
+
cwd: z13.string().optional(),
|
|
26846
|
+
portRange: z13.object({ base: portSchema, max: portSchema }).optional(),
|
|
26847
|
+
scope: z13.enum(["node", "instance"]),
|
|
26848
|
+
readyTimeoutMs: z13.number().int().positive().optional(),
|
|
26849
|
+
idleEvictMs: z13.number().int().positive().optional(),
|
|
26850
|
+
healthIntervalMs: z13.number().int().positive().optional()
|
|
26598
26851
|
}).superRefine((spec, ctx) => {
|
|
26599
26852
|
if (spec.transport === "http") {
|
|
26600
26853
|
if (!spec.portRange) {
|
|
@@ -26615,9 +26868,9 @@ var init_spec = __esm(() => {
|
|
|
26615
26868
|
});
|
|
26616
26869
|
|
|
26617
26870
|
// ../../packages/mcp-host/src/manifest.ts
|
|
26618
|
-
import { existsSync as
|
|
26871
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync26, readFileSync as readFileSync22, renameSync as renameSync12, writeFileSync as writeFileSync19 } from "fs";
|
|
26619
26872
|
import { dirname as dirname17 } from "path";
|
|
26620
|
-
import { z as
|
|
26873
|
+
import { z as z14 } from "zod";
|
|
26621
26874
|
|
|
26622
26875
|
class McpManifest {
|
|
26623
26876
|
file;
|
|
@@ -26660,11 +26913,11 @@ class McpManifest {
|
|
|
26660
26913
|
return this.entries.get(key)?.enabled ?? false;
|
|
26661
26914
|
}
|
|
26662
26915
|
load() {
|
|
26663
|
-
if (!
|
|
26916
|
+
if (!existsSync28(this.file))
|
|
26664
26917
|
return;
|
|
26665
26918
|
let parsed;
|
|
26666
26919
|
try {
|
|
26667
|
-
parsed = manifestFileSchema.parse(JSON.parse(
|
|
26920
|
+
parsed = manifestFileSchema.parse(JSON.parse(readFileSync22(this.file, "utf8")));
|
|
26668
26921
|
} catch (e) {
|
|
26669
26922
|
throw new Error(`Invalid MCP manifest at ${this.file}: ${e instanceof Error ? e.message : String(e)}`);
|
|
26670
26923
|
}
|
|
@@ -26691,13 +26944,13 @@ var manifestEntrySchema, manifestFileSchema;
|
|
|
26691
26944
|
var init_manifest = __esm(() => {
|
|
26692
26945
|
init_paths();
|
|
26693
26946
|
init_spec();
|
|
26694
|
-
manifestEntrySchema =
|
|
26947
|
+
manifestEntrySchema = z14.object({
|
|
26695
26948
|
spec: mcpServerSpecSchema,
|
|
26696
|
-
enabled:
|
|
26949
|
+
enabled: z14.boolean()
|
|
26697
26950
|
});
|
|
26698
|
-
manifestFileSchema =
|
|
26699
|
-
version:
|
|
26700
|
-
servers:
|
|
26951
|
+
manifestFileSchema = z14.object({
|
|
26952
|
+
version: z14.literal(1),
|
|
26953
|
+
servers: z14.array(manifestEntrySchema)
|
|
26701
26954
|
});
|
|
26702
26955
|
});
|
|
26703
26956
|
|
|
@@ -26714,7 +26967,7 @@ import { execFileSync as execFileSync8, spawn as spawn8 } from "child_process";
|
|
|
26714
26967
|
import {
|
|
26715
26968
|
mkdirSync as mkdirSync27,
|
|
26716
26969
|
readdirSync as readdirSync11,
|
|
26717
|
-
readFileSync as
|
|
26970
|
+
readFileSync as readFileSync23,
|
|
26718
26971
|
renameSync as renameSync13,
|
|
26719
26972
|
unlinkSync as unlinkSync20,
|
|
26720
26973
|
writeFileSync as writeFileSync20
|
|
@@ -26736,16 +26989,16 @@ function resolveInstanceKey(spec, instanceKey) {
|
|
|
26736
26989
|
return instanceKey;
|
|
26737
26990
|
}
|
|
26738
26991
|
function canBind(port) {
|
|
26739
|
-
return new Promise((
|
|
26992
|
+
return new Promise((resolve21) => {
|
|
26740
26993
|
const srv = createServer3();
|
|
26741
|
-
srv.once("error", () =>
|
|
26994
|
+
srv.once("error", () => resolve21(false));
|
|
26742
26995
|
srv.listen({ port, host: "127.0.0.1", exclusive: true }, () => {
|
|
26743
|
-
srv.close(() =>
|
|
26996
|
+
srv.close(() => resolve21(true));
|
|
26744
26997
|
});
|
|
26745
26998
|
});
|
|
26746
26999
|
}
|
|
26747
27000
|
function tcpProbe(port, timeoutMs = PROBE_TIMEOUT_MS2) {
|
|
26748
|
-
return new Promise((
|
|
27001
|
+
return new Promise((resolve21) => {
|
|
26749
27002
|
const sock = connect({ port, host: "127.0.0.1" });
|
|
26750
27003
|
let settled = false;
|
|
26751
27004
|
const done = (ok3) => {
|
|
@@ -26753,7 +27006,7 @@ function tcpProbe(port, timeoutMs = PROBE_TIMEOUT_MS2) {
|
|
|
26753
27006
|
return;
|
|
26754
27007
|
settled = true;
|
|
26755
27008
|
sock.destroy();
|
|
26756
|
-
|
|
27009
|
+
resolve21(ok3);
|
|
26757
27010
|
};
|
|
26758
27011
|
sock.once("connect", () => done(true));
|
|
26759
27012
|
sock.once("error", () => done(false));
|
|
@@ -26781,12 +27034,12 @@ function killProcessTree(pid) {
|
|
|
26781
27034
|
function waitExit(proc, timeoutMs) {
|
|
26782
27035
|
if (proc.exitCode !== null || proc.signalCode !== null)
|
|
26783
27036
|
return Promise.resolve(true);
|
|
26784
|
-
return new Promise((
|
|
26785
|
-
const timer = setTimeout(() =>
|
|
27037
|
+
return new Promise((resolve21) => {
|
|
27038
|
+
const timer = setTimeout(() => resolve21(false), timeoutMs);
|
|
26786
27039
|
timer.unref?.();
|
|
26787
27040
|
const settle = () => {
|
|
26788
27041
|
clearTimeout(timer);
|
|
26789
|
-
|
|
27042
|
+
resolve21(true);
|
|
26790
27043
|
};
|
|
26791
27044
|
proc.once("exit", settle);
|
|
26792
27045
|
proc.once("error", settle);
|
|
@@ -27022,7 +27275,7 @@ class McpHost {
|
|
|
27022
27275
|
if (file.includes(".tmp-"))
|
|
27023
27276
|
continue;
|
|
27024
27277
|
try {
|
|
27025
|
-
const port = Number.parseInt(
|
|
27278
|
+
const port = Number.parseInt(readFileSync23(join37(this.portsDir, file), "utf8").trim(), 10);
|
|
27026
27279
|
if (!Number.isNaN(port))
|
|
27027
27280
|
claims.set(file, port);
|
|
27028
27281
|
} catch {}
|
|
@@ -27129,7 +27382,7 @@ var DEFAULT_READY_TIMEOUT_MS = 20000, READY_POLL_MS = 150, KILL_GRACE_MS = 5000,
|
|
|
27129
27382
|
fn(`[mcp-host] ${msg}`, ctx);
|
|
27130
27383
|
else
|
|
27131
27384
|
fn(`[mcp-host] ${msg}`);
|
|
27132
|
-
}, delay2 = (ms) => new Promise((
|
|
27385
|
+
}, delay2 = (ms) => new Promise((resolve21) => setTimeout(resolve21, ms));
|
|
27133
27386
|
var init_manager3 = __esm(() => {
|
|
27134
27387
|
init_manifest();
|
|
27135
27388
|
init_paths();
|
|
@@ -27147,16 +27400,16 @@ var init_src3 = __esm(() => {
|
|
|
27147
27400
|
import { randomUUID as randomUUID24 } from "crypto";
|
|
27148
27401
|
import {
|
|
27149
27402
|
copyFileSync as copyFileSync4,
|
|
27150
|
-
existsSync as
|
|
27403
|
+
existsSync as existsSync29,
|
|
27151
27404
|
mkdirSync as mkdirSync28,
|
|
27152
|
-
readFileSync as
|
|
27405
|
+
readFileSync as readFileSync24,
|
|
27153
27406
|
rmSync as rmSync8,
|
|
27154
|
-
statSync as
|
|
27407
|
+
statSync as statSync17,
|
|
27155
27408
|
writeFileSync as writeFileSync21
|
|
27156
27409
|
} from "fs";
|
|
27157
|
-
import { basename as basename10, extname as
|
|
27410
|
+
import { basename as basename10, extname as extname4, join as join38 } from "path";
|
|
27158
27411
|
function safeExtension(filename) {
|
|
27159
|
-
const extension =
|
|
27412
|
+
const extension = extname4(basename10(filename));
|
|
27160
27413
|
return /^\.[A-Za-z0-9]{1,16}$/.test(extension) ? extension.toLowerCase() : "";
|
|
27161
27414
|
}
|
|
27162
27415
|
function attachmentType(mimeType) {
|
|
@@ -27185,7 +27438,7 @@ class NodeFileStore {
|
|
|
27185
27438
|
if (!FILE_ID_RE.test(fileId))
|
|
27186
27439
|
return null;
|
|
27187
27440
|
try {
|
|
27188
|
-
const parsed = JSON.parse(
|
|
27441
|
+
const parsed = JSON.parse(readFileSync24(this.#metadataPath(fileId), "utf8"));
|
|
27189
27442
|
if (typeof parsed.filename !== "string" || typeof parsed.mimeType !== "string" || typeof parsed.sizeBytes !== "number" || typeof parsed.savedName !== "string") {
|
|
27190
27443
|
return null;
|
|
27191
27444
|
}
|
|
@@ -27208,7 +27461,7 @@ class NodeFileStore {
|
|
|
27208
27461
|
hooks = {
|
|
27209
27462
|
resolveAttachmentByFileId: (fileId) => {
|
|
27210
27463
|
const metadata = this.#metadata(fileId);
|
|
27211
|
-
if (!metadata || !
|
|
27464
|
+
if (!metadata || !existsSync29(join38(this.uploadDir, metadata.savedName)))
|
|
27212
27465
|
return null;
|
|
27213
27466
|
return this.#attachment(fileId, metadata);
|
|
27214
27467
|
},
|
|
@@ -27217,7 +27470,7 @@ class NodeFileStore {
|
|
|
27217
27470
|
if (!metadata)
|
|
27218
27471
|
return null;
|
|
27219
27472
|
const path = join38(this.uploadDir, metadata.savedName);
|
|
27220
|
-
return
|
|
27473
|
+
return existsSync29(path) ? path : null;
|
|
27221
27474
|
},
|
|
27222
27475
|
storeLocalFileAsUpload: (absPath, access = {}) => this.store(absPath, access),
|
|
27223
27476
|
deleteFilesForTopic: (topicId) => this.deleteForTopic(topicId)
|
|
@@ -27229,12 +27482,12 @@ class NodeFileStore {
|
|
|
27229
27482
|
const savedName = `${fileId}${extension}`;
|
|
27230
27483
|
const savedPath = join38(this.uploadDir, savedName);
|
|
27231
27484
|
try {
|
|
27232
|
-
const stats =
|
|
27485
|
+
const stats = statSync17(absPath);
|
|
27233
27486
|
if (!stats.isFile() || stats.size > MAX_UPLOAD_BYTES)
|
|
27234
27487
|
return null;
|
|
27235
27488
|
const metadata = {
|
|
27236
27489
|
filename: basename10(absPath),
|
|
27237
|
-
mimeType:
|
|
27490
|
+
mimeType: MIME_BY_EXT2[extension] ?? "application/octet-stream",
|
|
27238
27491
|
sizeBytes: stats.size,
|
|
27239
27492
|
savedName,
|
|
27240
27493
|
...access.ownerUserId ? { ownerUserId: access.ownerUserId } : {},
|
|
@@ -27260,7 +27513,7 @@ class NodeFileStore {
|
|
|
27260
27513
|
if (!allowed)
|
|
27261
27514
|
return null;
|
|
27262
27515
|
const path = join38(this.uploadDir, metadata.savedName);
|
|
27263
|
-
if (!
|
|
27516
|
+
if (!existsSync29(path))
|
|
27264
27517
|
return null;
|
|
27265
27518
|
return new Response(Bun.file(path), {
|
|
27266
27519
|
headers: {
|
|
@@ -27283,12 +27536,12 @@ class NodeFileStore {
|
|
|
27283
27536
|
}
|
|
27284
27537
|
}
|
|
27285
27538
|
}
|
|
27286
|
-
var MAX_UPLOAD_BYTES, FILE_ID_RE,
|
|
27539
|
+
var MAX_UPLOAD_BYTES, FILE_ID_RE, MIME_BY_EXT2, nodeFileStore;
|
|
27287
27540
|
var init_files = __esm(async () => {
|
|
27288
27541
|
await init_node_host();
|
|
27289
27542
|
MAX_UPLOAD_BYTES = 2 * 1024 * 1024 * 1024;
|
|
27290
27543
|
FILE_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
27291
|
-
|
|
27544
|
+
MIME_BY_EXT2 = {
|
|
27292
27545
|
".csv": "text/csv",
|
|
27293
27546
|
".gif": "image/gif",
|
|
27294
27547
|
".html": "text/html",
|
|
@@ -27430,14 +27683,14 @@ function createPollingSseStream(req, options) {
|
|
|
27430
27683
|
import { randomUUID as randomUUID25, timingSafeEqual as timingSafeEqual4 } from "crypto";
|
|
27431
27684
|
import {
|
|
27432
27685
|
chmodSync as chmodSync5,
|
|
27433
|
-
existsSync as
|
|
27686
|
+
existsSync as existsSync30,
|
|
27434
27687
|
mkdirSync as mkdirSync29,
|
|
27435
|
-
readFileSync as
|
|
27688
|
+
readFileSync as readFileSync25,
|
|
27436
27689
|
renameSync as renameSync14,
|
|
27437
27690
|
unlinkSync as unlinkSync21,
|
|
27438
27691
|
writeFileSync as writeFileSync22
|
|
27439
27692
|
} from "fs";
|
|
27440
|
-
import { dirname as dirname18, resolve as
|
|
27693
|
+
import { dirname as dirname18, resolve as resolve21 } from "path";
|
|
27441
27694
|
function jsonError(status, error2) {
|
|
27442
27695
|
return Response.json({ ok: false, error: error2 }, { status });
|
|
27443
27696
|
}
|
|
@@ -27939,10 +28192,10 @@ function writeNodeDaemonInfo(port, startedAt) {
|
|
|
27939
28192
|
return info;
|
|
27940
28193
|
}
|
|
27941
28194
|
function readNodeDaemonInfo() {
|
|
27942
|
-
if (!
|
|
28195
|
+
if (!existsSync30(NODE_DAEMON_INFO_PATH))
|
|
27943
28196
|
return null;
|
|
27944
28197
|
try {
|
|
27945
|
-
const value = JSON.parse(
|
|
28198
|
+
const value = JSON.parse(readFileSync25(NODE_DAEMON_INFO_PATH, "utf8"));
|
|
27946
28199
|
if (value?.schemaVersion !== 1 || value.stateDir !== STATE_DIR || !Number.isInteger(value.protocolVersion) || !Number.isInteger(value.pid) || !Number.isInteger(value.port) || value.port <= 0) {
|
|
27947
28200
|
return null;
|
|
27948
28201
|
}
|
|
@@ -28026,7 +28279,7 @@ var init_control = __esm(async () => {
|
|
|
28026
28279
|
init_files()
|
|
28027
28280
|
]);
|
|
28028
28281
|
NODE_RUNTIME_CONTRACT_BASE_PATH = `${NODE_CONTROL_BASE_PATH}/runtime/v1`;
|
|
28029
|
-
NODE_DAEMON_INFO_PATH =
|
|
28282
|
+
NODE_DAEMON_INFO_PATH = resolve21(RUN_DIR, "node-daemon.json");
|
|
28030
28283
|
NODE_VERSION = NEGOTIUM_VERSION;
|
|
28031
28284
|
ControlRequestError = class ControlRequestError extends Error {
|
|
28032
28285
|
constructor(message) {
|
|
@@ -28230,8 +28483,8 @@ var init_schedule = __esm(() => {
|
|
|
28230
28483
|
|
|
28231
28484
|
// ../../packages/module-cron/src/scripts.ts
|
|
28232
28485
|
import { spawn as spawn9 } from "child_process";
|
|
28233
|
-
import { existsSync as
|
|
28234
|
-
import { resolve as
|
|
28486
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync30, readdirSync as readdirSync12 } from "fs";
|
|
28487
|
+
import { resolve as resolve22, sep as sep2 } from "path";
|
|
28235
28488
|
function validateCronScriptName(script) {
|
|
28236
28489
|
if (!/^[A-Za-z0-9_][A-Za-z0-9_.-]*\.py$/.test(script) || script.includes("..") || script.includes("/") || script.includes("\\")) {
|
|
28237
28490
|
return { ok: false, error: `script must be a plain .py filename under ${CRON_JOBS_DIR}` };
|
|
@@ -28242,7 +28495,7 @@ function resolveCronScriptPath(script) {
|
|
|
28242
28495
|
const valid = validateCronScriptName(script);
|
|
28243
28496
|
if (!valid.ok)
|
|
28244
28497
|
throw new Error(valid.error);
|
|
28245
|
-
const path =
|
|
28498
|
+
const path = resolve22(CRON_JOBS_DIR, script);
|
|
28246
28499
|
if (!path.startsWith(`${CRON_JOBS_DIR}${sep2}`))
|
|
28247
28500
|
throw new Error("script path escaped jobs directory");
|
|
28248
28501
|
return path;
|
|
@@ -28253,7 +28506,7 @@ function listCronScripts() {
|
|
|
28253
28506
|
}
|
|
28254
28507
|
function cronScriptExists(script) {
|
|
28255
28508
|
try {
|
|
28256
|
-
return
|
|
28509
|
+
return existsSync31(resolveCronScriptPath(script));
|
|
28257
28510
|
} catch {
|
|
28258
28511
|
return false;
|
|
28259
28512
|
}
|
|
@@ -28285,7 +28538,7 @@ async function runCronPromptScript(options) {
|
|
|
28285
28538
|
if (options.signal?.aborted)
|
|
28286
28539
|
throw new CronScriptError("script run aborted");
|
|
28287
28540
|
const scriptPath = resolveCronScriptPath(options.script);
|
|
28288
|
-
if (!
|
|
28541
|
+
if (!existsSync31(scriptPath))
|
|
28289
28542
|
throw new CronScriptError(`cron script not found: ${options.script}`);
|
|
28290
28543
|
const timeoutMs = Math.max(5000, options.timeoutMs ?? DEFAULT_SCRIPT_TIMEOUT_MS);
|
|
28291
28544
|
const outputLimitBytes = Math.max(1024, options.outputLimitBytes ?? DEFAULT_OUTPUT_LIMIT_BYTES);
|
|
@@ -28358,7 +28611,7 @@ async function runCronPromptScript(options) {
|
|
|
28358
28611
|
var CRON_JOBS_DIR, DEFAULT_SCRIPT_TIMEOUT_MS, DEFAULT_OUTPUT_LIMIT_BYTES, KILL_GRACE_MS2 = 2500, CronScriptError;
|
|
28359
28612
|
var init_scripts = __esm(async () => {
|
|
28360
28613
|
await init_cron_host();
|
|
28361
|
-
CRON_JOBS_DIR =
|
|
28614
|
+
CRON_JOBS_DIR = resolve22(process.env.NEGOTIUM_CRON_JOBS_DIR?.trim() || resolve22(WORKSPACE_DIR, "cron", "jobs"));
|
|
28362
28615
|
DEFAULT_SCRIPT_TIMEOUT_MS = 10 * 60000;
|
|
28363
28616
|
DEFAULT_OUTPUT_LIMIT_BYTES = 1024 * 1024;
|
|
28364
28617
|
CronScriptError = class CronScriptError extends Error {
|
|
@@ -29486,7 +29739,7 @@ var init_scheduler = __esm(async () => {
|
|
|
29486
29739
|
});
|
|
29487
29740
|
|
|
29488
29741
|
// ../../packages/module-cron/src/module.ts
|
|
29489
|
-
import { existsSync as
|
|
29742
|
+
import { existsSync as existsSync32 } from "fs";
|
|
29490
29743
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
29491
29744
|
function envMs(name, fallback) {
|
|
29492
29745
|
const parsed = Number.parseInt(process.env[name] ?? "", 10);
|
|
@@ -29517,7 +29770,7 @@ function createCronModule(options = {}) {
|
|
|
29517
29770
|
if (event.type === "topic-deleted")
|
|
29518
29771
|
trackCleanup(resetCronTopicContext(event.topicId));
|
|
29519
29772
|
});
|
|
29520
|
-
const mcpServerFile = options.mcpServerFile ?? (
|
|
29773
|
+
const mcpServerFile = options.mcpServerFile ?? (existsSync32(SOURCE_MCP_SERVER_FILE) ? SOURCE_MCP_SERVER_FILE : PACKAGED_MCP_SERVER_FILE);
|
|
29521
29774
|
const unregisterMcp = host2.registerRuntimeMcpServer("cron-manager", {
|
|
29522
29775
|
scopes: ["forum", "manager"],
|
|
29523
29776
|
forumRequired: true,
|
|
@@ -29805,7 +30058,7 @@ __export(exports_src3, {
|
|
|
29805
30058
|
});
|
|
29806
30059
|
import { createServer as createServer4 } from "net";
|
|
29807
30060
|
async function availableLoopbackPort() {
|
|
29808
|
-
return new Promise((
|
|
30061
|
+
return new Promise((resolve23, reject) => {
|
|
29809
30062
|
const probe = createServer4();
|
|
29810
30063
|
probe.unref();
|
|
29811
30064
|
probe.once("error", reject);
|
|
@@ -29816,7 +30069,7 @@ async function availableLoopbackPort() {
|
|
|
29816
30069
|
if (error2)
|
|
29817
30070
|
reject(error2);
|
|
29818
30071
|
else if (port > 0)
|
|
29819
|
-
|
|
30072
|
+
resolve23(port);
|
|
29820
30073
|
else
|
|
29821
30074
|
reject(new Error("failed to allocate a loopback port"));
|
|
29822
30075
|
});
|
|
@@ -29953,8 +30206,8 @@ function startNode(opts = {}) {
|
|
|
29953
30206
|
const manifest = new McpManifest;
|
|
29954
30207
|
const stopSweeper = mcpHost.startSweeper();
|
|
29955
30208
|
let resolveCompleted;
|
|
29956
|
-
const completed = new Promise((
|
|
29957
|
-
resolveCompleted =
|
|
30209
|
+
const completed = new Promise((resolve23) => {
|
|
30210
|
+
resolveCompleted = resolve23;
|
|
29958
30211
|
});
|
|
29959
30212
|
let advertised = null;
|
|
29960
30213
|
try {
|
|
@@ -30031,7 +30284,7 @@ async function runNodeDaemon(opts = {}) {
|
|
|
30031
30284
|
singleton: true
|
|
30032
30285
|
});
|
|
30033
30286
|
await node.completed;
|
|
30034
|
-
await new Promise((
|
|
30287
|
+
await new Promise((resolve23) => setImmediate(resolve23));
|
|
30035
30288
|
process.exit(0);
|
|
30036
30289
|
}
|
|
30037
30290
|
var init_src5 = __esm(async () => {
|
|
@@ -30191,7 +30444,7 @@ var init_commands = __esm(() => {
|
|
|
30191
30444
|
|
|
30192
30445
|
// ../../adapters/terminal/src/path-suggest.ts
|
|
30193
30446
|
import { execFile as execFile3 } from "child_process";
|
|
30194
|
-
import { existsSync as
|
|
30447
|
+
import { existsSync as existsSync33, readdirSync as readdirSync13, statSync as statSync18 } from "fs";
|
|
30195
30448
|
import { homedir as homedir10 } from "os";
|
|
30196
30449
|
import { basename as basename11, dirname as dirname19, join as join39, sep as sep3 } from "path";
|
|
30197
30450
|
import { promisify as promisify3 } from "util";
|
|
@@ -30374,7 +30627,7 @@ async function warmPathSuggestions(lineText, col, loader = listFilesWithRipgrep)
|
|
|
30374
30627
|
return false;
|
|
30375
30628
|
const { dir } = resolveFragment(token.frag);
|
|
30376
30629
|
try {
|
|
30377
|
-
if (!
|
|
30630
|
+
if (!statSync18(dir).isDirectory())
|
|
30378
30631
|
return false;
|
|
30379
30632
|
} catch {
|
|
30380
30633
|
return false;
|
|
@@ -30406,7 +30659,7 @@ function pathSuggestions(lineText, col) {
|
|
|
30406
30659
|
let isDir = entry.isDirectory();
|
|
30407
30660
|
if (!isDir && entry.isSymbolicLink()) {
|
|
30408
30661
|
try {
|
|
30409
|
-
isDir =
|
|
30662
|
+
isDir = statSync18(join39(dir, entry.name)).isDirectory();
|
|
30410
30663
|
} catch {}
|
|
30411
30664
|
}
|
|
30412
30665
|
return {
|
|
@@ -30440,7 +30693,7 @@ function fragmentToAbsolutePath(frag) {
|
|
|
30440
30693
|
}
|
|
30441
30694
|
function fragmentResolves(frag) {
|
|
30442
30695
|
try {
|
|
30443
|
-
return
|
|
30696
|
+
return existsSync33(fragmentToAbsolutePath(frag));
|
|
30444
30697
|
} catch {
|
|
30445
30698
|
return false;
|
|
30446
30699
|
}
|
|
@@ -34942,8 +35195,8 @@ class TerminalApp {
|
|
|
34942
35195
|
this.#refreshBackgroundSessions();
|
|
34943
35196
|
}, 1000);
|
|
34944
35197
|
this.#backgroundRefreshTimer.unref?.();
|
|
34945
|
-
await new Promise((
|
|
34946
|
-
this.#finishRun =
|
|
35198
|
+
await new Promise((resolve23) => {
|
|
35199
|
+
this.#finishRun = resolve23;
|
|
34947
35200
|
});
|
|
34948
35201
|
} finally {
|
|
34949
35202
|
this.#running = false;
|
|
@@ -37606,8 +37859,8 @@ ${caption}` : voiceText : caption;
|
|
|
37606
37859
|
return;
|
|
37607
37860
|
}
|
|
37608
37861
|
let release;
|
|
37609
|
-
const ready = new Promise((
|
|
37610
|
-
release =
|
|
37862
|
+
const ready = new Promise((resolve23) => {
|
|
37863
|
+
release = resolve23;
|
|
37611
37864
|
});
|
|
37612
37865
|
const entry = {
|
|
37613
37866
|
messages: [msg],
|
|
@@ -37729,8 +37982,8 @@ function renderOutbound(text2, maxLen = 4096) {
|
|
|
37729
37982
|
}
|
|
37730
37983
|
|
|
37731
37984
|
// ../../adapters/telegram/src/adapter.ts
|
|
37732
|
-
import { existsSync as
|
|
37733
|
-
import { basename as basename12, extname as
|
|
37985
|
+
import { existsSync as existsSync34 } from "fs";
|
|
37986
|
+
import { basename as basename12, extname as extname5 } from "path";
|
|
37734
37987
|
function startTelegramAdapter(opts) {
|
|
37735
37988
|
const { client, forumChatId } = opts;
|
|
37736
37989
|
const userId = opts.userId ?? "local";
|
|
@@ -37840,7 +38093,7 @@ function startTelegramAdapter(opts) {
|
|
|
37840
38093
|
attempt += 1;
|
|
37841
38094
|
const waitMs = Math.min(5000, 250 * 2 ** Math.min(attempt - 1, 5));
|
|
37842
38095
|
logger.warn({ err: err2, attempt, waitMs }, "telegram adapter: getMe failed while resolving onboarding identity; retrying");
|
|
37843
|
-
await new Promise((
|
|
38096
|
+
await new Promise((resolve23) => setTimeout(resolve23, waitMs));
|
|
37844
38097
|
}
|
|
37845
38098
|
}
|
|
37846
38099
|
return;
|
|
@@ -38217,7 +38470,7 @@ function startTelegramAdapter(opts) {
|
|
|
38217
38470
|
logger.warn({ path, chatId }, "telegram adapter: blocked sensitive file path");
|
|
38218
38471
|
return { ref: null, delivered: false, error: "path matches the sensitive-file blacklist" };
|
|
38219
38472
|
}
|
|
38220
|
-
if (!
|
|
38473
|
+
if (!existsSync34(path)) {
|
|
38221
38474
|
const sent = await client.sendMessage(chatId, `File: ${path}`, base).catch(() => null);
|
|
38222
38475
|
const messageId = sentMessageId(sent);
|
|
38223
38476
|
return {
|
|
@@ -38226,7 +38479,7 @@ function startTelegramAdapter(opts) {
|
|
|
38226
38479
|
error: "file not found on disk"
|
|
38227
38480
|
};
|
|
38228
38481
|
}
|
|
38229
|
-
const ext =
|
|
38482
|
+
const ext = extname5(path).slice(1).toLowerCase();
|
|
38230
38483
|
const fileOptions = {
|
|
38231
38484
|
filename: basename12(file.filename) || basename12(path),
|
|
38232
38485
|
...file.mimeType ? { contentType: file.mimeType } : {}
|
|
@@ -38317,14 +38570,14 @@ function startTelegramAdapter(opts) {
|
|
|
38317
38570
|
const sendQueues = new Map;
|
|
38318
38571
|
function enqueueSend(topicId, task) {
|
|
38319
38572
|
const prev = sendQueues.get(topicId) ?? Promise.resolve();
|
|
38320
|
-
const next = prev.then(() => new Promise((
|
|
38573
|
+
const next = prev.then(() => new Promise((resolve23) => {
|
|
38321
38574
|
const timer = setTimeout(() => {
|
|
38322
38575
|
logger.warn({ topicId, sendTimeoutMs }, "telegram adapter: send timed out \u2014 abandoning it and continuing the queue");
|
|
38323
|
-
|
|
38576
|
+
resolve23();
|
|
38324
38577
|
}, sendTimeoutMs);
|
|
38325
38578
|
task().catch((err2) => logger.warn({ err: err2, topicId }, "telegram adapter: send task failed")).finally(() => {
|
|
38326
38579
|
clearTimeout(timer);
|
|
38327
|
-
|
|
38580
|
+
resolve23();
|
|
38328
38581
|
});
|
|
38329
38582
|
}));
|
|
38330
38583
|
sendQueues.set(topicId, next);
|
|
@@ -38568,7 +38821,7 @@ ${suffix}`;
|
|
|
38568
38821
|
continue;
|
|
38569
38822
|
}
|
|
38570
38823
|
if (retryDelayMs > 0) {
|
|
38571
|
-
await new Promise((
|
|
38824
|
+
await new Promise((resolve23) => setTimeout(resolve23, retryDelayMs));
|
|
38572
38825
|
}
|
|
38573
38826
|
try {
|
|
38574
38827
|
await client.editMessageText(text2, editOptions);
|
|
@@ -39213,8 +39466,8 @@ async function runTelegramCli(args = process.argv.slice(2)) {
|
|
|
39213
39466
|
onShutdown("telegram-channel", 100, () => channel.stop());
|
|
39214
39467
|
onShutdown("telegram-singleton", 90, () => singleton.stop());
|
|
39215
39468
|
let resolveCompleted;
|
|
39216
|
-
const completed = new Promise((
|
|
39217
|
-
resolveCompleted =
|
|
39469
|
+
const completed = new Promise((resolve23) => {
|
|
39470
|
+
resolveCompleted = resolve23;
|
|
39218
39471
|
});
|
|
39219
39472
|
onShutdown("telegram-completed", -100, resolveCompleted);
|
|
39220
39473
|
process.stdout.write(`negotium Telegram adapter connected to canonical node pid ${initialNode.info?.pid ?? "unknown"}
|
|
@@ -39401,22 +39654,22 @@ import { createHash as createHash10, randomUUID as randomUUID27 } from "crypto";
|
|
|
39401
39654
|
import {
|
|
39402
39655
|
chmodSync as chmodSync6,
|
|
39403
39656
|
closeSync as closeSync4,
|
|
39404
|
-
existsSync as
|
|
39657
|
+
existsSync as existsSync35,
|
|
39405
39658
|
fsyncSync as fsyncSync2,
|
|
39406
39659
|
linkSync,
|
|
39407
39660
|
lstatSync,
|
|
39408
39661
|
mkdirSync as mkdirSync32,
|
|
39409
39662
|
openSync as openSync4,
|
|
39410
|
-
readFileSync as
|
|
39663
|
+
readFileSync as readFileSync26,
|
|
39411
39664
|
renameSync as renameSync15,
|
|
39412
39665
|
rmSync as rmSync9,
|
|
39413
|
-
statSync as
|
|
39666
|
+
statSync as statSync19,
|
|
39414
39667
|
unlinkSync as unlinkSync22,
|
|
39415
39668
|
writeFileSync as writeFileSync23
|
|
39416
39669
|
} from "fs";
|
|
39417
|
-
import { dirname as dirname21, resolve as
|
|
39670
|
+
import { dirname as dirname21, resolve as resolve23 } from "path";
|
|
39418
39671
|
function joinFilePath() {
|
|
39419
|
-
return
|
|
39672
|
+
return resolve23(DATA_DIR, "otium-join.json");
|
|
39420
39673
|
}
|
|
39421
39674
|
function isHttpUrl(value) {
|
|
39422
39675
|
return /^https?:\/\//.test(value);
|
|
@@ -39471,7 +39724,7 @@ function parseInviteCode(code) {
|
|
|
39471
39724
|
return normalizeJoin(parsed);
|
|
39472
39725
|
}
|
|
39473
39726
|
function joinLockPath() {
|
|
39474
|
-
return
|
|
39727
|
+
return resolve23(DATA_DIR, ".otium-join.lock");
|
|
39475
39728
|
}
|
|
39476
39729
|
function processIsAlive(pid) {
|
|
39477
39730
|
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
@@ -39485,7 +39738,7 @@ function processIsAlive(pid) {
|
|
|
39485
39738
|
}
|
|
39486
39739
|
function withJoinCredentialLock(operation) {
|
|
39487
39740
|
const lockPath = joinLockPath();
|
|
39488
|
-
const ownerPath =
|
|
39741
|
+
const ownerPath = resolve23(lockPath, "owner.json");
|
|
39489
39742
|
const owner = { pid: process.pid, token: randomUUID27() };
|
|
39490
39743
|
mkdirSync32(dirname21(lockPath), { recursive: true });
|
|
39491
39744
|
for (let attempt = 0;; attempt += 1) {
|
|
@@ -39511,11 +39764,11 @@ function withJoinCredentialLock(operation) {
|
|
|
39511
39764
|
throw error2;
|
|
39512
39765
|
let current3 = null;
|
|
39513
39766
|
try {
|
|
39514
|
-
current3 = JSON.parse(
|
|
39767
|
+
current3 = JSON.parse(readFileSync26(ownerPath, "utf8"));
|
|
39515
39768
|
} catch {}
|
|
39516
39769
|
let ageMs;
|
|
39517
39770
|
try {
|
|
39518
|
-
ageMs = Date.now() -
|
|
39771
|
+
ageMs = Date.now() - statSync19(lockPath).mtimeMs;
|
|
39519
39772
|
} catch (statError) {
|
|
39520
39773
|
if (statError.code === "ENOENT")
|
|
39521
39774
|
continue;
|
|
@@ -39541,7 +39794,7 @@ function withJoinCredentialLock(operation) {
|
|
|
39541
39794
|
return operation();
|
|
39542
39795
|
} finally {
|
|
39543
39796
|
try {
|
|
39544
|
-
const current3 = JSON.parse(
|
|
39797
|
+
const current3 = JSON.parse(readFileSync26(ownerPath, "utf8"));
|
|
39545
39798
|
if (current3.pid === owner.pid && current3.token === owner.token) {
|
|
39546
39799
|
rmSync9(lockPath, { recursive: true, force: true });
|
|
39547
39800
|
}
|
|
@@ -39564,9 +39817,9 @@ function joinCredentialDigest(join41) {
|
|
|
39564
39817
|
return createHash10("sha256").update(JSON.stringify(normalizedJoin(join41))).digest("base64url");
|
|
39565
39818
|
}
|
|
39566
39819
|
function readPersistedJoin(path = joinFilePath()) {
|
|
39567
|
-
if (!
|
|
39820
|
+
if (!existsSync35(path))
|
|
39568
39821
|
return null;
|
|
39569
|
-
const parsed = JSON.parse(
|
|
39822
|
+
const parsed = JSON.parse(readFileSync26(path, "utf-8"));
|
|
39570
39823
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
39571
39824
|
throw new Error("persisted join credentials are not a JSON object");
|
|
39572
39825
|
}
|
|
@@ -39585,7 +39838,7 @@ function saveJoinWhileLocked(join41, options = {}) {
|
|
|
39585
39838
|
const directory = dirname21(path);
|
|
39586
39839
|
const normalized = normalizedJoin(join41);
|
|
39587
39840
|
mkdirSync32(directory, { recursive: true });
|
|
39588
|
-
if (
|
|
39841
|
+
if (existsSync35(path)) {
|
|
39589
39842
|
if (lstatSync(path).isSymbolicLink()) {
|
|
39590
39843
|
throw new Error(`refusing to replace symlinked Otium join file at ${path}`);
|
|
39591
39844
|
}
|
|
@@ -39617,7 +39870,7 @@ function saveJoinWhileLocked(join41, options = {}) {
|
|
|
39617
39870
|
throw new Error(`this node is already joined${existing ? ` as ${existing.cellId}` : " with an invalid join file"}; pass --replace to replace its credentials`);
|
|
39618
39871
|
}
|
|
39619
39872
|
}
|
|
39620
|
-
const temporaryPath =
|
|
39873
|
+
const temporaryPath = resolve23(directory, `.otium-join.json.${process.pid}.${randomUUID27()}.tmp`);
|
|
39621
39874
|
let fd;
|
|
39622
39875
|
try {
|
|
39623
39876
|
fd = openSync4(temporaryPath, "wx", 384);
|
|
@@ -39642,7 +39895,7 @@ function saveJoinWhileLocked(join41, options = {}) {
|
|
|
39642
39895
|
} catch (error2) {
|
|
39643
39896
|
if (fd !== undefined)
|
|
39644
39897
|
closeSync4(fd);
|
|
39645
|
-
if (
|
|
39898
|
+
if (existsSync35(temporaryPath))
|
|
39646
39899
|
unlinkSync22(temporaryPath);
|
|
39647
39900
|
throw error2;
|
|
39648
39901
|
}
|
|
@@ -39654,7 +39907,7 @@ function saveJoin(join41, options = {}) {
|
|
|
39654
39907
|
function removeJoin() {
|
|
39655
39908
|
return withJoinCredentialLock(() => {
|
|
39656
39909
|
const path = joinFilePath();
|
|
39657
|
-
if (!
|
|
39910
|
+
if (!existsSync35(path))
|
|
39658
39911
|
return false;
|
|
39659
39912
|
if (lstatSync(path).isSymbolicLink()) {
|
|
39660
39913
|
throw new Error(`refusing to remove symlinked Otium join file at ${path}`);
|
|
@@ -39686,10 +39939,10 @@ function loadJoin() {
|
|
|
39686
39939
|
logger.warn("otium: OTIUM_CENTRAL_URL, OTIUM_CELL_ID, OTIUM_CELL_SECRET must be set together \u2014 ignoring partial env");
|
|
39687
39940
|
}
|
|
39688
39941
|
const path = joinFilePath();
|
|
39689
|
-
if (!
|
|
39942
|
+
if (!existsSync35(path))
|
|
39690
39943
|
return null;
|
|
39691
39944
|
try {
|
|
39692
|
-
const parsed = JSON.parse(
|
|
39945
|
+
const parsed = JSON.parse(readFileSync26(path, "utf-8"));
|
|
39693
39946
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
39694
39947
|
return null;
|
|
39695
39948
|
return normalizeJoin(parsed);
|
|
@@ -39713,11 +39966,11 @@ var exports_join_status = {};
|
|
|
39713
39966
|
__export(exports_join_status, {
|
|
39714
39967
|
hasConfiguredOtiumJoin: () => hasConfiguredOtiumJoin
|
|
39715
39968
|
});
|
|
39716
|
-
import { existsSync as
|
|
39717
|
-
import { resolve as
|
|
39969
|
+
import { existsSync as existsSync36 } from "fs";
|
|
39970
|
+
import { resolve as resolve24 } from "path";
|
|
39718
39971
|
function hasConfiguredOtiumJoin() {
|
|
39719
39972
|
const envJoin = Boolean(process.env.OTIUM_CENTRAL_URL?.trim() && process.env.OTIUM_CELL_ID?.trim() && process.env.OTIUM_CELL_SECRET?.trim());
|
|
39720
|
-
return envJoin ||
|
|
39973
|
+
return envJoin || existsSync36(resolve24(DATA_DIR, "otium-join.json"));
|
|
39721
39974
|
}
|
|
39722
39975
|
var init_join_status = __esm(() => {
|
|
39723
39976
|
init_config_public();
|
|
@@ -40524,7 +40777,7 @@ function createTurnForwarder(opts) {
|
|
|
40524
40777
|
logger.warn({ requestId, seq, type: event.type, attempt, error: result.error }, "otium: peer event delivery to hub failed");
|
|
40525
40778
|
if (attempt < PEER_EVENT_MAX_ATTEMPTS) {
|
|
40526
40779
|
const delayMs = retryBaseMs * 2 ** (attempt - 1);
|
|
40527
|
-
await new Promise((
|
|
40780
|
+
await new Promise((resolve25) => setTimeout(resolve25, delayMs));
|
|
40528
40781
|
}
|
|
40529
40782
|
}
|
|
40530
40783
|
forwarder.deliveryBlocked = true;
|
|
@@ -40666,8 +40919,8 @@ var init_event_backflow = __esm(async () => {
|
|
|
40666
40919
|
|
|
40667
40920
|
// ../../adapters/otium/src/peer-files.ts
|
|
40668
40921
|
import { randomUUID as randomUUID29 } from "crypto";
|
|
40669
|
-
import { copyFileSync as copyFileSync5, mkdirSync as mkdirSync33, rmSync as rmSync10, statSync as
|
|
40670
|
-
import { basename as basename13, extname as
|
|
40922
|
+
import { copyFileSync as copyFileSync5, mkdirSync as mkdirSync33, rmSync as rmSync10, statSync as statSync20 } from "fs";
|
|
40923
|
+
import { basename as basename13, extname as extname6, join as join41 } from "path";
|
|
40671
40924
|
function fileType(mimeType) {
|
|
40672
40925
|
if (mimeType.startsWith("image/"))
|
|
40673
40926
|
return "image";
|
|
@@ -40697,7 +40950,7 @@ function safeFilename(filename) {
|
|
|
40697
40950
|
return value || "upload";
|
|
40698
40951
|
}
|
|
40699
40952
|
function mimeFromPath(path) {
|
|
40700
|
-
const extension =
|
|
40953
|
+
const extension = extname6(path).toLowerCase();
|
|
40701
40954
|
const known = {
|
|
40702
40955
|
".png": "image/png",
|
|
40703
40956
|
".jpg": "image/jpeg",
|
|
@@ -40739,7 +40992,7 @@ function insertLocalFile(args) {
|
|
|
40739
40992
|
mkdirSync33(PEER_FILES_DIR, { recursive: true });
|
|
40740
40993
|
const path = join41(PEER_FILES_DIR, `${id}-${safeFilename(args.filename)}`);
|
|
40741
40994
|
copyFileSync5(args.sourcePath, path);
|
|
40742
|
-
return recordFile({ id, path, sizeBytes:
|
|
40995
|
+
return recordFile({ id, path, sizeBytes: statSync20(path).size, ...args });
|
|
40743
40996
|
}
|
|
40744
40997
|
function deletePeerFilesForTopic(topicId) {
|
|
40745
40998
|
const rows = db.query("SELECT path FROM otium_peer_files WHERE topic_id = ?").all(topicId);
|
|
@@ -42407,17 +42660,17 @@ import {
|
|
|
42407
42660
|
import {
|
|
42408
42661
|
chmodSync as chmodSync7,
|
|
42409
42662
|
closeSync as closeSync5,
|
|
42410
|
-
existsSync as
|
|
42663
|
+
existsSync as existsSync37,
|
|
42411
42664
|
fsyncSync as fsyncSync3,
|
|
42412
42665
|
linkSync as linkSync2,
|
|
42413
42666
|
mkdirSync as mkdirSync34,
|
|
42414
42667
|
openSync as openSync5,
|
|
42415
|
-
readFileSync as
|
|
42668
|
+
readFileSync as readFileSync27,
|
|
42416
42669
|
renameSync as renameSync16,
|
|
42417
42670
|
unlinkSync as unlinkSync23,
|
|
42418
42671
|
writeFileSync as writeFileSync24
|
|
42419
42672
|
} from "fs";
|
|
42420
|
-
import { dirname as dirname22, resolve as
|
|
42673
|
+
import { dirname as dirname22, resolve as resolve25 } from "path";
|
|
42421
42674
|
function parseEnrollmentInvite(code) {
|
|
42422
42675
|
let parsed;
|
|
42423
42676
|
try {
|
|
@@ -42438,14 +42691,14 @@ function parseEnrollmentInvite(code) {
|
|
|
42438
42691
|
return { v: 2, central, token };
|
|
42439
42692
|
}
|
|
42440
42693
|
function pendingEnrollmentPath() {
|
|
42441
|
-
return
|
|
42694
|
+
return resolve25(DATA_DIR, "otium-enrollment-pending.json");
|
|
42442
42695
|
}
|
|
42443
42696
|
function isEnrollmentPending(invite) {
|
|
42444
42697
|
const path = pendingEnrollmentPath();
|
|
42445
|
-
if (!
|
|
42698
|
+
if (!existsSync37(path))
|
|
42446
42699
|
return false;
|
|
42447
42700
|
try {
|
|
42448
|
-
const saved = JSON.parse(
|
|
42701
|
+
const saved = JSON.parse(readFileSync27(path, "utf8"));
|
|
42449
42702
|
return saved.central === invite.central && saved.token === invite.token;
|
|
42450
42703
|
} catch {
|
|
42451
42704
|
return false;
|
|
@@ -42453,8 +42706,8 @@ function isEnrollmentPending(invite) {
|
|
|
42453
42706
|
}
|
|
42454
42707
|
function loadOrCreatePending(invite, nodeName) {
|
|
42455
42708
|
const path = pendingEnrollmentPath();
|
|
42456
|
-
if (
|
|
42457
|
-
const saved = JSON.parse(
|
|
42709
|
+
if (existsSync37(path)) {
|
|
42710
|
+
const saved = JSON.parse(readFileSync27(path, "utf8"));
|
|
42458
42711
|
if (saved.central === invite.central && saved.token === invite.token)
|
|
42459
42712
|
return saved;
|
|
42460
42713
|
throw new Error(`another Otium enrollment is pending at ${path}`);
|
|
@@ -42468,7 +42721,7 @@ function loadOrCreatePending(invite, nodeName) {
|
|
|
42468
42721
|
...nodeName ? { nodeName } : {}
|
|
42469
42722
|
};
|
|
42470
42723
|
mkdirSync34(dirname22(path), { recursive: true });
|
|
42471
|
-
const temporaryPath =
|
|
42724
|
+
const temporaryPath = resolve25(dirname22(path), `.otium-enrollment-pending.json.${process.pid}.${randomUUID31()}.tmp`);
|
|
42472
42725
|
let fd;
|
|
42473
42726
|
try {
|
|
42474
42727
|
fd = openSync5(temporaryPath, "wx", 384);
|
|
@@ -42489,10 +42742,10 @@ function loadOrCreatePending(invite, nodeName) {
|
|
|
42489
42742
|
} catch (error2) {
|
|
42490
42743
|
if (fd !== undefined)
|
|
42491
42744
|
closeSync5(fd);
|
|
42492
|
-
if (
|
|
42745
|
+
if (existsSync37(temporaryPath))
|
|
42493
42746
|
unlinkSync23(temporaryPath);
|
|
42494
|
-
if (error2.code === "EEXIST" &&
|
|
42495
|
-
const saved = JSON.parse(
|
|
42747
|
+
if (error2.code === "EEXIST" && existsSync37(path)) {
|
|
42748
|
+
const saved = JSON.parse(readFileSync27(path, "utf8"));
|
|
42496
42749
|
if (saved.central === invite.central && saved.token === invite.token)
|
|
42497
42750
|
return saved;
|
|
42498
42751
|
}
|
|
@@ -42503,7 +42756,7 @@ function loadOrCreatePending(invite, nodeName) {
|
|
|
42503
42756
|
function replacePendingEnrollment(pending2) {
|
|
42504
42757
|
const path = pendingEnrollmentPath();
|
|
42505
42758
|
const directory = dirname22(path);
|
|
42506
|
-
const temporaryPath =
|
|
42759
|
+
const temporaryPath = resolve25(directory, `.otium-enrollment-pending.json.${process.pid}.${randomUUID31()}.tmp`);
|
|
42507
42760
|
let fd;
|
|
42508
42761
|
try {
|
|
42509
42762
|
fd = openSync5(temporaryPath, "wx", 384);
|
|
@@ -42522,14 +42775,14 @@ function replacePendingEnrollment(pending2) {
|
|
|
42522
42775
|
} catch (error2) {
|
|
42523
42776
|
if (fd !== undefined)
|
|
42524
42777
|
closeSync5(fd);
|
|
42525
|
-
if (
|
|
42778
|
+
if (existsSync37(temporaryPath))
|
|
42526
42779
|
unlinkSync23(temporaryPath);
|
|
42527
42780
|
throw error2;
|
|
42528
42781
|
}
|
|
42529
42782
|
}
|
|
42530
42783
|
function recordClaimedCredential(pending2, join42) {
|
|
42531
42784
|
withJoinCredentialLock(() => {
|
|
42532
|
-
const current3 = JSON.parse(
|
|
42785
|
+
const current3 = JSON.parse(readFileSync27(pendingEnrollmentPath(), "utf8"));
|
|
42533
42786
|
if (current3.central !== pending2.central || current3.token !== pending2.token || current3.idempotencyKey !== pending2.idempotencyKey || current3.publicKey !== pending2.publicKey) {
|
|
42534
42787
|
throw new Error("pending Otium enrollment changed while its claim was in flight");
|
|
42535
42788
|
}
|
|
@@ -42608,7 +42861,7 @@ async function claimEnrollment(invite, nodeName) {
|
|
|
42608
42861
|
function commitEnrollment(join42, options = {}) {
|
|
42609
42862
|
return withJoinCredentialLock(() => {
|
|
42610
42863
|
const pendingPath = pendingEnrollmentPath();
|
|
42611
|
-
const pending2 =
|
|
42864
|
+
const pending2 = existsSync37(pendingPath) ? JSON.parse(readFileSync27(pendingPath, "utf8")) : null;
|
|
42612
42865
|
const digest = joinCredentialDigest(join42);
|
|
42613
42866
|
if (pending2 && (!pending2.claimed || pending2.claimed.digest !== digest || pending2.claimed.cellId !== join42.cellId)) {
|
|
42614
42867
|
throw new Error(`pending Otium enrollment at ${pendingPath} does not match these credentials`);
|
|
@@ -43868,8 +44121,8 @@ async function runOtiumSidecar(options) {
|
|
|
43868
44121
|
}) : null;
|
|
43869
44122
|
tunnel?.start();
|
|
43870
44123
|
let resolveCompleted;
|
|
43871
|
-
const completed = new Promise((
|
|
43872
|
-
resolveCompleted =
|
|
44124
|
+
const completed = new Promise((resolve26) => {
|
|
44125
|
+
resolveCompleted = resolve26;
|
|
43873
44126
|
});
|
|
43874
44127
|
onShutdown("otium-sidecar-server", 130, () => server?.stop(true));
|
|
43875
44128
|
onShutdown("otium-sidecar-tunnel", 120, () => tunnel?.stop());
|
|
@@ -44677,7 +44930,7 @@ async function runCanonicalNode(port) {
|
|
|
44677
44930
|
});
|
|
44678
44931
|
console.log(`negotium node listening on 127.0.0.1:${node.port} (ctrl-c to stop)`);
|
|
44679
44932
|
await node.completed;
|
|
44680
|
-
await new Promise((
|
|
44933
|
+
await new Promise((resolve26) => setImmediate(resolve26));
|
|
44681
44934
|
process.exit(0);
|
|
44682
44935
|
}
|
|
44683
44936
|
async function stopAdapter(name) {
|
|
@@ -44808,4 +45061,4 @@ switch (command) {
|
|
|
44808
45061
|
}
|
|
44809
45062
|
}
|
|
44810
45063
|
|
|
44811
|
-
//# debugId=
|
|
45064
|
+
//# debugId=17BAA8882823716C64756E2164756E21
|