negotium 0.14.2 → 0.14.4

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.
Files changed (53) hide show
  1. package/dist/agent-helpers.js +137 -7
  2. package/dist/agent-helpers.js.map +12 -10
  3. package/dist/background-bash.js +2 -2
  4. package/dist/background-bash.js.map +4 -4
  5. package/dist/browser-runtime.js +2 -2
  6. package/dist/browser-runtime.js.map +4 -4
  7. package/dist/{chunk-z1nb140t.js → chunk-er68mpja.js} +17 -4
  8. package/dist/{chunk-z1nb140t.js.map → chunk-er68mpja.js.map} +5 -5
  9. package/dist/{chunk-m6aw1tb3.js → chunk-p7tprsvc.js} +1 -1
  10. package/dist/{chunk-bqdya494.js → chunk-zcazbmcs.js} +3 -3
  11. package/dist/{chunk-bqdya494.js.map → chunk-zcazbmcs.js.map} +2 -2
  12. package/dist/hosted-agent.js +23 -7
  13. package/dist/hosted-agent.js.map +7 -7
  14. package/dist/main.js +557 -390
  15. package/dist/main.js.map +15 -14
  16. package/dist/mcp-factories.js +137 -7
  17. package/dist/mcp-factories.js.map +12 -10
  18. package/dist/media.js +2 -2
  19. package/dist/media.js.map +4 -4
  20. package/dist/prompts.js +2 -2
  21. package/dist/prompts.js.map +4 -4
  22. package/dist/query-runtime.js +2 -2
  23. package/dist/query-runtime.js.map +4 -4
  24. package/dist/registry.js +3 -3
  25. package/dist/rollout.js +2 -2
  26. package/dist/runtime/src/agents/codex-provider.ts +8 -2
  27. package/dist/runtime/src/application/submit-runtime-gateway-turn.ts +21 -2
  28. package/dist/runtime/src/node-host.ts +1 -0
  29. package/dist/runtime/src/platform/config.ts +1 -1
  30. package/dist/runtime/src/platform/mcp-config.ts +38 -16
  31. package/dist/runtime/src/runtime/turn-runner.ts +13 -0
  32. package/dist/runtime/src/runtime-gateway.ts +86 -0
  33. package/dist/runtime/src/storage/topic-host-mcp-grants.ts +54 -0
  34. package/dist/runtime/src/topics/lifecycle.ts +2 -0
  35. package/dist/runtime/src/types.ts +4 -0
  36. package/dist/runtime/src/version.ts +1 -1
  37. package/dist/runtime-gateway.js +73 -1
  38. package/dist/runtime-gateway.js.map +3 -3
  39. package/dist/runtime-helpers.js +2 -2
  40. package/dist/runtime-helpers.js.map +4 -4
  41. package/dist/storage.js.map +2 -2
  42. package/dist/types/packages/core/src/platform/config.d.ts +1 -1
  43. package/dist/types/packages/core/src/platform/mcp-config.d.ts +2 -0
  44. package/dist/types/packages/core/src/runtime/turn-runner.d.ts +3 -1
  45. package/dist/types/packages/core/src/runtime-gateway.d.ts +11 -0
  46. package/dist/types/packages/core/src/storage/topic-host-mcp-grants.d.ts +5 -0
  47. package/dist/types/packages/core/src/types.d.ts +3 -0
  48. package/dist/types/packages/core/src/version.d.ts +1 -1
  49. package/dist/vault.js +2 -2
  50. package/dist/vault.js.map +4 -4
  51. package/install-browser-rs.mjs +3 -3
  52. package/package.json +1 -1
  53. /package/dist/{chunk-m6aw1tb3.js.map → chunk-p7tprsvc.js.map} +0 -0
package/dist/main.js CHANGED
@@ -281,7 +281,7 @@ function resolveWhisperBackend() {
281
281
  function codexAuthFilePath() {
282
282
  return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(process.env.CODEX_HOME || join(homedir(), ".codex"), "auth.json");
283
283
  }
284
- var HOME, PROJECT_ROOT, 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.3.2", BROWSER_RS_MIN_SECURE_VERSION = "0.2.1", BROWSER_RS_VERSION_PROBE_TIMEOUT_MS = 15000, SNIPPETS_API_URL, BROWSER_RS_BIN, BASH_RS_VERSION = "v0.1.7", BASH_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, DECISION_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-1", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, DEFAULT_TOPIC_EFFORT, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, WHISPER_BACKEND, MLX_WHISPER_BIN, WHISPER_MODEL_MLX, _envMaxTellDepth, MAX_TELL_DEPTH;
284
+ var HOME, PROJECT_ROOT, 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.4.0", BROWSER_RS_MIN_SECURE_VERSION = "0.2.1", BROWSER_RS_VERSION_PROBE_TIMEOUT_MS = 15000, SNIPPETS_API_URL, BROWSER_RS_BIN, BASH_RS_VERSION = "v0.1.7", BASH_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, DECISION_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-1", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, DEFAULT_TOPIC_EFFORT, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, WHISPER_BACKEND, MLX_WHISPER_BIN, WHISPER_MODEL_MLX, _envMaxTellDepth, MAX_TELL_DEPTH;
285
285
  var init_config = __esm(() => {
286
286
  init_config_helpers();
287
287
  init_logger();
@@ -2777,6 +2777,19 @@ function setNodeMcpServers(entries) {
2777
2777
  function getNodeMcpServers() {
2778
2778
  return nodeMcpEntries;
2779
2779
  }
2780
+ function isReservedRuntimeMcpServerName(name) {
2781
+ return Object.hasOwn(MCP_CATALOG, name) || nodeMcpEntries.some((entry) => entry.key === name);
2782
+ }
2783
+ function mergeHostMcpServers(base, hostMcpServers) {
2784
+ if (!hostMcpServers)
2785
+ return base;
2786
+ for (const name of Object.keys(hostMcpServers)) {
2787
+ if (Object.hasOwn(base, name) || isReservedRuntimeMcpServerName(name)) {
2788
+ throw new Error(`host MCP server name conflicts with node catalog: ${name}`);
2789
+ }
2790
+ }
2791
+ return { ...base, ...hostMcpServers };
2792
+ }
2780
2793
  function buildNodeMcpSpecs(agent, filter) {
2781
2794
  const out = {};
2782
2795
  for (const entry of nodeMcpEntries) {
@@ -2953,7 +2966,7 @@ function getMcpServersForQuery(opts) {
2953
2966
  });
2954
2967
  }
2955
2968
  if (opts.sessionType === "manager") {
2956
- return getManagerMcpServers({
2969
+ return mergeHostMcpServers(getManagerMcpServers({
2957
2970
  userId: opts.userId || "local",
2958
2971
  session: opts.session,
2959
2972
  topicId: opts.topicId,
@@ -2968,7 +2981,7 @@ function getMcpServersForQuery(opts) {
2968
2981
  autoContinue: opts.autoContinue,
2969
2982
  visualTools: opts.visualTools,
2970
2983
  fileDeliveryTools: opts.fileDeliveryTools
2971
- });
2984
+ }), opts.hostMcpServers);
2972
2985
  }
2973
2986
  return getForumMcpServers({
2974
2987
  userId: opts.userId || "local",
@@ -3836,7 +3849,7 @@ var exports_version = {};
3836
3849
  __export(exports_version, {
3837
3850
  NEGOTIUM_VERSION: () => NEGOTIUM_VERSION
3838
3851
  });
3839
- var NEGOTIUM_VERSION = "0.14.2";
3852
+ var NEGOTIUM_VERSION = "0.14.4";
3840
3853
 
3841
3854
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
3842
3855
  import { spawn as spawn2 } from "child_process";
@@ -6320,8 +6333,11 @@ function toCodexMcpServers(claudeShape) {
6320
6333
  if (typeof s.url === "string" && s.type !== "sse") {
6321
6334
  out[codexName] = withCodexMcpServerOverrides(name, {
6322
6335
  url: s.url,
6323
- ...s.http_headers && typeof s.http_headers === "object" ? { http_headers: s.http_headers } : {},
6324
- ...s.env_http_headers && typeof s.env_http_headers === "object" ? { env_http_headers: s.env_http_headers } : {}
6336
+ ...s.http_headers && typeof s.http_headers === "object" || s.headers && typeof s.headers === "object" ? {
6337
+ http_headers: s.http_headers ?? s.headers
6338
+ } : {},
6339
+ ...s.env_http_headers && typeof s.env_http_headers === "object" ? { env_http_headers: s.env_http_headers } : {},
6340
+ ...typeof s.timeout === "number" ? { tool_timeout_sec: Math.max(1, Math.ceil(s.timeout / 1000)) } : {}
6325
6341
  });
6326
6342
  }
6327
6343
  }
@@ -17731,6 +17747,487 @@ body{font-family:system-ui,-apple-system,"Segoe UI",sans-serif;background:var(--
17731
17747
  </style>`;
17732
17748
  });
17733
17749
 
17750
+ // ../../packages/core/src/runtime-gateway.ts
17751
+ function parseHostMcpServers(value) {
17752
+ if (value === undefined)
17753
+ return;
17754
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
17755
+ throw new TypeError("hostMcpServers must be an object");
17756
+ }
17757
+ const parsedEntries = [];
17758
+ for (const [name, raw] of Object.entries(value)) {
17759
+ if (!HOST_MCP_SERVER_NAME.test(name)) {
17760
+ throw new TypeError(`invalid host MCP server name: ${name}`);
17761
+ }
17762
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
17763
+ throw new TypeError(`hostMcpServers.${name} must be an object`);
17764
+ }
17765
+ const spec = raw;
17766
+ const unsupported = Object.keys(spec).find((key) => !HOST_MCP_SERVER_KEYS.has(key));
17767
+ if (unsupported) {
17768
+ throw new TypeError(`hostMcpServers.${name}.${unsupported} is not allowed`);
17769
+ }
17770
+ if (spec.type !== "sse" && spec.type !== "http") {
17771
+ throw new TypeError(`hostMcpServers.${name}.type must be sse or http`);
17772
+ }
17773
+ if (typeof spec.url !== "string") {
17774
+ throw new TypeError(`hostMcpServers.${name}.url must be a string`);
17775
+ }
17776
+ let url;
17777
+ try {
17778
+ url = new URL(spec.url);
17779
+ } catch {
17780
+ throw new TypeError(`hostMcpServers.${name}.url must be a valid URL`);
17781
+ }
17782
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
17783
+ throw new TypeError(`hostMcpServers.${name}.url must use http or https`);
17784
+ }
17785
+ let headers;
17786
+ if (spec.headers !== undefined) {
17787
+ if (!spec.headers || typeof spec.headers !== "object" || Array.isArray(spec.headers)) {
17788
+ throw new TypeError(`hostMcpServers.${name}.headers must be an object`);
17789
+ }
17790
+ const headerEntries = [];
17791
+ for (const [key, headerValue] of Object.entries(spec.headers)) {
17792
+ if (!HTTP_HEADER_NAME.test(key) || typeof headerValue !== "string") {
17793
+ throw new TypeError(`hostMcpServers.${name}.headers must contain string values`);
17794
+ }
17795
+ try {
17796
+ new Headers([[key, headerValue]]);
17797
+ } catch {
17798
+ throw new TypeError(`hostMcpServers.${name}.headers contains an invalid header`);
17799
+ }
17800
+ headerEntries.push([key, headerValue]);
17801
+ }
17802
+ headers = Object.fromEntries(headerEntries);
17803
+ }
17804
+ if (spec.timeout !== undefined && (typeof spec.timeout !== "number" || !Number.isFinite(spec.timeout) || spec.timeout <= 0)) {
17805
+ throw new TypeError(`hostMcpServers.${name}.timeout must be a positive number`);
17806
+ }
17807
+ parsedEntries.push([
17808
+ name,
17809
+ {
17810
+ type: spec.type,
17811
+ url: url.toString(),
17812
+ ...headers ? { headers } : {},
17813
+ ...spec.timeout !== undefined ? { timeout: spec.timeout } : {}
17814
+ }
17815
+ ]);
17816
+ }
17817
+ return Object.fromEntries(parsedEntries);
17818
+ }
17819
+ function record(value) {
17820
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
17821
+ }
17822
+ async function json(response) {
17823
+ try {
17824
+ return await response.json();
17825
+ } catch {
17826
+ throw new RuntimeGatewayError("Runtime Gateway returned invalid JSON", "protocol", response.status);
17827
+ }
17828
+ }
17829
+ function validHealth(value) {
17830
+ const body = record(value);
17831
+ return Boolean(body?.ok === true && body.v === RUNTIME_GATEWAY_VERSION && Array.isArray(body.capabilities) && typeof body.cursor === "number");
17832
+ }
17833
+ function validAcknowledgedMessage(value) {
17834
+ const message = record(value);
17835
+ return Boolean(message && typeof message.id === "string" && typeof message.topicId === "string" && typeof message.authorId === "string" && typeof message.text === "string" && typeof message.createdAt === "string");
17836
+ }
17837
+ function validTurnAck(value) {
17838
+ const body = record(value);
17839
+ return Boolean(body?.ok === true && body.v === RUNTIME_GATEWAY_VERSION && body.accepted === true && typeof body.deduplicated === "boolean" && typeof body.requestId === "string" && typeof body.clientMessageId === "string" && typeof body.topicId === "string" && typeof body.messageId === "string" && (body.message === undefined || validAcknowledgedMessage(body.message)) && typeof body.cursor === "number");
17840
+ }
17841
+ function validManagerTopic(value) {
17842
+ const topic = record(value);
17843
+ return Boolean(typeof topic?.id === "string" && typeof topic.title === "string" && topic.kind === "manager" && Array.isArray(topic.participants));
17844
+ }
17845
+ function validDeletedMessage(value) {
17846
+ const message = record(value);
17847
+ return Boolean(typeof message?.id === "string" && typeof message.topicId === "string" && typeof message.authorId === "string" && typeof message.text === "string" && message.deleted === true && typeof message.createdAt === "string");
17848
+ }
17849
+ function validCronJob(value) {
17850
+ const job = record(value);
17851
+ return Boolean(typeof job?.id === "string" && typeof job.name === "string" && typeof job.executionPrincipalUserId === "string" && typeof job.actorOwnerUserId === "string" && typeof job.topicId === "string" && typeof job.schedule === "string" && typeof job.enabled === "boolean" && typeof job.nextRunAt === "string" && typeof job.canMutate === "boolean");
17852
+ }
17853
+ async function* parseRuntimeGatewaySse(body) {
17854
+ const reader = body.getReader();
17855
+ const decoder = new TextDecoder;
17856
+ let buffer = "";
17857
+ let event = "message";
17858
+ let id;
17859
+ let data = [];
17860
+ const flush = () => {
17861
+ if (data.length === 0)
17862
+ return null;
17863
+ const raw = data.join(`
17864
+ `);
17865
+ data = [];
17866
+ try {
17867
+ return { event, id, data: JSON.parse(raw) };
17868
+ } catch {
17869
+ throw new RuntimeGatewayError("Runtime Gateway SSE event has invalid JSON", "protocol");
17870
+ } finally {
17871
+ event = "message";
17872
+ id = undefined;
17873
+ }
17874
+ };
17875
+ try {
17876
+ while (true) {
17877
+ const chunk = await reader.read();
17878
+ if (chunk.done)
17879
+ break;
17880
+ buffer += decoder.decode(chunk.value, { stream: true }).replace(/\r\n/g, `
17881
+ `);
17882
+ let newline = buffer.indexOf(`
17883
+ `);
17884
+ while (newline >= 0) {
17885
+ const line = buffer.slice(0, newline);
17886
+ buffer = buffer.slice(newline + 1);
17887
+ if (line === "") {
17888
+ const parsed2 = flush();
17889
+ if (parsed2)
17890
+ yield parsed2;
17891
+ } else if (line.startsWith("event:")) {
17892
+ event = line.slice(6).trim() || "message";
17893
+ } else if (line.startsWith("id:")) {
17894
+ const parsed2 = Number.parseInt(line.slice(3).trim(), 10);
17895
+ if (Number.isFinite(parsed2))
17896
+ id = parsed2;
17897
+ } else if (line.startsWith("data:")) {
17898
+ data.push(line.slice(5).trimStart());
17899
+ }
17900
+ newline = buffer.indexOf(`
17901
+ `);
17902
+ }
17903
+ }
17904
+ buffer += decoder.decode();
17905
+ if (buffer.startsWith("data:"))
17906
+ data.push(buffer.slice(5).trimStart());
17907
+ const parsed = flush();
17908
+ if (parsed)
17909
+ yield parsed;
17910
+ } finally {
17911
+ reader.releaseLock();
17912
+ }
17913
+ }
17914
+
17915
+ class RuntimeGatewayClient {
17916
+ options;
17917
+ pathPrefix;
17918
+ request;
17919
+ requestTimeoutMs;
17920
+ compactTimeoutMs;
17921
+ constructor(options) {
17922
+ this.options = options;
17923
+ this.pathPrefix = options.pathPrefix ?? RUNTIME_GATEWAY_CONTROL_PATH;
17924
+ this.request = options.fetch ?? fetch;
17925
+ this.requestTimeoutMs = options.requestTimeoutMs ?? 5000;
17926
+ this.compactTimeoutMs = options.compactTimeoutMs ?? 5 * 60000;
17927
+ }
17928
+ endpoint(path) {
17929
+ if (!this.options.baseUrl || !this.options.token) {
17930
+ throw new RuntimeGatewayError("Runtime Gateway is not configured", "config");
17931
+ }
17932
+ return `${this.options.baseUrl.replace(/\/+$/, "")}${this.pathPrefix}${path}`;
17933
+ }
17934
+ async authorization() {
17935
+ try {
17936
+ const token = typeof this.options.token === "function" ? await this.options.token() : this.options.token;
17937
+ return `Bearer ${token}`;
17938
+ } catch (error) {
17939
+ throw new RuntimeGatewayError(error instanceof Error ? error.message : "Runtime Gateway token resolution failed", "config");
17940
+ }
17941
+ }
17942
+ async send(path, init, timeoutMs) {
17943
+ const authorization = await this.authorization();
17944
+ try {
17945
+ return await this.request(this.endpoint(path), {
17946
+ ...init,
17947
+ headers: { authorization, ...init.headers },
17948
+ signal: timeoutMs === undefined ? init.signal : AbortSignal.timeout(timeoutMs)
17949
+ });
17950
+ } catch (error) {
17951
+ if (error instanceof DOMException && error.name === "TimeoutError") {
17952
+ throw new RuntimeGatewayError("Runtime Gateway request timed out", "timeout");
17953
+ }
17954
+ throw new RuntimeGatewayError("Runtime Gateway request failed", "transport");
17955
+ }
17956
+ }
17957
+ async health() {
17958
+ const response = await this.send("/health", { method: "GET" }, this.requestTimeoutMs);
17959
+ if (!response.ok) {
17960
+ throw new RuntimeGatewayError("Runtime Gateway health request failed", "http", response.status);
17961
+ }
17962
+ const body = await json(response);
17963
+ if (!validHealth(body)) {
17964
+ throw new RuntimeGatewayError("Runtime Gateway health response is incompatible", "protocol");
17965
+ }
17966
+ return body;
17967
+ }
17968
+ async submitTurn(input) {
17969
+ const response = await this.send("/turns", {
17970
+ method: "POST",
17971
+ headers: { "content-type": "application/json" },
17972
+ body: JSON.stringify({ v: RUNTIME_GATEWAY_VERSION, ...input })
17973
+ }, this.requestTimeoutMs);
17974
+ if (response.status !== 202) {
17975
+ throw new RuntimeGatewayError("Runtime Gateway turn was not accepted", "http", response.status);
17976
+ }
17977
+ const body = await json(response);
17978
+ if (!validTurnAck(body)) {
17979
+ throw new RuntimeGatewayError("Runtime Gateway turn acknowledgement is incompatible", "protocol");
17980
+ }
17981
+ return body;
17982
+ }
17983
+ async deriveTopic(topicId, input) {
17984
+ const response = await this.send(`/topics/${encodeURIComponent(topicId)}/derive`, {
17985
+ method: "POST",
17986
+ headers: { "content-type": "application/json" },
17987
+ body: JSON.stringify({ v: RUNTIME_GATEWAY_VERSION, ...input })
17988
+ }, this.requestTimeoutMs);
17989
+ if (response.status !== 201) {
17990
+ throw new RuntimeGatewayError("Runtime Gateway topic derive failed", "http", response.status);
17991
+ }
17992
+ const body = await json(response);
17993
+ const topic = record(body)?.topic;
17994
+ const topicRecord = record(topic);
17995
+ if (!topicRecord || typeof topicRecord.id !== "string" || typeof topicRecord.title !== "string") {
17996
+ throw new RuntimeGatewayError("Runtime Gateway topic derive response is incompatible", "protocol");
17997
+ }
17998
+ return topic;
17999
+ }
18000
+ async ensureManagerTopic(userId) {
18001
+ const response = await this.send("/manager-topic", {
18002
+ method: "POST",
18003
+ headers: { "content-type": "application/json" },
18004
+ body: JSON.stringify({ v: RUNTIME_GATEWAY_VERSION, userId })
18005
+ }, this.requestTimeoutMs);
18006
+ if (!response.ok) {
18007
+ throw new RuntimeGatewayError("Runtime Gateway manager topic request failed", "http", response.status);
18008
+ }
18009
+ const body = record(await json(response));
18010
+ if (body?.ok !== true || body.v !== RUNTIME_GATEWAY_VERSION || !validManagerTopic(body.topic)) {
18011
+ throw new RuntimeGatewayError("Runtime Gateway manager topic response is incompatible", "protocol");
18012
+ }
18013
+ return body.topic;
18014
+ }
18015
+ async deleteMessage(topicId, messageId, actorUserId, allowAdmin = false) {
18016
+ const response = await this.send(`/topics/${encodeURIComponent(topicId)}/messages/${encodeURIComponent(messageId)}`, {
18017
+ method: "DELETE",
18018
+ headers: { "content-type": "application/json" },
18019
+ body: JSON.stringify({
18020
+ v: RUNTIME_GATEWAY_VERSION,
18021
+ actorUserId,
18022
+ ...allowAdmin ? { allowAdmin: true } : {}
18023
+ })
18024
+ }, this.requestTimeoutMs);
18025
+ if (!response.ok) {
18026
+ throw new RuntimeGatewayError("Runtime Gateway message delete failed", "http", response.status);
18027
+ }
18028
+ const body = record(await json(response));
18029
+ if (body?.ok !== true || body.v !== RUNTIME_GATEWAY_VERSION || !validDeletedMessage(body.message)) {
18030
+ throw new RuntimeGatewayError("Runtime Gateway message delete response is incompatible", "protocol");
18031
+ }
18032
+ return body.message;
18033
+ }
18034
+ async cronRequest(path, init) {
18035
+ const response = await this.send(path, init, this.requestTimeoutMs);
18036
+ if (!response.ok) {
18037
+ throw new RuntimeGatewayError("Runtime Gateway cron request failed", "http", response.status);
18038
+ }
18039
+ const body = record(await json(response));
18040
+ if (body?.ok !== true || body.v !== RUNTIME_GATEWAY_VERSION) {
18041
+ throw new RuntimeGatewayError("Runtime Gateway cron response is incompatible", "protocol");
18042
+ }
18043
+ return body;
18044
+ }
18045
+ async listCronScripts() {
18046
+ const body = await this.cronRequest("/cron/scripts", { method: "GET" });
18047
+ if (!Array.isArray(body.scripts) || !body.scripts.every((value) => typeof value === "string")) {
18048
+ throw new RuntimeGatewayError("Runtime Gateway cron scripts are incompatible", "protocol");
18049
+ }
18050
+ return body.scripts;
18051
+ }
18052
+ async listCronJobs(userId, actorUserId, actorIsAdmin = false) {
18053
+ const search = new URLSearchParams({
18054
+ user: userId,
18055
+ actorUserId,
18056
+ actorIsAdmin: actorIsAdmin ? "1" : "0"
18057
+ });
18058
+ const body = await this.cronRequest(`/cron/jobs?${search}`, { method: "GET" });
18059
+ if (!Array.isArray(body.jobs) || !body.jobs.every(validCronJob)) {
18060
+ throw new RuntimeGatewayError("Runtime Gateway cron jobs are incompatible", "protocol");
18061
+ }
18062
+ return body.jobs;
18063
+ }
18064
+ async createCronJob(input) {
18065
+ const body = await this.cronRequest("/cron/jobs", {
18066
+ method: "POST",
18067
+ headers: { "content-type": "application/json" },
18068
+ body: JSON.stringify({ v: RUNTIME_GATEWAY_VERSION, ...input })
18069
+ });
18070
+ if (!validCronJob(body.job)) {
18071
+ throw new RuntimeGatewayError("Runtime Gateway cron job is incompatible", "protocol");
18072
+ }
18073
+ return body.job;
18074
+ }
18075
+ async updateCronJob(jobId, userId, actorUserId, patch, actorIsAdmin = false) {
18076
+ const body = await this.cronRequest(`/cron/jobs/${encodeURIComponent(jobId)}`, {
18077
+ method: "PATCH",
18078
+ headers: { "content-type": "application/json" },
18079
+ body: JSON.stringify({
18080
+ v: RUNTIME_GATEWAY_VERSION,
18081
+ userId,
18082
+ actorUserId,
18083
+ actorIsAdmin,
18084
+ patch
18085
+ })
18086
+ });
18087
+ if (!validCronJob(body.job)) {
18088
+ throw new RuntimeGatewayError("Runtime Gateway cron job is incompatible", "protocol");
18089
+ }
18090
+ return body.job;
18091
+ }
18092
+ async deleteCronJob(jobId, userId, actorUserId, actorIsAdmin = false) {
18093
+ const search = new URLSearchParams({
18094
+ user: userId,
18095
+ actorUserId,
18096
+ actorIsAdmin: actorIsAdmin ? "1" : "0"
18097
+ });
18098
+ const body = await this.cronRequest(`/cron/jobs/${encodeURIComponent(jobId)}?${search}`, {
18099
+ method: "DELETE"
18100
+ });
18101
+ if (typeof body.deleted !== "string") {
18102
+ throw new RuntimeGatewayError("Runtime Gateway cron delete is incompatible", "protocol");
18103
+ }
18104
+ return body.deleted;
18105
+ }
18106
+ async* events(after, topicId, signal) {
18107
+ const search = new URLSearchParams({ after: String(Math.max(0, after)) });
18108
+ if (topicId)
18109
+ search.set("topicId", topicId);
18110
+ const response = await this.send(`/events?${search}`, { method: "GET", signal });
18111
+ if (!response.ok || !response.body) {
18112
+ throw new RuntimeGatewayError("Runtime Gateway event stream failed", "http", response.status);
18113
+ }
18114
+ yield* parseRuntimeGatewaySse(response.body);
18115
+ }
18116
+ async getTopicUsage(topicId, userId) {
18117
+ const response = await this.send(`/topics/${encodeURIComponent(topicId)}/usage?user=${encodeURIComponent(userId)}`, { method: "GET" }, this.requestTimeoutMs);
18118
+ if (!response.ok) {
18119
+ throw new RuntimeGatewayError("Runtime Gateway topic usage request failed", "http", response.status);
18120
+ }
18121
+ const usage = record(record(await json(response))?.usage);
18122
+ const read = (key) => {
18123
+ const value = usage?.[key];
18124
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
18125
+ };
18126
+ return {
18127
+ queries: read("queries"),
18128
+ inputTokens: read("inputTokens"),
18129
+ outputTokens: read("outputTokens"),
18130
+ cacheCreationInputTokens: read("cacheCreationInputTokens"),
18131
+ cacheReadInputTokens: read("cacheReadInputTokens"),
18132
+ estimatedCostUsd: read("estimatedCostUsd")
18133
+ };
18134
+ }
18135
+ async abortTurn(topicId, userId) {
18136
+ const response = await this.send(`/topics/${encodeURIComponent(topicId)}/abort`, {
18137
+ method: "POST",
18138
+ headers: { "content-type": "application/json" },
18139
+ body: JSON.stringify({ v: RUNTIME_GATEWAY_VERSION, userId })
18140
+ }, this.requestTimeoutMs);
18141
+ if (!response.ok) {
18142
+ throw new RuntimeGatewayError("Runtime Gateway turn abort failed", "http", response.status);
18143
+ }
18144
+ const body = record(await json(response));
18145
+ if (body?.ok !== true || body.v !== RUNTIME_GATEWAY_VERSION || typeof body.aborted !== "boolean") {
18146
+ throw new RuntimeGatewayError("Runtime Gateway turn abort response is incompatible", "protocol");
18147
+ }
18148
+ return body.aborted;
18149
+ }
18150
+ async resetSession(topicId, userId, reason, actorUserId) {
18151
+ return this.sessionCommand(topicId, userId, "reset", reason, actorUserId);
18152
+ }
18153
+ async compactSession(topicId, userId, reason, actorUserId) {
18154
+ return this.sessionCommand(topicId, userId, "compact", reason, actorUserId);
18155
+ }
18156
+ async sessionCommand(topicId, userId, command, reason, actorUserId) {
18157
+ const response = await this.send(`/topics/${encodeURIComponent(topicId)}/session/${command}`, {
18158
+ method: "POST",
18159
+ headers: { "content-type": "application/json" },
18160
+ body: JSON.stringify({
18161
+ v: RUNTIME_GATEWAY_VERSION,
18162
+ userId,
18163
+ ...actorUserId ? { actorUserId } : {},
18164
+ ...reason ? { reason } : {}
18165
+ })
18166
+ }, command === "compact" ? this.compactTimeoutMs : this.requestTimeoutMs);
18167
+ if (!response.ok) {
18168
+ throw new RuntimeGatewayError(`Runtime Gateway session ${command} failed`, "http", response.status);
18169
+ }
18170
+ const body = record(await json(response));
18171
+ if (body?.ok !== true || body.v !== RUNTIME_GATEWAY_VERSION) {
18172
+ throw new RuntimeGatewayError(`Runtime Gateway session ${command} response is incompatible`, "protocol");
18173
+ }
18174
+ return typeof body.result === "string" ? body.result : undefined;
18175
+ }
18176
+ }
18177
+ var RUNTIME_GATEWAY_VERSION = 1, RUNTIME_GATEWAY_CONTROL_PATH = "/api/v1/control/runtime/v1", RuntimeGatewayError, HOST_MCP_SERVER_NAME, HOST_MCP_SERVER_KEYS, HTTP_HEADER_NAME;
18178
+ var init_runtime_gateway = __esm(() => {
18179
+ RuntimeGatewayError = class RuntimeGatewayError extends Error {
18180
+ kind;
18181
+ status;
18182
+ constructor(message, kind, status) {
18183
+ super(message);
18184
+ this.kind = kind;
18185
+ this.status = status;
18186
+ }
18187
+ };
18188
+ HOST_MCP_SERVER_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
18189
+ HOST_MCP_SERVER_KEYS = new Set(["type", "url", "headers", "timeout"]);
18190
+ HTTP_HEADER_NAME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
18191
+ });
18192
+
18193
+ // ../../packages/core/src/storage/topic-host-mcp-grants.ts
18194
+ function getTopicHostMcpGrant(topicId) {
18195
+ const row = db.query("SELECT servers_json FROM api_topic_host_mcp_grants WHERE topic_id = ?").get(topicId);
18196
+ if (!row)
18197
+ return null;
18198
+ try {
18199
+ return parseHostMcpServers(JSON.parse(row.servers_json)) ?? null;
18200
+ } catch {
18201
+ return null;
18202
+ }
18203
+ }
18204
+ function recordTopicHostMcpGrant(topicId, servers) {
18205
+ const validated = parseHostMcpServers(servers) ?? {};
18206
+ if (Object.keys(validated).length === 0) {
18207
+ deleteTopicHostMcpGrant(topicId);
18208
+ return;
18209
+ }
18210
+ db.query(`INSERT INTO api_topic_host_mcp_grants (topic_id, servers_json, updated_at)
18211
+ VALUES (?, ?, ?)
18212
+ ON CONFLICT(topic_id) DO UPDATE SET
18213
+ servers_json = excluded.servers_json,
18214
+ updated_at = excluded.updated_at`).run(topicId, JSON.stringify(validated), new Date().toISOString());
18215
+ }
18216
+ function deleteTopicHostMcpGrant(topicId) {
18217
+ db.query("DELETE FROM api_topic_host_mcp_grants WHERE topic_id = ?").run(topicId);
18218
+ }
18219
+ var init_topic_host_mcp_grants = __esm(async () => {
18220
+ init_runtime_gateway();
18221
+ await init_forum_db();
18222
+ db.exec(`
18223
+ CREATE TABLE IF NOT EXISTS api_topic_host_mcp_grants (
18224
+ topic_id TEXT PRIMARY KEY,
18225
+ servers_json TEXT NOT NULL,
18226
+ updated_at TEXT NOT NULL
18227
+ )
18228
+ `);
18229
+ });
18230
+
17734
18231
  // ../../packages/core/src/storage/topic-tool-capabilities.ts
17735
18232
  function getTopicToolCapabilities(topicId) {
17736
18233
  const row = db.query(`SELECT visual_tools, file_delivery_tools
@@ -17918,6 +18415,7 @@ async function deleteTopicCascadeImpl(topic, userId, options, deletingAncestorId
17918
18415
  logger.warn({ err: err2, topicId, cwd }, "deleteTopicCascade: workspace cleanup failed");
17919
18416
  }
17920
18417
  deleteTopicVisuals(topicId);
18418
+ deleteTopicHostMcpGrant(topicId);
17921
18419
  deleteTopicToolCapabilities(topicId);
17922
18420
  deleteTopicArchiveState(topicId);
17923
18421
  deleteMessagesForTopic(topicId);
@@ -17980,6 +18478,7 @@ var init_lifecycle = __esm(async () => {
17980
18478
  await init_token_stats();
17981
18479
  await init_topic_archive();
17982
18480
  await init_topic_archive_state();
18481
+ await init_topic_host_mcp_grants();
17983
18482
  await init_topic_tool_capabilities();
17984
18483
  TopicArchiveRequiredError = class TopicArchiveRequiredError extends Error {
17985
18484
  code = "TOPIC_ARCHIVE_FAILED";
@@ -18101,8 +18600,8 @@ function parseAttachmentNames(raw) {
18101
18600
  return parsed.map((item) => {
18102
18601
  if (!item || typeof item !== "object")
18103
18602
  return;
18104
- const record = item;
18105
- const name = record.filename ?? record.name ?? record.id;
18603
+ const record2 = item;
18604
+ const name = record2.filename ?? record2.name ?? record2.id;
18106
18605
  return typeof name === "string" && name.trim() ? name.trim() : undefined;
18107
18606
  }).filter((name) => Boolean(name));
18108
18607
  } catch {
@@ -19481,6 +19980,7 @@ __export(exports_turn_runner, {
19481
19980
  resolveWikiMemoryMirror: () => resolveWikiMemoryMirror,
19482
19981
  resolveVisualMediaInput: () => resolveVisualMediaInput,
19483
19982
  resolveTurnToolCapabilities: () => resolveTurnToolCapabilities,
19983
+ resolveTurnHostMcpServers: () => resolveTurnHostMcpServers,
19484
19984
  resolveTopicTurnSession: () => resolveTopicTurnSession,
19485
19985
  resolveTopicTurnExecution: () => resolveTopicTurnExecution,
19486
19986
  resolveSessionRetry: () => resolveSessionRetry,
@@ -19837,6 +20337,12 @@ function resolveTurnToolCapabilities(topicId, params) {
19837
20337
  fileDeliveryTools: params.fileDeliveryTools ?? granted?.fileDeliveryTools ?? false
19838
20338
  };
19839
20339
  }
20340
+ function resolveTurnHostMcpServers(topic, sessionType) {
20341
+ const effectiveSessionType = sessionType ?? (topic.kind === "manager" ? "manager" : "forum");
20342
+ if (topic.kind !== "manager" || effectiveSessionType !== "manager")
20343
+ return;
20344
+ return getTopicHostMcpGrant(topic.id) ?? undefined;
20345
+ }
19840
20346
  function mergeSupersedingUserTurn(running, incoming) {
19841
20347
  const userMessages = [
19842
20348
  ...running.userMessages ?? [legacyUserTurnEnvelope(running.prompt, running.attachments)],
@@ -20056,6 +20562,7 @@ function startAiTurn(params) {
20056
20562
  const sessionName = params.sessionName ?? topic.title;
20057
20563
  const sessionType = params.sessionType;
20058
20564
  const { visualTools, fileDeliveryTools } = resolveTurnToolCapabilities(topicId, params);
20565
+ const hostMcpServers = resolveTurnHostMcpServers(topic, sessionType);
20059
20566
  const onSessionId = params.onSessionId;
20060
20567
  const onSessionReset = params.onSessionReset;
20061
20568
  const bridgeSessionFromHistory = params.bridgeSessionFromHistory === true;
@@ -20555,6 +21062,7 @@ function startAiTurn(params) {
20555
21062
  autoContinue: allowAutoContinue && !silent,
20556
21063
  visualTools,
20557
21064
  fileDeliveryTools,
21065
+ hostMcpServers,
20558
21066
  ...threadRootId ? { threadRootId } : {},
20559
21067
  peerBridge
20560
21068
  });
@@ -20898,6 +21406,7 @@ var init_turn_runner = __esm(async () => {
20898
21406
  await init_runtime_leases();
20899
21407
  await init_runtime_topic_state();
20900
21408
  await init_runtime_turn_requests();
21409
+ await init_topic_host_mcp_grants();
20901
21410
  await init_topic_tool_capabilities();
20902
21411
  await init_wiki();
20903
21412
  init_wiki_summary_names();
@@ -21921,6 +22430,14 @@ function gatewayPayloadHash(params, requestId, actorUserId) {
21921
22430
  ])).digest("hex");
21922
22431
  }
21923
22432
  function submitRuntimeGatewayTurn(params) {
22433
+ const hostMcpServers = parseHostMcpServers(params.hostMcpServers);
22434
+ if (hostMcpServers !== undefined && params.topic.kind !== "manager") {
22435
+ throw new TypeError("hostMcpServers is only allowed for manager topics");
22436
+ }
22437
+ const conflictingHostMcpName = Object.keys(hostMcpServers ?? {}).find(isReservedRuntimeMcpServerName);
22438
+ if (conflictingHostMcpName) {
22439
+ throw new TypeError(`host MCP server name conflicts with node catalog: ${conflictingHostMcpName}`);
22440
+ }
21924
22441
  const requestId = params.requestId ?? params.clientMessageId;
21925
22442
  const actorUserId = params.actorUserId ?? params.userId;
21926
22443
  const sourceAdapter = params.sourceAdapter?.trim() || "runtime-gateway";
@@ -22001,6 +22518,9 @@ function submitRuntimeGatewayTurn(params) {
22001
22518
  }
22002
22519
  });
22003
22520
  }
22521
+ if (hostMcpServers !== undefined) {
22522
+ recordTopicHostMcpGrant(params.topic.id, hostMcpServers);
22523
+ }
22004
22524
  const acceptedEvent = appendRuntimeEvent("runtime-gateway-ingress", {
22005
22525
  type: "ai-status",
22006
22526
  topicId: params.topic.id,
@@ -22032,7 +22552,9 @@ function submitRuntimeGatewayTurn(params) {
22032
22552
  }
22033
22553
  var RuntimeGatewayIdempotencyConflictError;
22034
22554
  var init_submit_runtime_gateway_turn = __esm(async () => {
22555
+ init_mcp_config();
22035
22556
  init_file_hooks();
22557
+ init_runtime_gateway();
22036
22558
  await init_api_messages();
22037
22559
  await init_api_topics();
22038
22560
  await init_forum_db();
@@ -22041,6 +22563,7 @@ var init_submit_runtime_gateway_turn = __esm(async () => {
22041
22563
  await init_runtime_leases();
22042
22564
  await init_runtime_topic_state();
22043
22565
  await init_runtime_turn_requests();
22566
+ await init_topic_host_mcp_grants();
22044
22567
  await init_topic_tool_capabilities();
22045
22568
  RuntimeGatewayIdempotencyConflictError = class RuntimeGatewayIdempotencyConflictError extends Error {
22046
22569
  constructor(message = "clientMessageId or requestId is already bound to another turn") {
@@ -25328,6 +25851,7 @@ __export(exports_node_host, {
25328
25851
  killOwnedCodexTreesForShutdown: () => killOwnedCodexTreesForShutdown,
25329
25852
  killAllPlaywright: () => killAllPlaywright,
25330
25853
  killAllBgBash: () => killAllBgBash,
25854
+ isReservedRuntimeMcpServerName: () => isReservedRuntimeMcpServerName,
25331
25855
  isParticipant: () => isParticipant,
25332
25856
  getVisibleTopics: () => getVisibleTopics,
25333
25857
  getTopicStats: () => getTopicStats,
@@ -26842,10 +27366,10 @@ var init_agent_health = __esm(async () => {
26842
27366
 
26843
27367
  // ../../packages/core/src/mcp/session-comm/tell-permissions.ts
26844
27368
  function resolveSubagentTellIdentity(currentTopicId, assertedParentTopicId) {
26845
- const record = currentTopicId ? getTopic(currentTopicId) : undefined;
26846
- const identityInvalid = Boolean(assertedParentTopicId && (!record?.isSubagent || record.parentTopicId !== assertedParentTopicId)) || Boolean(record?.isSubagent && !record.parentTopicId);
26847
- const restricted = identityInvalid || Boolean(assertedParentTopicId) || Boolean(record?.isSubagent);
26848
- const parentTopicId = identityInvalid ? undefined : assertedParentTopicId || (record?.isSubagent ? record.parentTopicId : undefined);
27369
+ const record2 = currentTopicId ? getTopic(currentTopicId) : undefined;
27370
+ const identityInvalid = Boolean(assertedParentTopicId && (!record2?.isSubagent || record2.parentTopicId !== assertedParentTopicId)) || Boolean(record2?.isSubagent && !record2.parentTopicId);
27371
+ const restricted = identityInvalid || Boolean(assertedParentTopicId) || Boolean(record2?.isSubagent);
27372
+ const parentTopicId = identityInvalid ? undefined : assertedParentTopicId || (record2?.isSubagent ? record2.parentTopicId : undefined);
26849
27373
  return { currentTopicId, parentTopicId, restricted };
26850
27374
  }
26851
27375
  function canSubagentTellTarget(identity, targetTopicId) {
@@ -28561,12 +29085,12 @@ function wikiLastConversation(args) {
28561
29085
  };
28562
29086
  for (const line of lines) {
28563
29087
  try {
28564
- const record = JSON.parse(line);
28565
- if (record.type === "message" && record.role === "user") {
29088
+ const record2 = JSON.parse(line);
29089
+ if (record2.type === "message" && record2.role === "user") {
28566
29090
  flush();
28567
- currentUser = record.text ?? "";
28568
- } else if (record.type === "message" && record.role === "assistant") {
28569
- currentAssistant += record.text ?? "";
29091
+ currentUser = record2.text ?? "";
29092
+ } else if (record2.type === "message" && record2.role === "assistant") {
29093
+ currentAssistant += record2.text ?? "";
28570
29094
  }
28571
29095
  } catch {}
28572
29096
  }
@@ -31756,378 +32280,6 @@ var init_src3 = __esm(() => {
31756
32280
  init_spec();
31757
32281
  });
31758
32282
 
31759
- // ../../packages/core/src/runtime-gateway.ts
31760
- function record(value) {
31761
- return value && typeof value === "object" && !Array.isArray(value) ? value : null;
31762
- }
31763
- async function json(response) {
31764
- try {
31765
- return await response.json();
31766
- } catch {
31767
- throw new RuntimeGatewayError("Runtime Gateway returned invalid JSON", "protocol", response.status);
31768
- }
31769
- }
31770
- function validHealth(value) {
31771
- const body = record(value);
31772
- return Boolean(body?.ok === true && body.v === RUNTIME_GATEWAY_VERSION && Array.isArray(body.capabilities) && typeof body.cursor === "number");
31773
- }
31774
- function validAcknowledgedMessage(value) {
31775
- const message = record(value);
31776
- return Boolean(message && typeof message.id === "string" && typeof message.topicId === "string" && typeof message.authorId === "string" && typeof message.text === "string" && typeof message.createdAt === "string");
31777
- }
31778
- function validTurnAck(value) {
31779
- const body = record(value);
31780
- return Boolean(body?.ok === true && body.v === RUNTIME_GATEWAY_VERSION && body.accepted === true && typeof body.deduplicated === "boolean" && typeof body.requestId === "string" && typeof body.clientMessageId === "string" && typeof body.topicId === "string" && typeof body.messageId === "string" && (body.message === undefined || validAcknowledgedMessage(body.message)) && typeof body.cursor === "number");
31781
- }
31782
- function validManagerTopic(value) {
31783
- const topic = record(value);
31784
- return Boolean(typeof topic?.id === "string" && typeof topic.title === "string" && topic.kind === "manager" && Array.isArray(topic.participants));
31785
- }
31786
- function validDeletedMessage(value) {
31787
- const message = record(value);
31788
- return Boolean(typeof message?.id === "string" && typeof message.topicId === "string" && typeof message.authorId === "string" && typeof message.text === "string" && message.deleted === true && typeof message.createdAt === "string");
31789
- }
31790
- function validCronJob(value) {
31791
- const job = record(value);
31792
- return Boolean(typeof job?.id === "string" && typeof job.name === "string" && typeof job.executionPrincipalUserId === "string" && typeof job.actorOwnerUserId === "string" && typeof job.topicId === "string" && typeof job.schedule === "string" && typeof job.enabled === "boolean" && typeof job.nextRunAt === "string" && typeof job.canMutate === "boolean");
31793
- }
31794
- async function* parseRuntimeGatewaySse(body) {
31795
- const reader = body.getReader();
31796
- const decoder = new TextDecoder;
31797
- let buffer = "";
31798
- let event = "message";
31799
- let id;
31800
- let data = [];
31801
- const flush = () => {
31802
- if (data.length === 0)
31803
- return null;
31804
- const raw = data.join(`
31805
- `);
31806
- data = [];
31807
- try {
31808
- return { event, id, data: JSON.parse(raw) };
31809
- } catch {
31810
- throw new RuntimeGatewayError("Runtime Gateway SSE event has invalid JSON", "protocol");
31811
- } finally {
31812
- event = "message";
31813
- id = undefined;
31814
- }
31815
- };
31816
- try {
31817
- while (true) {
31818
- const chunk = await reader.read();
31819
- if (chunk.done)
31820
- break;
31821
- buffer += decoder.decode(chunk.value, { stream: true }).replace(/\r\n/g, `
31822
- `);
31823
- let newline = buffer.indexOf(`
31824
- `);
31825
- while (newline >= 0) {
31826
- const line = buffer.slice(0, newline);
31827
- buffer = buffer.slice(newline + 1);
31828
- if (line === "") {
31829
- const parsed2 = flush();
31830
- if (parsed2)
31831
- yield parsed2;
31832
- } else if (line.startsWith("event:")) {
31833
- event = line.slice(6).trim() || "message";
31834
- } else if (line.startsWith("id:")) {
31835
- const parsed2 = Number.parseInt(line.slice(3).trim(), 10);
31836
- if (Number.isFinite(parsed2))
31837
- id = parsed2;
31838
- } else if (line.startsWith("data:")) {
31839
- data.push(line.slice(5).trimStart());
31840
- }
31841
- newline = buffer.indexOf(`
31842
- `);
31843
- }
31844
- }
31845
- buffer += decoder.decode();
31846
- if (buffer.startsWith("data:"))
31847
- data.push(buffer.slice(5).trimStart());
31848
- const parsed = flush();
31849
- if (parsed)
31850
- yield parsed;
31851
- } finally {
31852
- reader.releaseLock();
31853
- }
31854
- }
31855
-
31856
- class RuntimeGatewayClient {
31857
- options;
31858
- pathPrefix;
31859
- request;
31860
- requestTimeoutMs;
31861
- compactTimeoutMs;
31862
- constructor(options) {
31863
- this.options = options;
31864
- this.pathPrefix = options.pathPrefix ?? RUNTIME_GATEWAY_CONTROL_PATH;
31865
- this.request = options.fetch ?? fetch;
31866
- this.requestTimeoutMs = options.requestTimeoutMs ?? 5000;
31867
- this.compactTimeoutMs = options.compactTimeoutMs ?? 5 * 60000;
31868
- }
31869
- endpoint(path) {
31870
- if (!this.options.baseUrl || !this.options.token) {
31871
- throw new RuntimeGatewayError("Runtime Gateway is not configured", "config");
31872
- }
31873
- return `${this.options.baseUrl.replace(/\/+$/, "")}${this.pathPrefix}${path}`;
31874
- }
31875
- async authorization() {
31876
- try {
31877
- const token = typeof this.options.token === "function" ? await this.options.token() : this.options.token;
31878
- return `Bearer ${token}`;
31879
- } catch (error2) {
31880
- throw new RuntimeGatewayError(error2 instanceof Error ? error2.message : "Runtime Gateway token resolution failed", "config");
31881
- }
31882
- }
31883
- async send(path, init, timeoutMs) {
31884
- const authorization = await this.authorization();
31885
- try {
31886
- return await this.request(this.endpoint(path), {
31887
- ...init,
31888
- headers: { authorization, ...init.headers },
31889
- signal: timeoutMs === undefined ? init.signal : AbortSignal.timeout(timeoutMs)
31890
- });
31891
- } catch (error2) {
31892
- if (error2 instanceof DOMException && error2.name === "TimeoutError") {
31893
- throw new RuntimeGatewayError("Runtime Gateway request timed out", "timeout");
31894
- }
31895
- throw new RuntimeGatewayError("Runtime Gateway request failed", "transport");
31896
- }
31897
- }
31898
- async health() {
31899
- const response = await this.send("/health", { method: "GET" }, this.requestTimeoutMs);
31900
- if (!response.ok) {
31901
- throw new RuntimeGatewayError("Runtime Gateway health request failed", "http", response.status);
31902
- }
31903
- const body = await json(response);
31904
- if (!validHealth(body)) {
31905
- throw new RuntimeGatewayError("Runtime Gateway health response is incompatible", "protocol");
31906
- }
31907
- return body;
31908
- }
31909
- async submitTurn(input) {
31910
- const response = await this.send("/turns", {
31911
- method: "POST",
31912
- headers: { "content-type": "application/json" },
31913
- body: JSON.stringify({ v: RUNTIME_GATEWAY_VERSION, ...input })
31914
- }, this.requestTimeoutMs);
31915
- if (response.status !== 202) {
31916
- throw new RuntimeGatewayError("Runtime Gateway turn was not accepted", "http", response.status);
31917
- }
31918
- const body = await json(response);
31919
- if (!validTurnAck(body)) {
31920
- throw new RuntimeGatewayError("Runtime Gateway turn acknowledgement is incompatible", "protocol");
31921
- }
31922
- return body;
31923
- }
31924
- async deriveTopic(topicId, input) {
31925
- const response = await this.send(`/topics/${encodeURIComponent(topicId)}/derive`, {
31926
- method: "POST",
31927
- headers: { "content-type": "application/json" },
31928
- body: JSON.stringify({ v: RUNTIME_GATEWAY_VERSION, ...input })
31929
- }, this.requestTimeoutMs);
31930
- if (response.status !== 201) {
31931
- throw new RuntimeGatewayError("Runtime Gateway topic derive failed", "http", response.status);
31932
- }
31933
- const body = await json(response);
31934
- const topic = record(body)?.topic;
31935
- const topicRecord = record(topic);
31936
- if (!topicRecord || typeof topicRecord.id !== "string" || typeof topicRecord.title !== "string") {
31937
- throw new RuntimeGatewayError("Runtime Gateway topic derive response is incompatible", "protocol");
31938
- }
31939
- return topic;
31940
- }
31941
- async ensureManagerTopic(userId) {
31942
- const response = await this.send("/manager-topic", {
31943
- method: "POST",
31944
- headers: { "content-type": "application/json" },
31945
- body: JSON.stringify({ v: RUNTIME_GATEWAY_VERSION, userId })
31946
- }, this.requestTimeoutMs);
31947
- if (!response.ok) {
31948
- throw new RuntimeGatewayError("Runtime Gateway manager topic request failed", "http", response.status);
31949
- }
31950
- const body = record(await json(response));
31951
- if (body?.ok !== true || body.v !== RUNTIME_GATEWAY_VERSION || !validManagerTopic(body.topic)) {
31952
- throw new RuntimeGatewayError("Runtime Gateway manager topic response is incompatible", "protocol");
31953
- }
31954
- return body.topic;
31955
- }
31956
- async deleteMessage(topicId, messageId, actorUserId, allowAdmin = false) {
31957
- const response = await this.send(`/topics/${encodeURIComponent(topicId)}/messages/${encodeURIComponent(messageId)}`, {
31958
- method: "DELETE",
31959
- headers: { "content-type": "application/json" },
31960
- body: JSON.stringify({
31961
- v: RUNTIME_GATEWAY_VERSION,
31962
- actorUserId,
31963
- ...allowAdmin ? { allowAdmin: true } : {}
31964
- })
31965
- }, this.requestTimeoutMs);
31966
- if (!response.ok) {
31967
- throw new RuntimeGatewayError("Runtime Gateway message delete failed", "http", response.status);
31968
- }
31969
- const body = record(await json(response));
31970
- if (body?.ok !== true || body.v !== RUNTIME_GATEWAY_VERSION || !validDeletedMessage(body.message)) {
31971
- throw new RuntimeGatewayError("Runtime Gateway message delete response is incompatible", "protocol");
31972
- }
31973
- return body.message;
31974
- }
31975
- async cronRequest(path, init) {
31976
- const response = await this.send(path, init, this.requestTimeoutMs);
31977
- if (!response.ok) {
31978
- throw new RuntimeGatewayError("Runtime Gateway cron request failed", "http", response.status);
31979
- }
31980
- const body = record(await json(response));
31981
- if (body?.ok !== true || body.v !== RUNTIME_GATEWAY_VERSION) {
31982
- throw new RuntimeGatewayError("Runtime Gateway cron response is incompatible", "protocol");
31983
- }
31984
- return body;
31985
- }
31986
- async listCronScripts() {
31987
- const body = await this.cronRequest("/cron/scripts", { method: "GET" });
31988
- if (!Array.isArray(body.scripts) || !body.scripts.every((value) => typeof value === "string")) {
31989
- throw new RuntimeGatewayError("Runtime Gateway cron scripts are incompatible", "protocol");
31990
- }
31991
- return body.scripts;
31992
- }
31993
- async listCronJobs(userId, actorUserId, actorIsAdmin = false) {
31994
- const search = new URLSearchParams({
31995
- user: userId,
31996
- actorUserId,
31997
- actorIsAdmin: actorIsAdmin ? "1" : "0"
31998
- });
31999
- const body = await this.cronRequest(`/cron/jobs?${search}`, { method: "GET" });
32000
- if (!Array.isArray(body.jobs) || !body.jobs.every(validCronJob)) {
32001
- throw new RuntimeGatewayError("Runtime Gateway cron jobs are incompatible", "protocol");
32002
- }
32003
- return body.jobs;
32004
- }
32005
- async createCronJob(input) {
32006
- const body = await this.cronRequest("/cron/jobs", {
32007
- method: "POST",
32008
- headers: { "content-type": "application/json" },
32009
- body: JSON.stringify({ v: RUNTIME_GATEWAY_VERSION, ...input })
32010
- });
32011
- if (!validCronJob(body.job)) {
32012
- throw new RuntimeGatewayError("Runtime Gateway cron job is incompatible", "protocol");
32013
- }
32014
- return body.job;
32015
- }
32016
- async updateCronJob(jobId, userId, actorUserId, patch, actorIsAdmin = false) {
32017
- const body = await this.cronRequest(`/cron/jobs/${encodeURIComponent(jobId)}`, {
32018
- method: "PATCH",
32019
- headers: { "content-type": "application/json" },
32020
- body: JSON.stringify({
32021
- v: RUNTIME_GATEWAY_VERSION,
32022
- userId,
32023
- actorUserId,
32024
- actorIsAdmin,
32025
- patch
32026
- })
32027
- });
32028
- if (!validCronJob(body.job)) {
32029
- throw new RuntimeGatewayError("Runtime Gateway cron job is incompatible", "protocol");
32030
- }
32031
- return body.job;
32032
- }
32033
- async deleteCronJob(jobId, userId, actorUserId, actorIsAdmin = false) {
32034
- const search = new URLSearchParams({
32035
- user: userId,
32036
- actorUserId,
32037
- actorIsAdmin: actorIsAdmin ? "1" : "0"
32038
- });
32039
- const body = await this.cronRequest(`/cron/jobs/${encodeURIComponent(jobId)}?${search}`, {
32040
- method: "DELETE"
32041
- });
32042
- if (typeof body.deleted !== "string") {
32043
- throw new RuntimeGatewayError("Runtime Gateway cron delete is incompatible", "protocol");
32044
- }
32045
- return body.deleted;
32046
- }
32047
- async* events(after, topicId, signal) {
32048
- const search = new URLSearchParams({ after: String(Math.max(0, after)) });
32049
- if (topicId)
32050
- search.set("topicId", topicId);
32051
- const response = await this.send(`/events?${search}`, { method: "GET", signal });
32052
- if (!response.ok || !response.body) {
32053
- throw new RuntimeGatewayError("Runtime Gateway event stream failed", "http", response.status);
32054
- }
32055
- yield* parseRuntimeGatewaySse(response.body);
32056
- }
32057
- async getTopicUsage(topicId, userId) {
32058
- const response = await this.send(`/topics/${encodeURIComponent(topicId)}/usage?user=${encodeURIComponent(userId)}`, { method: "GET" }, this.requestTimeoutMs);
32059
- if (!response.ok) {
32060
- throw new RuntimeGatewayError("Runtime Gateway topic usage request failed", "http", response.status);
32061
- }
32062
- const usage = record(record(await json(response))?.usage);
32063
- const read = (key) => {
32064
- const value = usage?.[key];
32065
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
32066
- };
32067
- return {
32068
- queries: read("queries"),
32069
- inputTokens: read("inputTokens"),
32070
- outputTokens: read("outputTokens"),
32071
- cacheCreationInputTokens: read("cacheCreationInputTokens"),
32072
- cacheReadInputTokens: read("cacheReadInputTokens"),
32073
- estimatedCostUsd: read("estimatedCostUsd")
32074
- };
32075
- }
32076
- async abortTurn(topicId, userId) {
32077
- const response = await this.send(`/topics/${encodeURIComponent(topicId)}/abort`, {
32078
- method: "POST",
32079
- headers: { "content-type": "application/json" },
32080
- body: JSON.stringify({ v: RUNTIME_GATEWAY_VERSION, userId })
32081
- }, this.requestTimeoutMs);
32082
- if (!response.ok) {
32083
- throw new RuntimeGatewayError("Runtime Gateway turn abort failed", "http", response.status);
32084
- }
32085
- const body = record(await json(response));
32086
- if (body?.ok !== true || body.v !== RUNTIME_GATEWAY_VERSION || typeof body.aborted !== "boolean") {
32087
- throw new RuntimeGatewayError("Runtime Gateway turn abort response is incompatible", "protocol");
32088
- }
32089
- return body.aborted;
32090
- }
32091
- async resetSession(topicId, userId, reason, actorUserId) {
32092
- return this.sessionCommand(topicId, userId, "reset", reason, actorUserId);
32093
- }
32094
- async compactSession(topicId, userId, reason, actorUserId) {
32095
- return this.sessionCommand(topicId, userId, "compact", reason, actorUserId);
32096
- }
32097
- async sessionCommand(topicId, userId, command, reason, actorUserId) {
32098
- const response = await this.send(`/topics/${encodeURIComponent(topicId)}/session/${command}`, {
32099
- method: "POST",
32100
- headers: { "content-type": "application/json" },
32101
- body: JSON.stringify({
32102
- v: RUNTIME_GATEWAY_VERSION,
32103
- userId,
32104
- ...actorUserId ? { actorUserId } : {},
32105
- ...reason ? { reason } : {}
32106
- })
32107
- }, command === "compact" ? this.compactTimeoutMs : this.requestTimeoutMs);
32108
- if (!response.ok) {
32109
- throw new RuntimeGatewayError(`Runtime Gateway session ${command} failed`, "http", response.status);
32110
- }
32111
- const body = record(await json(response));
32112
- if (body?.ok !== true || body.v !== RUNTIME_GATEWAY_VERSION) {
32113
- throw new RuntimeGatewayError(`Runtime Gateway session ${command} response is incompatible`, "protocol");
32114
- }
32115
- return typeof body.result === "string" ? body.result : undefined;
32116
- }
32117
- }
32118
- var RUNTIME_GATEWAY_VERSION = 1, RUNTIME_GATEWAY_CONTROL_PATH = "/api/v1/control/runtime/v1", RuntimeGatewayError;
32119
- var init_runtime_gateway = __esm(() => {
32120
- RuntimeGatewayError = class RuntimeGatewayError extends Error {
32121
- kind;
32122
- status;
32123
- constructor(message, kind, status) {
32124
- super(message);
32125
- this.kind = kind;
32126
- this.status = status;
32127
- }
32128
- };
32129
- });
32130
-
32131
32283
  // ../../packages/core/src/cron-host.ts
32132
32284
  var init_cron_host = __esm(async () => {
32133
32285
  await init_topic_cleanup();
@@ -34504,6 +34656,7 @@ function createNodeControlHandler(options) {
34504
34656
  aiName: getGlobalAiName(),
34505
34657
  capabilities: [
34506
34658
  "turn-submit-idempotent",
34659
+ "host-mcp-turn-injection",
34507
34660
  "turn-events-sse-resume",
34508
34661
  "canonical-topic-read",
34509
34662
  "canonical-message-read",
@@ -34728,6 +34881,16 @@ function createNodeControlHandler(options) {
34728
34881
  const requestId = body.requestId === undefined ? undefined : asRequestId(requiredText(body.requestId, "requestId"));
34729
34882
  const threadRootId = body.threadRootId === undefined ? undefined : asMessageId(requiredText(body.threadRootId, "threadRootId"));
34730
34883
  const parentId = body.parentId === undefined ? undefined : asMessageId(requiredText(body.parentId, "parentId"));
34884
+ let hostMcpServers;
34885
+ try {
34886
+ hostMcpServers = parseHostMcpServers(body.hostMcpServers);
34887
+ } catch (error2) {
34888
+ throw new ControlRequestError(error2 instanceof Error ? error2.message : "invalid hostMcpServers");
34889
+ }
34890
+ const conflictingHostMcpName = Object.keys(hostMcpServers ?? {}).find(isReservedRuntimeMcpServerName);
34891
+ if (conflictingHostMcpName) {
34892
+ return jsonError(400, `host MCP server name conflicts with node catalog: ${conflictingHostMcpName}`);
34893
+ }
34731
34894
  const attachments2 = body.attachments === undefined ? [] : Array.isArray(body.attachments) && body.attachments.every((value) => typeof value === "string" && value.trim()) ? body.attachments : (() => {
34732
34895
  throw new ControlRequestError("attachments must be an array of file ids");
34733
34896
  })();
@@ -34740,6 +34903,9 @@ function createNodeControlHandler(options) {
34740
34903
  if (!topic.participants.some((participant) => participant.userId === userId)) {
34741
34904
  return jsonError(404, "Topic not found");
34742
34905
  }
34906
+ if (hostMcpServers !== undefined && topic.kind !== "manager") {
34907
+ return jsonError(400, "hostMcpServers is only allowed for manager topics");
34908
+ }
34743
34909
  if (attachments2.some((fileId) => !nodeFileStore.allows(fileId, { topicId, ownerUserId: userId }))) {
34744
34910
  return jsonError(404, "Attachment not found");
34745
34911
  }
@@ -34773,6 +34939,7 @@ function createNodeControlHandler(options) {
34773
34939
  silent: body.silent === true,
34774
34940
  visualTools: body.visualTools === true,
34775
34941
  fileDeliveryTools: body.fileDeliveryTools === true,
34942
+ hostMcpServers,
34776
34943
  ...attachments2.length ? { attachments: attachments2 } : {},
34777
34944
  ...threadRootId ? { threadRootId } : {},
34778
34945
  ...parentId ? { parentId } : {}
@@ -51271,4 +51438,4 @@ switch (command) {
51271
51438
  }
51272
51439
  }
51273
51440
 
51274
- //# debugId=1BEBC79E50C92F0764756E2164756E21
51441
+ //# debugId=F4E62DD52039241264756E2164756E21