negotium 0.3.0 → 0.3.1
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 +57 -5
- package/dist/agent-helpers.js.map +11 -11
- package/dist/background-bash.js +3 -2
- package/dist/background-bash.js.map +4 -4
- package/dist/browser-runtime.js +3 -2
- package/dist/browser-runtime.js.map +4 -4
- package/dist/chunk-s2gez3wg.js.map +1 -1
- package/dist/{chunk-1y2xw0xe.js → chunk-vvfwqxnh.js} +33 -3
- package/dist/{chunk-1y2xw0xe.js.map → chunk-vvfwqxnh.js.map} +8 -8
- package/dist/hosted-agent.js +34 -4
- package/dist/hosted-agent.js.map +8 -8
- package/dist/main.js +1129 -365
- package/dist/main.js.map +24 -21
- package/dist/mcp-catalog.js +2 -1
- package/dist/mcp-catalog.js.map +3 -3
- package/dist/mcp-factories.js +585 -255
- package/dist/mcp-factories.js.map +15 -13
- package/dist/mcp-servers.js +3 -1
- package/dist/mcp-servers.js.map +3 -3
- package/dist/prompts.js +8 -2
- package/dist/prompts.js.map +5 -5
- package/dist/query-runtime.js +3 -2
- package/dist/query-runtime.js.map +4 -4
- package/dist/registry.js +3 -3
- package/dist/registry.js.map +2 -2
- package/dist/rollout.js +1 -1
- package/dist/runtime/src/mcp/canonical-bridge-config.ts +1 -1
- package/dist/runtime/src/mcp/canonical-proxy-server.ts +74 -1
- package/dist/runtime/src/mcp/decision-server.ts +21 -0
- package/dist/runtime/src/mcp/factories/decision.ts +178 -0
- package/dist/runtime/src/mcp/factories/index.ts +6 -0
- package/dist/runtime/src/mcp/runtime-spec.ts +1 -0
- package/dist/runtime/src/node-host.ts +1 -0
- package/dist/runtime/src/platform/config.ts +2 -1
- package/dist/runtime/src/platform/mcp-catalog-policy.ts +1 -0
- package/dist/runtime/src/platform/mcp-config.ts +31 -1
- package/dist/runtime/src/prompts/builders.ts +5 -0
- package/dist/runtime/src/runtime/visual-html.ts +18 -1
- package/dist/runtime/src/storage/decisions.ts +231 -0
- package/dist/runtime/src/storage/storage-public.ts +2 -0
- package/dist/runtime/src/types.ts +13 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js +21 -3
- package/dist/runtime-helpers.js.map +5 -5
- package/dist/storage.js +261 -56
- package/dist/storage.js.map +5 -4
- package/dist/types/apps/negotium/src/mcp-servers.d.ts +1 -1
- package/dist/types/packages/core/src/mcp/canonical-bridge-config.d.ts +1 -1
- package/dist/types/packages/core/src/mcp/factories/decision.d.ts +16 -0
- package/dist/types/packages/core/src/mcp/factories/index.d.ts +1 -0
- package/dist/types/packages/core/src/mcp/runtime-spec.d.ts +1 -1
- package/dist/types/packages/core/src/platform/config.d.ts +2 -1
- package/dist/types/packages/core/src/platform/mcp-catalog-policy.d.ts +4 -0
- package/dist/types/packages/core/src/storage/decisions.d.ts +45 -0
- package/dist/types/packages/core/src/storage/storage-public.d.ts +2 -0
- package/dist/types/packages/core/src/types.d.ts +12 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/dist/vault.js +3 -2
- package/dist/vault.js.map +4 -4
- package/install-browser-rs.mjs +3 -3
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -273,7 +273,7 @@ function resolveDefaultModel(agent, registryDefaultModel) {
|
|
|
273
273
|
function codexAuthFilePath() {
|
|
274
274
|
return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(process.env.CODEX_HOME || join(homedir(), ".codex"), "auth.json");
|
|
275
275
|
}
|
|
276
|
-
var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CRON_WORKSPACE_DIR, BROWSER_DIR, BROWSER_PROFILES_DIR, BINARIES_DIR, SECRETS_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", BROWSER_RS_VERSION = "v0.1.
|
|
276
|
+
var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CRON_WORKSPACE_DIR, BROWSER_DIR, BROWSER_PROFILES_DIR, BINARIES_DIR, SECRETS_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", BROWSER_RS_VERSION = "v0.1.21", BROWSER_RS_MIN_SECURE_VERSION = "0.1.15", BROWSER_RS_VERSION_PROBE_TIMEOUT_MS = 15000, SNIPPETS_API_URL, BROWSER_RS_BIN, BASH_RS_VERSION = "v0.1.5", BASH_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, DECISION_SERVER, BROWSER_MCP_SSE_PROXY_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, NODE_ID, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, UPLOADS_DIR, VAULT_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, BASHRS_SPILL_ROOT, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5", MODEL_FABLE = "claude-fable-5", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, _envMaxTellDepth, MAX_TELL_DEPTH;
|
|
277
277
|
var init_config = __esm(() => {
|
|
278
278
|
init_config_helpers();
|
|
279
279
|
init_logger();
|
|
@@ -303,6 +303,7 @@ var init_config = __esm(() => {
|
|
|
303
303
|
TSCONFIG_PATH = resolve(PROJECT_ROOT, "tsconfig.json");
|
|
304
304
|
SESSION_COMM_SERVER = resolve(PROJECT_ROOT, "src/mcp/session-comm/server.ts");
|
|
305
305
|
TASK_SERVER = resolve(PROJECT_ROOT, "src/mcp/task-server.ts");
|
|
306
|
+
DECISION_SERVER = resolve(PROJECT_ROOT, "src/mcp/decision-server.ts");
|
|
306
307
|
BROWSER_MCP_SSE_PROXY_SERVER = resolve(PROJECT_ROOT, "src/mcp/browser-sse-proxy-server.ts");
|
|
307
308
|
CANONICAL_MCP_PROXY_SERVER = resolve(PROJECT_ROOT, "src/mcp/canonical-proxy-server.ts");
|
|
308
309
|
WIKI_SERVER = resolve(PROJECT_ROOT, "src/mcp/wiki-server.ts");
|
|
@@ -1868,7 +1869,7 @@ var exports_version = {};
|
|
|
1868
1869
|
__export(exports_version, {
|
|
1869
1870
|
NEGOTIUM_VERSION: () => NEGOTIUM_VERSION
|
|
1870
1871
|
});
|
|
1871
|
-
var NEGOTIUM_VERSION = "0.3.
|
|
1872
|
+
var NEGOTIUM_VERSION = "0.3.1";
|
|
1872
1873
|
|
|
1873
1874
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
1874
1875
|
import { spawn } from "child_process";
|
|
@@ -2391,6 +2392,7 @@ var init_runtime_spec = __esm(() => {
|
|
|
2391
2392
|
init_types();
|
|
2392
2393
|
HOSTED_MCP_SURFACES = [
|
|
2393
2394
|
"task",
|
|
2395
|
+
"decision",
|
|
2394
2396
|
"token-stats",
|
|
2395
2397
|
"system-health",
|
|
2396
2398
|
"vault",
|
|
@@ -2661,6 +2663,7 @@ var init_mcp_catalog_policy = __esm(() => {
|
|
|
2661
2663
|
runtime: { scopes: ["forum", "manager", "fork", "cron"], forumRequired: true },
|
|
2662
2664
|
"token-stats": { scopes: ["dm", "forum", "manager", "cron"], forumRequired: true },
|
|
2663
2665
|
task: { scopes: ["dm", "forum", "manager", "cron"], forumRequired: true },
|
|
2666
|
+
decision: { scopes: ["dm", "forum", "manager", "cron"], forumRequired: true },
|
|
2664
2667
|
"session-comm": { scopes: ["forum", "fork", "manager"], forumRequired: true },
|
|
2665
2668
|
wiki: { scopes: ["dm", "forum", "manager", "cron"], forumRequired: true },
|
|
2666
2669
|
skills: { scopes: ["dm", "forum", "manager", "cron"], forumRequired: true },
|
|
@@ -2913,7 +2916,7 @@ function getForumMcpServers(opts) {
|
|
|
2913
2916
|
peerBridge
|
|
2914
2917
|
} = opts;
|
|
2915
2918
|
const filter = (name) => {
|
|
2916
|
-
if (silent && name === "task")
|
|
2919
|
+
if (silent && (name === "task" || name === "decision"))
|
|
2917
2920
|
return false;
|
|
2918
2921
|
if (enabled === null)
|
|
2919
2922
|
return true;
|
|
@@ -3131,6 +3134,34 @@ var init_mcp_config = __esm(() => {
|
|
|
3131
3134
|
return buildBuiltinMcpServer("task", ctx, () => buildStdioMcpServer(agent, TASK_SERVER, args));
|
|
3132
3135
|
}
|
|
3133
3136
|
},
|
|
3137
|
+
decision: {
|
|
3138
|
+
...commonRuntimeMcpPolicy("decision"),
|
|
3139
|
+
build(ctx) {
|
|
3140
|
+
const { userId, session, topicId, queryId, agent, model, peerBridge } = ctx;
|
|
3141
|
+
if (peerBridge) {
|
|
3142
|
+
if (!topicId || !queryId)
|
|
3143
|
+
return null;
|
|
3144
|
+
const env = canonicalMcpBridgeEnv({
|
|
3145
|
+
surface: "decision",
|
|
3146
|
+
userId,
|
|
3147
|
+
topicId,
|
|
3148
|
+
queryId,
|
|
3149
|
+
peerBridge
|
|
3150
|
+
});
|
|
3151
|
+
return env ? buildStdioMcpServer(agent, CANONICAL_MCP_PROXY_SERVER, ["--surface=decision"], env) : null;
|
|
3152
|
+
}
|
|
3153
|
+
const args = [
|
|
3154
|
+
`--user-id=${userId}`,
|
|
3155
|
+
`--topic=${session}`,
|
|
3156
|
+
`--agent=${agent ?? FALLBACK_AGENT}`
|
|
3157
|
+
];
|
|
3158
|
+
if (topicId)
|
|
3159
|
+
args.push(`--topic-id=${topicId}`);
|
|
3160
|
+
if (model)
|
|
3161
|
+
args.push(`--model=${model}`);
|
|
3162
|
+
return buildBuiltinMcpServer("decision", ctx, () => buildStdioMcpServer(agent, DECISION_SERVER, args));
|
|
3163
|
+
}
|
|
3164
|
+
},
|
|
3134
3165
|
"session-comm": {
|
|
3135
3166
|
...commonRuntimeMcpPolicy("session-comm"),
|
|
3136
3167
|
build(ctx) {
|
|
@@ -11655,6 +11686,7 @@ function buildRuntimeToolSection(opts, extensions) {
|
|
|
11655
11686
|
} = opts;
|
|
11656
11687
|
const runtimeNamespace = "mcp__runtime";
|
|
11657
11688
|
const taskNamespace = "mcp__task";
|
|
11689
|
+
const decisionNamespace = "mcp__decision";
|
|
11658
11690
|
const visualToolLine = agentKind === "codex" ? `To display charts, tables, or interactive HTML results to the user, call the \`show_html\` function in the \`${runtimeNamespace}\` namespace with { html: "<complete HTML string>", title?: "optional title" }.` : `To display charts, tables, or interactive HTML results to the user, call the MCP tool "${runtimeNamespace}__show_html" with { html: "<complete HTML string>", title?: "optional title" }.`;
|
|
11659
11691
|
const mermaidToolLine = agentKind === "codex" ? `For diagrams that Mermaid supports, prefer the \`show_mermaid\` function in the \`${runtimeNamespace}\` namespace with { code: "<Mermaid DSL without markdown fences>", title?: "...", theme?: "neutral" }.` : `For diagrams that Mermaid supports, prefer the MCP tool "${runtimeNamespace}__show_mermaid" with { code: "<Mermaid DSL without markdown fences>", title?: "...", theme?: "neutral" }.`;
|
|
11660
11692
|
const mediaToolLine = agentKind === "codex" ? `To display an existing image or video in the visual panel, use \`show_image\` or \`show_video\` in the \`${runtimeNamespace}\` namespace with either { file_path: "...", title?: "..." } for a topic-workspace file or { file_id: "...", title?: "..." } for an uploaded file already attached in this topic.` : `To display an existing image or video in the visual panel, use MCP tool "${runtimeNamespace}__show_image" or "${runtimeNamespace}__show_video" with either { file_path: "...", title?: "..." } for a topic-workspace file or { file_id: "...", title?: "..." } for an uploaded file already attached in this topic.`;
|
|
@@ -11662,6 +11694,7 @@ function buildRuntimeToolSection(opts, extensions) {
|
|
|
11662
11694
|
const askUserToolLine = agentKind === "codex" ? `When you need a blocking user choice, call the \`ask_user_question\` function in the \`${runtimeNamespace}\` namespace with { question: "...", choices: [{ label: "...", description?: "..." }] }.` : `When you need a blocking user choice, call the MCP tool "${runtimeNamespace}__ask_user_question" with { question: "...", choices: [{ label: "...", description?: "..." }] }.`;
|
|
11663
11695
|
const scheduleSelfToolLine = agentKind === "codex" ? `For a one-shot delayed continuation within 24 hours, call the \`schedule_self\` function in the \`${runtimeNamespace}\` namespace with { delay_seconds: number, message: "self-contained future instruction" }. Only one pending self-schedule is allowed per topic; use \`get_self_schedule\`, \`update_self_schedule\`, or \`cancel_self_schedule\` in that namespace to manage it. Use cron-manager for recurring schedules.` : `For a one-shot delayed continuation within 24 hours, call the MCP tool "${runtimeNamespace}__schedule_self" with { delay_seconds: number, message: "self-contained future instruction" }. Only one pending self-schedule is allowed per topic; manage it with "${runtimeNamespace}__get_self_schedule", "${runtimeNamespace}__update_self_schedule", or "${runtimeNamespace}__cancel_self_schedule". Use cron-manager for recurring schedules.`;
|
|
11664
11696
|
const taskToolLine = agentKind === "codex" ? `For task tracking, use \`task_create\`, \`task_update\`, \`task_list\`, \`task_get\`, and \`task_delete\` functions in the \`${taskNamespace}\` namespace.` : `For task tracking, use MCP tools "${taskNamespace}__task_create", "${taskNamespace}__task_update", "${taskNamespace}__task_list", "${taskNamespace}__task_get", and "${taskNamespace}__task_delete".`;
|
|
11697
|
+
const decisionToolLine = `Use the shared Decision tools in the \`${decisionNamespace}\` namespace when an architectural, product, or operational choice establishes or changes a durable direction or constraint. Do not record routine task progress or temporary implementation details; link causal predecessors when relevant.`;
|
|
11665
11698
|
const runtimeToolRef = (name) => agentKind === "codex" ? `\`${name}\`` : `"${runtimeNamespace}__${name}"`;
|
|
11666
11699
|
const spawnSubagentToolLine = `Use ${runtimeToolRef("spawn_subagent")} for self-contained parallel or long-running background work; keep quick work inline.`;
|
|
11667
11700
|
const lifecycleToolLine = `For staged work, call ${runtimeToolRef("create_subagent")} then ${runtimeToolRef("start_subagent")}. Create fixes \`task\` and \`report_mode\`; start takes only the room ID, so create after inputs are known unless preparing a \`tell_session\` receiver. Manage descendants with ${runtimeToolRef("list_subagents")} and ${runtimeToolRef("delete_subagent")}, and non-parent tell routes with ${runtimeToolRef("grant_subagent_tell")} and ${runtimeToolRef("revoke_subagent_tell")}. Direct-parent reporting needs no grant. Use ${runtimeToolRef("list_memory_topics")} to select \`memory_topic\`.`;
|
|
@@ -11708,6 +11741,9 @@ function buildRuntimeToolSection(opts, extensions) {
|
|
|
11708
11741
|
taskToolLine,
|
|
11709
11742
|
"Use this shared task store for plans, progress, and checklist updates; it is visible across claude/codex/maestro turns.",
|
|
11710
11743
|
nativeTaskPolicyLine,
|
|
11744
|
+
"",
|
|
11745
|
+
"## Shared Decisions",
|
|
11746
|
+
decisionToolLine,
|
|
11711
11747
|
...extensions.render("after-shared-tasks"),
|
|
11712
11748
|
...fileDeliverySection,
|
|
11713
11749
|
...extensions.render("before-session-communication"),
|
|
@@ -17162,6 +17198,10 @@ function buildMermaidHtml(code, theme, scriptUrl = MERMAID_BROWSER_ASSET_RELATIV
|
|
|
17162
17198
|
.controls button:focus-visible{outline:2px solid var(--celadon);outline-offset:1px}
|
|
17163
17199
|
.zoom-value{min-width:46px;color:var(--graphite);font:500 11px/1 Geist,system-ui,sans-serif;text-align:center;font-variant-numeric:tabular-nums}
|
|
17164
17200
|
.error{margin:0;white-space:pre-wrap;color:#7D2E2E;background:#FFF5F2;border:1px solid #E4B9B1;border-radius:6px;padding:14px;font:13px ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
17201
|
+
.failure{max-width:56ch;margin:0 auto}
|
|
17202
|
+
.failure p{margin:0 0 12px;color:var(--graphite);font:14px/1.6 Geist,system-ui,sans-serif}
|
|
17203
|
+
.failure details{color:var(--graphite);font:12px/1.5 Geist,system-ui,sans-serif}
|
|
17204
|
+
.failure summary{cursor:pointer;padding:4px 0}
|
|
17165
17205
|
@media(max-width:600px){.viewport{padding:54px 14px 18px}.controls{top:10px;right:10px}}
|
|
17166
17206
|
</style>
|
|
17167
17207
|
</head>
|
|
@@ -17207,7 +17247,20 @@ function buildMermaidHtml(code, theme, scriptUrl = MERMAID_BROWSER_ASSET_RELATIV
|
|
|
17207
17247
|
applyScale(scale, false);
|
|
17208
17248
|
} catch (error) {
|
|
17209
17249
|
document.querySelector(".controls")?.remove();
|
|
17210
|
-
|
|
17250
|
+
const raw = String(error && error.message ? error.message : error);
|
|
17251
|
+
const escape = (value) => value.replace(/[&<>]/g, c => ({'&':'&','<':'<','>':'>'}[c]));
|
|
17252
|
+
// Mermaid aborts the whole render when it cannot place an edge label:
|
|
17253
|
+
// cardinality markers ask for a point a fixed distance along the edge,
|
|
17254
|
+
// and a relation laid out shorter than that walks off the end. Nothing
|
|
17255
|
+
// is wrong with the diagram, so the raw message sends authors looking
|
|
17256
|
+
// for a syntax error that does not exist. Say what actually moves it.
|
|
17257
|
+
const note = raw.indexOf("Could not find a suitable point") !== -1
|
|
17258
|
+
? "Two nodes ended up too close together for Mermaid to fit a label on the edge between them. Renaming a node, adding another, or setting an explicit direction usually spreads the layout enough to render."
|
|
17259
|
+
: "This diagram could not be rendered.";
|
|
17260
|
+
document.querySelector(".viewport").innerHTML =
|
|
17261
|
+
'<div class="failure"><p>' + escape(note) +
|
|
17262
|
+
'</p><details><summary>Technical detail</summary><pre class="error">' +
|
|
17263
|
+
escape(raw) + '</pre></details></div>';
|
|
17211
17264
|
}
|
|
17212
17265
|
})();
|
|
17213
17266
|
</script>
|
|
@@ -22945,9 +22998,208 @@ var init_conversation_migration = __esm(async () => {
|
|
|
22945
22998
|
init_types();
|
|
22946
22999
|
});
|
|
22947
23000
|
|
|
23001
|
+
// ../../packages/core/src/storage/decisions.ts
|
|
23002
|
+
var exports_decisions = {};
|
|
23003
|
+
__export(exports_decisions, {
|
|
23004
|
+
writeDecisions: () => writeDecisions,
|
|
23005
|
+
writeDecisionGraphSvg: () => writeDecisionGraphSvg,
|
|
23006
|
+
validateDecisionGraph: () => validateDecisionGraph,
|
|
23007
|
+
updateDecisions: () => updateDecisions,
|
|
23008
|
+
renderDecisionList: () => renderDecisionList,
|
|
23009
|
+
readDecisions: () => readDecisions,
|
|
23010
|
+
getDecisionGraphSvgPath: () => getDecisionGraphSvgPath,
|
|
23011
|
+
getDecisionFilePath: () => getDecisionFilePath,
|
|
23012
|
+
deleteDecisions: () => deleteDecisions,
|
|
23013
|
+
decisionScopeKey: () => decisionScopeKey,
|
|
23014
|
+
createDecisions: () => createDecisions,
|
|
23015
|
+
DECISION_STATUS_VALUES: () => DECISION_STATUS_VALUES
|
|
23016
|
+
});
|
|
23017
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync24, readFileSync as readFileSync21, renameSync as renameSync11, writeFileSync as writeFileSync18 } from "fs";
|
|
23018
|
+
import { dirname as dirname16, join as join34 } from "path";
|
|
23019
|
+
function safeDecisionScopeKey(scopeKey) {
|
|
23020
|
+
const safe = sanitizeFileName(scopeKey);
|
|
23021
|
+
if (!safe || safe === "." || safe === "..") {
|
|
23022
|
+
throw new Error(`decisions: refusing unsafe scope key: ${scopeKey}`);
|
|
23023
|
+
}
|
|
23024
|
+
return safe;
|
|
23025
|
+
}
|
|
23026
|
+
function decisionScopeKey(opts) {
|
|
23027
|
+
return opts.topicId?.trim() || opts.session || "default";
|
|
23028
|
+
}
|
|
23029
|
+
function getDecisionFilePath(userId, scopeKey) {
|
|
23030
|
+
return join34(resolveStorageDataDir(), "decisions", `${safeDecisionScopeKey(scopeKey)}.json`);
|
|
23031
|
+
}
|
|
23032
|
+
function getDecisionGraphSvgPath(userId, scopeKey) {
|
|
23033
|
+
return join34(resolveStorageDataDir(), "decision-renders", safeDecisionScopeKey(scopeKey), "latest.svg");
|
|
23034
|
+
}
|
|
23035
|
+
function writeDecisionGraphSvg(userId, scopeKey, svg) {
|
|
23036
|
+
const path = getDecisionGraphSvgPath(userId, scopeKey);
|
|
23037
|
+
mkdirSync24(dirname16(path), { recursive: true });
|
|
23038
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
23039
|
+
writeFileSync18(tmp, svg, "utf-8");
|
|
23040
|
+
renameSync11(tmp, path);
|
|
23041
|
+
return path;
|
|
23042
|
+
}
|
|
23043
|
+
function readDecisions(userId, scopeKey) {
|
|
23044
|
+
const path = getDecisionFilePath(userId, scopeKey);
|
|
23045
|
+
if (!existsSync24(path))
|
|
23046
|
+
return [];
|
|
23047
|
+
try {
|
|
23048
|
+
const parsed = JSON.parse(readFileSync21(path, "utf-8"));
|
|
23049
|
+
return Array.isArray(parsed?.decisions) ? parsed.decisions : [];
|
|
23050
|
+
} catch (error) {
|
|
23051
|
+
logger.warn({ err: error, path }, "decisions: failed to read decision store");
|
|
23052
|
+
return [];
|
|
23053
|
+
}
|
|
23054
|
+
}
|
|
23055
|
+
function writeDecisions(userId, scopeKey, decisions) {
|
|
23056
|
+
const path = getDecisionFilePath(userId, scopeKey);
|
|
23057
|
+
mkdirSync24(dirname16(path), { recursive: true });
|
|
23058
|
+
const payload = { version: 1, decisions };
|
|
23059
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
23060
|
+
writeFileSync18(tmp, JSON.stringify(payload, null, 2), "utf-8");
|
|
23061
|
+
renameSync11(tmp, path);
|
|
23062
|
+
}
|
|
23063
|
+
function nextDecisionId(decisions) {
|
|
23064
|
+
let max = 0;
|
|
23065
|
+
for (const decision of decisions) {
|
|
23066
|
+
const id = Number(decision.id);
|
|
23067
|
+
if (Number.isInteger(id) && id > max)
|
|
23068
|
+
max = id;
|
|
23069
|
+
}
|
|
23070
|
+
return max + 1;
|
|
23071
|
+
}
|
|
23072
|
+
function normalizedIds(ids) {
|
|
23073
|
+
if (!ids)
|
|
23074
|
+
return;
|
|
23075
|
+
const unique = [...new Set(ids.map((id) => id.trim()).filter(Boolean))];
|
|
23076
|
+
return unique.length > 0 ? unique : undefined;
|
|
23077
|
+
}
|
|
23078
|
+
function validateDecisionGraph(decisions) {
|
|
23079
|
+
const ids = new Set(decisions.map((decision) => decision.id));
|
|
23080
|
+
for (const decision of decisions) {
|
|
23081
|
+
for (const upstream of decision.causedBy ?? []) {
|
|
23082
|
+
if (!ids.has(upstream)) {
|
|
23083
|
+
throw new Error(`Decision #${decision.id} references missing decision #${upstream}.`);
|
|
23084
|
+
}
|
|
23085
|
+
if (upstream === decision.id) {
|
|
23086
|
+
throw new Error(`Decision #${decision.id} cannot cause itself.`);
|
|
23087
|
+
}
|
|
23088
|
+
}
|
|
23089
|
+
}
|
|
23090
|
+
const visiting = new Set;
|
|
23091
|
+
const visited = new Set;
|
|
23092
|
+
const byId = new Map(decisions.map((decision) => [decision.id, decision]));
|
|
23093
|
+
const visit = (id) => {
|
|
23094
|
+
if (visited.has(id))
|
|
23095
|
+
return;
|
|
23096
|
+
if (visiting.has(id))
|
|
23097
|
+
throw new Error(`Decision graph contains a cycle at #${id}.`);
|
|
23098
|
+
visiting.add(id);
|
|
23099
|
+
for (const upstream of byId.get(id)?.causedBy ?? [])
|
|
23100
|
+
visit(upstream);
|
|
23101
|
+
visiting.delete(id);
|
|
23102
|
+
visited.add(id);
|
|
23103
|
+
};
|
|
23104
|
+
for (const id of ids)
|
|
23105
|
+
visit(id);
|
|
23106
|
+
}
|
|
23107
|
+
function createDecisions(decisions, inputs) {
|
|
23108
|
+
const out = [...decisions];
|
|
23109
|
+
const created = [];
|
|
23110
|
+
let id = nextDecisionId(out);
|
|
23111
|
+
for (const input of inputs) {
|
|
23112
|
+
const decision = {
|
|
23113
|
+
id: String(id++),
|
|
23114
|
+
action: input.action.trim(),
|
|
23115
|
+
reasoning: input.reasoning.trim(),
|
|
23116
|
+
agent: input.agent,
|
|
23117
|
+
status: input.status ?? "accepted",
|
|
23118
|
+
timestamp: input.timestamp ?? Date.now(),
|
|
23119
|
+
...input.model ? { model: input.model } : {},
|
|
23120
|
+
...normalizedIds(input.causedBy) ? { causedBy: normalizedIds(input.causedBy) } : {}
|
|
23121
|
+
};
|
|
23122
|
+
out.push(decision);
|
|
23123
|
+
created.push(decision);
|
|
23124
|
+
}
|
|
23125
|
+
validateDecisionGraph(out);
|
|
23126
|
+
return { decisions: out, created };
|
|
23127
|
+
}
|
|
23128
|
+
function updateDecisions(decisions, updates) {
|
|
23129
|
+
const out = decisions.map((decision) => ({
|
|
23130
|
+
...decision,
|
|
23131
|
+
...decision.causedBy ? { causedBy: [...decision.causedBy] } : {}
|
|
23132
|
+
}));
|
|
23133
|
+
const byId = new Map(out.map((decision) => [decision.id, decision]));
|
|
23134
|
+
const missing = [];
|
|
23135
|
+
for (const update of updates) {
|
|
23136
|
+
const decision = byId.get(update.id);
|
|
23137
|
+
if (!decision) {
|
|
23138
|
+
missing.push(update.id);
|
|
23139
|
+
continue;
|
|
23140
|
+
}
|
|
23141
|
+
if (update.action !== undefined)
|
|
23142
|
+
decision.action = update.action.trim();
|
|
23143
|
+
if (update.reasoning !== undefined)
|
|
23144
|
+
decision.reasoning = update.reasoning.trim();
|
|
23145
|
+
if (update.status !== undefined)
|
|
23146
|
+
decision.status = update.status;
|
|
23147
|
+
if (update.causedBy !== undefined) {
|
|
23148
|
+
const ids = normalizedIds(update.causedBy);
|
|
23149
|
+
if (ids)
|
|
23150
|
+
decision.causedBy = ids;
|
|
23151
|
+
else
|
|
23152
|
+
delete decision.causedBy;
|
|
23153
|
+
}
|
|
23154
|
+
}
|
|
23155
|
+
validateDecisionGraph(out);
|
|
23156
|
+
return { decisions: out, missing };
|
|
23157
|
+
}
|
|
23158
|
+
function deleteDecisions(decisions, opts) {
|
|
23159
|
+
if (opts.all)
|
|
23160
|
+
return { decisions: [], removed: decisions.length };
|
|
23161
|
+
const ids = new Set(opts.ids ?? []);
|
|
23162
|
+
const kept = decisions.filter((decision) => !ids.has(decision.id)).map((decision) => {
|
|
23163
|
+
const causedBy = decision.causedBy?.filter((id) => !ids.has(id));
|
|
23164
|
+
const next = { ...decision };
|
|
23165
|
+
if (causedBy && causedBy.length > 0)
|
|
23166
|
+
next.causedBy = causedBy;
|
|
23167
|
+
else
|
|
23168
|
+
delete next.causedBy;
|
|
23169
|
+
return next;
|
|
23170
|
+
});
|
|
23171
|
+
return { decisions: kept, removed: decisions.length - kept.length };
|
|
23172
|
+
}
|
|
23173
|
+
function renderDecisionList(decisions) {
|
|
23174
|
+
if (decisions.length === 0)
|
|
23175
|
+
return "Decisions (0 recorded)";
|
|
23176
|
+
return [
|
|
23177
|
+
`Decisions (${decisions.length} recorded)`,
|
|
23178
|
+
...decisions.map((decision) => {
|
|
23179
|
+
const causes = decision.causedBy?.length ? ` <- ${decision.causedBy.map((id) => `#${id}`).join(", ")}` : "";
|
|
23180
|
+
return `[${decision.status}] #${decision.id} ${decision.action}${causes}
|
|
23181
|
+
${decision.reasoning}`;
|
|
23182
|
+
})
|
|
23183
|
+
].join(`
|
|
23184
|
+
`);
|
|
23185
|
+
}
|
|
23186
|
+
var DECISION_STATUS_VALUES;
|
|
23187
|
+
var init_decisions = __esm(async () => {
|
|
23188
|
+
init_logger();
|
|
23189
|
+
await init_storage_host();
|
|
23190
|
+
DECISION_STATUS_VALUES = [
|
|
23191
|
+
"proposed",
|
|
23192
|
+
"accepted",
|
|
23193
|
+
"executed",
|
|
23194
|
+
"rejected",
|
|
23195
|
+
"superseded"
|
|
23196
|
+
];
|
|
23197
|
+
});
|
|
23198
|
+
|
|
22948
23199
|
// ../../packages/core/src/node-host.ts
|
|
22949
23200
|
var exports_node_host = {};
|
|
22950
23201
|
__export(exports_node_host, {
|
|
23202
|
+
writeDecisionGraphSvg: () => writeDecisionGraphSvg,
|
|
22951
23203
|
upsertTopic: () => upsertTopic,
|
|
22952
23204
|
topicService: () => topicService,
|
|
22953
23205
|
switchTopicModel: () => switchTopicModel,
|
|
@@ -22972,6 +23224,7 @@ __export(exports_node_host, {
|
|
|
22972
23224
|
runNodeRequestHandlers: () => runNodeRequestHandlers,
|
|
22973
23225
|
reconcilePendingAskUserQuestionGates: () => reconcilePendingAskUserQuestionGates,
|
|
22974
23226
|
reapOrphanBrowsers: () => reapPlaywrightOrphans,
|
|
23227
|
+
readDecisions: () => readDecisions,
|
|
22975
23228
|
onShutdown: () => onShutdown,
|
|
22976
23229
|
nodeRequestHandlerNames: () => nodeRequestHandlerNames,
|
|
22977
23230
|
logger: () => logger,
|
|
@@ -23041,6 +23294,7 @@ var init_node_host = __esm(async () => {
|
|
|
23041
23294
|
await init_turn_runner();
|
|
23042
23295
|
await init_api_messages();
|
|
23043
23296
|
await init_api_topics();
|
|
23297
|
+
await init_decisions();
|
|
23044
23298
|
await init_runtime_events();
|
|
23045
23299
|
await init_runtime_process_leases();
|
|
23046
23300
|
await init_token_stats();
|
|
@@ -23050,7 +23304,7 @@ var init_node_host = __esm(async () => {
|
|
|
23050
23304
|
});
|
|
23051
23305
|
|
|
23052
23306
|
// ../../packages/core/src/agents/mcp-tools/inline-assets.ts
|
|
23053
|
-
import { existsSync as
|
|
23307
|
+
import { existsSync as existsSync25, readFileSync as readFileSync22, statSync as statSync14 } from "fs";
|
|
23054
23308
|
import { extname as extname2, isAbsolute as isAbsolute6, resolve as resolve16 } from "path";
|
|
23055
23309
|
function isNonLocal(ref) {
|
|
23056
23310
|
return /^(?:[a-z][a-z0-9+.-]*:|\/\/|#)/i.test(ref.trim());
|
|
@@ -23078,7 +23332,7 @@ function inlineLocalAssets(html, options) {
|
|
|
23078
23332
|
const abs = isAbsolute6(relPath) ? relPath : resolve16(baseDir, relPath);
|
|
23079
23333
|
let bytes;
|
|
23080
23334
|
try {
|
|
23081
|
-
if (!
|
|
23335
|
+
if (!existsSync25(abs) || !statSync14(abs).isFile()) {
|
|
23082
23336
|
unresolved.push(ref);
|
|
23083
23337
|
cache.set(ref, null);
|
|
23084
23338
|
return null;
|
|
@@ -23103,7 +23357,7 @@ function inlineLocalAssets(html, options) {
|
|
|
23103
23357
|
let uri;
|
|
23104
23358
|
try {
|
|
23105
23359
|
const mime = MIME_BY_EXT[extname2(abs).toLowerCase()] ?? "application/octet-stream";
|
|
23106
|
-
uri = `data:${mime};base64,${
|
|
23360
|
+
uri = `data:${mime};base64,${readFileSync22(abs).toString("base64")}`;
|
|
23107
23361
|
} catch {
|
|
23108
23362
|
unresolved.push(ref);
|
|
23109
23363
|
cache.set(ref, null);
|
|
@@ -23661,18 +23915,133 @@ var init_task = __esm(async () => {
|
|
|
23661
23915
|
};
|
|
23662
23916
|
});
|
|
23663
23917
|
|
|
23918
|
+
// ../../packages/core/src/mcp/factories/decision.ts
|
|
23919
|
+
var exports_decision = {};
|
|
23920
|
+
__export(exports_decision, {
|
|
23921
|
+
defaultDecisionMcpHost: () => defaultDecisionMcpHost,
|
|
23922
|
+
createDecisionMcpServer: () => createDecisionMcpServer
|
|
23923
|
+
});
|
|
23924
|
+
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
23925
|
+
import { z as z8 } from "zod";
|
|
23926
|
+
function createDecisionMcpServer(context, host2 = defaultDecisionMcpHost) {
|
|
23927
|
+
const scopeKey = context.topic ? decisionScopeKey({ topicId: context.topicId, session: context.topic }) : "";
|
|
23928
|
+
const server = new McpServer2({ name: "decision", version: "1.0.0" });
|
|
23929
|
+
const requireContext = () => !context.userId || !scopeKey ? mcpError("Error: missing userId/topic context.") : null;
|
|
23930
|
+
const statusEnum = z8.enum(DECISION_STATUS_VALUES);
|
|
23931
|
+
server.tool("decision_create", "Record one or more durable decisions in this topic. Include the rationale and causal predecessors.", {
|
|
23932
|
+
decisions: z8.array(z8.object({
|
|
23933
|
+
action: z8.string().min(1).describe("Concise statement of what was decided"),
|
|
23934
|
+
reasoning: z8.string().min(1).describe("Why this choice was made"),
|
|
23935
|
+
status: statusEnum.optional().describe("Defaults to accepted"),
|
|
23936
|
+
caused_by: z8.array(z8.string()).optional().describe("Upstream decision ids")
|
|
23937
|
+
})).min(1)
|
|
23938
|
+
}, async ({ decisions: inputs }) => {
|
|
23939
|
+
const guard = requireContext();
|
|
23940
|
+
if (guard)
|
|
23941
|
+
return guard;
|
|
23942
|
+
try {
|
|
23943
|
+
const current3 = host2.readDecisions(context.userId, scopeKey);
|
|
23944
|
+
const { decisions, created } = createDecisions(current3, inputs.map((input) => ({
|
|
23945
|
+
action: input.action,
|
|
23946
|
+
reasoning: input.reasoning,
|
|
23947
|
+
status: input.status,
|
|
23948
|
+
causedBy: input.caused_by,
|
|
23949
|
+
agent: context.agent,
|
|
23950
|
+
model: context.model
|
|
23951
|
+
})));
|
|
23952
|
+
host2.writeDecisions(context.userId, scopeKey, decisions);
|
|
23953
|
+
return mcpOk(`${created.length} decision(s) recorded (${created.map((item) => `#${item.id}`).join(", ")})
|
|
23954
|
+
|
|
23955
|
+
${renderDecisionList(decisions)}`);
|
|
23956
|
+
} catch (error) {
|
|
23957
|
+
return mcpError(`decision_create failed: ${errMsg(error)}`);
|
|
23958
|
+
}
|
|
23959
|
+
});
|
|
23960
|
+
server.tool("decision_update", "Update decisions or their causal links in this topic.", {
|
|
23961
|
+
updates: z8.array(z8.object({
|
|
23962
|
+
id: z8.string(),
|
|
23963
|
+
action: z8.string().min(1).optional(),
|
|
23964
|
+
reasoning: z8.string().min(1).optional(),
|
|
23965
|
+
status: statusEnum.optional(),
|
|
23966
|
+
caused_by: z8.array(z8.string()).optional().describe("Replacement upstream decision ids")
|
|
23967
|
+
})).min(1)
|
|
23968
|
+
}, async ({ updates }) => {
|
|
23969
|
+
const guard = requireContext();
|
|
23970
|
+
if (guard)
|
|
23971
|
+
return guard;
|
|
23972
|
+
try {
|
|
23973
|
+
const current3 = host2.readDecisions(context.userId, scopeKey);
|
|
23974
|
+
const { decisions, missing } = updateDecisions(current3, updates.map((update) => ({
|
|
23975
|
+
...update,
|
|
23976
|
+
causedBy: update.caused_by
|
|
23977
|
+
})));
|
|
23978
|
+
host2.writeDecisions(context.userId, scopeKey, decisions);
|
|
23979
|
+
const warning = missing.length ? `
|
|
23980
|
+
Missing ids ignored: ${missing.join(", ")}` : "";
|
|
23981
|
+
return mcpOk(`${renderDecisionList(decisions)}${warning}`);
|
|
23982
|
+
} catch (error) {
|
|
23983
|
+
return mcpError(`decision_update failed: ${errMsg(error)}`);
|
|
23984
|
+
}
|
|
23985
|
+
});
|
|
23986
|
+
server.tool("decision_list", "Read this topic's decision graph as a concise list.", {}, async () => {
|
|
23987
|
+
const guard = requireContext();
|
|
23988
|
+
if (guard)
|
|
23989
|
+
return guard;
|
|
23990
|
+
try {
|
|
23991
|
+
return mcpOk(renderDecisionList(host2.readDecisions(context.userId, scopeKey)));
|
|
23992
|
+
} catch (error) {
|
|
23993
|
+
return mcpError(`decision_list failed: ${errMsg(error)}`);
|
|
23994
|
+
}
|
|
23995
|
+
});
|
|
23996
|
+
server.tool("decision_get", "Read one topic decision as JSON.", { id: z8.string() }, async ({ id }) => {
|
|
23997
|
+
const guard = requireContext();
|
|
23998
|
+
if (guard)
|
|
23999
|
+
return guard;
|
|
24000
|
+
try {
|
|
24001
|
+
const decision = host2.readDecisions(context.userId, scopeKey).find((item) => item.id === id);
|
|
24002
|
+
return decision ? mcpOk(JSON.stringify(decision, null, 2)) : mcpError(`Decision #${id} not found.`);
|
|
24003
|
+
} catch (error) {
|
|
24004
|
+
return mcpError(`decision_get failed: ${errMsg(error)}`);
|
|
24005
|
+
}
|
|
24006
|
+
});
|
|
24007
|
+
server.tool("decision_delete", "Delete decisions. Downstream causal references to deleted decisions are removed.", { ids: z8.array(z8.string()).optional(), all: z8.boolean().optional() }, async ({ ids, all }) => {
|
|
24008
|
+
const guard = requireContext();
|
|
24009
|
+
if (guard)
|
|
24010
|
+
return guard;
|
|
24011
|
+
if (!all && (!ids || ids.length === 0))
|
|
24012
|
+
return mcpError("Provide ids or all=true.");
|
|
24013
|
+
try {
|
|
24014
|
+
const current3 = host2.readDecisions(context.userId, scopeKey);
|
|
24015
|
+
const { decisions, removed } = deleteDecisions(current3, { ids, all });
|
|
24016
|
+
host2.writeDecisions(context.userId, scopeKey, decisions);
|
|
24017
|
+
return mcpOk(`${removed} decision(s) deleted
|
|
24018
|
+
|
|
24019
|
+
${renderDecisionList(decisions)}`);
|
|
24020
|
+
} catch (error) {
|
|
24021
|
+
return mcpError(`decision_delete failed: ${errMsg(error)}`);
|
|
24022
|
+
}
|
|
24023
|
+
});
|
|
24024
|
+
return server;
|
|
24025
|
+
}
|
|
24026
|
+
var defaultDecisionMcpHost;
|
|
24027
|
+
var init_decision = __esm(async () => {
|
|
24028
|
+
await init_decisions();
|
|
24029
|
+
init_mcp_helpers();
|
|
24030
|
+
defaultDecisionMcpHost = { readDecisions, writeDecisions };
|
|
24031
|
+
});
|
|
24032
|
+
|
|
23664
24033
|
// ../../packages/core/src/mcp/factories/token-stats.ts
|
|
23665
24034
|
var exports_token_stats2 = {};
|
|
23666
24035
|
__export(exports_token_stats2, {
|
|
23667
24036
|
defaultTokenStatsMcpHost: () => defaultTokenStatsMcpHost,
|
|
23668
24037
|
createTokenStatsMcpServer: () => createTokenStatsMcpServer
|
|
23669
24038
|
});
|
|
23670
|
-
import { McpServer as
|
|
23671
|
-
import { z as
|
|
24039
|
+
import { McpServer as McpServer3 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24040
|
+
import { z as z9 } from "zod";
|
|
23672
24041
|
function createTokenStatsMcpServer(context, host2 = defaultTokenStatsMcpHost) {
|
|
23673
24042
|
if (!context.userId)
|
|
23674
24043
|
throw new Error("token-stats MCP requires userId");
|
|
23675
|
-
const server = new
|
|
24044
|
+
const server = new McpServer3({ name: "token-stats", version: "1.0.0" });
|
|
23676
24045
|
server.tool("get_usage_stats", [
|
|
23677
24046
|
"Claude/Codex/Maestro \uC0AC\uC6A9\uB7C9\uACFC provider session\uC758 \uCD5C\uC2E0 context \uC810\uC720\uC728\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. \uD604\uC7AC \uC2E4\uD589 \uC911\uC778 turn\uC740 \uC644\uB8CC \uD6C4 \uC9D1\uACC4\uB429\uB2C8\uB2E4.",
|
|
23678
24047
|
"",
|
|
@@ -23684,9 +24053,9 @@ function createTokenStatsMcpServer(context, host2 = defaultTokenStatsMcpHost) {
|
|
|
23684
24053
|
"groupBy: 'session'\uC73C\uB85C \uC124\uC815\uD558\uBA74 \uC138\uC158\uBCC4 \uD1A0\uD070 \uC0AC\uC6A9\uB7C9 \uB7AD\uD0B9\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4."
|
|
23685
24054
|
].join(`
|
|
23686
24055
|
`), {
|
|
23687
|
-
from:
|
|
23688
|
-
to:
|
|
23689
|
-
groupBy:
|
|
24056
|
+
from: z9.string().optional().describe("\uC2DC\uC791 \uC2DC\uAC01 ISO 8601. \uC608: '2026-03-28T09:00:00Z'"),
|
|
24057
|
+
to: z9.string().optional().describe("\uC885\uB8CC \uC2DC\uAC01 ISO 8601. \uC0DD\uB7B5 \uC2DC \uD604\uC7AC"),
|
|
24058
|
+
groupBy: z9.enum(["hour", "session"]).optional().describe("\uC9D1\uACC4 \uAE30\uC900. \uAE30\uBCF8\uAC12: hour")
|
|
23690
24059
|
}, async ({ from, to, groupBy = "hour" }) => {
|
|
23691
24060
|
const snapshot = host2.getStats(context.userId, from, to);
|
|
23692
24061
|
const {
|
|
@@ -23777,7 +24146,7 @@ __export(exports_system_health, {
|
|
|
23777
24146
|
import { execFile as execFile2 } from "child_process";
|
|
23778
24147
|
import { cpus, freemem, loadavg, totalmem } from "os";
|
|
23779
24148
|
import { promisify as promisify2 } from "util";
|
|
23780
|
-
import { McpServer as
|
|
24149
|
+
import { McpServer as McpServer4 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
23781
24150
|
function fmtBytes(bytes) {
|
|
23782
24151
|
const gb = bytes / 1024 ** 3;
|
|
23783
24152
|
return gb >= 1 ? `${gb.toFixed(1)}GB` : `${(bytes / 1024 ** 2).toFixed(0)}MB`;
|
|
@@ -23846,7 +24215,7 @@ async function getDisk() {
|
|
|
23846
24215
|
}
|
|
23847
24216
|
}
|
|
23848
24217
|
function createSystemHealthMcpServer(host2 = defaultSystemHealthMcpHost) {
|
|
23849
|
-
const server = new
|
|
24218
|
+
const server = new McpServer4({ name: "system-health", version: "1.0.0" });
|
|
23850
24219
|
server.tool("get_system_health", [
|
|
23851
24220
|
"\uC2DC\uC2A4\uD15C \uC0C1\uD0DC\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.",
|
|
23852
24221
|
"- CPU \uBD80\uD558 (load average 1/5/15\uBD84)",
|
|
@@ -23911,10 +24280,10 @@ __export(exports_agent_health, {
|
|
|
23911
24280
|
createAgentHealthMcpServer: () => createAgentHealthMcpServer
|
|
23912
24281
|
});
|
|
23913
24282
|
import { spawn as spawn6 } from "child_process";
|
|
23914
|
-
import { existsSync as
|
|
23915
|
-
import { join as
|
|
23916
|
-
import { McpServer as
|
|
23917
|
-
import { z as
|
|
24283
|
+
import { existsSync as existsSync26, readdirSync as readdirSync9 } from "fs";
|
|
24284
|
+
import { join as join35 } from "path";
|
|
24285
|
+
import { McpServer as McpServer5 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24286
|
+
import { z as z10 } from "zod";
|
|
23918
24287
|
function signalProcessTree2(child, signal) {
|
|
23919
24288
|
try {
|
|
23920
24289
|
if (child.pid && process.platform !== "win32")
|
|
@@ -24109,7 +24478,7 @@ async function withCheckSlot(operation) {
|
|
|
24109
24478
|
}
|
|
24110
24479
|
}
|
|
24111
24480
|
function createAgentHealthMcpServer(context) {
|
|
24112
|
-
const server = new
|
|
24481
|
+
const server = new McpServer5({ name: "agent-health", version: "1.0.0" });
|
|
24113
24482
|
const abortController = new AbortController;
|
|
24114
24483
|
const originalClose = server.close.bind(server);
|
|
24115
24484
|
server.close = async () => {
|
|
@@ -24117,9 +24486,9 @@ function createAgentHealthMcpServer(context) {
|
|
|
24117
24486
|
await originalClose();
|
|
24118
24487
|
};
|
|
24119
24488
|
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.", {
|
|
24120
|
-
agent:
|
|
24121
|
-
model:
|
|
24122
|
-
timeoutMs:
|
|
24489
|
+
agent: z10.enum(["claude", "codex", "maestro"]).describe("\uCCB4\uD06C\uD560 agent"),
|
|
24490
|
+
model: z10.string().optional().describe("\uCCB4\uD06C\uD560 \uBAA8\uB378 (\uC5C6\uC73C\uBA74 agent\uBCC4 default \uC0AC\uC6A9)"),
|
|
24491
|
+
timeoutMs: z10.number().default(30000).describe("\uD0C0\uC784\uC544\uC6C3 (ms), \uAE30\uBCF8 30\uCD08")
|
|
24123
24492
|
}, async ({ agent, model, timeoutMs }) => {
|
|
24124
24493
|
const entry = AGENTS.find((candidate) => candidate.kind === agent);
|
|
24125
24494
|
if (!entry)
|
|
@@ -24135,7 +24504,7 @@ function createAgentHealthMcpServer(context) {
|
|
|
24135
24504
|
latency: ${result.latency}ms
|
|
24136
24505
|
error: ${result.error}`);
|
|
24137
24506
|
});
|
|
24138
|
-
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:
|
|
24507
|
+
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: z10.number().default(30000).describe("agent\uBCC4 \uD0C0\uC784\uC544\uC6C3 (ms), \uAE30\uBCF8 30\uCD08") }, async ({ timeoutMs }) => {
|
|
24139
24508
|
const userKey = context.userId ?? "unscoped";
|
|
24140
24509
|
if (activeCheckAllUsers.has(userKey)) {
|
|
24141
24510
|
return mcpOk("FAIL check_all is already running for this user");
|
|
@@ -24156,8 +24525,8 @@ ${results.every((result) => result.ok) ? "All agents healthy" : "Some agents fai
|
|
|
24156
24525
|
server.tool("list_active_queries", "\uD604\uC7AC \uC0AC\uC6A9\uC790 \uBC94\uC704\uC5D0\uC11C \uC2E4\uD589 \uC911\uC778 \uD1A0\uD53D \uCFFC\uB9AC \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4.", {}, async () => {
|
|
24157
24526
|
if (!context.userId)
|
|
24158
24527
|
return mcpOk("\uC2E4\uD589 \uC911\uC778 \uCFFC\uB9AC \uC870\uD68C \uBD88\uAC00 (user context \uC5C6\uC74C)");
|
|
24159
|
-
const stateDir =
|
|
24160
|
-
if (!
|
|
24528
|
+
const stateDir = join35(USERS_LOG_DIR, context.userId, "active-queries");
|
|
24529
|
+
if (!existsSync26(stateDir))
|
|
24161
24530
|
return mcpOk("\uC2E4\uD589 \uC911\uC778 \uCFFC\uB9AC \uC5C6\uC74C");
|
|
24162
24531
|
let files;
|
|
24163
24532
|
try {
|
|
@@ -24169,7 +24538,7 @@ ${results.every((result) => result.ok) ? "All agents healthy" : "Some agents fai
|
|
|
24169
24538
|
const entries = files.flatMap((file) => {
|
|
24170
24539
|
if (!file.endsWith(".json"))
|
|
24171
24540
|
return [];
|
|
24172
|
-
const state = readJsonFile(
|
|
24541
|
+
const state = readJsonFile(join35(stateDir, file));
|
|
24173
24542
|
if (!state)
|
|
24174
24543
|
return [];
|
|
24175
24544
|
const sinceMs = new Date(state.since).getTime();
|
|
@@ -24245,37 +24614,37 @@ var exports_session_comm = {};
|
|
|
24245
24614
|
__export(exports_session_comm, {
|
|
24246
24615
|
createSessionCommMcpServer: () => createSessionCommMcpServer
|
|
24247
24616
|
});
|
|
24248
|
-
import { McpServer as
|
|
24249
|
-
import { z as
|
|
24617
|
+
import { McpServer as McpServer6 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24618
|
+
import { z as z11 } from "zod";
|
|
24250
24619
|
function createSessionCommMcpServer(context, host2, options = {}) {
|
|
24251
|
-
const server = new
|
|
24620
|
+
const server = new McpServer6({ name: "session-comm", version: "2.0.0" });
|
|
24252
24621
|
const required = options.requiredMcpServers?.join(", ") || "none";
|
|
24253
24622
|
const optional = options.optionalMcpServers?.join(", ") || "none";
|
|
24254
24623
|
const subagentIdentity = resolveSubagentTellIdentity(context.currentTopicId, context.subagentParentTopicId);
|
|
24255
24624
|
server.tool("list_sessions", "List available sessions and remote peer sessions for inter-session communication.", {}, async () => host2.listSessions(context));
|
|
24256
24625
|
server.tool("configure_mcp", `Configure optional MCP servers for the current topic. Required: ${required}. Optional: ${optional}.`, {
|
|
24257
|
-
enabled:
|
|
24626
|
+
enabled: z11.array(z11.string()).nullable().optional()
|
|
24258
24627
|
}, async ({ enabled }) => host2.configureMcp(context, enabled));
|
|
24259
24628
|
server.tool("get_mcp_config", "Get the current topic MCP configuration.", {}, async () => host2.getMcpConfig(context));
|
|
24260
24629
|
if (host2.getBrowserProfile) {
|
|
24261
24630
|
server.tool("get_browser_profile", "Get this topic's browser profile and the profiles owned by the same user.", {}, async () => host2.getBrowserProfile(context));
|
|
24262
24631
|
}
|
|
24263
24632
|
if (host2.setBrowserProfile) {
|
|
24264
|
-
server.tool("set_browser_profile", "Assign this topic to a named shared browser profile. Takes effect next turn.", { profile:
|
|
24633
|
+
server.tool("set_browser_profile", "Assign this topic to a named shared browser profile. Takes effect next turn.", { profile: z11.string() }, async ({ profile }) => host2.setBrowserProfile(context, profile));
|
|
24265
24634
|
}
|
|
24266
24635
|
if (host2.deleteBrowserProfile) {
|
|
24267
|
-
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:
|
|
24636
|
+
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: z11.string() }, async ({ profile }) => host2.deleteBrowserProfile(context, profile));
|
|
24268
24637
|
}
|
|
24269
24638
|
server.tool("peek_session", "Inspect running and idle sessions, including pending ask_session calls.", {}, async () => host2.peekSession(context));
|
|
24270
|
-
server.tool("set_description", "Set the current session description used as a routing hint.", { description:
|
|
24639
|
+
server.tool("set_description", "Set the current session description used as a routing hint.", { description: z11.string() }, async ({ description }) => host2.setDescription(context, description));
|
|
24271
24640
|
if (!context.replyOnly) {
|
|
24272
24641
|
if (!subagentIdentity.restricted) {
|
|
24273
|
-
server.tool("ask_session", "Ask another local or remote session a question and wait for its answer.", { to:
|
|
24642
|
+
server.tool("ask_session", "Ask another local or remote session a question and wait for its answer.", { to: z11.string(), message: z11.string() }, async (input) => host2.askSession(context, input));
|
|
24274
24643
|
}
|
|
24275
24644
|
if (!subagentIdentity.restricted) {
|
|
24276
|
-
server.tool("abort_session", "Abort the active query in another session.", { to:
|
|
24645
|
+
server.tool("abort_session", "Abort the active query in another session.", { to: z11.string() }, async ({ to }) => host2.abortSession(context, to));
|
|
24277
24646
|
}
|
|
24278
|
-
server.tool("tell_session", "Send a one-way message to another local or remote session.", { to:
|
|
24647
|
+
server.tool("tell_session", "Send a one-way message to another local or remote session.", { to: z11.string(), message: z11.string() }, async (input) => {
|
|
24279
24648
|
if (subagentIdentity.restricted && !canSubagentTellTargetByName(subagentIdentity, input.to)) {
|
|
24280
24649
|
return {
|
|
24281
24650
|
content: [
|
|
@@ -24402,7 +24771,7 @@ var exports_default_host = {};
|
|
|
24402
24771
|
__export(exports_default_host, {
|
|
24403
24772
|
createDefaultSessionCommMcpHost: () => createDefaultSessionCommMcpHost
|
|
24404
24773
|
});
|
|
24405
|
-
import { basename as basename6, join as
|
|
24774
|
+
import { basename as basename6, join as join36 } from "path";
|
|
24406
24775
|
function ok2(text2) {
|
|
24407
24776
|
return { content: [{ type: "text", text: text2 }] };
|
|
24408
24777
|
}
|
|
@@ -24449,10 +24818,10 @@ function remoteTarget(context, to) {
|
|
|
24449
24818
|
return { node: to.slice(0, slash), topic: to.slice(slash + 1) };
|
|
24450
24819
|
}
|
|
24451
24820
|
function activeQuery(context, topicId, title) {
|
|
24452
|
-
const dir =
|
|
24453
|
-
const candidates = [
|
|
24821
|
+
const dir = join36(USERS_LOG_DIR, context.userId, "active-queries");
|
|
24822
|
+
const candidates = [join36(dir, `${sanitizeId(topicId)}.json`)];
|
|
24454
24823
|
if (title && basename6(title) === title && title !== "." && title !== "..") {
|
|
24455
|
-
candidates.push(
|
|
24824
|
+
candidates.push(join36(dir, `${title}.json`));
|
|
24456
24825
|
}
|
|
24457
24826
|
for (const path of candidates) {
|
|
24458
24827
|
const state = readJsonFile(path);
|
|
@@ -25028,10 +25397,10 @@ var exports_vault = {};
|
|
|
25028
25397
|
__export(exports_vault, {
|
|
25029
25398
|
createVaultMcpServer: () => createVaultMcpServer
|
|
25030
25399
|
});
|
|
25031
|
-
import { McpServer as
|
|
25032
|
-
import { z as
|
|
25400
|
+
import { McpServer as McpServer7 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
25401
|
+
import { z as z12 } from "zod";
|
|
25033
25402
|
function createVaultMcpServer(context, host2, executors = {}) {
|
|
25034
|
-
const server = new
|
|
25403
|
+
const server = new McpServer7({ name: "vault", version: "1.0.0" });
|
|
25035
25404
|
const run = executors.run ?? executeVaultRun;
|
|
25036
25405
|
const http = executors.http ?? executeVaultHttpRequest;
|
|
25037
25406
|
server.tool("vault_list", context.httpOnly ? "List the user's Vault keys and descriptions without exposing values. Use vault_http_request for HTTPS APIs that need a credential." : "List the user's Vault keys and descriptions without exposing values. Use vault_http_request for APIs and vault_run for shell/CLI work that needs a credential.", {}, () => {
|
|
@@ -25047,9 +25416,9 @@ ${lines.join(`
|
|
|
25047
25416
|
});
|
|
25048
25417
|
if (!context.listOnly && !context.httpOnly) {
|
|
25049
25418
|
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.", {
|
|
25050
|
-
command:
|
|
25051
|
-
timeout_ms:
|
|
25052
|
-
max_output_bytes:
|
|
25419
|
+
command: z12.string().min(1).max(64 * 1024),
|
|
25420
|
+
timeout_ms: z12.number().int().min(1000).max(600000).optional(),
|
|
25421
|
+
max_output_bytes: z12.number().int().min(1024).max(2 * 1024 * 1024).optional()
|
|
25053
25422
|
}, async ({ command, timeout_ms, max_output_bytes }) => {
|
|
25054
25423
|
if (!context.userId)
|
|
25055
25424
|
return mcpError("Vault unavailable: no user context");
|
|
@@ -25064,12 +25433,12 @@ ${lines.join(`
|
|
|
25064
25433
|
}
|
|
25065
25434
|
if (!context.listOnly)
|
|
25066
25435
|
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.", {
|
|
25067
|
-
method:
|
|
25068
|
-
url:
|
|
25069
|
-
headers:
|
|
25070
|
-
body:
|
|
25071
|
-
timeout_ms:
|
|
25072
|
-
max_response_bytes:
|
|
25436
|
+
method: z12.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).default("GET"),
|
|
25437
|
+
url: z12.string().url().describe("Absolute HTTPS URL without Vault placeholders"),
|
|
25438
|
+
headers: z12.record(z12.string(), z12.string()).optional(),
|
|
25439
|
+
body: z12.string().optional(),
|
|
25440
|
+
timeout_ms: z12.number().int().min(1000).max(120000).optional(),
|
|
25441
|
+
max_response_bytes: z12.number().int().min(1024).max(1024 * 1024).optional()
|
|
25073
25442
|
}, async ({ method, url, headers, body, timeout_ms, max_response_bytes }) => {
|
|
25074
25443
|
if (!context.userId)
|
|
25075
25444
|
return mcpError("Vault unavailable: no user context");
|
|
@@ -25092,7 +25461,7 @@ var init_vault2 = __esm(() => {
|
|
|
25092
25461
|
});
|
|
25093
25462
|
|
|
25094
25463
|
// ../../packages/core/src/mcp/wiki-search-index.ts
|
|
25095
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
25464
|
+
import { chmodSync as chmodSync5, existsSync as existsSync27, readFileSync as readFileSync23, unlinkSync as unlinkSync18 } from "fs";
|
|
25096
25465
|
function documentId(source) {
|
|
25097
25466
|
return `${source.kind}:${source.key}`;
|
|
25098
25467
|
}
|
|
@@ -25176,7 +25545,7 @@ class WikiSearchIndex {
|
|
|
25176
25545
|
continue;
|
|
25177
25546
|
}
|
|
25178
25547
|
try {
|
|
25179
|
-
const text2 =
|
|
25548
|
+
const text2 = readFileSync23(source.path, "utf-8");
|
|
25180
25549
|
changed.push({ source, document: prepare(source, text2), added: !stored });
|
|
25181
25550
|
} catch {}
|
|
25182
25551
|
}
|
|
@@ -25294,7 +25663,7 @@ class WikiSearchIndex {
|
|
|
25294
25663
|
}
|
|
25295
25664
|
function resetCorruptWikiSearchIndex(path) {
|
|
25296
25665
|
for (const candidate of [path, `${path}-journal`, `${path}-wal`, `${path}-shm`]) {
|
|
25297
|
-
if (!
|
|
25666
|
+
if (!existsSync27(candidate))
|
|
25298
25667
|
continue;
|
|
25299
25668
|
unlinkSync18(candidate);
|
|
25300
25669
|
}
|
|
@@ -25314,17 +25683,17 @@ import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
|
|
|
25314
25683
|
import { randomUUID as randomUUID21 } from "crypto";
|
|
25315
25684
|
import {
|
|
25316
25685
|
closeSync as closeSync4,
|
|
25317
|
-
existsSync as
|
|
25318
|
-
mkdirSync as
|
|
25686
|
+
existsSync as existsSync28,
|
|
25687
|
+
mkdirSync as mkdirSync25,
|
|
25319
25688
|
openSync as openSync4,
|
|
25320
25689
|
readdirSync as readdirSync10,
|
|
25321
|
-
readFileSync as
|
|
25322
|
-
renameSync as
|
|
25690
|
+
readFileSync as readFileSync24,
|
|
25691
|
+
renameSync as renameSync12,
|
|
25323
25692
|
statSync as statSync15,
|
|
25324
25693
|
unlinkSync as unlinkSync19,
|
|
25325
|
-
writeFileSync as
|
|
25694
|
+
writeFileSync as writeFileSync19
|
|
25326
25695
|
} from "fs";
|
|
25327
|
-
import { basename as basename7, dirname as
|
|
25696
|
+
import { basename as basename7, dirname as dirname17, join as join37, relative as relative2, resolve as resolve17 } from "path";
|
|
25328
25697
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
25329
25698
|
import { StdioServerTransport as StdioServerTransport2 } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
25330
25699
|
import {
|
|
@@ -25350,7 +25719,7 @@ function runtime() {
|
|
|
25350
25719
|
return current3;
|
|
25351
25720
|
}
|
|
25352
25721
|
function ensureDir(dir) {
|
|
25353
|
-
|
|
25722
|
+
mkdirSync25(dir, { recursive: true });
|
|
25354
25723
|
}
|
|
25355
25724
|
function acquireRecoveryGuard(lockPath) {
|
|
25356
25725
|
const guardPath = `${lockPath}.reclaim`;
|
|
@@ -25360,7 +25729,7 @@ function acquireRecoveryGuard(lockPath) {
|
|
|
25360
25729
|
try {
|
|
25361
25730
|
const fd = openSync4(guardPath, "wx");
|
|
25362
25731
|
try {
|
|
25363
|
-
|
|
25732
|
+
writeFileSync19(fd, ownerToken, "utf-8");
|
|
25364
25733
|
} catch (error2) {
|
|
25365
25734
|
closeSync4(fd);
|
|
25366
25735
|
try {
|
|
@@ -25371,7 +25740,7 @@ function acquireRecoveryGuard(lockPath) {
|
|
|
25371
25740
|
return () => {
|
|
25372
25741
|
closeSync4(fd);
|
|
25373
25742
|
try {
|
|
25374
|
-
if (
|
|
25743
|
+
if (readFileSync24(guardPath, "utf-8") === ownerToken)
|
|
25375
25744
|
unlinkSync19(guardPath);
|
|
25376
25745
|
} catch {}
|
|
25377
25746
|
};
|
|
@@ -25393,7 +25762,7 @@ function acquireFileLock(path) {
|
|
|
25393
25762
|
try {
|
|
25394
25763
|
const fd = openSync4(lockPath, "wx");
|
|
25395
25764
|
try {
|
|
25396
|
-
|
|
25765
|
+
writeFileSync19(fd, ownerToken, "utf-8");
|
|
25397
25766
|
} catch (error2) {
|
|
25398
25767
|
closeSync4(fd);
|
|
25399
25768
|
try {
|
|
@@ -25405,7 +25774,7 @@ function acquireFileLock(path) {
|
|
|
25405
25774
|
closeSync4(fd);
|
|
25406
25775
|
const releaseGuard = acquireRecoveryGuard(lockPath);
|
|
25407
25776
|
try {
|
|
25408
|
-
if (
|
|
25777
|
+
if (readFileSync24(lockPath, "utf-8") === ownerToken)
|
|
25409
25778
|
unlinkSync19(lockPath);
|
|
25410
25779
|
} catch {} finally {
|
|
25411
25780
|
releaseGuard();
|
|
@@ -25417,8 +25786,8 @@ function acquireFileLock(path) {
|
|
|
25417
25786
|
const releaseGuard = acquireRecoveryGuard(lockPath);
|
|
25418
25787
|
try {
|
|
25419
25788
|
try {
|
|
25420
|
-
const staleToken =
|
|
25421
|
-
if (Date.now() - statSync15(lockPath).mtimeMs > FILE_LOCK_STALE_MS &&
|
|
25789
|
+
const staleToken = readFileSync24(lockPath, "utf-8");
|
|
25790
|
+
if (Date.now() - statSync15(lockPath).mtimeMs > FILE_LOCK_STALE_MS && readFileSync24(lockPath, "utf-8") === staleToken) {
|
|
25422
25791
|
unlinkSync19(lockPath);
|
|
25423
25792
|
continue;
|
|
25424
25793
|
}
|
|
@@ -25437,9 +25806,9 @@ function acquireFileLock(path) {
|
|
|
25437
25806
|
}
|
|
25438
25807
|
function atomicWriteFile(path, content) {
|
|
25439
25808
|
const temporaryPath = `${path}.tmp.${process.pid}.${Date.now()}`;
|
|
25440
|
-
|
|
25809
|
+
writeFileSync19(temporaryPath, content, "utf-8");
|
|
25441
25810
|
try {
|
|
25442
|
-
|
|
25811
|
+
renameSync12(temporaryPath, path);
|
|
25443
25812
|
} catch (error2) {
|
|
25444
25813
|
try {
|
|
25445
25814
|
unlinkSync19(temporaryPath);
|
|
@@ -25628,7 +25997,7 @@ function normalizeWikiSlug(raw) {
|
|
|
25628
25997
|
function parseWikiIndex(path) {
|
|
25629
25998
|
let text2;
|
|
25630
25999
|
try {
|
|
25631
|
-
text2 =
|
|
26000
|
+
text2 = readFileSync24(path, "utf-8");
|
|
25632
26001
|
} catch {
|
|
25633
26002
|
return [];
|
|
25634
26003
|
}
|
|
@@ -25774,15 +26143,15 @@ function wikiQuery(args) {
|
|
|
25774
26143
|
try {
|
|
25775
26144
|
for (const entry of readdirSync10(dir, { withFileTypes: true })) {
|
|
25776
26145
|
if (entry.isDirectory()) {
|
|
25777
|
-
const sub =
|
|
26146
|
+
const sub = join37(dir, entry.name);
|
|
25778
26147
|
if (label === "articles" || label === "skills") {
|
|
25779
26148
|
try {
|
|
25780
26149
|
for (const f of readdirSync10(sub, { withFileTypes: true })) {
|
|
25781
26150
|
if (!f.isFile() || !f.name.endsWith(".md"))
|
|
25782
26151
|
continue;
|
|
25783
|
-
const fp =
|
|
26152
|
+
const fp = join37(sub, f.name);
|
|
25784
26153
|
try {
|
|
25785
|
-
const text2 =
|
|
26154
|
+
const text2 = readFileSync24(fp, "utf-8");
|
|
25786
26155
|
const key = `${entry.name}/${f.name.replace(/\.md$/i, "")}`;
|
|
25787
26156
|
scored.push({
|
|
25788
26157
|
kind: label === "articles" ? "article" : "skill",
|
|
@@ -25801,9 +26170,9 @@ function wikiQuery(args) {
|
|
|
25801
26170
|
if (label === "topic" && canReadTopicMemory && !canReadTopicMemory(entry.name.replace(/\.md$/i, ""), runtime().userId)) {
|
|
25802
26171
|
continue;
|
|
25803
26172
|
}
|
|
25804
|
-
const fp =
|
|
26173
|
+
const fp = join37(dir, entry.name);
|
|
25805
26174
|
try {
|
|
25806
|
-
const text2 =
|
|
26175
|
+
const text2 = readFileSync24(fp, "utf-8");
|
|
25807
26176
|
const key = entry.name.replace(/\.md$/i, "");
|
|
25808
26177
|
const resultKind = label === "articles" ? "article" : label === "topic" ? "topic" : label === "summaries" ? "summary" : "skill";
|
|
25809
26178
|
scored.push({
|
|
@@ -26087,7 +26456,7 @@ function wikiTopicBrief(args) {
|
|
|
26087
26456
|
const topicKey = wikiSummarySlug(rawTopic.replace(/^topic\//, "").replace(/\.md$/i, ""));
|
|
26088
26457
|
const filePath = resolve17(runtime().topicsDir, `${topicKey}.md`);
|
|
26089
26458
|
try {
|
|
26090
|
-
const content =
|
|
26459
|
+
const content = readFileSync24(filePath, "utf-8");
|
|
26091
26460
|
return {
|
|
26092
26461
|
content: [{ type: "text", text: `# Topic Brief: ${topicKey}
|
|
26093
26462
|
|
|
@@ -26139,7 +26508,7 @@ Adopted topic memory: ${memoryKey}` }]
|
|
|
26139
26508
|
return { content: [{ type: "text", text: "Invalid wiki document key." }], isError: true };
|
|
26140
26509
|
}
|
|
26141
26510
|
try {
|
|
26142
|
-
const content =
|
|
26511
|
+
const content = readFileSync24(filePath, "utf-8");
|
|
26143
26512
|
return {
|
|
26144
26513
|
content: [{ type: "text", text: `# Wiki ${kind}: ${key}
|
|
26145
26514
|
|
|
@@ -26159,18 +26528,18 @@ function wikiLastConversation(args) {
|
|
|
26159
26528
|
const name = slugify(topicNameFrom(rawTopic));
|
|
26160
26529
|
const archiveDir = resolve17(runtime().archiveDir, name);
|
|
26161
26530
|
try {
|
|
26162
|
-
let files =
|
|
26531
|
+
let files = existsSync28(archiveDir) ? readdirSync10(archiveDir).filter((f) => f.endsWith(".jsonl")).sort().reverse() : [];
|
|
26163
26532
|
if (files.length === 0) {
|
|
26164
|
-
files =
|
|
26533
|
+
files = existsSync28(runtime().archiveDir) ? readdirSync10(runtime().archiveDir).filter((f) => f.startsWith(name) && f.endsWith(".jsonl")).sort().reverse() : [];
|
|
26165
26534
|
}
|
|
26166
26535
|
if (files.length === 0) {
|
|
26167
26536
|
return {
|
|
26168
26537
|
content: [{ type: "text", text: `No archive found for topic: ${rawTopic}` }]
|
|
26169
26538
|
};
|
|
26170
26539
|
}
|
|
26171
|
-
const actualPath = files[0].includes("/") ? files[0] : resolve17(
|
|
26540
|
+
const actualPath = files[0].includes("/") ? files[0] : resolve17(existsSync28(archiveDir) && readdirSync10(archiveDir).includes(files[0]) ? archiveDir : runtime().archiveDir, files[0]);
|
|
26172
26541
|
try {
|
|
26173
|
-
const raw =
|
|
26542
|
+
const raw = readFileSync24(actualPath, "utf-8");
|
|
26174
26543
|
const lines = raw.trim().split(`
|
|
26175
26544
|
`).filter(Boolean);
|
|
26176
26545
|
const turns2 = [];
|
|
@@ -26243,7 +26612,7 @@ function skillQuery(args) {
|
|
|
26243
26612
|
continue;
|
|
26244
26613
|
const skillFile = resolve17(runtime().skillsDir, entry.name, "skill.md");
|
|
26245
26614
|
try {
|
|
26246
|
-
const text2 =
|
|
26615
|
+
const text2 = readFileSync24(skillFile, "utf-8");
|
|
26247
26616
|
const lower = text2.toLowerCase();
|
|
26248
26617
|
let score = 0;
|
|
26249
26618
|
for (const w of query2.split(/\s+/).filter(Boolean)) {
|
|
@@ -26314,10 +26683,10 @@ ${fresh.join(`
|
|
|
26314
26683
|
const skillPath = resolve17(skillDir, "skill.md");
|
|
26315
26684
|
let finalContent = content;
|
|
26316
26685
|
try {
|
|
26317
|
-
const existing =
|
|
26686
|
+
const existing = readFileSync24(skillPath, "utf-8");
|
|
26318
26687
|
finalContent = mergeGotchas(content, extractGotchas(existing));
|
|
26319
26688
|
} catch {}
|
|
26320
|
-
|
|
26689
|
+
writeFileSync19(skillPath, finalContent, "utf-8");
|
|
26321
26690
|
return {
|
|
26322
26691
|
content: [
|
|
26323
26692
|
{
|
|
@@ -26336,7 +26705,7 @@ function writeSummaryDocument(rawTopic, content, dateStr) {
|
|
|
26336
26705
|
let counter = 1;
|
|
26337
26706
|
while (true) {
|
|
26338
26707
|
try {
|
|
26339
|
-
|
|
26708
|
+
writeFileSync19(resolve17(runtime().summariesDir, summaryName), content, {
|
|
26340
26709
|
encoding: "utf-8",
|
|
26341
26710
|
flag: "wx"
|
|
26342
26711
|
});
|
|
@@ -26354,7 +26723,7 @@ function writeSummaryDocument(rawTopic, content, dateStr) {
|
|
|
26354
26723
|
if (topicId && setTopicBrief2) {
|
|
26355
26724
|
try {
|
|
26356
26725
|
const briefPath = resolve17(runtime().topicsDir, `${fileSlug}.md`);
|
|
26357
|
-
let briefMd =
|
|
26726
|
+
let briefMd = existsSync28(briefPath) ? readFileSync24(briefPath, "utf-8") : undefined;
|
|
26358
26727
|
if (briefMd === undefined && getTopicBrief2 && !getTopicBrief2(fileSlug)?.briefMd) {
|
|
26359
26728
|
const legacy = getTopicBrief2(topicId)?.briefMd;
|
|
26360
26729
|
if (legacy)
|
|
@@ -26374,9 +26743,9 @@ function writeTopicDocument(rawTopic, content) {
|
|
|
26374
26743
|
const topicId = runtime().topicId;
|
|
26375
26744
|
const fileSlug = wikiBriefStorageKey(rawTopic, topicId);
|
|
26376
26745
|
const briefPath = resolve17(runtime().topicsDir, `${fileSlug}.md`);
|
|
26377
|
-
ensureDir(
|
|
26378
|
-
const existed =
|
|
26379
|
-
|
|
26746
|
+
ensureDir(dirname17(briefPath));
|
|
26747
|
+
const existed = existsSync28(briefPath);
|
|
26748
|
+
writeFileSync19(briefPath, content, "utf-8");
|
|
26380
26749
|
const setTopicBrief2 = runtime().host.setTopicBrief;
|
|
26381
26750
|
let sqliteUpdated = false;
|
|
26382
26751
|
if (topicId && setTopicBrief2) {
|
|
@@ -26389,8 +26758,8 @@ function writeTopicDocument(rawTopic, content) {
|
|
|
26389
26758
|
}
|
|
26390
26759
|
function writeArticleDocument(slug, content) {
|
|
26391
26760
|
const articlePath = resolve17(runtime().articlesDir, `${slug}.md`);
|
|
26392
|
-
ensureDir(
|
|
26393
|
-
const existed =
|
|
26761
|
+
ensureDir(dirname17(articlePath));
|
|
26762
|
+
const existed = existsSync28(articlePath);
|
|
26394
26763
|
atomicWriteFile(articlePath, content);
|
|
26395
26764
|
return { existed };
|
|
26396
26765
|
}
|
|
@@ -26522,21 +26891,21 @@ function collectIndexableDocuments() {
|
|
|
26522
26891
|
}
|
|
26523
26892
|
for (const entry of entries) {
|
|
26524
26893
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
26525
|
-
add(kind, entry.name.replace(/\.md$/i, ""),
|
|
26894
|
+
add(kind, entry.name.replace(/\.md$/i, ""), join37(root, entry.name));
|
|
26526
26895
|
continue;
|
|
26527
26896
|
}
|
|
26528
26897
|
if (!entry.isDirectory() || !allowSubdirectories)
|
|
26529
26898
|
continue;
|
|
26530
26899
|
let nested;
|
|
26531
26900
|
try {
|
|
26532
|
-
nested = readdirSync10(
|
|
26901
|
+
nested = readdirSync10(join37(root, entry.name), { withFileTypes: true });
|
|
26533
26902
|
} catch {
|
|
26534
26903
|
continue;
|
|
26535
26904
|
}
|
|
26536
26905
|
for (const file of nested) {
|
|
26537
26906
|
if (!file.isFile() || !file.name.endsWith(".md"))
|
|
26538
26907
|
continue;
|
|
26539
|
-
add(kind, `${entry.name}/${file.name.replace(/\.md$/i, "")}`,
|
|
26908
|
+
add(kind, `${entry.name}/${file.name.replace(/\.md$/i, "")}`, join37(root, entry.name, file.name));
|
|
26540
26909
|
}
|
|
26541
26910
|
}
|
|
26542
26911
|
}
|
|
@@ -26593,12 +26962,12 @@ function indexRowOrPartialWrite(label, options) {
|
|
|
26593
26962
|
function upsertIndexRow(options) {
|
|
26594
26963
|
const { kind, slug, description, date, section } = options;
|
|
26595
26964
|
const indexPath = wikiIndexPath(kind);
|
|
26596
|
-
ensureDir(
|
|
26965
|
+
ensureDir(dirname17(indexPath));
|
|
26597
26966
|
const releaseLock = acquireFileLock(indexPath);
|
|
26598
26967
|
try {
|
|
26599
26968
|
let index;
|
|
26600
26969
|
try {
|
|
26601
|
-
index =
|
|
26970
|
+
index = readFileSync24(indexPath, "utf-8");
|
|
26602
26971
|
} catch {
|
|
26603
26972
|
index = "";
|
|
26604
26973
|
}
|
|
@@ -27037,15 +27406,15 @@ __export(exports_activity_log, {
|
|
|
27037
27406
|
readSentFilesForTopic: () => readSentFilesForTopic
|
|
27038
27407
|
});
|
|
27039
27408
|
import {
|
|
27040
|
-
existsSync as
|
|
27041
|
-
mkdirSync as
|
|
27409
|
+
existsSync as existsSync29,
|
|
27410
|
+
mkdirSync as mkdirSync26,
|
|
27042
27411
|
readdirSync as readdirSync11,
|
|
27043
|
-
renameSync as
|
|
27412
|
+
renameSync as renameSync13,
|
|
27044
27413
|
statSync as statSync16,
|
|
27045
27414
|
unlinkSync as unlinkSync20,
|
|
27046
|
-
writeFileSync as
|
|
27415
|
+
writeFileSync as writeFileSync20
|
|
27047
27416
|
} from "fs";
|
|
27048
|
-
import { basename as basename8, join as
|
|
27417
|
+
import { basename as basename8, join as join38 } from "path";
|
|
27049
27418
|
function startLogRotation() {
|
|
27050
27419
|
if (rotationTimer)
|
|
27051
27420
|
return;
|
|
@@ -27056,15 +27425,15 @@ function startLogRotation() {
|
|
|
27056
27425
|
function writeLog(entry) {
|
|
27057
27426
|
startLogRotation();
|
|
27058
27427
|
const logDir = resolveStorageLogDir();
|
|
27059
|
-
|
|
27428
|
+
mkdirSync26(logDir, { recursive: true });
|
|
27060
27429
|
const safeSession = sanitizeTopicName(entry.session);
|
|
27061
27430
|
const sidShort = entry.sessionId ? entry.sessionId.slice(0, 8) : "new";
|
|
27062
|
-
const file =
|
|
27431
|
+
const file = join38(logDir, `${entry.userId}_${safeSession}_${sidShort}.jsonl`);
|
|
27063
27432
|
try {
|
|
27064
27433
|
const stat = statSync16(file);
|
|
27065
27434
|
if (stat.size >= MAX_FILE_SIZE) {
|
|
27066
27435
|
const rotated = file.replace(/\.jsonl$/, `.${Date.now()}.jsonl`);
|
|
27067
|
-
|
|
27436
|
+
renameSync13(file, rotated);
|
|
27068
27437
|
}
|
|
27069
27438
|
} catch {}
|
|
27070
27439
|
try {
|
|
@@ -27075,11 +27444,11 @@ function writeLog(entry) {
|
|
|
27075
27444
|
}
|
|
27076
27445
|
function rotateOldLogs() {
|
|
27077
27446
|
const logDir = resolveStorageLogDir();
|
|
27078
|
-
|
|
27447
|
+
mkdirSync26(logDir, { recursive: true });
|
|
27079
27448
|
try {
|
|
27080
27449
|
const files = readdirSync11(logDir).filter((f) => f.endsWith(".jsonl")).map((f) => {
|
|
27081
27450
|
try {
|
|
27082
|
-
const stat = statSync16(
|
|
27451
|
+
const stat = statSync16(join38(logDir, f));
|
|
27083
27452
|
return { name: f, size: stat.size, mtimeMs: stat.mtimeMs };
|
|
27084
27453
|
} catch {
|
|
27085
27454
|
return null;
|
|
@@ -27095,7 +27464,7 @@ function rotateOldLogs() {
|
|
|
27095
27464
|
if (freed >= toFree)
|
|
27096
27465
|
break;
|
|
27097
27466
|
try {
|
|
27098
|
-
unlinkSync20(
|
|
27467
|
+
unlinkSync20(join38(logDir, file.name));
|
|
27099
27468
|
freed += file.size;
|
|
27100
27469
|
logger.info({ file: file.name, sizeKB: (file.size / 1024).toFixed(0) }, "Rotated out old log");
|
|
27101
27470
|
} catch (e) {
|
|
@@ -27107,9 +27476,9 @@ function rotateOldLogs() {
|
|
|
27107
27476
|
}
|
|
27108
27477
|
}
|
|
27109
27478
|
function writeSentFileLog(entry) {
|
|
27110
|
-
const dir =
|
|
27111
|
-
|
|
27112
|
-
const file =
|
|
27479
|
+
const dir = join38(resolveStorageUsersLogDir(), String(entry.userId));
|
|
27480
|
+
mkdirSync26(dir, { recursive: true });
|
|
27481
|
+
const file = join38(dir, "sent-files.jsonl");
|
|
27113
27482
|
try {
|
|
27114
27483
|
appendJsonlEntry(file, { ...entry, fileName: basename8(entry.filePath) });
|
|
27115
27484
|
} catch (e) {
|
|
@@ -27120,8 +27489,8 @@ function entryMatchesTopic(e, topicName) {
|
|
|
27120
27489
|
return e.topicName === topicName;
|
|
27121
27490
|
}
|
|
27122
27491
|
function readSentFilesForTopic(userId, topicName) {
|
|
27123
|
-
const file =
|
|
27124
|
-
if (!
|
|
27492
|
+
const file = join38(resolveStorageUsersLogDir(), String(userId), "sent-files.jsonl");
|
|
27493
|
+
if (!existsSync29(file))
|
|
27125
27494
|
return [];
|
|
27126
27495
|
try {
|
|
27127
27496
|
return readJsonlLines(file).map((line) => JSON.parse(line)).filter((e) => entryMatchesTopic(e, topicName));
|
|
@@ -27131,8 +27500,8 @@ function readSentFilesForTopic(userId, topicName) {
|
|
|
27131
27500
|
}
|
|
27132
27501
|
}
|
|
27133
27502
|
function removeSentFilesForTopic(userId, topicName) {
|
|
27134
|
-
const file =
|
|
27135
|
-
if (!
|
|
27503
|
+
const file = join38(resolveStorageUsersLogDir(), String(userId), "sent-files.jsonl");
|
|
27504
|
+
if (!existsSync29(file))
|
|
27136
27505
|
return;
|
|
27137
27506
|
try {
|
|
27138
27507
|
const remaining = readJsonlLines(file).filter((line) => {
|
|
@@ -27143,7 +27512,7 @@ function removeSentFilesForTopic(userId, topicName) {
|
|
|
27143
27512
|
}
|
|
27144
27513
|
});
|
|
27145
27514
|
if (remaining.length > 0) {
|
|
27146
|
-
|
|
27515
|
+
writeFileSync20(file, `${remaining.join(`
|
|
27147
27516
|
`)}
|
|
27148
27517
|
`);
|
|
27149
27518
|
} else {
|
|
@@ -27640,6 +28009,8 @@ __export(exports_storage_public, {
|
|
|
27640
28009
|
writeTasks: () => writeTasks,
|
|
27641
28010
|
writeSentFileLog: () => writeSentFileLog,
|
|
27642
28011
|
writeLog: () => writeLog,
|
|
28012
|
+
writeDecisions: () => writeDecisions,
|
|
28013
|
+
writeDecisionGraphSvg: () => writeDecisionGraphSvg,
|
|
27643
28014
|
wikiSummaryStorageSlug: () => wikiSummaryStorageSlug,
|
|
27644
28015
|
wikiSummarySlug: () => wikiSummarySlug,
|
|
27645
28016
|
wikiSummaryNames: () => exports_wiki_summary_names,
|
|
@@ -27648,9 +28019,11 @@ __export(exports_storage_public, {
|
|
|
27648
28019
|
wiki: () => exports_wiki,
|
|
27649
28020
|
waitForRuntimeProcessLease: () => waitForRuntimeProcessLease,
|
|
27650
28021
|
waitForRequiredRuntimeProcessLease: () => waitForRequiredRuntimeProcessLease,
|
|
28022
|
+
validateDecisionGraph: () => validateDecisionGraph,
|
|
27651
28023
|
upsertTopic: () => upsertTopic,
|
|
27652
28024
|
updateTopicThreadId: () => updateTopicThreadId,
|
|
27653
28025
|
updateTasks: () => updateTasks,
|
|
28026
|
+
updateDecisions: () => updateDecisions,
|
|
27654
28027
|
updateApiMessageUsage: () => updateApiMessageUsage,
|
|
27655
28028
|
updateApiMessageText: () => updateApiMessageText,
|
|
27656
28029
|
updateApiMessageSubagentCard: () => updateApiMessageSubagentCard,
|
|
@@ -27699,6 +28072,7 @@ __export(exports_storage_public, {
|
|
|
27699
28072
|
replaceConversationStrict: () => replaceConversationStrict,
|
|
27700
28073
|
reparentTopicChildren: () => reparentTopicChildren,
|
|
27701
28074
|
renderTaskList: () => renderTaskList,
|
|
28075
|
+
renderDecisionList: () => renderDecisionList,
|
|
27702
28076
|
renameTopic: () => renameTopic,
|
|
27703
28077
|
removeTopic: () => removeTopic,
|
|
27704
28078
|
removeSentFilesForTopic: () => removeSentFilesForTopic,
|
|
@@ -27710,6 +28084,7 @@ __export(exports_storage_public, {
|
|
|
27710
28084
|
readTasks: () => readTasks,
|
|
27711
28085
|
readSentFilesForTopic: () => readSentFilesForTopic,
|
|
27712
28086
|
readRawConversation: () => readRawConversation,
|
|
28087
|
+
readDecisions: () => readDecisions,
|
|
27713
28088
|
readConversation: () => readConversation,
|
|
27714
28089
|
quarantineForeignAskUserGates: () => quarantineForeignAskUserGates,
|
|
27715
28090
|
quarantineAskUserGate: () => quarantineAskUserGate,
|
|
@@ -27771,6 +28146,8 @@ __export(exports_storage_public, {
|
|
|
27771
28146
|
getGlobalAiName: () => getGlobalAiName,
|
|
27772
28147
|
getForumTopicByName: () => getTopicByName2,
|
|
27773
28148
|
getDmSessionId: () => getDmSessionId,
|
|
28149
|
+
getDecisionGraphSvgPath: () => getDecisionGraphSvgPath,
|
|
28150
|
+
getDecisionFilePath: () => getDecisionFilePath,
|
|
27774
28151
|
getConversationPath: () => getConversationPath,
|
|
27775
28152
|
getCommunicateThreadId: () => getCommunicateThreadId,
|
|
27776
28153
|
getApiTopicConfig: () => getApiTopicConfig,
|
|
@@ -27794,12 +28171,16 @@ __export(exports_storage_public, {
|
|
|
27794
28171
|
deleteTasks: () => deleteTasks,
|
|
27795
28172
|
deletePendingAsksForTopic: () => deletePendingAsksForTopic,
|
|
27796
28173
|
deleteMessagesForTopic: () => deleteMessagesForTopic,
|
|
28174
|
+
deleteDecisions: () => deleteDecisions,
|
|
27797
28175
|
deleteApiTopicConfig: () => deleteApiTopicConfig,
|
|
27798
28176
|
defaultTopicSurface: () => defaultTopicSurface,
|
|
27799
28177
|
defaultSurfaceScope: () => defaultSurfaceScope,
|
|
28178
|
+
decisions: () => exports_decisions,
|
|
28179
|
+
decisionScopeKey: () => decisionScopeKey,
|
|
27800
28180
|
db: () => db2,
|
|
27801
28181
|
createTasks: () => createTasks,
|
|
27802
28182
|
createPendingAsk: () => createPendingAsk,
|
|
28183
|
+
createDecisions: () => createDecisions,
|
|
27803
28184
|
copyMessagesForTopic: () => copyMessagesForTopic,
|
|
27804
28185
|
copyMessageSnapshotToTopic: () => copyMessageSnapshotToTopic,
|
|
27805
28186
|
conversations: () => exports_conversations,
|
|
@@ -27838,6 +28219,7 @@ __export(exports_storage_public, {
|
|
|
27838
28219
|
PROCESS_LEASE_HEARTBEAT_MS: () => PROCESS_LEASE_HEARTBEAT_MS,
|
|
27839
28220
|
PENDING_ASK_TTL_MS: () => PENDING_ASK_TTL_MS,
|
|
27840
28221
|
DEFAULT_AI_NAME: () => DEFAULT_AI_NAME,
|
|
28222
|
+
DECISION_STATUS_VALUES: () => DECISION_STATUS_VALUES,
|
|
27841
28223
|
ConversationLogDivergedError: () => ConversationLogDivergedError
|
|
27842
28224
|
});
|
|
27843
28225
|
var init_storage_public = __esm(async () => {
|
|
@@ -27857,6 +28239,8 @@ var init_storage_public = __esm(async () => {
|
|
|
27857
28239
|
init_ask_user_gates();
|
|
27858
28240
|
await init_conversations();
|
|
27859
28241
|
init_conversations();
|
|
28242
|
+
await init_decisions();
|
|
28243
|
+
init_decisions();
|
|
27860
28244
|
await init_forum();
|
|
27861
28245
|
await init_forum();
|
|
27862
28246
|
await init_runtime_process_leases();
|
|
@@ -27895,6 +28279,16 @@ async function buildHostedSurfaceServer(surface, context) {
|
|
|
27895
28279
|
topicId: context.topicId
|
|
27896
28280
|
});
|
|
27897
28281
|
}
|
|
28282
|
+
case "decision": {
|
|
28283
|
+
const { createDecisionMcpServer: createDecisionMcpServer2 } = await init_decision().then(() => exports_decision);
|
|
28284
|
+
return createDecisionMcpServer2({
|
|
28285
|
+
userId: context.userId,
|
|
28286
|
+
topic: context.topicTitle,
|
|
28287
|
+
topicId: context.topicId,
|
|
28288
|
+
agent: context.agent,
|
|
28289
|
+
model: context.model
|
|
28290
|
+
});
|
|
28291
|
+
}
|
|
27898
28292
|
case "token-stats": {
|
|
27899
28293
|
const { createTokenStatsMcpServer: createTokenStatsMcpServer2 } = await init_token_stats2().then(() => exports_token_stats2);
|
|
27900
28294
|
return createTokenStatsMcpServer2({ userId: context.userId });
|
|
@@ -27979,6 +28373,7 @@ var ACTIVE_HOSTED_MCP_SURFACES;
|
|
|
27979
28373
|
var init_hosted_surfaces = __esm(() => {
|
|
27980
28374
|
ACTIVE_HOSTED_MCP_SURFACES = [
|
|
27981
28375
|
"task",
|
|
28376
|
+
"decision",
|
|
27982
28377
|
"token-stats",
|
|
27983
28378
|
"system-health",
|
|
27984
28379
|
"vault",
|
|
@@ -28098,9 +28493,9 @@ var init_sse_transport = () => {};
|
|
|
28098
28493
|
import { createHash as createHash10, randomUUID as randomUUID23 } from "crypto";
|
|
28099
28494
|
import { realpathSync as realpathSync6, statSync as statSync17 } from "fs";
|
|
28100
28495
|
import { basename as basename9, extname as extname3, resolve as resolve19 } from "path";
|
|
28101
|
-
import { McpServer as
|
|
28496
|
+
import { McpServer as McpServer8 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
28102
28497
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
28103
|
-
import { z as
|
|
28498
|
+
import { z as z13 } from "zod";
|
|
28104
28499
|
function requireTopicAccess(ctx) {
|
|
28105
28500
|
const topic = getTopic(ctx.topicId);
|
|
28106
28501
|
if (!topic)
|
|
@@ -28219,7 +28614,7 @@ function appendAutoContinue(ctx, field) {
|
|
|
28219
28614
|
});
|
|
28220
28615
|
}
|
|
28221
28616
|
function buildNegotiumMcpServer(ctx) {
|
|
28222
|
-
const server = new
|
|
28617
|
+
const server = new McpServer8({ name: RUNTIME_MCP_KEY, version: "1.0.0" });
|
|
28223
28618
|
if (ctx.visualTools === true) {
|
|
28224
28619
|
for (const def of visualToolDefinitions) {
|
|
28225
28620
|
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;
|
|
@@ -28320,8 +28715,8 @@ function buildNegotiumMcpServer(ctx) {
|
|
|
28320
28715
|
}
|
|
28321
28716
|
}
|
|
28322
28717
|
if (ctx.fileDeliveryTools === true) {
|
|
28323
|
-
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:
|
|
28324
|
-
server.tool("send_files", "Send multiple local files to the user in the chat at once.", { file_paths:
|
|
28718
|
+
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: z13.string().describe("Absolute path to the file to send") }, async ({ file_path }) => deliverFile(ctx, file_path));
|
|
28719
|
+
server.tool("send_files", "Send multiple local files to the user in the chat at once.", { file_paths: z13.array(z13.string()).describe("Array of absolute file paths to send") }, async ({ file_paths }) => {
|
|
28325
28720
|
const results = await Promise.all(file_paths.map((filePath) => deliverFile(ctx, filePath)));
|
|
28326
28721
|
const hasError = results.some((result) => ("isError" in result) && result.isError);
|
|
28327
28722
|
return {
|
|
@@ -28619,23 +29014,23 @@ function defaultManifestFile() {
|
|
|
28619
29014
|
var init_paths = () => {};
|
|
28620
29015
|
|
|
28621
29016
|
// ../../packages/mcp-host/src/spec.ts
|
|
28622
|
-
import { z as
|
|
29017
|
+
import { z as z14 } from "zod";
|
|
28623
29018
|
var KEY_PATTERN, portSchema, mcpServerSpecSchema;
|
|
28624
29019
|
var init_spec = __esm(() => {
|
|
28625
29020
|
KEY_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
28626
|
-
portSchema =
|
|
28627
|
-
mcpServerSpecSchema =
|
|
28628
|
-
key:
|
|
28629
|
-
transport:
|
|
28630
|
-
command:
|
|
28631
|
-
args:
|
|
28632
|
-
env:
|
|
28633
|
-
cwd:
|
|
28634
|
-
portRange:
|
|
28635
|
-
scope:
|
|
28636
|
-
readyTimeoutMs:
|
|
28637
|
-
idleEvictMs:
|
|
28638
|
-
healthIntervalMs:
|
|
29021
|
+
portSchema = z14.number().int().min(1).max(65535);
|
|
29022
|
+
mcpServerSpecSchema = z14.object({
|
|
29023
|
+
key: z14.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"),
|
|
29024
|
+
transport: z14.enum(["stdio", "http"]),
|
|
29025
|
+
command: z14.string().min(1),
|
|
29026
|
+
args: z14.array(z14.string()).optional(),
|
|
29027
|
+
env: z14.record(z14.string(), z14.string()).optional(),
|
|
29028
|
+
cwd: z14.string().optional(),
|
|
29029
|
+
portRange: z14.object({ base: portSchema, max: portSchema }).optional(),
|
|
29030
|
+
scope: z14.enum(["node", "instance"]),
|
|
29031
|
+
readyTimeoutMs: z14.number().int().positive().optional(),
|
|
29032
|
+
idleEvictMs: z14.number().int().positive().optional(),
|
|
29033
|
+
healthIntervalMs: z14.number().int().positive().optional()
|
|
28639
29034
|
}).superRefine((spec, ctx) => {
|
|
28640
29035
|
if (spec.transport === "http") {
|
|
28641
29036
|
if (!spec.portRange) {
|
|
@@ -28656,9 +29051,9 @@ var init_spec = __esm(() => {
|
|
|
28656
29051
|
});
|
|
28657
29052
|
|
|
28658
29053
|
// ../../packages/mcp-host/src/manifest.ts
|
|
28659
|
-
import { existsSync as
|
|
28660
|
-
import { dirname as
|
|
28661
|
-
import { z as
|
|
29054
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync27, readFileSync as readFileSync25, renameSync as renameSync14, writeFileSync as writeFileSync21 } from "fs";
|
|
29055
|
+
import { dirname as dirname18 } from "path";
|
|
29056
|
+
import { z as z15 } from "zod";
|
|
28662
29057
|
|
|
28663
29058
|
class McpManifest {
|
|
28664
29059
|
file;
|
|
@@ -28701,11 +29096,11 @@ class McpManifest {
|
|
|
28701
29096
|
return this.entries.get(key)?.enabled ?? false;
|
|
28702
29097
|
}
|
|
28703
29098
|
load() {
|
|
28704
|
-
if (!
|
|
29099
|
+
if (!existsSync30(this.file))
|
|
28705
29100
|
return;
|
|
28706
29101
|
let parsed;
|
|
28707
29102
|
try {
|
|
28708
|
-
parsed = manifestFileSchema.parse(JSON.parse(
|
|
29103
|
+
parsed = manifestFileSchema.parse(JSON.parse(readFileSync25(this.file, "utf8")));
|
|
28709
29104
|
} catch (e) {
|
|
28710
29105
|
throw new Error(`Invalid MCP manifest at ${this.file}: ${e instanceof Error ? e.message : String(e)}`);
|
|
28711
29106
|
}
|
|
@@ -28717,28 +29112,28 @@ class McpManifest {
|
|
|
28717
29112
|
}
|
|
28718
29113
|
}
|
|
28719
29114
|
save() {
|
|
28720
|
-
|
|
29115
|
+
mkdirSync27(dirname18(this.file), { recursive: true });
|
|
28721
29116
|
const payload = {
|
|
28722
29117
|
version: 1,
|
|
28723
29118
|
servers: [...this.entries.values()]
|
|
28724
29119
|
};
|
|
28725
29120
|
const tmp = `${this.file}.tmp-${process.pid}`;
|
|
28726
|
-
|
|
29121
|
+
writeFileSync21(tmp, `${JSON.stringify(payload, null, 2)}
|
|
28727
29122
|
`);
|
|
28728
|
-
|
|
29123
|
+
renameSync14(tmp, this.file);
|
|
28729
29124
|
}
|
|
28730
29125
|
}
|
|
28731
29126
|
var manifestEntrySchema, manifestFileSchema;
|
|
28732
29127
|
var init_manifest = __esm(() => {
|
|
28733
29128
|
init_paths();
|
|
28734
29129
|
init_spec();
|
|
28735
|
-
manifestEntrySchema =
|
|
29130
|
+
manifestEntrySchema = z15.object({
|
|
28736
29131
|
spec: mcpServerSpecSchema,
|
|
28737
|
-
enabled:
|
|
29132
|
+
enabled: z15.boolean()
|
|
28738
29133
|
});
|
|
28739
|
-
manifestFileSchema =
|
|
28740
|
-
version:
|
|
28741
|
-
servers:
|
|
29134
|
+
manifestFileSchema = z15.object({
|
|
29135
|
+
version: z15.literal(1),
|
|
29136
|
+
servers: z15.array(manifestEntrySchema)
|
|
28742
29137
|
});
|
|
28743
29138
|
});
|
|
28744
29139
|
|
|
@@ -28753,15 +29148,15 @@ function sanitizePathComponent(name) {
|
|
|
28753
29148
|
// ../../packages/mcp-host/src/manager.ts
|
|
28754
29149
|
import { execFileSync as execFileSync8, spawn as spawn8 } from "child_process";
|
|
28755
29150
|
import {
|
|
28756
|
-
mkdirSync as
|
|
29151
|
+
mkdirSync as mkdirSync28,
|
|
28757
29152
|
readdirSync as readdirSync12,
|
|
28758
|
-
readFileSync as
|
|
28759
|
-
renameSync as
|
|
29153
|
+
readFileSync as readFileSync26,
|
|
29154
|
+
renameSync as renameSync15,
|
|
28760
29155
|
unlinkSync as unlinkSync21,
|
|
28761
|
-
writeFileSync as
|
|
29156
|
+
writeFileSync as writeFileSync22
|
|
28762
29157
|
} from "fs";
|
|
28763
29158
|
import { connect, createServer as createServer3 } from "net";
|
|
28764
|
-
import { join as
|
|
29159
|
+
import { join as join39 } from "path";
|
|
28765
29160
|
function regKey(key, instanceKey) {
|
|
28766
29161
|
return `${key}\x00${instanceKey}`;
|
|
28767
29162
|
}
|
|
@@ -29027,7 +29422,7 @@ class McpHost {
|
|
|
29027
29422
|
const range = spec.portRange;
|
|
29028
29423
|
if (!range)
|
|
29029
29424
|
throw new Error(`MCP spec "${spec.key}" is http but has no portRange`);
|
|
29030
|
-
|
|
29425
|
+
mkdirSync28(this.portsDir, { recursive: true });
|
|
29031
29426
|
const claims = this.readPortClaims();
|
|
29032
29427
|
const own = claims.get(fileName);
|
|
29033
29428
|
if (own !== undefined && own >= range.base && own <= range.max && !this.usedPorts.has(own)) {
|
|
@@ -29063,7 +29458,7 @@ class McpHost {
|
|
|
29063
29458
|
if (file.includes(".tmp-"))
|
|
29064
29459
|
continue;
|
|
29065
29460
|
try {
|
|
29066
|
-
const port = Number.parseInt(
|
|
29461
|
+
const port = Number.parseInt(readFileSync26(join39(this.portsDir, file), "utf8").trim(), 10);
|
|
29067
29462
|
if (!Number.isNaN(port))
|
|
29068
29463
|
claims.set(file, port);
|
|
29069
29464
|
} catch {}
|
|
@@ -29072,18 +29467,18 @@ class McpHost {
|
|
|
29072
29467
|
}
|
|
29073
29468
|
writePortFile(fileName, port) {
|
|
29074
29469
|
try {
|
|
29075
|
-
|
|
29076
|
-
const file =
|
|
29470
|
+
mkdirSync28(this.portsDir, { recursive: true });
|
|
29471
|
+
const file = join39(this.portsDir, fileName);
|
|
29077
29472
|
const tmp = `${file}.tmp-${process.pid}`;
|
|
29078
|
-
|
|
29079
|
-
|
|
29473
|
+
writeFileSync22(tmp, String(port));
|
|
29474
|
+
renameSync15(tmp, file);
|
|
29080
29475
|
} catch (e) {
|
|
29081
29476
|
this.log("warn", "Failed to write MCP port file", { fileName, port, err: String(e) });
|
|
29082
29477
|
}
|
|
29083
29478
|
}
|
|
29084
29479
|
deletePortFile(fileName) {
|
|
29085
29480
|
try {
|
|
29086
|
-
unlinkSync21(
|
|
29481
|
+
unlinkSync21(join39(this.portsDir, fileName));
|
|
29087
29482
|
} catch {}
|
|
29088
29483
|
}
|
|
29089
29484
|
cleanupRunning(rkey, inst) {
|
|
@@ -29188,14 +29583,14 @@ var init_src3 = __esm(() => {
|
|
|
29188
29583
|
import { randomUUID as randomUUID24 } from "crypto";
|
|
29189
29584
|
import {
|
|
29190
29585
|
copyFileSync as copyFileSync4,
|
|
29191
|
-
existsSync as
|
|
29192
|
-
mkdirSync as
|
|
29193
|
-
readFileSync as
|
|
29586
|
+
existsSync as existsSync31,
|
|
29587
|
+
mkdirSync as mkdirSync29,
|
|
29588
|
+
readFileSync as readFileSync27,
|
|
29194
29589
|
rmSync as rmSync9,
|
|
29195
29590
|
statSync as statSync18,
|
|
29196
|
-
writeFileSync as
|
|
29591
|
+
writeFileSync as writeFileSync23
|
|
29197
29592
|
} from "fs";
|
|
29198
|
-
import { basename as basename10, extname as extname4, join as
|
|
29593
|
+
import { basename as basename10, extname as extname4, join as join40 } from "path";
|
|
29199
29594
|
function safeExtension(filename) {
|
|
29200
29595
|
const extension = extname4(basename10(filename));
|
|
29201
29596
|
return /^\.[A-Za-z0-9]{1,16}$/.test(extension) ? extension.toLowerCase() : "";
|
|
@@ -29213,20 +29608,20 @@ function contentDisposition(filename) {
|
|
|
29213
29608
|
|
|
29214
29609
|
class NodeFileStore {
|
|
29215
29610
|
uploadDir;
|
|
29216
|
-
constructor(uploadDir =
|
|
29611
|
+
constructor(uploadDir = join40(DATA_DIR, "uploads")) {
|
|
29217
29612
|
this.uploadDir = uploadDir;
|
|
29218
29613
|
}
|
|
29219
29614
|
#ensureDir() {
|
|
29220
|
-
|
|
29615
|
+
mkdirSync29(this.uploadDir, { recursive: true });
|
|
29221
29616
|
}
|
|
29222
29617
|
#metadataPath(fileId) {
|
|
29223
|
-
return
|
|
29618
|
+
return join40(this.uploadDir, `${fileId}.meta.json`);
|
|
29224
29619
|
}
|
|
29225
29620
|
#metadata(fileId) {
|
|
29226
29621
|
if (!FILE_ID_RE.test(fileId))
|
|
29227
29622
|
return null;
|
|
29228
29623
|
try {
|
|
29229
|
-
const parsed = JSON.parse(
|
|
29624
|
+
const parsed = JSON.parse(readFileSync27(this.#metadataPath(fileId), "utf8"));
|
|
29230
29625
|
if (typeof parsed.filename !== "string" || typeof parsed.mimeType !== "string" || typeof parsed.sizeBytes !== "number" || typeof parsed.savedName !== "string") {
|
|
29231
29626
|
return null;
|
|
29232
29627
|
}
|
|
@@ -29249,7 +29644,7 @@ class NodeFileStore {
|
|
|
29249
29644
|
hooks = {
|
|
29250
29645
|
resolveAttachmentByFileId: (fileId) => {
|
|
29251
29646
|
const metadata = this.#metadata(fileId);
|
|
29252
|
-
if (!metadata || !
|
|
29647
|
+
if (!metadata || !existsSync31(join40(this.uploadDir, metadata.savedName)))
|
|
29253
29648
|
return null;
|
|
29254
29649
|
return this.#attachment(fileId, metadata);
|
|
29255
29650
|
},
|
|
@@ -29257,8 +29652,8 @@ class NodeFileStore {
|
|
|
29257
29652
|
const metadata = this.#metadata(fileId);
|
|
29258
29653
|
if (!metadata)
|
|
29259
29654
|
return null;
|
|
29260
|
-
const path =
|
|
29261
|
-
return
|
|
29655
|
+
const path = join40(this.uploadDir, metadata.savedName);
|
|
29656
|
+
return existsSync31(path) ? path : null;
|
|
29262
29657
|
},
|
|
29263
29658
|
storeLocalFileAsUpload: (absPath, access = {}) => this.store(absPath, access),
|
|
29264
29659
|
deleteFilesForTopic: (topicId) => this.deleteForTopic(topicId)
|
|
@@ -29268,7 +29663,7 @@ class NodeFileStore {
|
|
|
29268
29663
|
const fileId = randomUUID24();
|
|
29269
29664
|
const extension = safeExtension(absPath);
|
|
29270
29665
|
const savedName = `${fileId}${extension}`;
|
|
29271
|
-
const savedPath =
|
|
29666
|
+
const savedPath = join40(this.uploadDir, savedName);
|
|
29272
29667
|
try {
|
|
29273
29668
|
const stats = statSync18(absPath);
|
|
29274
29669
|
if (!stats.isFile() || stats.size > MAX_UPLOAD_BYTES)
|
|
@@ -29283,7 +29678,7 @@ class NodeFileStore {
|
|
|
29283
29678
|
...access.visibility ? { visibility: access.visibility } : {}
|
|
29284
29679
|
};
|
|
29285
29680
|
copyFileSync4(absPath, savedPath);
|
|
29286
|
-
|
|
29681
|
+
writeFileSync23(this.#metadataPath(fileId), JSON.stringify(metadata), { mode: 384 });
|
|
29287
29682
|
return this.#attachment(fileId, metadata);
|
|
29288
29683
|
} catch (error2) {
|
|
29289
29684
|
rmSync9(savedPath, { force: true });
|
|
@@ -29300,8 +29695,8 @@ class NodeFileStore {
|
|
|
29300
29695
|
const allowed = metadata.visibility === "workspace" || metadata.ownerUserId === userId || Boolean(topic && isParticipant(topic, userId));
|
|
29301
29696
|
if (!allowed)
|
|
29302
29697
|
return null;
|
|
29303
|
-
const path =
|
|
29304
|
-
if (!
|
|
29698
|
+
const path = join40(this.uploadDir, metadata.savedName);
|
|
29699
|
+
if (!existsSync31(path))
|
|
29305
29700
|
return null;
|
|
29306
29701
|
return new Response(Bun.file(path), {
|
|
29307
29702
|
headers: {
|
|
@@ -29319,7 +29714,7 @@ class NodeFileStore {
|
|
|
29319
29714
|
const metadata = this.#metadata(fileId);
|
|
29320
29715
|
if (metadata?.topicId !== topicId)
|
|
29321
29716
|
continue;
|
|
29322
|
-
rmSync9(
|
|
29717
|
+
rmSync9(join40(this.uploadDir, metadata.savedName), { force: true });
|
|
29323
29718
|
rmSync9(this.#metadataPath(fileId), { force: true });
|
|
29324
29719
|
}
|
|
29325
29720
|
}
|
|
@@ -29471,14 +29866,14 @@ function createPollingSseStream(req, options) {
|
|
|
29471
29866
|
import { randomUUID as randomUUID25, timingSafeEqual as timingSafeEqual4 } from "crypto";
|
|
29472
29867
|
import {
|
|
29473
29868
|
chmodSync as chmodSync6,
|
|
29474
|
-
existsSync as
|
|
29475
|
-
mkdirSync as
|
|
29476
|
-
readFileSync as
|
|
29477
|
-
renameSync as
|
|
29869
|
+
existsSync as existsSync32,
|
|
29870
|
+
mkdirSync as mkdirSync30,
|
|
29871
|
+
readFileSync as readFileSync28,
|
|
29872
|
+
renameSync as renameSync16,
|
|
29478
29873
|
unlinkSync as unlinkSync22,
|
|
29479
|
-
writeFileSync as
|
|
29874
|
+
writeFileSync as writeFileSync24
|
|
29480
29875
|
} from "fs";
|
|
29481
|
-
import { dirname as
|
|
29876
|
+
import { dirname as dirname19, resolve as resolve21 } from "path";
|
|
29482
29877
|
function jsonError(status, error2) {
|
|
29483
29878
|
return Response.json({ ok: false, error: error2 }, { status });
|
|
29484
29879
|
}
|
|
@@ -29968,6 +30363,25 @@ function createNodeControlHandler(options) {
|
|
|
29968
30363
|
return jsonError(404, "Topic not found");
|
|
29969
30364
|
return Response.json({ ok: true, usage: getTopicStats(userId, topicId) });
|
|
29970
30365
|
}
|
|
30366
|
+
const decisionsMatch = path.match(/^\/topics\/([^/]+)\/decisions$/);
|
|
30367
|
+
if (decisionsMatch && req.method === "GET") {
|
|
30368
|
+
const topicId = decodeURIComponent(decisionsMatch[1]);
|
|
30369
|
+
const userId = requiredText(url.searchParams.get("user"), "user");
|
|
30370
|
+
if (!topicForUser(topicId, userId))
|
|
30371
|
+
return jsonError(404, "Topic not found");
|
|
30372
|
+
return Response.json({ ok: true, decisions: readDecisions(userId, topicId) });
|
|
30373
|
+
}
|
|
30374
|
+
const decisionGraphMatch = path.match(/^\/topics\/([^/]+)\/decision-graph$/);
|
|
30375
|
+
if (decisionGraphMatch && req.method === "POST") {
|
|
30376
|
+
const topicId = decodeURIComponent(decisionGraphMatch[1]);
|
|
30377
|
+
const body = await bodyRecord(req);
|
|
30378
|
+
const userId = requiredText(body.userId, "userId");
|
|
30379
|
+
const svg = requiredText(body.svg, "svg");
|
|
30380
|
+
if (!topicForUser(topicId, userId))
|
|
30381
|
+
return jsonError(404, "Topic not found");
|
|
30382
|
+
writeDecisionGraphSvg(userId, topicId, svg);
|
|
30383
|
+
return Response.json({ ok: true });
|
|
30384
|
+
}
|
|
29971
30385
|
const modelMatch = path.match(/^\/topics\/([^/]+)\/model$/);
|
|
29972
30386
|
if (modelMatch && req.method === "POST") {
|
|
29973
30387
|
const topicId = decodeURIComponent(modelMatch[1]);
|
|
@@ -30105,20 +30519,20 @@ function writeNodeDaemonInfo(port, startedAt) {
|
|
|
30105
30519
|
stateDir: STATE_DIR,
|
|
30106
30520
|
startedAt
|
|
30107
30521
|
};
|
|
30108
|
-
|
|
30522
|
+
mkdirSync30(dirname19(NODE_DAEMON_INFO_PATH), { recursive: true });
|
|
30109
30523
|
const temporary = `${NODE_DAEMON_INFO_PATH}.${process.pid}.${randomUUID25()}.tmp`;
|
|
30110
|
-
|
|
30524
|
+
writeFileSync24(temporary, `${JSON.stringify(info, null, 2)}
|
|
30111
30525
|
`, { mode: 384 });
|
|
30112
30526
|
chmodSync6(temporary, 384);
|
|
30113
|
-
|
|
30527
|
+
renameSync16(temporary, NODE_DAEMON_INFO_PATH);
|
|
30114
30528
|
chmodSync6(NODE_DAEMON_INFO_PATH, 384);
|
|
30115
30529
|
return info;
|
|
30116
30530
|
}
|
|
30117
30531
|
function readNodeDaemonInfo() {
|
|
30118
|
-
if (!
|
|
30532
|
+
if (!existsSync32(NODE_DAEMON_INFO_PATH))
|
|
30119
30533
|
return null;
|
|
30120
30534
|
try {
|
|
30121
|
-
const value = JSON.parse(
|
|
30535
|
+
const value = JSON.parse(readFileSync28(NODE_DAEMON_INFO_PATH, "utf8"));
|
|
30122
30536
|
if (value?.schemaVersion !== 1 || value.stateDir !== STATE_DIR || !Number.isInteger(value.protocolVersion) || !Number.isInteger(value.pid) || !Number.isInteger(value.port) || value.port <= 0) {
|
|
30123
30537
|
return null;
|
|
30124
30538
|
}
|
|
@@ -30402,7 +30816,7 @@ var init_schedule = __esm(() => {
|
|
|
30402
30816
|
|
|
30403
30817
|
// ../../packages/module-cron/src/scripts.ts
|
|
30404
30818
|
import { spawn as spawn9 } from "child_process";
|
|
30405
|
-
import { existsSync as
|
|
30819
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync31, readdirSync as readdirSync13 } from "fs";
|
|
30406
30820
|
import { resolve as resolve22, sep as sep2 } from "path";
|
|
30407
30821
|
function validateCronScriptName(script) {
|
|
30408
30822
|
if (!/^[A-Za-z0-9_][A-Za-z0-9_.-]*\.py$/.test(script) || script.includes("..") || script.includes("/") || script.includes("\\")) {
|
|
@@ -30420,12 +30834,12 @@ function resolveCronScriptPath(script) {
|
|
|
30420
30834
|
return path;
|
|
30421
30835
|
}
|
|
30422
30836
|
function listCronScripts() {
|
|
30423
|
-
|
|
30837
|
+
mkdirSync31(CRON_JOBS_DIR, { recursive: true });
|
|
30424
30838
|
return readdirSync13(CRON_JOBS_DIR, { withFileTypes: true }).filter((entry) => entry.isFile() && validateCronScriptName(entry.name).ok).map((entry) => entry.name).sort();
|
|
30425
30839
|
}
|
|
30426
30840
|
function cronScriptExists(script) {
|
|
30427
30841
|
try {
|
|
30428
|
-
return
|
|
30842
|
+
return existsSync33(resolveCronScriptPath(script));
|
|
30429
30843
|
} catch {
|
|
30430
30844
|
return false;
|
|
30431
30845
|
}
|
|
@@ -30457,7 +30871,7 @@ async function runCronPromptScript(options) {
|
|
|
30457
30871
|
if (options.signal?.aborted)
|
|
30458
30872
|
throw new CronScriptError("script run aborted");
|
|
30459
30873
|
const scriptPath = resolveCronScriptPath(options.script);
|
|
30460
|
-
if (!
|
|
30874
|
+
if (!existsSync33(scriptPath))
|
|
30461
30875
|
throw new CronScriptError(`cron script not found: ${options.script}`);
|
|
30462
30876
|
const timeoutMs = Math.max(5000, options.timeoutMs ?? DEFAULT_SCRIPT_TIMEOUT_MS);
|
|
30463
30877
|
const outputLimitBytes = Math.max(1024, options.outputLimitBytes ?? DEFAULT_OUTPUT_LIMIT_BYTES);
|
|
@@ -31650,7 +32064,7 @@ var init_scheduler = __esm(async () => {
|
|
|
31650
32064
|
});
|
|
31651
32065
|
|
|
31652
32066
|
// ../../packages/module-cron/src/module.ts
|
|
31653
|
-
import { existsSync as
|
|
32067
|
+
import { existsSync as existsSync34 } from "fs";
|
|
31654
32068
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
31655
32069
|
function envMs(name, fallback) {
|
|
31656
32070
|
const parsed = Number.parseInt(process.env[name] ?? "", 10);
|
|
@@ -31681,7 +32095,7 @@ function createCronModule(options = {}) {
|
|
|
31681
32095
|
if (event.type === "topic-deleted")
|
|
31682
32096
|
trackCleanup(resetCronTopicContext(event.topicId));
|
|
31683
32097
|
});
|
|
31684
|
-
const mcpServerFile = options.mcpServerFile ?? (
|
|
32098
|
+
const mcpServerFile = options.mcpServerFile ?? (existsSync34(SOURCE_MCP_SERVER_FILE) ? SOURCE_MCP_SERVER_FILE : PACKAGED_MCP_SERVER_FILE);
|
|
31685
32099
|
const unregisterMcp = host2.registerRuntimeMcpServer("cron-manager", {
|
|
31686
32100
|
scopes: ["forum", "manager"],
|
|
31687
32101
|
forumRequired: true,
|
|
@@ -32348,6 +32762,73 @@ var init_commands = __esm(() => {
|
|
|
32348
32762
|
];
|
|
32349
32763
|
});
|
|
32350
32764
|
|
|
32765
|
+
// ../../adapters/terminal/src/decision-graph.ts
|
|
32766
|
+
import {
|
|
32767
|
+
layoutGraph,
|
|
32768
|
+
renderSvgGraph,
|
|
32769
|
+
renderTerminalGraph
|
|
32770
|
+
} from "orchgraph";
|
|
32771
|
+
function adjustDecisionGraphSpacing(current3, delta) {
|
|
32772
|
+
return Math.min(MAX_DECISION_GRAPH_SPACING, Math.max(MIN_DECISION_GRAPH_SPACING, current3 + delta));
|
|
32773
|
+
}
|
|
32774
|
+
function decisionNodeState(status) {
|
|
32775
|
+
switch (status) {
|
|
32776
|
+
case "proposed":
|
|
32777
|
+
return "queued";
|
|
32778
|
+
case "executed":
|
|
32779
|
+
return "succeeded";
|
|
32780
|
+
case "rejected":
|
|
32781
|
+
return "failed";
|
|
32782
|
+
case "superseded":
|
|
32783
|
+
return "blocked";
|
|
32784
|
+
default:
|
|
32785
|
+
return "idle";
|
|
32786
|
+
}
|
|
32787
|
+
}
|
|
32788
|
+
function compactDetail(decision) {
|
|
32789
|
+
const rationale = decision.reasoning.replace(/\s+/g, " ").trim();
|
|
32790
|
+
return `${rationale} \xB7 ${decision.agent}${decision.model ? `/${decision.model}` : ""}`;
|
|
32791
|
+
}
|
|
32792
|
+
function buildDecisionGraph(decisions2, topicTitle) {
|
|
32793
|
+
const ids = new Set(decisions2.map((decision) => decision.id));
|
|
32794
|
+
return {
|
|
32795
|
+
id: "decisions",
|
|
32796
|
+
title: topicTitle,
|
|
32797
|
+
direction: "DOWN",
|
|
32798
|
+
nodes: decisions2.map((decision) => ({
|
|
32799
|
+
id: decision.id,
|
|
32800
|
+
label: `#${decision.id} ${decision.action}`,
|
|
32801
|
+
detail: compactDetail(decision),
|
|
32802
|
+
kind: decision.status,
|
|
32803
|
+
state: decisionNodeState(decision.status),
|
|
32804
|
+
metadata: { timestamp: decision.timestamp, agent: decision.agent, model: decision.model }
|
|
32805
|
+
})),
|
|
32806
|
+
edges: decisions2.flatMap((decision) => (decision.causedBy ?? []).filter((upstream) => ids.has(upstream)).map((upstream) => ({
|
|
32807
|
+
id: `caused:${upstream}:${decision.id}`,
|
|
32808
|
+
source: upstream,
|
|
32809
|
+
target: decision.id,
|
|
32810
|
+
label: "caused",
|
|
32811
|
+
kind: "caused",
|
|
32812
|
+
direction: "forward",
|
|
32813
|
+
style: "solid"
|
|
32814
|
+
})))
|
|
32815
|
+
};
|
|
32816
|
+
}
|
|
32817
|
+
async function renderDecisionGraph(graph, spacing = DEFAULT_DECISION_GRAPH_SPACING, signal) {
|
|
32818
|
+
const geometry = await layoutGraph(graph, {
|
|
32819
|
+
direction: "DOWN",
|
|
32820
|
+
spacing,
|
|
32821
|
+
signal,
|
|
32822
|
+
timeoutMs: 15000
|
|
32823
|
+
});
|
|
32824
|
+
return {
|
|
32825
|
+
canvas: { ...renderTerminalGraph(graph, geometry), title: graph.title },
|
|
32826
|
+
svg: renderSvgGraph(graph, geometry, { title: graph.title })
|
|
32827
|
+
};
|
|
32828
|
+
}
|
|
32829
|
+
var DEFAULT_DECISION_GRAPH_SPACING = 4, MIN_DECISION_GRAPH_SPACING = 2, MAX_DECISION_GRAPH_SPACING = 10;
|
|
32830
|
+
var init_decision_graph = () => {};
|
|
32831
|
+
|
|
32351
32832
|
// ../../adapters/terminal/src/subagent-graph.ts
|
|
32352
32833
|
import {
|
|
32353
32834
|
layoutTerminalGraph,
|
|
@@ -32541,6 +33022,9 @@ function createInitialState(userId) {
|
|
|
32541
33022
|
subagentGraphLoading: false,
|
|
32542
33023
|
subagentGraphOffset: { x: 0, y: 0 },
|
|
32543
33024
|
subagentGraphSpacing: DEFAULT_SUBAGENT_GRAPH_SPACING,
|
|
33025
|
+
decisionGraphLoading: false,
|
|
33026
|
+
decisionGraphOffset: { x: 0, y: 0 },
|
|
33027
|
+
decisionGraphSpacing: DEFAULT_DECISION_GRAPH_SPACING,
|
|
32544
33028
|
askChoiceIndex: 0,
|
|
32545
33029
|
taskSidebarEnabled: true,
|
|
32546
33030
|
overlay: null,
|
|
@@ -33104,6 +33588,7 @@ function applyRuntimeEvent(state, event) {
|
|
|
33104
33588
|
var NO_TOPIC_PICKER_SELECTION = -1;
|
|
33105
33589
|
var init_state2 = __esm(async () => {
|
|
33106
33590
|
await init_src();
|
|
33591
|
+
init_decision_graph();
|
|
33107
33592
|
init_subagent_graph();
|
|
33108
33593
|
});
|
|
33109
33594
|
|
|
@@ -33168,9 +33653,9 @@ var init_context_usage = __esm(async () => {
|
|
|
33168
33653
|
|
|
33169
33654
|
// ../../adapters/terminal/src/path-suggest.ts
|
|
33170
33655
|
import { execFile as execFile3 } from "child_process";
|
|
33171
|
-
import { existsSync as
|
|
33656
|
+
import { existsSync as existsSync35, readdirSync as readdirSync14, statSync as statSync19 } from "fs";
|
|
33172
33657
|
import { homedir as homedir10 } from "os";
|
|
33173
|
-
import { basename as basename11, dirname as
|
|
33658
|
+
import { basename as basename11, dirname as dirname20, join as join41, sep as sep3 } from "path";
|
|
33174
33659
|
import { promisify as promisify3 } from "util";
|
|
33175
33660
|
function indexRecursivePaths(files) {
|
|
33176
33661
|
const paths = [];
|
|
@@ -33271,15 +33756,15 @@ function resolveFragment(frag) {
|
|
|
33271
33756
|
else if (frag === "~/")
|
|
33272
33757
|
path = home;
|
|
33273
33758
|
else if (frag.startsWith("~/"))
|
|
33274
|
-
path =
|
|
33759
|
+
path = join41(home, frag.slice(2));
|
|
33275
33760
|
else if (frag.startsWith("/"))
|
|
33276
33761
|
path = frag;
|
|
33277
33762
|
else
|
|
33278
|
-
path =
|
|
33763
|
+
path = join41(home, frag);
|
|
33279
33764
|
if (frag.endsWith("/") || frag === "" || frag === "~") {
|
|
33280
33765
|
return { dir: path, prefix: "" };
|
|
33281
33766
|
}
|
|
33282
|
-
return { dir:
|
|
33767
|
+
return { dir: dirname20(path), prefix: basename11(path) };
|
|
33283
33768
|
}
|
|
33284
33769
|
function toToken(fullPath, isDir) {
|
|
33285
33770
|
const home = homedir10();
|
|
@@ -33304,7 +33789,7 @@ function rankAndSlice(dir, candidates) {
|
|
|
33304
33789
|
});
|
|
33305
33790
|
return candidates.slice(0, MAX_SUGGESTIONS).map(({ relPath, isDir }) => ({
|
|
33306
33791
|
label: `${relPath}${isDir ? "/" : ""}`,
|
|
33307
|
-
value: toToken(
|
|
33792
|
+
value: toToken(join41(dir, relPath), isDir),
|
|
33308
33793
|
isDir
|
|
33309
33794
|
}));
|
|
33310
33795
|
}
|
|
@@ -33383,7 +33868,7 @@ function pathSuggestions(lineText, col) {
|
|
|
33383
33868
|
let isDir = entry.isDirectory();
|
|
33384
33869
|
if (!isDir && entry.isSymbolicLink()) {
|
|
33385
33870
|
try {
|
|
33386
|
-
isDir = statSync19(
|
|
33871
|
+
isDir = statSync19(join41(dir, entry.name)).isDirectory();
|
|
33387
33872
|
} catch {}
|
|
33388
33873
|
}
|
|
33389
33874
|
return {
|
|
@@ -33410,14 +33895,14 @@ function fragmentToAbsolutePath(frag) {
|
|
|
33410
33895
|
if (frag === "~" || frag === "~/")
|
|
33411
33896
|
return home;
|
|
33412
33897
|
if (frag.startsWith("~/"))
|
|
33413
|
-
return
|
|
33898
|
+
return join41(home, frag.slice(2));
|
|
33414
33899
|
if (frag.startsWith("/"))
|
|
33415
33900
|
return frag;
|
|
33416
|
-
return
|
|
33901
|
+
return join41(home, frag);
|
|
33417
33902
|
}
|
|
33418
33903
|
function fragmentResolves(frag) {
|
|
33419
33904
|
try {
|
|
33420
|
-
return
|
|
33905
|
+
return existsSync35(fragmentToAbsolutePath(frag));
|
|
33421
33906
|
} catch {
|
|
33422
33907
|
return false;
|
|
33423
33908
|
}
|
|
@@ -34283,7 +34768,7 @@ function helpLines() {
|
|
|
34283
34768
|
line(" Alt-Backspace delete word \xB7 Ctrl-U clear before cursor"),
|
|
34284
34769
|
line(" Cmd-Backspace works when forwarded \xB7 Ctrl-W/K aliases"),
|
|
34285
34770
|
line(" Mouse wheel / PgUp/PgDn scroll \xB7 Ctrl-E load older \xB7 Ctrl-T tasks"),
|
|
34286
|
-
line(" Ctrl-O topics \xB7 Ctrl-G subagents \xB7 Ctrl-X abort"),
|
|
34771
|
+
line(" Ctrl-O topics \xB7 Ctrl-G subagents \xB7 Ctrl-D decisions \xB7 Ctrl-X abort"),
|
|
34287
34772
|
line(" In topics: type to filter \xB7 Ctrl-N new \xB7 Ctrl-D delete"),
|
|
34288
34773
|
line(" Esc/Ctrl-C stop active turn \xB7 Ctrl-C twice when idle to quit"),
|
|
34289
34774
|
line(""),
|
|
@@ -34630,6 +35115,43 @@ function subagentGraphLines(state, width, height, animationFrame) {
|
|
|
34630
35115
|
})
|
|
34631
35116
|
].slice(0, height);
|
|
34632
35117
|
}
|
|
35118
|
+
function decisionGraphLines(state, width, height) {
|
|
35119
|
+
const headerHeight = 5;
|
|
35120
|
+
const canvas = state.decisionGraph;
|
|
35121
|
+
const offset = state.decisionGraphOffset;
|
|
35122
|
+
const viewportWidth = Math.max(1, width - 4);
|
|
35123
|
+
const viewportHeight = Math.max(1, height - headerHeight);
|
|
35124
|
+
const maxX = Math.max(0, (canvas?.width ?? 0) - viewportWidth);
|
|
35125
|
+
const maxY = Math.max(0, (canvas?.height ?? 0) - viewportHeight);
|
|
35126
|
+
const x = Math.min(maxX, offset.x);
|
|
35127
|
+
const y = Math.min(maxY, offset.y);
|
|
35128
|
+
const position = maxX > 0 || maxY > 0 ? ` \xB7 view ${x + 1},${y + 1}/${maxX + 1},${maxY + 1}` : "";
|
|
35129
|
+
const header = [
|
|
35130
|
+
line(" Decision graph", { fg: theme.accent, bold: true }),
|
|
35131
|
+
line(` ${canvas?.title ?? activeTopic(state)?.title ?? "Current topic"}`, {
|
|
35132
|
+
fg: theme.text,
|
|
35133
|
+
bold: true
|
|
35134
|
+
}),
|
|
35135
|
+
line(` [/] spacing ${state.decisionGraphSpacing} \xB7 drag/wheel/arrows/hjkl move \xB7 Ctrl-D/Esc close${position}`, { fg: theme.muted }),
|
|
35136
|
+
line(" \u25CB accepted \xB7 \u25F7 proposed \xB7 \u2713 executed \xB7 \u2717 rejected \xB7 ! superseded", {
|
|
35137
|
+
fg: theme.subtle
|
|
35138
|
+
}),
|
|
35139
|
+
line("")
|
|
35140
|
+
];
|
|
35141
|
+
if (state.decisionGraphLoading) {
|
|
35142
|
+
return [...header, line(" Laying out decisions with Orchgraph\u2026", { fg: theme.cyan })].slice(0, height);
|
|
35143
|
+
}
|
|
35144
|
+
if (!canvas || canvas.lines.length === 0) {
|
|
35145
|
+
return [...header, line(" No decision graph data", { fg: theme.muted })].slice(0, height);
|
|
35146
|
+
}
|
|
35147
|
+
return [
|
|
35148
|
+
...header,
|
|
35149
|
+
...Array.from({ length: viewportHeight }, (_, index) => {
|
|
35150
|
+
const text2 = sliceWidthRange(canvas.lines[y + index] ?? "", x, viewportWidth);
|
|
35151
|
+
return line(` ${text2}`, { fg: /[\u256D\u256E\u2570\u256F]/.test(text2) ? theme.cyan : theme.muted });
|
|
35152
|
+
})
|
|
35153
|
+
].slice(0, height);
|
|
35154
|
+
}
|
|
34633
35155
|
function modelOverlayLines(state, height) {
|
|
34634
35156
|
const currentModel = effectiveTopicModel(activeTopic(state));
|
|
34635
35157
|
const modelColumnWidth = Math.max(...SELECTABLE_MODELS.map(({ model }) => `${model} (current)`.length));
|
|
@@ -34747,6 +35269,8 @@ function conversationLines(state, width, height, animationFrame = 0, nowMs = ter
|
|
|
34747
35269
|
return topicOverlayLines(state, width, height, animationFrame).slice(0, height);
|
|
34748
35270
|
if (state.overlay === "subagents")
|
|
34749
35271
|
return subagentGraphLines(state, width, height, animationFrame);
|
|
35272
|
+
if (state.overlay === "decisions")
|
|
35273
|
+
return decisionGraphLines(state, width, height);
|
|
34750
35274
|
if (state.overlay === "background-session")
|
|
34751
35275
|
return backgroundSessionViewportLines(state, width, height, nowMs);
|
|
34752
35276
|
if (state.overlay === "models")
|
|
@@ -34889,7 +35413,7 @@ function inputVisualLines(state, width) {
|
|
|
34889
35413
|
return { lines: result, cursorLine, cursorColumn };
|
|
34890
35414
|
}
|
|
34891
35415
|
function composerPane(state, width) {
|
|
34892
|
-
const title = state.creatingTopic ? "new topic \xB7 type a name \xB7 Enter create" : state.overlay === "vault" ? state.vaultMode === "list" ? "Vault command \xB7 Enter run \xB7 Esc close" : `${state.vaultMode === "key" ? "key" : state.vaultMode === "value" ? "secret value" : "description"} \xB7 Enter continue \xB7 Esc cancel` : "Ctrl-O topics \xB7 Ctrl-G subagents";
|
|
35416
|
+
const title = state.creatingTopic ? "new topic \xB7 type a name \xB7 Enter create" : state.overlay === "vault" ? state.vaultMode === "list" ? "Vault command \xB7 Enter run \xB7 Esc close" : `${state.vaultMode === "key" ? "key" : state.vaultMode === "value" ? "secret value" : "description"} \xB7 Enter continue \xB7 Esc cancel` : "Ctrl-O topics \xB7 Ctrl-G subagents \xB7 Ctrl-D decisions";
|
|
34893
35417
|
const visual = inputVisualLines(state, width);
|
|
34894
35418
|
let inputStart = Math.max(0, visual.lines.length - 5);
|
|
34895
35419
|
if (visual.cursorLine < inputStart)
|
|
@@ -35051,6 +35575,13 @@ function footerHintText(state) {
|
|
|
35051
35575
|
if (state.overlay === "subagents") {
|
|
35052
35576
|
return ["arrows/hjkl/wheel move \xB7 Esc close ", "arrows move \xB7 Esc close ", "Esc close "];
|
|
35053
35577
|
}
|
|
35578
|
+
if (state.overlay === "decisions") {
|
|
35579
|
+
return [
|
|
35580
|
+
"arrows/hjkl/wheel move \xB7 Ctrl-D/Esc close ",
|
|
35581
|
+
"arrows move \xB7 Esc close ",
|
|
35582
|
+
"Esc close "
|
|
35583
|
+
];
|
|
35584
|
+
}
|
|
35054
35585
|
if (state.overlay === "vault") {
|
|
35055
35586
|
if (state.vaultMode === "list")
|
|
35056
35587
|
return ["Enter run \xB7 Esc close ", "Esc close "];
|
|
@@ -35123,7 +35654,7 @@ function renderAppFrame(state, columns, rows, animationFrame = 0, nowMs = termin
|
|
|
35123
35654
|
const height = Math.max(14, rows);
|
|
35124
35655
|
const footer = footerLines(state, width);
|
|
35125
35656
|
const decision = decisionPane(state, width);
|
|
35126
|
-
const hideComposer = state.overlay === "background-session" || state.overlay === "topics" || state.overlay === "subagents" || state.overlay === "vault" && state.vaultMode === "confirm-delete";
|
|
35657
|
+
const hideComposer = state.overlay === "background-session" || state.overlay === "topics" || state.overlay === "subagents" || state.overlay === "decisions" || state.overlay === "vault" && state.vaultMode === "confirm-delete";
|
|
35127
35658
|
const composer = hideComposer ? { lines: [], cursor: null } : composerPane(state, width);
|
|
35128
35659
|
const bodyHeight = Math.max(3, height - footer.length - decision.length - composer.lines.length);
|
|
35129
35660
|
const layout = terminalBodyLayout(state, width);
|
|
@@ -35659,6 +36190,12 @@ class EmbeddedNegotiumClient {
|
|
|
35659
36190
|
listTopicUsage(topicId) {
|
|
35660
36191
|
return getTopicStats(this.#userId, topicId);
|
|
35661
36192
|
}
|
|
36193
|
+
listDecisions(topicId) {
|
|
36194
|
+
return readDecisions(this.#userId, topicId);
|
|
36195
|
+
}
|
|
36196
|
+
saveDecisionGraph(topicId, svg) {
|
|
36197
|
+
writeDecisionGraphSvg(this.#userId, topicId, svg);
|
|
36198
|
+
}
|
|
35662
36199
|
listMessagePage(topicId, cursor, limit = MESSAGE_HISTORY_PAGE_SIZE) {
|
|
35663
36200
|
const result = listApiMessages(topicId, { cursor, limit });
|
|
35664
36201
|
return { messages: result.page, cursor: result.cursor, hasMore: result.hasMore };
|
|
@@ -35848,6 +36385,16 @@ class RemoteNegotiumClient {
|
|
|
35848
36385
|
const result = await this.#request(`/topics/${encodeURIComponent(topicId)}/usage?user=${encodeURIComponent(this.#userId)}`);
|
|
35849
36386
|
return result.usage;
|
|
35850
36387
|
}
|
|
36388
|
+
async listDecisions(topicId) {
|
|
36389
|
+
const result = await this.#request(`/topics/${encodeURIComponent(topicId)}/decisions?user=${encodeURIComponent(this.#userId)}`);
|
|
36390
|
+
return result.decisions ?? [];
|
|
36391
|
+
}
|
|
36392
|
+
async saveDecisionGraph(topicId, svg) {
|
|
36393
|
+
await this.#request(`/topics/${encodeURIComponent(topicId)}/decision-graph`, {
|
|
36394
|
+
method: "POST",
|
|
36395
|
+
body: JSON.stringify({ userId: this.#userId, svg })
|
|
36396
|
+
});
|
|
36397
|
+
}
|
|
35851
36398
|
async listMessagePage(topicId, cursor, limit = MESSAGE_HISTORY_PAGE_SIZE) {
|
|
35852
36399
|
const query2 = new URLSearchParams({ user: this.#userId, limit: String(limit) });
|
|
35853
36400
|
if (cursor)
|
|
@@ -37183,6 +37730,10 @@ class TerminalApp {
|
|
|
37183
37730
|
#subagentGraphSpacingTimer = null;
|
|
37184
37731
|
#subagentGraphCache = null;
|
|
37185
37732
|
#pendingSubagentGraphSpacing = null;
|
|
37733
|
+
#decisionGraphGeneration = 0;
|
|
37734
|
+
#decisionGraphAbortController = null;
|
|
37735
|
+
#decisionGraphSpacingTimer = null;
|
|
37736
|
+
#pendingDecisionGraphSpacing = null;
|
|
37186
37737
|
#running = false;
|
|
37187
37738
|
#stopRequested = false;
|
|
37188
37739
|
#finishRun = null;
|
|
@@ -37540,6 +38091,9 @@ class TerminalApp {
|
|
|
37540
38091
|
const topicId = this.#state.activeTopicId;
|
|
37541
38092
|
if (!topicId)
|
|
37542
38093
|
return;
|
|
38094
|
+
this.#decisionGraphGeneration += 1;
|
|
38095
|
+
this.#decisionGraphAbortController?.abort();
|
|
38096
|
+
this.#decisionGraphAbortController = null;
|
|
37543
38097
|
if (this.#state.overlay === "subagents") {
|
|
37544
38098
|
this.#subagentGraphGeneration += 1;
|
|
37545
38099
|
this.#subagentGraphAbortController?.abort();
|
|
@@ -37592,6 +38146,143 @@ class TerminalApp {
|
|
|
37592
38146
|
}
|
|
37593
38147
|
this.#queueRender();
|
|
37594
38148
|
}
|
|
38149
|
+
async#toggleDecisionGraph() {
|
|
38150
|
+
const topic = activeTopic(this.#state);
|
|
38151
|
+
if (!topic || !this.#client.listDecisions)
|
|
38152
|
+
return;
|
|
38153
|
+
if (this.#state.overlay === "decisions") {
|
|
38154
|
+
this.#decisionGraphGeneration += 1;
|
|
38155
|
+
this.#decisionGraphAbortController?.abort();
|
|
38156
|
+
this.#decisionGraphAbortController = null;
|
|
38157
|
+
this.#subagentGraphDragPoint = null;
|
|
38158
|
+
this.#state = { ...this.#state, overlay: null, decisionGraphLoading: false };
|
|
38159
|
+
this.#queueRender();
|
|
38160
|
+
return;
|
|
38161
|
+
}
|
|
38162
|
+
this.#subagentGraphGeneration += 1;
|
|
38163
|
+
this.#subagentGraphAbortController?.abort();
|
|
38164
|
+
this.#subagentGraphAbortController = null;
|
|
38165
|
+
const generation = ++this.#decisionGraphGeneration;
|
|
38166
|
+
this.#decisionGraphAbortController?.abort();
|
|
38167
|
+
const controller = new AbortController;
|
|
38168
|
+
this.#decisionGraphAbortController = controller;
|
|
38169
|
+
this.#subagentGraphDragPoint = null;
|
|
38170
|
+
this.#state = {
|
|
38171
|
+
...this.#state,
|
|
38172
|
+
overlay: "decisions",
|
|
38173
|
+
decisionGraph: undefined,
|
|
38174
|
+
decisionGraphLoading: true,
|
|
38175
|
+
decisionGraphOffset: { x: 0, y: 0 },
|
|
38176
|
+
notice: undefined,
|
|
38177
|
+
noticeLevel: undefined
|
|
38178
|
+
};
|
|
38179
|
+
this.#queueRender();
|
|
38180
|
+
try {
|
|
38181
|
+
const decisions2 = await this.#client.listDecisions(topic.id);
|
|
38182
|
+
if (generation !== this.#decisionGraphGeneration || this.#state.overlay !== "decisions") {
|
|
38183
|
+
return;
|
|
38184
|
+
}
|
|
38185
|
+
if (decisions2.length === 0) {
|
|
38186
|
+
this.#decisionGraphAbortController = null;
|
|
38187
|
+
this.#state = {
|
|
38188
|
+
...this.#state,
|
|
38189
|
+
overlay: null,
|
|
38190
|
+
decisionGraphLoading: false,
|
|
38191
|
+
notice: "No decisions recorded in this topic",
|
|
38192
|
+
noticeLevel: "info"
|
|
38193
|
+
};
|
|
38194
|
+
this.#queueRender();
|
|
38195
|
+
return;
|
|
38196
|
+
}
|
|
38197
|
+
const rendered = await renderDecisionGraph(buildDecisionGraph(decisions2, topic.title), this.#state.decisionGraphSpacing, controller.signal);
|
|
38198
|
+
if (generation !== this.#decisionGraphGeneration || this.#state.overlay !== "decisions") {
|
|
38199
|
+
return;
|
|
38200
|
+
}
|
|
38201
|
+
let saveWarning;
|
|
38202
|
+
try {
|
|
38203
|
+
await this.#client.saveDecisionGraph?.(topic.id, rendered.svg);
|
|
38204
|
+
} catch (error2) {
|
|
38205
|
+
saveWarning = `Decision graph shown but SVG save failed: ${error2 instanceof Error ? error2.message : String(error2)}`;
|
|
38206
|
+
}
|
|
38207
|
+
this.#decisionGraphAbortController = null;
|
|
38208
|
+
this.#state = {
|
|
38209
|
+
...this.#state,
|
|
38210
|
+
decisionGraph: rendered.canvas,
|
|
38211
|
+
decisionGraphLoading: false,
|
|
38212
|
+
...saveWarning ? { notice: saveWarning, noticeLevel: "warn" } : {}
|
|
38213
|
+
};
|
|
38214
|
+
} catch (error2) {
|
|
38215
|
+
if (generation !== this.#decisionGraphGeneration)
|
|
38216
|
+
return;
|
|
38217
|
+
this.#decisionGraphAbortController = null;
|
|
38218
|
+
this.#state = {
|
|
38219
|
+
...this.#state,
|
|
38220
|
+
overlay: null,
|
|
38221
|
+
decisionGraphLoading: false,
|
|
38222
|
+
notice: `Decision graph failed: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
38223
|
+
noticeLevel: "error"
|
|
38224
|
+
};
|
|
38225
|
+
}
|
|
38226
|
+
this.#queueRender();
|
|
38227
|
+
}
|
|
38228
|
+
#changeDecisionGraphSpacing(delta) {
|
|
38229
|
+
const previous = this.#pendingDecisionGraphSpacing ?? this.#state.decisionGraphSpacing;
|
|
38230
|
+
const spacing = adjustDecisionGraphSpacing(previous, delta);
|
|
38231
|
+
if (spacing === previous)
|
|
38232
|
+
return;
|
|
38233
|
+
this.#pendingDecisionGraphSpacing = spacing;
|
|
38234
|
+
if (this.#decisionGraphSpacingTimer)
|
|
38235
|
+
clearTimeout(this.#decisionGraphSpacingTimer);
|
|
38236
|
+
this.#decisionGraphSpacingTimer = setTimeout(() => {
|
|
38237
|
+
this.#decisionGraphSpacingTimer = null;
|
|
38238
|
+
const target = this.#pendingDecisionGraphSpacing;
|
|
38239
|
+
this.#pendingDecisionGraphSpacing = null;
|
|
38240
|
+
if (target !== null)
|
|
38241
|
+
this.#applyDecisionGraphSpacing(target);
|
|
38242
|
+
}, 120);
|
|
38243
|
+
}
|
|
38244
|
+
async#applyDecisionGraphSpacing(spacing) {
|
|
38245
|
+
const topic = activeTopic(this.#state);
|
|
38246
|
+
if (!topic || this.#state.overlay !== "decisions" || !this.#client.listDecisions)
|
|
38247
|
+
return;
|
|
38248
|
+
const generation = ++this.#decisionGraphGeneration;
|
|
38249
|
+
this.#decisionGraphAbortController?.abort();
|
|
38250
|
+
const controller = new AbortController;
|
|
38251
|
+
this.#decisionGraphAbortController = controller;
|
|
38252
|
+
this.#state = { ...this.#state, decisionGraphSpacing: spacing, decisionGraphLoading: true };
|
|
38253
|
+
this.#queueRender();
|
|
38254
|
+
try {
|
|
38255
|
+
const decisions2 = await this.#client.listDecisions(topic.id);
|
|
38256
|
+
const rendered = await renderDecisionGraph(buildDecisionGraph(decisions2, topic.title), spacing, controller.signal);
|
|
38257
|
+
if (generation !== this.#decisionGraphGeneration || this.#state.overlay !== "decisions") {
|
|
38258
|
+
return;
|
|
38259
|
+
}
|
|
38260
|
+
let saveWarning;
|
|
38261
|
+
try {
|
|
38262
|
+
await this.#client.saveDecisionGraph?.(topic.id, rendered.svg);
|
|
38263
|
+
} catch (error2) {
|
|
38264
|
+
saveWarning = `Decision graph shown but SVG save failed: ${error2 instanceof Error ? error2.message : String(error2)}`;
|
|
38265
|
+
}
|
|
38266
|
+
this.#decisionGraphAbortController = null;
|
|
38267
|
+
this.#state = {
|
|
38268
|
+
...this.#state,
|
|
38269
|
+
decisionGraph: rendered.canvas,
|
|
38270
|
+
decisionGraphLoading: false,
|
|
38271
|
+
...saveWarning ? { notice: saveWarning, noticeLevel: "warn" } : {}
|
|
38272
|
+
};
|
|
38273
|
+
} catch (error2) {
|
|
38274
|
+
if (generation !== this.#decisionGraphGeneration)
|
|
38275
|
+
return;
|
|
38276
|
+
this.#decisionGraphAbortController = null;
|
|
38277
|
+
this.#state = {
|
|
38278
|
+
...this.#state,
|
|
38279
|
+
decisionGraphLoading: false,
|
|
38280
|
+
notice: `Decision graph failed: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
38281
|
+
noticeLevel: "error"
|
|
38282
|
+
};
|
|
38283
|
+
}
|
|
38284
|
+
this.#queueRender();
|
|
38285
|
+
}
|
|
37595
38286
|
#changeSubagentGraphSpacing(delta) {
|
|
37596
38287
|
const previousSpacing = this.#pendingSubagentGraphSpacing ?? this.#state.subagentGraphSpacing;
|
|
37597
38288
|
const spacing = adjustSubagentGraphSpacing(previousSpacing, delta);
|
|
@@ -37772,18 +38463,18 @@ class TerminalApp {
|
|
|
37772
38463
|
}
|
|
37773
38464
|
#handleKeySegment(segment) {
|
|
37774
38465
|
const mouse = consumeMouseInput(segment);
|
|
37775
|
-
if (mouse.horizontalScrollDelta !== 0 && this.#state.overlay === "subagents") {
|
|
38466
|
+
if (mouse.horizontalScrollDelta !== 0 && (this.#state.overlay === "subagents" || this.#state.overlay === "decisions")) {
|
|
37776
38467
|
this.#selection = null;
|
|
37777
|
-
this.#
|
|
38468
|
+
this.#panActiveGraph(-mouse.horizontalScrollDelta, 0);
|
|
37778
38469
|
}
|
|
37779
38470
|
if (mouse.scrollDelta !== 0) {
|
|
37780
38471
|
this.#selection = null;
|
|
37781
38472
|
this.#scroll(mouse.scrollDelta);
|
|
37782
38473
|
}
|
|
37783
38474
|
for (const event of mouse.events) {
|
|
37784
|
-
if (this.#state.overlay === "subagents")
|
|
37785
|
-
this.#
|
|
37786
|
-
else
|
|
38475
|
+
if (this.#state.overlay === "subagents" || this.#state.overlay === "decisions") {
|
|
38476
|
+
this.#handleGraphMouse(event);
|
|
38477
|
+
} else
|
|
37787
38478
|
this.#handleMouseSelection(event);
|
|
37788
38479
|
}
|
|
37789
38480
|
const chunk = normalizeKeySequences(mouse.input);
|
|
@@ -37817,6 +38508,12 @@ class TerminalApp {
|
|
|
37817
38508
|
this.#toggleSubagentGraph();
|
|
37818
38509
|
return;
|
|
37819
38510
|
}
|
|
38511
|
+
if (chunk === "\x04" && this.#state.overlay !== "topics") {
|
|
38512
|
+
if (editingVaultSecret)
|
|
38513
|
+
return;
|
|
38514
|
+
this.#toggleDecisionGraph();
|
|
38515
|
+
return;
|
|
38516
|
+
}
|
|
37820
38517
|
if (chunk === "\x14") {
|
|
37821
38518
|
if (editingVaultSecret)
|
|
37822
38519
|
return;
|
|
@@ -37869,6 +38566,38 @@ class TerminalApp {
|
|
|
37869
38566
|
this.#queueRender();
|
|
37870
38567
|
return;
|
|
37871
38568
|
}
|
|
38569
|
+
if (this.#state.overlay === "decisions") {
|
|
38570
|
+
if (chunk === "\x1B") {
|
|
38571
|
+
this.#decisionGraphGeneration += 1;
|
|
38572
|
+
this.#decisionGraphAbortController?.abort();
|
|
38573
|
+
this.#decisionGraphAbortController = null;
|
|
38574
|
+
this.#subagentGraphDragPoint = null;
|
|
38575
|
+
this.#state = { ...this.#state, overlay: null, decisionGraphLoading: false };
|
|
38576
|
+
} else if (chunk === "\x1B[A" || chunk === "k") {
|
|
38577
|
+
this.#panDecisionGraph(0, -2);
|
|
38578
|
+
return;
|
|
38579
|
+
} else if (chunk === "\x1B[B" || chunk === "j") {
|
|
38580
|
+
this.#panDecisionGraph(0, 2);
|
|
38581
|
+
return;
|
|
38582
|
+
} else if (chunk === "\x1B[D" || chunk === "h") {
|
|
38583
|
+
this.#panDecisionGraph(-4, 0);
|
|
38584
|
+
return;
|
|
38585
|
+
} else if (chunk === "\x1B[C" || chunk === "l") {
|
|
38586
|
+
this.#panDecisionGraph(4, 0);
|
|
38587
|
+
return;
|
|
38588
|
+
} else if (chunk === "\x1B[5~") {
|
|
38589
|
+
this.#panDecisionGraph(0, -8);
|
|
38590
|
+
return;
|
|
38591
|
+
} else if (chunk === "\x1B[6~") {
|
|
38592
|
+
this.#panDecisionGraph(0, 8);
|
|
38593
|
+
return;
|
|
38594
|
+
} else if (chunk === "[" || chunk === "]") {
|
|
38595
|
+
this.#changeDecisionGraphSpacing(chunk === "[" ? -1 : 1);
|
|
38596
|
+
return;
|
|
38597
|
+
}
|
|
38598
|
+
this.#queueRender();
|
|
38599
|
+
return;
|
|
38600
|
+
}
|
|
37872
38601
|
if (chunk === "\x01" || chunk === "\x1B[H" || chunk === "\x1B[1~") {
|
|
37873
38602
|
this.#input.moveHome();
|
|
37874
38603
|
this.#syncInput();
|
|
@@ -38809,7 +39538,7 @@ class TerminalApp {
|
|
|
38809
39538
|
this.#copySelection(text2);
|
|
38810
39539
|
this.#queueRender();
|
|
38811
39540
|
}
|
|
38812
|
-
#
|
|
39541
|
+
#handleGraphMouse(event) {
|
|
38813
39542
|
if ((event.button & 3) !== 0 && event.kind !== "release")
|
|
38814
39543
|
return;
|
|
38815
39544
|
const point = { x: event.x, y: event.y };
|
|
@@ -38826,7 +39555,7 @@ class TerminalApp {
|
|
|
38826
39555
|
this.#subagentGraphDragPoint = point;
|
|
38827
39556
|
if (!previous)
|
|
38828
39557
|
return;
|
|
38829
|
-
this.#
|
|
39558
|
+
this.#panActiveGraph(previous.x - point.x, previous.y - point.y);
|
|
38830
39559
|
}
|
|
38831
39560
|
async#copySelection(text2) {
|
|
38832
39561
|
try {
|
|
@@ -38887,8 +39616,8 @@ class TerminalApp {
|
|
|
38887
39616
|
this.#loadOlderMessages(topic.id, currentMax + 8);
|
|
38888
39617
|
}
|
|
38889
39618
|
#scroll(delta) {
|
|
38890
|
-
if (this.#state.overlay === "subagents") {
|
|
38891
|
-
this.#
|
|
39619
|
+
if (this.#state.overlay === "subagents" || this.#state.overlay === "decisions") {
|
|
39620
|
+
this.#panActiveGraph(0, -delta);
|
|
38892
39621
|
return;
|
|
38893
39622
|
}
|
|
38894
39623
|
if (this.#state.overlay === "background-session") {
|
|
@@ -38922,6 +39651,26 @@ class TerminalApp {
|
|
|
38922
39651
|
};
|
|
38923
39652
|
this.#queueRender();
|
|
38924
39653
|
}
|
|
39654
|
+
#panDecisionGraph(deltaX, deltaY) {
|
|
39655
|
+
const canvas = this.#state.decisionGraph;
|
|
39656
|
+
const viewportWidth = Math.max(1, (process.stdout.columns ?? 100) - 4);
|
|
39657
|
+
const viewportHeight = Math.max(1, (process.stdout.rows ?? 30) - 8);
|
|
39658
|
+
const current3 = this.#state.decisionGraphOffset;
|
|
39659
|
+
this.#state = {
|
|
39660
|
+
...this.#state,
|
|
39661
|
+
decisionGraphOffset: {
|
|
39662
|
+
x: Math.min(Math.max(0, (canvas?.width ?? 0) - viewportWidth), Math.max(0, current3.x + deltaX)),
|
|
39663
|
+
y: Math.min(Math.max(0, (canvas?.height ?? 0) - viewportHeight), Math.max(0, current3.y + deltaY))
|
|
39664
|
+
}
|
|
39665
|
+
};
|
|
39666
|
+
this.#queueRender();
|
|
39667
|
+
}
|
|
39668
|
+
#panActiveGraph(deltaX, deltaY) {
|
|
39669
|
+
if (this.#state.overlay === "decisions")
|
|
39670
|
+
this.#panDecisionGraph(deltaX, deltaY);
|
|
39671
|
+
else
|
|
39672
|
+
this.#panSubagentGraph(deltaX, deltaY);
|
|
39673
|
+
}
|
|
38925
39674
|
async#abort() {
|
|
38926
39675
|
const topic = activeTopic(this.#state);
|
|
38927
39676
|
let aborted = false;
|
|
@@ -39005,6 +39754,13 @@ class TerminalApp {
|
|
|
39005
39754
|
this.#subagentGraphGeneration += 1;
|
|
39006
39755
|
this.#subagentGraphAbortController?.abort();
|
|
39007
39756
|
this.#subagentGraphAbortController = null;
|
|
39757
|
+
this.#decisionGraphGeneration += 1;
|
|
39758
|
+
this.#decisionGraphAbortController?.abort();
|
|
39759
|
+
this.#decisionGraphAbortController = null;
|
|
39760
|
+
if (this.#subagentGraphSpacingTimer)
|
|
39761
|
+
clearTimeout(this.#subagentGraphSpacingTimer);
|
|
39762
|
+
if (this.#decisionGraphSpacingTimer)
|
|
39763
|
+
clearTimeout(this.#decisionGraphSpacingTimer);
|
|
39008
39764
|
if (this.#renderTimer)
|
|
39009
39765
|
clearTimeout(this.#renderTimer);
|
|
39010
39766
|
if (this.#inputCarryTimer)
|
|
@@ -39044,6 +39800,7 @@ var init_app = __esm(async () => {
|
|
|
39044
39800
|
init_color_depth();
|
|
39045
39801
|
await init_command_router();
|
|
39046
39802
|
init_commands();
|
|
39803
|
+
init_decision_graph();
|
|
39047
39804
|
init_path_suggest();
|
|
39048
39805
|
await init_render();
|
|
39049
39806
|
init_screen_renderer();
|
|
@@ -39646,8 +40403,8 @@ var init_commands2 = __esm(async () => {
|
|
|
39646
40403
|
|
|
39647
40404
|
// ../../adapters/telegram/src/mapping-store.ts
|
|
39648
40405
|
import { Database as Database2 } from "bun:sqlite";
|
|
39649
|
-
import { mkdirSync as
|
|
39650
|
-
import { dirname as
|
|
40406
|
+
import { mkdirSync as mkdirSync32 } from "fs";
|
|
40407
|
+
import { dirname as dirname21, join as join42 } from "path";
|
|
39651
40408
|
function outboxRowToEntry(row) {
|
|
39652
40409
|
return {
|
|
39653
40410
|
id: row.id,
|
|
@@ -39713,9 +40470,9 @@ function migrateOutboxSchema(db4) {
|
|
|
39713
40470
|
db4.run("CREATE INDEX IF NOT EXISTS idx_telegram_outbox_runtime_message ON outbox(runtime_message_id)");
|
|
39714
40471
|
}
|
|
39715
40472
|
function openMappingStore(path) {
|
|
39716
|
-
const dbPath = path ??
|
|
40473
|
+
const dbPath = path ?? join42(DATA_DIR, "adapter-telegram.db");
|
|
39717
40474
|
if (dbPath !== ":memory:")
|
|
39718
|
-
|
|
40475
|
+
mkdirSync32(dirname21(dbPath), { recursive: true });
|
|
39719
40476
|
const db4 = new Database2(dbPath);
|
|
39720
40477
|
migrateLegacySchema(db4);
|
|
39721
40478
|
createTables(db4);
|
|
@@ -40073,7 +40830,7 @@ function renderOutbound(text2, maxLen = 4096) {
|
|
|
40073
40830
|
}
|
|
40074
40831
|
|
|
40075
40832
|
// ../../adapters/telegram/src/adapter.ts
|
|
40076
|
-
import { existsSync as
|
|
40833
|
+
import { existsSync as existsSync36 } from "fs";
|
|
40077
40834
|
import { basename as basename12, extname as extname5 } from "path";
|
|
40078
40835
|
function startTelegramAdapter(opts) {
|
|
40079
40836
|
const { client, forumChatId } = opts;
|
|
@@ -40571,7 +41328,7 @@ function startTelegramAdapter(opts) {
|
|
|
40571
41328
|
logger.warn({ path, chatId }, "telegram adapter: blocked sensitive file path");
|
|
40572
41329
|
return { ref: null, delivered: false, error: "path matches the sensitive-file blacklist" };
|
|
40573
41330
|
}
|
|
40574
|
-
if (!
|
|
41331
|
+
if (!existsSync36(path)) {
|
|
40575
41332
|
const sent = await client.sendMessage(chatId, `File: ${path}`, base).catch(() => null);
|
|
40576
41333
|
const messageId = sentMessageId(sent);
|
|
40577
41334
|
return {
|
|
@@ -41591,11 +42348,11 @@ var exports_join_status = {};
|
|
|
41591
42348
|
__export(exports_join_status, {
|
|
41592
42349
|
hasConfiguredOtiumJoin: () => hasConfiguredOtiumJoin
|
|
41593
42350
|
});
|
|
41594
|
-
import { existsSync as
|
|
42351
|
+
import { existsSync as existsSync37 } from "fs";
|
|
41595
42352
|
import { resolve as resolve23 } from "path";
|
|
41596
42353
|
function hasConfiguredOtiumJoin() {
|
|
41597
42354
|
const envJoin = Boolean(process.env.OTIUM_CENTRAL_URL?.trim() && process.env.OTIUM_CELL_ID?.trim() && process.env.OTIUM_CELL_SECRET?.trim());
|
|
41598
|
-
return envJoin ||
|
|
42355
|
+
return envJoin || existsSync37(resolve23(DATA_DIR, "otium-join.json"));
|
|
41599
42356
|
}
|
|
41600
42357
|
var init_join_status = __esm(() => {
|
|
41601
42358
|
init_config_public();
|
|
@@ -41613,9 +42370,9 @@ function firstCell() {
|
|
|
41613
42370
|
return cell;
|
|
41614
42371
|
return null;
|
|
41615
42372
|
}
|
|
41616
|
-
function attachOtiumCentralCell(
|
|
41617
|
-
cells.set(
|
|
41618
|
-
join:
|
|
42373
|
+
function attachOtiumCentralCell(join43) {
|
|
42374
|
+
cells.set(join43.cellId, {
|
|
42375
|
+
join: join43,
|
|
41619
42376
|
nodesCache: null,
|
|
41620
42377
|
verifyCache: new Map,
|
|
41621
42378
|
tokenCache: new Map
|
|
@@ -41627,10 +42384,10 @@ function detachOtiumCentralCell(cellId) {
|
|
|
41627
42384
|
function attachedOtiumCells() {
|
|
41628
42385
|
return [...cells.values()].map((cell) => cell.join);
|
|
41629
42386
|
}
|
|
41630
|
-
function configureOtiumCentral(
|
|
42387
|
+
function configureOtiumCentral(join43) {
|
|
41631
42388
|
cells.clear();
|
|
41632
|
-
if (
|
|
41633
|
-
attachOtiumCentralCell(
|
|
42389
|
+
if (join43)
|
|
42390
|
+
attachOtiumCentralCell(join43);
|
|
41634
42391
|
}
|
|
41635
42392
|
function isOtiumCentralConfigured() {
|
|
41636
42393
|
return cells.size > 0;
|
|
@@ -41971,6 +42728,13 @@ var init_canonical_mcp_bridge = __esm(async () => {
|
|
|
41971
42728
|
CAPABILITY_TTL_MS = 60 * 60 * 1000;
|
|
41972
42729
|
TOOL_ALLOWLIST = {
|
|
41973
42730
|
task: new Set(["task_create", "task_update", "task_list", "task_get", "task_delete"]),
|
|
42731
|
+
decision: new Set([
|
|
42732
|
+
"decision_create",
|
|
42733
|
+
"decision_update",
|
|
42734
|
+
"decision_list",
|
|
42735
|
+
"decision_get",
|
|
42736
|
+
"decision_delete"
|
|
42737
|
+
]),
|
|
41974
42738
|
wiki: new Set([
|
|
41975
42739
|
"wiki_query",
|
|
41976
42740
|
"wiki_read",
|
|
@@ -42037,20 +42801,20 @@ import { createHash as createHash11, randomUUID as randomUUID28 } from "crypto";
|
|
|
42037
42801
|
import {
|
|
42038
42802
|
chmodSync as chmodSync7,
|
|
42039
42803
|
closeSync as closeSync5,
|
|
42040
|
-
existsSync as
|
|
42804
|
+
existsSync as existsSync38,
|
|
42041
42805
|
fsyncSync as fsyncSync2,
|
|
42042
42806
|
linkSync,
|
|
42043
42807
|
lstatSync,
|
|
42044
|
-
mkdirSync as
|
|
42808
|
+
mkdirSync as mkdirSync33,
|
|
42045
42809
|
openSync as openSync5,
|
|
42046
|
-
readFileSync as
|
|
42047
|
-
renameSync as
|
|
42810
|
+
readFileSync as readFileSync29,
|
|
42811
|
+
renameSync as renameSync17,
|
|
42048
42812
|
rmSync as rmSync10,
|
|
42049
42813
|
statSync as statSync20,
|
|
42050
42814
|
unlinkSync as unlinkSync23,
|
|
42051
|
-
writeFileSync as
|
|
42815
|
+
writeFileSync as writeFileSync25
|
|
42052
42816
|
} from "fs";
|
|
42053
|
-
import { dirname as
|
|
42817
|
+
import { dirname as dirname22, resolve as resolve24 } from "path";
|
|
42054
42818
|
function joinFilePath() {
|
|
42055
42819
|
return resolve24(DATA_DIR, "otium-join.json");
|
|
42056
42820
|
}
|
|
@@ -42123,13 +42887,13 @@ function withJoinCredentialLock(operation) {
|
|
|
42123
42887
|
const lockPath = joinLockPath();
|
|
42124
42888
|
const ownerPath = resolve24(lockPath, "owner.json");
|
|
42125
42889
|
const owner = { pid: process.pid, token: randomUUID28() };
|
|
42126
|
-
|
|
42890
|
+
mkdirSync33(dirname22(lockPath), { recursive: true });
|
|
42127
42891
|
for (let attempt = 0;; attempt += 1) {
|
|
42128
42892
|
let created = false;
|
|
42129
42893
|
try {
|
|
42130
|
-
|
|
42894
|
+
mkdirSync33(lockPath, { mode: 448 });
|
|
42131
42895
|
created = true;
|
|
42132
|
-
|
|
42896
|
+
writeFileSync25(ownerPath, `${JSON.stringify(owner)}
|
|
42133
42897
|
`, { mode: 384 });
|
|
42134
42898
|
const ownerFd = openSync5(ownerPath, "r");
|
|
42135
42899
|
try {
|
|
@@ -42147,7 +42911,7 @@ function withJoinCredentialLock(operation) {
|
|
|
42147
42911
|
throw error2;
|
|
42148
42912
|
let current3 = null;
|
|
42149
42913
|
try {
|
|
42150
|
-
current3 = JSON.parse(
|
|
42914
|
+
current3 = JSON.parse(readFileSync29(ownerPath, "utf8"));
|
|
42151
42915
|
} catch {}
|
|
42152
42916
|
let ageMs;
|
|
42153
42917
|
try {
|
|
@@ -42165,7 +42929,7 @@ function withJoinCredentialLock(operation) {
|
|
|
42165
42929
|
}
|
|
42166
42930
|
const stalePath = `${lockPath}.stale.${process.pid}.${randomUUID28()}`;
|
|
42167
42931
|
try {
|
|
42168
|
-
|
|
42932
|
+
renameSync17(lockPath, stalePath);
|
|
42169
42933
|
rmSync10(stalePath, { recursive: true, force: true });
|
|
42170
42934
|
} catch (staleError) {
|
|
42171
42935
|
if (staleError.code !== "ENOENT")
|
|
@@ -42177,7 +42941,7 @@ function withJoinCredentialLock(operation) {
|
|
|
42177
42941
|
return operation();
|
|
42178
42942
|
} finally {
|
|
42179
42943
|
try {
|
|
42180
|
-
const current3 = JSON.parse(
|
|
42944
|
+
const current3 = JSON.parse(readFileSync29(ownerPath, "utf8"));
|
|
42181
42945
|
if (current3.pid === owner.pid && current3.token === owner.token) {
|
|
42182
42946
|
rmSync10(lockPath, { recursive: true, force: true });
|
|
42183
42947
|
}
|
|
@@ -42187,22 +42951,22 @@ function withJoinCredentialLock(operation) {
|
|
|
42187
42951
|
function joinsEqual(left, right) {
|
|
42188
42952
|
return left.central === right.central && left.relay === right.relay && left.cellId === right.cellId && left.secret === right.secret;
|
|
42189
42953
|
}
|
|
42190
|
-
function normalizedJoin(
|
|
42954
|
+
function normalizedJoin(join43) {
|
|
42191
42955
|
return normalizeJoin({
|
|
42192
|
-
v:
|
|
42193
|
-
central:
|
|
42194
|
-
relay:
|
|
42195
|
-
cellId:
|
|
42196
|
-
secret:
|
|
42956
|
+
v: join43.v,
|
|
42957
|
+
central: join43.central,
|
|
42958
|
+
relay: join43.relay,
|
|
42959
|
+
cellId: join43.cellId,
|
|
42960
|
+
secret: join43.secret
|
|
42197
42961
|
});
|
|
42198
42962
|
}
|
|
42199
|
-
function joinCredentialDigest(
|
|
42200
|
-
return createHash11("sha256").update(JSON.stringify(normalizedJoin(
|
|
42963
|
+
function joinCredentialDigest(join43) {
|
|
42964
|
+
return createHash11("sha256").update(JSON.stringify(normalizedJoin(join43))).digest("base64url");
|
|
42201
42965
|
}
|
|
42202
42966
|
function readPersistedJoins(path = joinFilePath()) {
|
|
42203
|
-
if (!
|
|
42967
|
+
if (!existsSync38(path))
|
|
42204
42968
|
return [];
|
|
42205
|
-
const parsed = JSON.parse(
|
|
42969
|
+
const parsed = JSON.parse(readFileSync29(path, "utf-8"));
|
|
42206
42970
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
42207
42971
|
throw new Error("persisted join credentials are not a JSON object");
|
|
42208
42972
|
}
|
|
@@ -42216,9 +42980,9 @@ function readPersistedJoins(path = joinFilePath()) {
|
|
|
42216
42980
|
return normalizeJoin(entry);
|
|
42217
42981
|
});
|
|
42218
42982
|
}
|
|
42219
|
-
function isJoinPersisted(
|
|
42983
|
+
function isJoinPersisted(join43) {
|
|
42220
42984
|
try {
|
|
42221
|
-
const normalized = normalizedJoin(
|
|
42985
|
+
const normalized = normalizedJoin(join43);
|
|
42222
42986
|
return readPersistedJoins().some((persisted) => joinsEqual(persisted, normalized));
|
|
42223
42987
|
} catch {
|
|
42224
42988
|
return false;
|
|
@@ -42234,19 +42998,19 @@ function fsyncPath(target) {
|
|
|
42234
42998
|
}
|
|
42235
42999
|
function writeJoins(joins, allowOverwrite) {
|
|
42236
43000
|
const path = joinFilePath();
|
|
42237
|
-
const directory =
|
|
43001
|
+
const directory = dirname22(path);
|
|
42238
43002
|
const temporaryPath = resolve24(directory, `.otium-join.json.${process.pid}.${randomUUID28()}.tmp`);
|
|
42239
43003
|
const payload = { v: JOIN_FILE_VERSION, joins };
|
|
42240
43004
|
let fd;
|
|
42241
43005
|
try {
|
|
42242
43006
|
fd = openSync5(temporaryPath, "wx", 384);
|
|
42243
|
-
|
|
43007
|
+
writeFileSync25(fd, `${JSON.stringify(payload, null, 2)}
|
|
42244
43008
|
`, "utf8");
|
|
42245
43009
|
fsyncSync2(fd);
|
|
42246
43010
|
closeSync5(fd);
|
|
42247
43011
|
fd = undefined;
|
|
42248
43012
|
if (allowOverwrite) {
|
|
42249
|
-
|
|
43013
|
+
renameSync17(temporaryPath, path);
|
|
42250
43014
|
} else {
|
|
42251
43015
|
linkSync(temporaryPath, path);
|
|
42252
43016
|
unlinkSync23(temporaryPath);
|
|
@@ -42256,18 +43020,18 @@ function writeJoins(joins, allowOverwrite) {
|
|
|
42256
43020
|
} catch (error2) {
|
|
42257
43021
|
if (fd !== undefined)
|
|
42258
43022
|
closeSync5(fd);
|
|
42259
|
-
if (
|
|
43023
|
+
if (existsSync38(temporaryPath))
|
|
42260
43024
|
unlinkSync23(temporaryPath);
|
|
42261
43025
|
throw error2;
|
|
42262
43026
|
}
|
|
42263
43027
|
return path;
|
|
42264
43028
|
}
|
|
42265
|
-
function saveJoinWhileLocked(
|
|
43029
|
+
function saveJoinWhileLocked(join43, options = {}) {
|
|
42266
43030
|
const path = joinFilePath();
|
|
42267
|
-
const directory =
|
|
42268
|
-
const normalized = normalizedJoin(
|
|
42269
|
-
|
|
42270
|
-
if (!
|
|
43031
|
+
const directory = dirname22(path);
|
|
43032
|
+
const normalized = normalizedJoin(join43);
|
|
43033
|
+
mkdirSync33(directory, { recursive: true });
|
|
43034
|
+
if (!existsSync38(path))
|
|
42271
43035
|
return writeJoins([normalized], false);
|
|
42272
43036
|
if (lstatSync(path).isSymbolicLink()) {
|
|
42273
43037
|
throw new Error(`refusing to replace symlinked Otium join file at ${path}`);
|
|
@@ -42295,13 +43059,13 @@ function saveJoinWhileLocked(join42, options = {}) {
|
|
|
42295
43059
|
const next = conflict ? existing.map((persisted) => persisted.cellId === normalized.cellId ? normalized : persisted) : [...existing, normalized];
|
|
42296
43060
|
return writeJoins(next, true);
|
|
42297
43061
|
}
|
|
42298
|
-
function saveJoin(
|
|
42299
|
-
return withJoinCredentialLock(() => saveJoinWhileLocked(
|
|
43062
|
+
function saveJoin(join43, options = {}) {
|
|
43063
|
+
return withJoinCredentialLock(() => saveJoinWhileLocked(join43, options));
|
|
42300
43064
|
}
|
|
42301
43065
|
function removeJoin(cellId) {
|
|
42302
43066
|
return withJoinCredentialLock(() => {
|
|
42303
43067
|
const path = joinFilePath();
|
|
42304
|
-
if (!
|
|
43068
|
+
if (!existsSync38(path))
|
|
42305
43069
|
return false;
|
|
42306
43070
|
if (lstatSync(path).isSymbolicLink()) {
|
|
42307
43071
|
throw new Error(`refusing to remove symlinked Otium join file at ${path}`);
|
|
@@ -42309,7 +43073,7 @@ function removeJoin(cellId) {
|
|
|
42309
43073
|
if (cellId) {
|
|
42310
43074
|
let remaining;
|
|
42311
43075
|
try {
|
|
42312
|
-
remaining = readPersistedJoins(path).filter((
|
|
43076
|
+
remaining = readPersistedJoins(path).filter((join43) => join43.cellId !== cellId);
|
|
42313
43077
|
} catch {
|
|
42314
43078
|
return false;
|
|
42315
43079
|
}
|
|
@@ -42321,7 +43085,7 @@ function removeJoin(cellId) {
|
|
|
42321
43085
|
}
|
|
42322
43086
|
}
|
|
42323
43087
|
unlinkSync23(path);
|
|
42324
|
-
fsyncPath(
|
|
43088
|
+
fsyncPath(dirname22(path));
|
|
42325
43089
|
return true;
|
|
42326
43090
|
});
|
|
42327
43091
|
}
|
|
@@ -42342,7 +43106,7 @@ function loadJoins() {
|
|
|
42342
43106
|
logger.warn("otium: OTIUM_CENTRAL_URL, OTIUM_CELL_ID, OTIUM_CELL_SECRET must be set together \u2014 ignoring partial env");
|
|
42343
43107
|
}
|
|
42344
43108
|
const path = joinFilePath();
|
|
42345
|
-
if (!
|
|
43109
|
+
if (!existsSync38(path))
|
|
42346
43110
|
return [];
|
|
42347
43111
|
try {
|
|
42348
43112
|
return readPersistedJoins(path);
|
|
@@ -42361,8 +43125,8 @@ var init_join = __esm(async () => {
|
|
|
42361
43125
|
|
|
42362
43126
|
// ../../adapters/otium/src/peer-files.ts
|
|
42363
43127
|
import { randomUUID as randomUUID29 } from "crypto";
|
|
42364
|
-
import { copyFileSync as copyFileSync5, mkdirSync as
|
|
42365
|
-
import { basename as basename13, extname as extname6, join as
|
|
43128
|
+
import { copyFileSync as copyFileSync5, mkdirSync as mkdirSync34, rmSync as rmSync11, statSync as statSync21 } from "fs";
|
|
43129
|
+
import { basename as basename13, extname as extname6, join as join43 } from "path";
|
|
42366
43130
|
function fileType(mimeType) {
|
|
42367
43131
|
if (mimeType.startsWith("image/"))
|
|
42368
43132
|
return "image";
|
|
@@ -42427,8 +43191,8 @@ function recordFile(args) {
|
|
|
42427
43191
|
}
|
|
42428
43192
|
function insertLocalFile(args) {
|
|
42429
43193
|
const id = randomUUID29();
|
|
42430
|
-
|
|
42431
|
-
const path =
|
|
43194
|
+
mkdirSync34(PEER_FILES_DIR, { recursive: true });
|
|
43195
|
+
const path = join43(PEER_FILES_DIR, `${id}-${safeFilename(args.filename)}`);
|
|
42432
43196
|
copyFileSync5(args.sourcePath, path);
|
|
42433
43197
|
return recordFile({ id, path, sizeBytes: statSync21(path).size, ...args });
|
|
42434
43198
|
}
|
|
@@ -42474,7 +43238,7 @@ function installPeerFileHooks() {
|
|
|
42474
43238
|
var PEER_FILES_DIR;
|
|
42475
43239
|
var init_peer_files = __esm(async () => {
|
|
42476
43240
|
await init_src();
|
|
42477
|
-
PEER_FILES_DIR =
|
|
43241
|
+
PEER_FILES_DIR = join43(DATA_DIR, "otium-peer-files");
|
|
42478
43242
|
db.exec(`
|
|
42479
43243
|
CREATE TABLE IF NOT EXISTS otium_peer_files (
|
|
42480
43244
|
id TEXT PRIMARY KEY,
|
|
@@ -42866,8 +43630,8 @@ var init_store2 = __esm(async () => {
|
|
|
42866
43630
|
|
|
42867
43631
|
// ../../adapters/otium/src/workspace-scope.ts
|
|
42868
43632
|
import { createHash as createHash13 } from "crypto";
|
|
42869
|
-
import { mkdirSync as
|
|
42870
|
-
import { dirname as
|
|
43633
|
+
import { mkdirSync as mkdirSync35, readFileSync as readFileSync30, writeFileSync as writeFileSync26 } from "fs";
|
|
43634
|
+
import { dirname as dirname23, resolve as resolve25 } from "path";
|
|
42871
43635
|
function surfaceScopeFor(central, workspaceId) {
|
|
42872
43636
|
const digest = createHash13("sha256").update(`${central.trim().replace(/\/+$/, "")}
|
|
42873
43637
|
${workspaceId.trim()}`).digest("hex");
|
|
@@ -42878,7 +43642,7 @@ function scopeCachePath() {
|
|
|
42878
43642
|
}
|
|
42879
43643
|
function readCache() {
|
|
42880
43644
|
try {
|
|
42881
|
-
const parsed = JSON.parse(
|
|
43645
|
+
const parsed = JSON.parse(readFileSync30(scopeCachePath(), "utf-8"));
|
|
42882
43646
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
42883
43647
|
return {};
|
|
42884
43648
|
return parsed;
|
|
@@ -42886,43 +43650,43 @@ function readCache() {
|
|
|
42886
43650
|
return {};
|
|
42887
43651
|
}
|
|
42888
43652
|
}
|
|
42889
|
-
function cachedSurfaceScope(
|
|
42890
|
-
const record = readCache()[
|
|
42891
|
-
if (!record || record.central !==
|
|
43653
|
+
function cachedSurfaceScope(join44) {
|
|
43654
|
+
const record = readCache()[join44.cellId];
|
|
43655
|
+
if (!record || record.central !== join44.central || !record.workspaceId)
|
|
42892
43656
|
return null;
|
|
42893
43657
|
return surfaceScopeFor(record.central, record.workspaceId);
|
|
42894
43658
|
}
|
|
42895
|
-
function cacheSurfaceScope(
|
|
42896
|
-
const scope = surfaceScopeFor(
|
|
43659
|
+
function cacheSurfaceScope(join44, workspaceId) {
|
|
43660
|
+
const scope = surfaceScopeFor(join44.central, workspaceId);
|
|
42897
43661
|
const path = scopeCachePath();
|
|
42898
43662
|
try {
|
|
42899
|
-
|
|
43663
|
+
mkdirSync35(dirname23(path), { recursive: true });
|
|
42900
43664
|
const cache = readCache();
|
|
42901
|
-
cache[
|
|
42902
|
-
|
|
43665
|
+
cache[join44.cellId] = { central: join44.central, workspaceId, scope };
|
|
43666
|
+
writeFileSync26(path, `${JSON.stringify(cache, null, 2)}
|
|
42903
43667
|
`, { mode: 384 });
|
|
42904
43668
|
} catch (err2) {
|
|
42905
43669
|
logger.warn({ err: err2, path }, "otium: could not cache the workspace scope");
|
|
42906
43670
|
}
|
|
42907
43671
|
return scope;
|
|
42908
43672
|
}
|
|
42909
|
-
async function resolveSurfaceScope(
|
|
42910
|
-
const cached = cachedSurfaceScope(
|
|
43673
|
+
async function resolveSurfaceScope(join44) {
|
|
43674
|
+
const cached = cachedSurfaceScope(join44);
|
|
42911
43675
|
if (cached)
|
|
42912
43676
|
return cached;
|
|
42913
43677
|
try {
|
|
42914
|
-
const workspaceId = await peerWorkspaceIdForCell(
|
|
43678
|
+
const workspaceId = await peerWorkspaceIdForCell(join44.cellId);
|
|
42915
43679
|
if (!workspaceId)
|
|
42916
43680
|
return null;
|
|
42917
|
-
return cacheSurfaceScope(
|
|
43681
|
+
return cacheSurfaceScope(join44, workspaceId);
|
|
42918
43682
|
} catch (err2) {
|
|
42919
43683
|
logger.warn({ err: err2 }, "otium: workspace scope unresolved (will retry on the next contact)");
|
|
42920
43684
|
return null;
|
|
42921
43685
|
}
|
|
42922
43686
|
}
|
|
42923
43687
|
function surfaceScopeForCell(cellId) {
|
|
42924
|
-
const
|
|
42925
|
-
return
|
|
43688
|
+
const join44 = attachedOtiumCells().find((candidate) => candidate.cellId === cellId);
|
|
43689
|
+
return join44 ? cachedSurfaceScope(join44) : null;
|
|
42926
43690
|
}
|
|
42927
43691
|
function unscopedRoomsAddressable() {
|
|
42928
43692
|
return attachedOtiumCells().length < 2;
|
|
@@ -43935,17 +44699,17 @@ import {
|
|
|
43935
44699
|
import {
|
|
43936
44700
|
chmodSync as chmodSync8,
|
|
43937
44701
|
closeSync as closeSync6,
|
|
43938
|
-
existsSync as
|
|
44702
|
+
existsSync as existsSync39,
|
|
43939
44703
|
fsyncSync as fsyncSync3,
|
|
43940
44704
|
linkSync as linkSync2,
|
|
43941
|
-
mkdirSync as
|
|
44705
|
+
mkdirSync as mkdirSync36,
|
|
43942
44706
|
openSync as openSync6,
|
|
43943
|
-
readFileSync as
|
|
43944
|
-
renameSync as
|
|
44707
|
+
readFileSync as readFileSync31,
|
|
44708
|
+
renameSync as renameSync18,
|
|
43945
44709
|
unlinkSync as unlinkSync24,
|
|
43946
|
-
writeFileSync as
|
|
44710
|
+
writeFileSync as writeFileSync27
|
|
43947
44711
|
} from "fs";
|
|
43948
|
-
import { dirname as
|
|
44712
|
+
import { dirname as dirname24, resolve as resolve26 } from "path";
|
|
43949
44713
|
function parseEnrollmentInvite(code) {
|
|
43950
44714
|
let parsed;
|
|
43951
44715
|
try {
|
|
@@ -43970,10 +44734,10 @@ function pendingEnrollmentPath() {
|
|
|
43970
44734
|
}
|
|
43971
44735
|
function isEnrollmentPending(invite) {
|
|
43972
44736
|
const path = pendingEnrollmentPath();
|
|
43973
|
-
if (!
|
|
44737
|
+
if (!existsSync39(path))
|
|
43974
44738
|
return false;
|
|
43975
44739
|
try {
|
|
43976
|
-
const saved = JSON.parse(
|
|
44740
|
+
const saved = JSON.parse(readFileSync31(path, "utf8"));
|
|
43977
44741
|
return saved.central === invite.central && saved.token === invite.token;
|
|
43978
44742
|
} catch {
|
|
43979
44743
|
return false;
|
|
@@ -43981,8 +44745,8 @@ function isEnrollmentPending(invite) {
|
|
|
43981
44745
|
}
|
|
43982
44746
|
function loadOrCreatePending(invite, nodeName) {
|
|
43983
44747
|
const path = pendingEnrollmentPath();
|
|
43984
|
-
if (
|
|
43985
|
-
const saved = JSON.parse(
|
|
44748
|
+
if (existsSync39(path)) {
|
|
44749
|
+
const saved = JSON.parse(readFileSync31(path, "utf8"));
|
|
43986
44750
|
if (saved.central === invite.central && saved.token === invite.token)
|
|
43987
44751
|
return saved;
|
|
43988
44752
|
throw new Error(`another Otium enrollment is pending at ${path}`);
|
|
@@ -43995,12 +44759,12 @@ function loadOrCreatePending(invite, nodeName) {
|
|
|
43995
44759
|
publicKey: pair.publicKey.export({ format: "der", type: "spki" }).toString("base64url"),
|
|
43996
44760
|
...nodeName ? { nodeName } : {}
|
|
43997
44761
|
};
|
|
43998
|
-
|
|
43999
|
-
const temporaryPath = resolve26(
|
|
44762
|
+
mkdirSync36(dirname24(path), { recursive: true });
|
|
44763
|
+
const temporaryPath = resolve26(dirname24(path), `.otium-enrollment-pending.json.${process.pid}.${randomUUID31()}.tmp`);
|
|
44000
44764
|
let fd;
|
|
44001
44765
|
try {
|
|
44002
44766
|
fd = openSync6(temporaryPath, "wx", 384);
|
|
44003
|
-
|
|
44767
|
+
writeFileSync27(fd, `${JSON.stringify(pending2, null, 2)}
|
|
44004
44768
|
`, "utf8");
|
|
44005
44769
|
fsyncSync3(fd);
|
|
44006
44770
|
closeSync6(fd);
|
|
@@ -44008,7 +44772,7 @@ function loadOrCreatePending(invite, nodeName) {
|
|
|
44008
44772
|
linkSync2(temporaryPath, path);
|
|
44009
44773
|
unlinkSync24(temporaryPath);
|
|
44010
44774
|
chmodSync8(path, 384);
|
|
44011
|
-
const directoryFd = openSync6(
|
|
44775
|
+
const directoryFd = openSync6(dirname24(path), "r");
|
|
44012
44776
|
try {
|
|
44013
44777
|
fsyncSync3(directoryFd);
|
|
44014
44778
|
} finally {
|
|
@@ -44017,10 +44781,10 @@ function loadOrCreatePending(invite, nodeName) {
|
|
|
44017
44781
|
} catch (error2) {
|
|
44018
44782
|
if (fd !== undefined)
|
|
44019
44783
|
closeSync6(fd);
|
|
44020
|
-
if (
|
|
44784
|
+
if (existsSync39(temporaryPath))
|
|
44021
44785
|
unlinkSync24(temporaryPath);
|
|
44022
|
-
if (error2.code === "EEXIST" &&
|
|
44023
|
-
const saved = JSON.parse(
|
|
44786
|
+
if (error2.code === "EEXIST" && existsSync39(path)) {
|
|
44787
|
+
const saved = JSON.parse(readFileSync31(path, "utf8"));
|
|
44024
44788
|
if (saved.central === invite.central && saved.token === invite.token)
|
|
44025
44789
|
return saved;
|
|
44026
44790
|
}
|
|
@@ -44030,17 +44794,17 @@ function loadOrCreatePending(invite, nodeName) {
|
|
|
44030
44794
|
}
|
|
44031
44795
|
function replacePendingEnrollment(pending2) {
|
|
44032
44796
|
const path = pendingEnrollmentPath();
|
|
44033
|
-
const directory =
|
|
44797
|
+
const directory = dirname24(path);
|
|
44034
44798
|
const temporaryPath = resolve26(directory, `.otium-enrollment-pending.json.${process.pid}.${randomUUID31()}.tmp`);
|
|
44035
44799
|
let fd;
|
|
44036
44800
|
try {
|
|
44037
44801
|
fd = openSync6(temporaryPath, "wx", 384);
|
|
44038
|
-
|
|
44802
|
+
writeFileSync27(fd, `${JSON.stringify(pending2, null, 2)}
|
|
44039
44803
|
`, "utf8");
|
|
44040
44804
|
fsyncSync3(fd);
|
|
44041
44805
|
closeSync6(fd);
|
|
44042
44806
|
fd = undefined;
|
|
44043
|
-
|
|
44807
|
+
renameSync18(temporaryPath, path);
|
|
44044
44808
|
const directoryFd = openSync6(directory, "r");
|
|
44045
44809
|
try {
|
|
44046
44810
|
fsyncSync3(directoryFd);
|
|
@@ -44050,18 +44814,18 @@ function replacePendingEnrollment(pending2) {
|
|
|
44050
44814
|
} catch (error2) {
|
|
44051
44815
|
if (fd !== undefined)
|
|
44052
44816
|
closeSync6(fd);
|
|
44053
|
-
if (
|
|
44817
|
+
if (existsSync39(temporaryPath))
|
|
44054
44818
|
unlinkSync24(temporaryPath);
|
|
44055
44819
|
throw error2;
|
|
44056
44820
|
}
|
|
44057
44821
|
}
|
|
44058
|
-
function recordClaimedCredential(pending2,
|
|
44822
|
+
function recordClaimedCredential(pending2, join44) {
|
|
44059
44823
|
withJoinCredentialLock(() => {
|
|
44060
|
-
const current3 = JSON.parse(
|
|
44824
|
+
const current3 = JSON.parse(readFileSync31(pendingEnrollmentPath(), "utf8"));
|
|
44061
44825
|
if (current3.central !== pending2.central || current3.token !== pending2.token || current3.idempotencyKey !== pending2.idempotencyKey || current3.publicKey !== pending2.publicKey) {
|
|
44062
44826
|
throw new Error("pending Otium enrollment changed while its claim was in flight");
|
|
44063
44827
|
}
|
|
44064
|
-
const claimed = { digest: joinCredentialDigest(
|
|
44828
|
+
const claimed = { digest: joinCredentialDigest(join44), cellId: join44.cellId };
|
|
44065
44829
|
if (current3.claimed && (current3.claimed.digest !== claimed.digest || current3.claimed.cellId !== claimed.cellId)) {
|
|
44066
44830
|
throw new Error("central returned different credentials for an idempotent enrollment claim");
|
|
44067
44831
|
}
|
|
@@ -44119,36 +44883,36 @@ async function claimEnrollment(invite, nodeName) {
|
|
|
44119
44883
|
const secret = openCredential(credential, pending2.privateKey);
|
|
44120
44884
|
if (!secret.startsWith("rcs_"))
|
|
44121
44885
|
throw new Error("central returned an invalid runtime credential");
|
|
44122
|
-
const
|
|
44886
|
+
const join44 = {
|
|
44123
44887
|
v: 2,
|
|
44124
44888
|
central: invite.central,
|
|
44125
44889
|
relay: String(response.relayUrl),
|
|
44126
44890
|
cellId: String(response.cell?.id),
|
|
44127
44891
|
secret
|
|
44128
44892
|
};
|
|
44129
|
-
if (!
|
|
44893
|
+
if (!join44.relay || !join44.cellId || join44.cellId === "undefined") {
|
|
44130
44894
|
throw new Error("central returned an incomplete enrollment response");
|
|
44131
44895
|
}
|
|
44132
|
-
assertSecureRelayUrl(
|
|
44133
|
-
recordClaimedCredential(pending2,
|
|
44134
|
-
return
|
|
44896
|
+
assertSecureRelayUrl(join44.relay);
|
|
44897
|
+
recordClaimedCredential(pending2, join44);
|
|
44898
|
+
return join44;
|
|
44135
44899
|
}
|
|
44136
|
-
function commitEnrollment(
|
|
44900
|
+
function commitEnrollment(join44, options = {}) {
|
|
44137
44901
|
return withJoinCredentialLock(() => {
|
|
44138
44902
|
const pendingPath = pendingEnrollmentPath();
|
|
44139
|
-
const pending2 =
|
|
44140
|
-
const digest = joinCredentialDigest(
|
|
44141
|
-
if (pending2 && (!pending2.claimed || pending2.claimed.digest !== digest || pending2.claimed.cellId !==
|
|
44903
|
+
const pending2 = existsSync39(pendingPath) ? JSON.parse(readFileSync31(pendingPath, "utf8")) : null;
|
|
44904
|
+
const digest = joinCredentialDigest(join44);
|
|
44905
|
+
if (pending2 && (!pending2.claimed || pending2.claimed.digest !== digest || pending2.claimed.cellId !== join44.cellId)) {
|
|
44142
44906
|
throw new Error(`pending Otium enrollment at ${pendingPath} does not match these credentials`);
|
|
44143
44907
|
}
|
|
44144
|
-
const path = saveJoinWhileLocked(
|
|
44145
|
-
if (!isJoinPersisted(
|
|
44908
|
+
const path = saveJoinWhileLocked(join44, options);
|
|
44909
|
+
if (!isJoinPersisted(join44)) {
|
|
44146
44910
|
throw new Error("Otium join credentials were not durably persisted");
|
|
44147
44911
|
}
|
|
44148
44912
|
if (!pending2)
|
|
44149
44913
|
return path;
|
|
44150
44914
|
unlinkSync24(pendingPath);
|
|
44151
|
-
const directoryFd = openSync6(
|
|
44915
|
+
const directoryFd = openSync6(dirname24(pendingPath), "r");
|
|
44152
44916
|
try {
|
|
44153
44917
|
fsyncSync3(directoryFd);
|
|
44154
44918
|
} finally {
|
|
@@ -44729,24 +45493,24 @@ function refreshDefaultSurfaceScope() {
|
|
|
44729
45493
|
setSurfaceScopeRequired(scopes.length > 1);
|
|
44730
45494
|
}
|
|
44731
45495
|
function startOtiumNodeRuntime(options) {
|
|
44732
|
-
const { join:
|
|
44733
|
-
attachOtiumCentralCell(
|
|
45496
|
+
const { join: join44 } = options;
|
|
45497
|
+
attachOtiumCentralCell(join44);
|
|
44734
45498
|
const releaseGlobals = acquireGlobalOtiumServices();
|
|
44735
45499
|
let stopped = false;
|
|
44736
|
-
logger.info({ central:
|
|
44737
|
-
mountedScopes.set(
|
|
45500
|
+
logger.info({ central: join44.central, cellId: join44.cellId }, "otium: worker mode enabled");
|
|
45501
|
+
mountedScopes.set(join44.cellId, cachedSurfaceScope(join44));
|
|
44738
45502
|
refreshDefaultSurfaceScope();
|
|
44739
|
-
selfPeerNodeForCell(
|
|
45503
|
+
selfPeerNodeForCell(join44.cellId).then((self) => {
|
|
44740
45504
|
if (self) {
|
|
44741
45505
|
logger.info({ nodeName: self.nodeName, baseUrl: self.baseUrl }, "otium: attached to workspace");
|
|
44742
45506
|
}
|
|
44743
45507
|
}).catch((err2) => {
|
|
44744
45508
|
logger.warn({ err: err2 }, "otium: self check against central failed (will retry per request)");
|
|
44745
45509
|
});
|
|
44746
|
-
resolveSurfaceScope(
|
|
45510
|
+
resolveSurfaceScope(join44).then((scope) => {
|
|
44747
45511
|
if (!scope || stopped)
|
|
44748
45512
|
return;
|
|
44749
|
-
mountedScopes.set(
|
|
45513
|
+
mountedScopes.set(join44.cellId, scope);
|
|
44750
45514
|
refreshDefaultSurfaceScope();
|
|
44751
45515
|
if (mountedScopes.size === 1)
|
|
44752
45516
|
stampUnscopedOtiumTopics(scope);
|
|
@@ -44755,14 +45519,14 @@ function startOtiumNodeRuntime(options) {
|
|
|
44755
45519
|
});
|
|
44756
45520
|
return {
|
|
44757
45521
|
name: "otium",
|
|
44758
|
-
join:
|
|
45522
|
+
join: join44,
|
|
44759
45523
|
stop: () => {
|
|
44760
45524
|
if (stopped)
|
|
44761
45525
|
return;
|
|
44762
45526
|
stopped = true;
|
|
44763
|
-
mountedScopes.delete(
|
|
45527
|
+
mountedScopes.delete(join44.cellId);
|
|
44764
45528
|
refreshDefaultSurfaceScope();
|
|
44765
|
-
detachOtiumCentralCell(
|
|
45529
|
+
detachOtiumCentralCell(join44.cellId);
|
|
44766
45530
|
releaseGlobals();
|
|
44767
45531
|
}
|
|
44768
45532
|
};
|
|
@@ -44857,14 +45621,14 @@ __export(exports_node_runtime, {
|
|
|
44857
45621
|
function sameCredentials(left, right) {
|
|
44858
45622
|
return left.central === right.central && left.relay === right.relay && left.secret === right.secret;
|
|
44859
45623
|
}
|
|
44860
|
-
function attachOtiumWorkspace(
|
|
44861
|
-
const current3 = mounted.get(
|
|
45624
|
+
function attachOtiumWorkspace(join44) {
|
|
45625
|
+
const current3 = mounted.get(join44.cellId);
|
|
44862
45626
|
if (current3) {
|
|
44863
|
-
if (sameCredentials(current3.join,
|
|
45627
|
+
if (sameCredentials(current3.join, join44))
|
|
44864
45628
|
return false;
|
|
44865
|
-
detachOtiumWorkspace(
|
|
45629
|
+
detachOtiumWorkspace(join44.cellId);
|
|
44866
45630
|
}
|
|
44867
|
-
mounted.set(
|
|
45631
|
+
mounted.set(join44.cellId, startOtiumNodeRuntime({ join: join44 }));
|
|
44868
45632
|
return true;
|
|
44869
45633
|
}
|
|
44870
45634
|
function detachOtiumWorkspace(cellId) {
|
|
@@ -44879,7 +45643,7 @@ function mountedOtiumWorkspaces() {
|
|
|
44879
45643
|
return [...mounted.values()].map((runtime2) => runtime2.join);
|
|
44880
45644
|
}
|
|
44881
45645
|
function reconcileOtiumWorkspaces(joins = loadJoins()) {
|
|
44882
|
-
const wanted = new Map(joins.map((
|
|
45646
|
+
const wanted = new Map(joins.map((join44) => [join44.cellId, join44]));
|
|
44883
45647
|
const detached = [];
|
|
44884
45648
|
for (const cellId of [...mounted.keys()]) {
|
|
44885
45649
|
if (wanted.has(cellId))
|
|
@@ -44888,8 +45652,8 @@ function reconcileOtiumWorkspaces(joins = loadJoins()) {
|
|
|
44888
45652
|
detached.push(cellId);
|
|
44889
45653
|
}
|
|
44890
45654
|
const attached = [];
|
|
44891
|
-
for (const [cellId,
|
|
44892
|
-
if (attachOtiumWorkspace(
|
|
45655
|
+
for (const [cellId, join44] of wanted) {
|
|
45656
|
+
if (attachOtiumWorkspace(join44))
|
|
44893
45657
|
attached.push(cellId);
|
|
44894
45658
|
}
|
|
44895
45659
|
if (attached.length > 0 || detached.length > 0) {
|
|
@@ -44912,9 +45676,9 @@ async function handleOtiumAdapterControlRequest(req) {
|
|
|
44912
45676
|
if (req.method === "GET") {
|
|
44913
45677
|
return Response.json({
|
|
44914
45678
|
ok: true,
|
|
44915
|
-
workspaces: mountedOtiumWorkspaces().map((
|
|
44916
|
-
cellId:
|
|
44917
|
-
central:
|
|
45679
|
+
workspaces: mountedOtiumWorkspaces().map((join44) => ({
|
|
45680
|
+
cellId: join44.cellId,
|
|
45681
|
+
central: join44.central
|
|
44918
45682
|
}))
|
|
44919
45683
|
});
|
|
44920
45684
|
}
|
|
@@ -44935,8 +45699,8 @@ function mountConfiguredOtiumNodeRuntime() {
|
|
|
44935
45699
|
const joins = loadJoins();
|
|
44936
45700
|
if (joins.length === 0)
|
|
44937
45701
|
return null;
|
|
44938
|
-
for (const
|
|
44939
|
-
attachOtiumWorkspace(
|
|
45702
|
+
for (const join44 of joins)
|
|
45703
|
+
attachOtiumWorkspace(join44);
|
|
44940
45704
|
registerNodeRequestHandler("otium-adapter-control", handleOtiumAdapterControlRequest);
|
|
44941
45705
|
let stopped = false;
|
|
44942
45706
|
return {
|
|
@@ -45027,11 +45791,11 @@ async function joinCommand(args) {
|
|
|
45027
45791
|
process.exitCode = 1;
|
|
45028
45792
|
return;
|
|
45029
45793
|
}
|
|
45030
|
-
let
|
|
45794
|
+
let join44;
|
|
45031
45795
|
let productionEnrollment = false;
|
|
45032
45796
|
if (args.includes("--legacy")) {
|
|
45033
45797
|
try {
|
|
45034
|
-
|
|
45798
|
+
join44 = parseInviteCode(code);
|
|
45035
45799
|
} catch (err2) {
|
|
45036
45800
|
console.error(`invalid legacy invite code: ${err2 instanceof Error ? err2.message : err2}`);
|
|
45037
45801
|
process.exitCode = 1;
|
|
@@ -45058,7 +45822,7 @@ async function joinCommand(args) {
|
|
|
45058
45822
|
return;
|
|
45059
45823
|
}
|
|
45060
45824
|
}
|
|
45061
|
-
|
|
45825
|
+
join44 = await claimEnrollment(invite, nodeName);
|
|
45062
45826
|
productionEnrollment = true;
|
|
45063
45827
|
} catch (err2) {
|
|
45064
45828
|
console.error(`enrollment failed: ${err2 instanceof Error ? err2.message : err2}`);
|
|
@@ -45069,16 +45833,16 @@ async function joinCommand(args) {
|
|
|
45069
45833
|
let path;
|
|
45070
45834
|
try {
|
|
45071
45835
|
const saveOptions = { replaceExisting: args.includes("--replace") };
|
|
45072
|
-
path = productionEnrollment ? commitEnrollment(
|
|
45836
|
+
path = productionEnrollment ? commitEnrollment(join44, saveOptions) : saveJoin(join44, saveOptions);
|
|
45073
45837
|
} catch (err2) {
|
|
45074
45838
|
console.error(`could not save join credentials: ${err2 instanceof Error ? err2.message : err2}`);
|
|
45075
45839
|
process.exitCode = 1;
|
|
45076
45840
|
return;
|
|
45077
45841
|
}
|
|
45078
45842
|
console.log(`otium join credentials saved to ${path}`);
|
|
45079
|
-
console.log(` central: ${
|
|
45080
|
-
console.log(` cellId: ${
|
|
45081
|
-
configureOtiumCentral(
|
|
45843
|
+
console.log(` central: ${join44.central}`);
|
|
45844
|
+
console.log(` cellId: ${join44.cellId}`);
|
|
45845
|
+
configureOtiumCentral(join44);
|
|
45082
45846
|
try {
|
|
45083
45847
|
const self = await selfPeerNode();
|
|
45084
45848
|
if (self) {
|
|
@@ -45144,8 +45908,8 @@ async function proxyOtiumPeerRequest(req, dependencies = {}) {
|
|
|
45144
45908
|
}
|
|
45145
45909
|
}
|
|
45146
45910
|
async function runOtiumSidecar(options) {
|
|
45147
|
-
const
|
|
45148
|
-
if (!
|
|
45911
|
+
const join44 = loadJoin();
|
|
45912
|
+
if (!join44) {
|
|
45149
45913
|
throw new Error("not joined to an Otium workspace \u2014 run `negotium otium join <code>` first");
|
|
45150
45914
|
}
|
|
45151
45915
|
const initialNode = await inspectNodeDaemon();
|
|
@@ -45177,10 +45941,10 @@ async function runOtiumSidecar(options) {
|
|
|
45177
45941
|
lease.stop();
|
|
45178
45942
|
throw error2;
|
|
45179
45943
|
}
|
|
45180
|
-
const selectedRelay = options.relayUrl?.trim() ||
|
|
45944
|
+
const selectedRelay = options.relayUrl?.trim() || join44.relay || process.env.OTIUM_RELAY_URL?.trim();
|
|
45181
45945
|
const tunnel = selectedRelay ? new TunnelClient({
|
|
45182
45946
|
relayUrl: selectedRelay,
|
|
45183
|
-
token:
|
|
45947
|
+
token: join44.secret,
|
|
45184
45948
|
targetOrigin: `http://127.0.0.1:${server.port}`,
|
|
45185
45949
|
nodeVersion: `negotium@${NEGOTIUM_VERSION}`,
|
|
45186
45950
|
logger
|
|
@@ -45326,9 +46090,9 @@ async function runOtiumCli(args = process.argv.slice(2)) {
|
|
|
45326
46090
|
if (joins.length === 0)
|
|
45327
46091
|
throw new Error("not joined to an Otium workspace");
|
|
45328
46092
|
if (!targetCellId && joins.length > 1) {
|
|
45329
|
-
throw new Error(`this node is joined to ${joins.length} workspaces; name one to leave: ${joins.map((
|
|
46093
|
+
throw new Error(`this node is joined to ${joins.length} workspaces; name one to leave: ${joins.map((join44) => join44.cellId).join(", ")}`);
|
|
45330
46094
|
}
|
|
45331
|
-
if (targetCellId && !joins.some((
|
|
46095
|
+
if (targetCellId && !joins.some((join44) => join44.cellId === targetCellId)) {
|
|
45332
46096
|
throw new Error(`not joined as ${targetCellId}`);
|
|
45333
46097
|
}
|
|
45334
46098
|
removeJoin2(targetCellId);
|
|
@@ -46066,4 +46830,4 @@ switch (command) {
|
|
|
46066
46830
|
}
|
|
46067
46831
|
}
|
|
46068
46832
|
|
|
46069
|
-
//# debugId=
|
|
46833
|
+
//# debugId=65E123B94EDD2F5C64756E2164756E21
|