negotium 0.1.7 → 0.1.9

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 (270) hide show
  1. package/README.md +26 -2
  2. package/dist/canonical-mcp-bridge.js +14 -0
  3. package/dist/canonical-mcp-bridge.js.map +9 -0
  4. package/dist/chunk-10ct8814.js +429 -0
  5. package/dist/chunk-10ct8814.js.map +11 -0
  6. package/dist/chunk-18fzftjf.js +214 -0
  7. package/dist/chunk-18fzftjf.js.map +10 -0
  8. package/dist/chunk-1k7t7bnx.js +97 -0
  9. package/dist/chunk-1k7t7bnx.js.map +10 -0
  10. package/dist/chunk-1msje2zf.js +23 -0
  11. package/dist/chunk-1msje2zf.js.map +10 -0
  12. package/dist/chunk-2dgkah1z.js +706 -0
  13. package/dist/chunk-2dgkah1z.js.map +14 -0
  14. package/dist/chunk-3t4dtm26.js +61 -0
  15. package/dist/chunk-3t4dtm26.js.map +10 -0
  16. package/dist/chunk-3vn2vnj1.js +51 -0
  17. package/dist/chunk-3vn2vnj1.js.map +10 -0
  18. package/dist/chunk-6hknphtc.js +2054 -0
  19. package/dist/chunk-6hknphtc.js.map +29 -0
  20. package/dist/chunk-6tfzpcvk.js +2149 -0
  21. package/dist/chunk-6tfzpcvk.js.map +15 -0
  22. package/dist/chunk-8g0c4as0.js +23 -0
  23. package/dist/chunk-8g0c4as0.js.map +10 -0
  24. package/dist/chunk-9spx3h9n.js +279 -0
  25. package/dist/chunk-9spx3h9n.js.map +10 -0
  26. package/dist/chunk-c500xe9z.js +33 -0
  27. package/dist/chunk-c500xe9z.js.map +10 -0
  28. package/dist/chunk-e9b2xpff.js +147 -0
  29. package/dist/chunk-e9b2xpff.js.map +10 -0
  30. package/dist/chunk-edb07sjp.js +567 -0
  31. package/dist/chunk-edb07sjp.js.map +14 -0
  32. package/dist/chunk-eexqsee7.js +368 -0
  33. package/dist/chunk-eexqsee7.js.map +11 -0
  34. package/dist/chunk-efyxk97v.js +500 -0
  35. package/dist/chunk-efyxk97v.js.map +13 -0
  36. package/dist/chunk-epqxwkvh.js +3534 -0
  37. package/dist/chunk-epqxwkvh.js.map +20 -0
  38. package/dist/chunk-f1pzh3xh.js +115 -0
  39. package/dist/chunk-f1pzh3xh.js.map +10 -0
  40. package/dist/chunk-f5xgprg1.js +59 -0
  41. package/dist/chunk-f5xgprg1.js.map +10 -0
  42. package/dist/chunk-f97qybwk.js +584 -0
  43. package/dist/chunk-f97qybwk.js.map +11 -0
  44. package/dist/chunk-fvc7ncxd.js +252 -0
  45. package/dist/chunk-fvc7ncxd.js.map +10 -0
  46. package/dist/chunk-g7pvfj5e.js +574 -0
  47. package/dist/chunk-g7pvfj5e.js.map +14 -0
  48. package/dist/chunk-h8vmb9gf.js +315 -0
  49. package/dist/chunk-h8vmb9gf.js.map +10 -0
  50. package/dist/chunk-hgw7n8cm.js +43 -0
  51. package/dist/chunk-hgw7n8cm.js.map +10 -0
  52. package/dist/chunk-hsq7j9jk.js +126 -0
  53. package/dist/chunk-hsq7j9jk.js.map +10 -0
  54. package/dist/chunk-hwy3n31w.js +21 -0
  55. package/dist/chunk-hwy3n31w.js.map +9 -0
  56. package/dist/chunk-j0kjscb7.js +53 -0
  57. package/dist/chunk-j0kjscb7.js.map +10 -0
  58. package/dist/chunk-jzz91jwh.js +70 -0
  59. package/dist/chunk-jzz91jwh.js.map +10 -0
  60. package/dist/chunk-k9z47mab.js +247 -0
  61. package/dist/chunk-k9z47mab.js.map +12 -0
  62. package/dist/chunk-kbm35t7w.js +1331 -0
  63. package/dist/chunk-kbm35t7w.js.map +16 -0
  64. package/dist/chunk-m4veth0c.js +1649 -0
  65. package/dist/chunk-m4veth0c.js.map +17 -0
  66. package/dist/chunk-mhty3nna.js +150 -0
  67. package/dist/chunk-mhty3nna.js.map +11 -0
  68. package/dist/chunk-mmgh670x.js +248 -0
  69. package/dist/chunk-mmgh670x.js.map +11 -0
  70. package/dist/chunk-n0kk5xbq.js +2358 -0
  71. package/dist/chunk-n0kk5xbq.js.map +26 -0
  72. package/dist/chunk-nmv41qx9.js +2847 -0
  73. package/dist/chunk-nmv41qx9.js.map +23 -0
  74. package/dist/chunk-nqdp52aj.js +113 -0
  75. package/dist/chunk-nqdp52aj.js.map +12 -0
  76. package/dist/chunk-ns2m25dz.js +139 -0
  77. package/dist/chunk-ns2m25dz.js.map +10 -0
  78. package/dist/chunk-pkdmypdd.js +147 -0
  79. package/dist/chunk-pkdmypdd.js.map +11 -0
  80. package/dist/chunk-rmyfmrrr.js +1634 -0
  81. package/dist/chunk-rmyfmrrr.js.map +15 -0
  82. package/dist/chunk-sa874j5c.js +9 -0
  83. package/dist/chunk-sa874j5c.js.map +10 -0
  84. package/dist/chunk-sgsz2fpq.js +289 -0
  85. package/dist/chunk-sgsz2fpq.js.map +10 -0
  86. package/dist/chunk-sn51wnf1.js +3020 -0
  87. package/dist/chunk-sn51wnf1.js.map +32 -0
  88. package/dist/chunk-sqhr1a7e.js +587 -0
  89. package/dist/chunk-sqhr1a7e.js.map +11 -0
  90. package/dist/chunk-tmj5r2zv.js +2933 -0
  91. package/dist/chunk-tmj5r2zv.js.map +20 -0
  92. package/dist/chunk-vefj3880.js +56 -0
  93. package/dist/chunk-vefj3880.js.map +10 -0
  94. package/dist/chunk-vp2db42d.js +121 -0
  95. package/dist/chunk-vp2db42d.js.map +10 -0
  96. package/dist/chunk-wry79q55.js +108 -0
  97. package/dist/chunk-wry79q55.js.map +10 -0
  98. package/dist/chunk-x1arr7ze.js +63 -0
  99. package/dist/chunk-x1arr7ze.js.map +10 -0
  100. package/dist/chunk-z2xy5n2q.js +103 -0
  101. package/dist/chunk-z2xy5n2q.js.map +10 -0
  102. package/dist/hosted-agent.js +53 -0
  103. package/dist/hosted-agent.js.map +10 -0
  104. package/dist/main.js +132 -20
  105. package/dist/main.js.map +4 -4
  106. package/dist/runtime/scripts/faster-whisper-wrapper.py +87 -0
  107. package/dist/runtime/scripts/mcp-patchright-http.mjs +349 -0
  108. package/dist/runtime/src/agents/api-topic-agent-switch.ts +178 -0
  109. package/dist/runtime/src/agents/archiver.ts +396 -0
  110. package/dist/runtime/src/agents/auth-check.ts +64 -0
  111. package/dist/runtime/src/agents/claude-provider.ts +737 -0
  112. package/dist/runtime/src/agents/claude-registry.ts +115 -0
  113. package/dist/runtime/src/agents/codex-native-multi-agent.ts +177 -0
  114. package/dist/runtime/src/agents/codex-provider.ts +785 -0
  115. package/dist/runtime/src/agents/codex-registry.ts +115 -0
  116. package/dist/runtime/src/agents/codex-tree-kill.ts +365 -0
  117. package/dist/runtime/src/agents/contracts.ts +69 -0
  118. package/dist/runtime/src/agents/deep-map.ts +13 -0
  119. package/dist/runtime/src/agents/execution-host.ts +91 -0
  120. package/dist/runtime/src/agents/fixtures/claude-attachments.jsonl +2 -0
  121. package/dist/runtime/src/agents/fixtures/codex-shell.jsonl +5 -0
  122. package/dist/runtime/src/agents/fork.ts +65 -0
  123. package/dist/runtime/src/agents/hosted-agent.ts +49 -0
  124. package/dist/runtime/src/agents/idle-archiver.ts +128 -0
  125. package/dist/runtime/src/agents/index.ts +205 -0
  126. package/dist/runtime/src/agents/maestro-provider.ts +206 -0
  127. package/dist/runtime/src/agents/maestro-registry.ts +31 -0
  128. package/dist/runtime/src/agents/mcp-tools/ask-user.ts +280 -0
  129. package/dist/runtime/src/agents/mcp-tools/common.ts +21 -0
  130. package/dist/runtime/src/agents/mcp-tools/self-config.ts +220 -0
  131. package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +525 -0
  132. package/dist/runtime/src/agents/mcp-tools/visuals.ts +88 -0
  133. package/dist/runtime/src/agents/model-catalog.ts +151 -0
  134. package/dist/runtime/src/agents/registry.ts +24 -0
  135. package/dist/runtime/src/agents/rollout/claude.ts +331 -0
  136. package/dist/runtime/src/agents/rollout/codex.ts +569 -0
  137. package/dist/runtime/src/agents/rollout/shared.ts +205 -0
  138. package/dist/runtime/src/agents/self-config-core.ts +415 -0
  139. package/dist/runtime/src/agents/task-events.ts +44 -0
  140. package/dist/runtime/src/agents/tool-format.ts +352 -0
  141. package/dist/runtime/src/agents/topic-agent-switch.ts +193 -0
  142. package/dist/runtime/src/agents/topic-cleanup.ts +231 -0
  143. package/dist/runtime/src/agents/vault-tool-policy.ts +33 -0
  144. package/dist/runtime/src/application/execute-external-user-turn.ts +23 -0
  145. package/dist/runtime/src/application/submit-user-message.ts +50 -0
  146. package/dist/runtime/src/application/switch-topic-effort.ts +48 -0
  147. package/dist/runtime/src/application/switch-topic-model.ts +79 -0
  148. package/dist/runtime/src/application/topic-service.ts +139 -0
  149. package/dist/runtime/src/application/vault-command.ts +170 -0
  150. package/dist/runtime/src/bus.ts +319 -0
  151. package/dist/runtime/src/index.ts +333 -0
  152. package/dist/runtime/src/mcp/agent-health-server.ts +309 -0
  153. package/dist/runtime/src/mcp/background-bash-server.ts +346 -0
  154. package/dist/runtime/src/mcp/canonical-bridge-config.ts +84 -0
  155. package/dist/runtime/src/mcp/canonical-proxy-server.ts +171 -0
  156. package/dist/runtime/src/mcp/dm-ipc.ts +60 -0
  157. package/dist/runtime/src/mcp/mcp-helpers.ts +40 -0
  158. package/dist/runtime/src/mcp/paddleocr-server.ts +105 -0
  159. package/dist/runtime/src/mcp/peer-bridge.ts +114 -0
  160. package/dist/runtime/src/mcp/runtime-spec.ts +137 -0
  161. package/dist/runtime/src/mcp/session-comm/bridge-ipc-config.ts +27 -0
  162. package/dist/runtime/src/mcp/session-comm/peer-forward.ts +165 -0
  163. package/dist/runtime/src/mcp/session-comm/runtime.ts +115 -0
  164. package/dist/runtime/src/mcp/session-comm/server.ts +649 -0
  165. package/dist/runtime/src/mcp/session-comm/topic-config.ts +131 -0
  166. package/dist/runtime/src/mcp/session-comm/topics.ts +229 -0
  167. package/dist/runtime/src/mcp/stdio-protect.ts +84 -0
  168. package/dist/runtime/src/mcp/system-health-server.ts +130 -0
  169. package/dist/runtime/src/mcp/task-server.ts +173 -0
  170. package/dist/runtime/src/mcp/token-stats-server.ts +98 -0
  171. package/dist/runtime/src/mcp/vault-http.ts +219 -0
  172. package/dist/runtime/src/mcp/vault-run.ts +153 -0
  173. package/dist/runtime/src/mcp/vault-server.ts +104 -0
  174. package/dist/runtime/src/mcp/wiki-server.ts +755 -0
  175. package/dist/runtime/src/media/file-events.ts +33 -0
  176. package/dist/runtime/src/media/text-extractor.ts +312 -0
  177. package/dist/runtime/src/media/video.ts +92 -0
  178. package/dist/runtime/src/outbox/file-ops.ts +210 -0
  179. package/dist/runtime/src/outbox/index.ts +8 -0
  180. package/dist/runtime/src/outbox/utils.ts +51 -0
  181. package/dist/runtime/src/platform/background-bash/manager.ts +279 -0
  182. package/dist/runtime/src/platform/config.ts +371 -0
  183. package/dist/runtime/src/platform/constants.ts +18 -0
  184. package/dist/runtime/src/platform/debug-users.ts +14 -0
  185. package/dist/runtime/src/platform/delay.ts +1 -0
  186. package/dist/runtime/src/platform/error.ts +9 -0
  187. package/dist/runtime/src/platform/file-utils.ts +16 -0
  188. package/dist/runtime/src/platform/jsonl.ts +202 -0
  189. package/dist/runtime/src/platform/lifecycle.ts +174 -0
  190. package/dist/runtime/src/platform/logger.ts +31 -0
  191. package/dist/runtime/src/platform/maestro-bootstrap-env.ts +3 -0
  192. package/dist/runtime/src/platform/mcp-config.ts +878 -0
  193. package/dist/runtime/src/platform/modules.ts +145 -0
  194. package/dist/runtime/src/platform/node-plugins.ts +46 -0
  195. package/dist/runtime/src/platform/playwright/manager.ts +1233 -0
  196. package/dist/runtime/src/prompts/agents/wiki-archiver.md +261 -0
  197. package/dist/runtime/src/prompts/builders.ts +368 -0
  198. package/dist/runtime/src/prompts/sessions/channel-system.md +48 -0
  199. package/dist/runtime/src/prompts/sessions/manager-system.md +37 -0
  200. package/dist/runtime/src/prompts/sessions/topic-system.md +47 -0
  201. package/dist/runtime/src/prompts/sessions/visual-design.md +86 -0
  202. package/dist/runtime/src/query/active-rooms.ts +534 -0
  203. package/dist/runtime/src/query/control.ts +313 -0
  204. package/dist/runtime/src/query/index.ts +24 -0
  205. package/dist/runtime/src/query/session-inbox-cleanup.ts +44 -0
  206. package/dist/runtime/src/query/session-inbox-path.ts +42 -0
  207. package/dist/runtime/src/query/state.ts +94 -0
  208. package/dist/runtime/src/query/types.ts +83 -0
  209. package/dist/runtime/src/runtime/ask-callbacks.ts +153 -0
  210. package/dist/runtime/src/runtime/attachments.ts +151 -0
  211. package/dist/runtime/src/runtime/background-sessions.ts +83 -0
  212. package/dist/runtime/src/runtime/channel-context.ts +107 -0
  213. package/dist/runtime/src/runtime/errors.ts +69 -0
  214. package/dist/runtime/src/runtime/event-heartbeat.ts +77 -0
  215. package/dist/runtime/src/runtime/file-hooks.ts +81 -0
  216. package/dist/runtime/src/runtime/footer.ts +23 -0
  217. package/dist/runtime/src/runtime/inbox.ts +946 -0
  218. package/dist/runtime/src/runtime/tasks.ts +56 -0
  219. package/dist/runtime/src/runtime/topic-config.ts +29 -0
  220. package/dist/runtime/src/runtime/turn-runner.ts +2298 -0
  221. package/dist/runtime/src/runtime/usage-alert.ts +84 -0
  222. package/dist/runtime/src/runtime/visual-html.ts +109 -0
  223. package/dist/runtime/src/runtime/visual-store.ts +430 -0
  224. package/dist/runtime/src/runtime/visuals.ts +171 -0
  225. package/dist/runtime/src/security/sanitize.ts +27 -0
  226. package/dist/runtime/src/security/sensitive-path.ts +34 -0
  227. package/dist/runtime/src/storage/activity-log.ts +152 -0
  228. package/dist/runtime/src/storage/api-messages.ts +617 -0
  229. package/dist/runtime/src/storage/api-topic-brief.ts +127 -0
  230. package/dist/runtime/src/storage/api-topic-config.ts +192 -0
  231. package/dist/runtime/src/storage/api-topics.ts +844 -0
  232. package/dist/runtime/src/storage/app-settings.ts +44 -0
  233. package/dist/runtime/src/storage/browser-profiles.ts +133 -0
  234. package/dist/runtime/src/storage/conversations.ts +268 -0
  235. package/dist/runtime/src/storage/forum/agent-settings.ts +20 -0
  236. package/dist/runtime/src/storage/forum/index.ts +4 -0
  237. package/dist/runtime/src/storage/forum/repository.ts +283 -0
  238. package/dist/runtime/src/storage/forum/schema.ts +300 -0
  239. package/dist/runtime/src/storage/forum-db.ts +23 -0
  240. package/dist/runtime/src/storage/runtime-events.ts +130 -0
  241. package/dist/runtime/src/storage/runtime-leases.ts +189 -0
  242. package/dist/runtime/src/storage/runtime-process-leases.ts +216 -0
  243. package/dist/runtime/src/storage/runtime-topic-state.ts +148 -0
  244. package/dist/runtime/src/storage/runtime-turn-requests.ts +301 -0
  245. package/dist/runtime/src/storage/self-schedules.ts +298 -0
  246. package/dist/runtime/src/storage/session-asks.ts +347 -0
  247. package/dist/runtime/src/storage/sqlite.ts +129 -0
  248. package/dist/runtime/src/storage/tasks.ts +179 -0
  249. package/dist/runtime/src/storage/token-stats.ts +144 -0
  250. package/dist/runtime/src/storage/topic-archive-state.ts +59 -0
  251. package/dist/runtime/src/storage/topic-archive.ts +74 -0
  252. package/dist/runtime/src/storage/topic-settings.ts +67 -0
  253. package/dist/runtime/src/storage/topic-transcript.ts +98 -0
  254. package/dist/runtime/src/storage/vault-crypto.ts +65 -0
  255. package/dist/runtime/src/storage/vault.ts +225 -0
  256. package/dist/runtime/src/storage/wiki-summary-names.ts +33 -0
  257. package/dist/runtime/src/storage/wiki.ts +85 -0
  258. package/dist/runtime/src/topics/create.ts +123 -0
  259. package/dist/runtime/src/topics/derive.ts +398 -0
  260. package/dist/runtime/src/topics/lifecycle.ts +311 -0
  261. package/dist/runtime/src/topics/links.ts +7 -0
  262. package/dist/runtime/src/topics/personal-general.ts +65 -0
  263. package/dist/runtime/src/topics/session.ts +381 -0
  264. package/dist/runtime/src/types/api.ts +497 -0
  265. package/dist/runtime/src/types.ts +257 -0
  266. package/dist/runtime/src/version.ts +1 -0
  267. package/dist/runtime/tsconfig.json +13 -0
  268. package/package.json +28 -3
  269. package/dist/main.d.ts +0 -3
  270. package/dist/main.d.ts.map +0 -1
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync } from "node:child_process";
3
+ import { resolve } from "node:path";
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { z } from "zod";
6
+ import { connectStdio } from "#mcp/mcp-helpers";
7
+ import { errMsg } from "#platform/error";
8
+
9
+ const PYTHON = process.env.PADDLEOCR_PYTHON;
10
+ if (!PYTHON) {
11
+ throw new Error(
12
+ "PADDLEOCR_PYTHON not configured. Set PADDLEOCR_PYTHON in .env to your paddleocr venv python path. " +
13
+ "See INSTALL.md section 5 for setup instructions.",
14
+ );
15
+ }
16
+ const TIMEOUT_MS = Number.parseInt(process.env.PADDLEOCR_TIMEOUT_MS ?? "300000", 10);
17
+ const DET_LIMIT_SIDE_LEN = Number.parseInt(process.env.PADDLEOCR_DET_LIMIT_SIDE_LEN ?? "1280", 10);
18
+
19
+ const server = new McpServer({
20
+ name: "paddleocr",
21
+ version: "1.0.0",
22
+ });
23
+
24
+ const ALLOWED_LANGS = new Set([
25
+ "korean",
26
+ "ch",
27
+ "en",
28
+ "japan",
29
+ "latin",
30
+ "arabic",
31
+ "cyrillic",
32
+ "devanagari",
33
+ ]);
34
+
35
+ // Inline Python script — runs in the venv, no temp file needed
36
+ function runPaddleOCR(filePath: string, lang: string): string {
37
+ if (!ALLOWED_LANGS.has(lang)) {
38
+ throw new Error(`Unsupported lang: "${lang}". Allowed: ${[...ALLOWED_LANGS].join(", ")}`);
39
+ }
40
+ const script = `
41
+ import sys, os
42
+ os.environ["PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK"] = "True"
43
+ import warnings, logging
44
+ warnings.filterwarnings("ignore")
45
+ logging.disable(logging.CRITICAL)
46
+
47
+ from paddleocr import PaddleOCR
48
+ ocr = PaddleOCR(
49
+ lang="${lang}",
50
+ use_doc_orientation_classify=False,
51
+ use_doc_unwarping=False,
52
+ use_textline_orientation=False,
53
+ text_det_limit_side_len=${Number.isFinite(DET_LIMIT_SIDE_LEN) ? DET_LIMIT_SIDE_LEN : 1280},
54
+ text_det_limit_type="max",
55
+ )
56
+ result = ocr.predict(sys.argv[1])
57
+ for res in result:
58
+ for text in res.get("rec_texts", []):
59
+ if text.strip():
60
+ print(text.strip())
61
+ `.trim();
62
+
63
+ return execFileSync(PYTHON!, ["-c", script, filePath], {
64
+ encoding: "utf-8",
65
+ timeout: Number.isFinite(TIMEOUT_MS) ? TIMEOUT_MS : 300000,
66
+ env: {
67
+ ...process.env,
68
+ PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK: "True",
69
+ },
70
+ }).trim();
71
+ }
72
+
73
+ // ── paddleocr_file ─────────────────────────────────────────────────────────
74
+ server.tool(
75
+ "paddleocr_file",
76
+ [
77
+ "Extract text from an image using PaddleOCR (PP-OCRv5).",
78
+ "Better than macOS Vision for Korean, Chinese, Japanese, and mixed-language documents.",
79
+ "Supports: PNG, JPG, JPEG, WEBP, BMP, TIFF.",
80
+ "Use lang='korean' for Korean docs, 'ch' for Chinese, 'en' for English-only.",
81
+ ].join(" "),
82
+ {
83
+ file_path: z.string().describe("Absolute path to the image file"),
84
+ lang: z
85
+ .string()
86
+ .optional()
87
+ .default("korean")
88
+ .describe("OCR language: 'korean' (default), 'ch' (Chinese/mixed), 'en', 'japan'"),
89
+ },
90
+ async ({ file_path, lang }) => {
91
+ try {
92
+ const absPath = resolve(file_path);
93
+ const output = runPaddleOCR(absPath, lang ?? "korean");
94
+ if (!output) {
95
+ return { content: [{ type: "text", text: "No text found in the file." }] };
96
+ }
97
+ return { content: [{ type: "text", text: output }] };
98
+ } catch (err: unknown) {
99
+ return { content: [{ type: "text", text: `Error: ${errMsg(err)}` }], isError: true };
100
+ }
101
+ },
102
+ );
103
+
104
+ // ── main ───────────────────────────────────────────────────────────────────
105
+ await connectStdio(server);
@@ -0,0 +1,114 @@
1
+ import type { McpToolResult } from "#agents/mcp-tools/common";
2
+ import type { AgentKind, PeerRuntimeBridgeContext } from "#types";
3
+
4
+ export interface PeerRuntimeSpawnRequest {
5
+ bridge: PeerRuntimeBridgeContext;
6
+ userId: string;
7
+ agent: AgentKind;
8
+ model?: string;
9
+ input: Record<string, unknown>;
10
+ }
11
+
12
+ export interface PeerRuntimeVisualRequest {
13
+ bridge: PeerRuntimeBridgeContext;
14
+ userId: string;
15
+ agent: AgentKind;
16
+ model?: string;
17
+ kind: "html" | "mermaid" | "image" | "video";
18
+ title?: string;
19
+ html?: string;
20
+ code?: string;
21
+ theme?: string;
22
+ fileId?: string;
23
+ mimeType?: string;
24
+ source?: string;
25
+ }
26
+
27
+ export type PeerRuntimeVisualResult =
28
+ | { ok: true; id: number; url: string; title: string | null }
29
+ | { ok: false; error: string };
30
+
31
+ export interface PeerRuntimeFileRequest {
32
+ bridge: PeerRuntimeBridgeContext;
33
+ userId: string;
34
+ agent: AgentKind;
35
+ model?: string;
36
+ path: string;
37
+ source: string;
38
+ }
39
+
40
+ export interface PeerRuntimeAskUserRequest {
41
+ bridge: PeerRuntimeBridgeContext;
42
+ userId: string;
43
+ agent: AgentKind;
44
+ model?: string;
45
+ input: Record<string, unknown>;
46
+ }
47
+
48
+ export interface PeerRuntimeSelfConfigRequest {
49
+ bridge: PeerRuntimeBridgeContext;
50
+ userId: string;
51
+ tool: string;
52
+ input: Record<string, unknown>;
53
+ currentUserPrompt?: string;
54
+ }
55
+
56
+ export interface PeerRuntimeBridge {
57
+ spawnSubagent(request: PeerRuntimeSpawnRequest): Promise<McpToolResult>;
58
+ /** Wait until already-broadcast runtime events for this local turn have
59
+ * reached the canonical host before an out-of-band bridge mutation. */
60
+ flushEvents?(localTopicId: string): Promise<boolean>;
61
+ showVisual?(request: PeerRuntimeVisualRequest): Promise<PeerRuntimeVisualResult>;
62
+ sendFile?(request: PeerRuntimeFileRequest): Promise<{ ok: true } | { ok: false; error: string }>;
63
+ askUser?(request: PeerRuntimeAskUserRequest): Promise<McpToolResult>;
64
+ selfConfig?(request: PeerRuntimeSelfConfigRequest): Promise<McpToolResult>;
65
+ }
66
+
67
+ export function flushPeerRuntimeEvents(localTopicId: string): Promise<boolean> {
68
+ return activeBridge?.flushEvents?.(localTopicId) ?? Promise.resolve(true);
69
+ }
70
+
71
+ let activeBridge: PeerRuntimeBridge | null = null;
72
+
73
+ /**
74
+ * Install the channel/placement adapter that owns remote runtime mutations.
75
+ * Core carries the per-turn bridge identity, but deliberately does not know
76
+ * how a workspace hub is discovered or authenticated.
77
+ */
78
+ export function registerPeerRuntimeBridge(bridge: PeerRuntimeBridge): () => void {
79
+ activeBridge = bridge;
80
+ return () => {
81
+ if (activeBridge === bridge) activeBridge = null;
82
+ };
83
+ }
84
+
85
+ /** Returns null when no placement adapter is active. */
86
+ export function dispatchPeerRuntimeSpawn(
87
+ request: PeerRuntimeSpawnRequest,
88
+ ): Promise<McpToolResult> | null {
89
+ return activeBridge?.spawnSubagent(request) ?? null;
90
+ }
91
+
92
+ export function dispatchPeerRuntimeVisual(
93
+ request: PeerRuntimeVisualRequest,
94
+ ): Promise<PeerRuntimeVisualResult> | null {
95
+ return activeBridge?.showVisual?.(request) ?? null;
96
+ }
97
+
98
+ export function dispatchPeerRuntimeFile(
99
+ request: PeerRuntimeFileRequest,
100
+ ): Promise<{ ok: true } | { ok: false; error: string }> | null {
101
+ return activeBridge?.sendFile?.(request) ?? null;
102
+ }
103
+
104
+ export function dispatchPeerRuntimeAskUser(
105
+ request: PeerRuntimeAskUserRequest,
106
+ ): Promise<McpToolResult> | null {
107
+ return activeBridge?.askUser?.(request) ?? null;
108
+ }
109
+
110
+ export function dispatchPeerRuntimeSelfConfig(
111
+ request: PeerRuntimeSelfConfigRequest,
112
+ ): Promise<McpToolResult> | null {
113
+ return activeBridge?.selfConfig?.(request) ?? null;
114
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Runtime-MCP wiring shared between core (which injects the MCP into agent
3
+ * sessions) and @negotium/mcp (which serves the endpoint).
4
+ *
5
+ * The runtime process exposes one HTTP MCP endpoint; every agent turn gets a
6
+ * per-turn signed token carrying its full execution context, so the MCP layer
7
+ * never trusts the agent to say who it is. Ported from otium runtime-api
8
+ * `mcp/runtime-server.ts` (transport-agnostic parts only).
9
+ */
10
+
11
+ import { createHmac, timingSafeEqual } from "node:crypto";
12
+ import { NEGOTIUM_PORT, RUNTIME_MCP_SECRET } from "#platform/config";
13
+ import { type AgentKind, isAgentKind, type PeerRuntimeBridgeContext } from "#types";
14
+
15
+ export const RUNTIME_MCP_KEY = "runtime";
16
+
17
+ export const RUNTIME_MCP_BASE_PATH = "/mcp/runtime";
18
+ const TOKEN_TTL_MS = 4 * 60 * 60 * 1000;
19
+ const CLAUDE_MCP_TOOL_TIMEOUT_MS = 600_000;
20
+
21
+ export interface RuntimeMcpContext {
22
+ userId: string;
23
+ topicId: string;
24
+ topicTitle: string;
25
+ queryId?: string;
26
+ cwd: string;
27
+ agent: AgentKind;
28
+ model?: string;
29
+ currentUserPrompt?: string;
30
+ autoContinue?: boolean;
31
+ /** Capability minted by the adapter. Visual tools are absent unless true. */
32
+ visualTools?: boolean;
33
+ peerBridge?: PeerRuntimeBridgeContext;
34
+ }
35
+
36
+ type RuntimeTokenPayload = {
37
+ v: 1;
38
+ exp: number;
39
+ ctx: RuntimeMcpContext;
40
+ };
41
+
42
+ let runtimePort = NEGOTIUM_PORT;
43
+
44
+ /** The runtime host calls this once it knows which port it actually bound. */
45
+ export function setRuntimeMcpPort(port: number): void {
46
+ runtimePort = port;
47
+ }
48
+
49
+ export function getRuntimeMcpPort(): number {
50
+ return runtimePort;
51
+ }
52
+
53
+ function encodeTokenPart(value: unknown): string {
54
+ return Buffer.from(JSON.stringify(value), "utf-8").toString("base64url");
55
+ }
56
+
57
+ function decodeTokenPart(part: string): unknown {
58
+ return JSON.parse(Buffer.from(part, "base64url").toString("utf-8")) as unknown;
59
+ }
60
+
61
+ function signTokenPayload(payloadPart: string): string {
62
+ return createHmac("sha256", RUNTIME_MCP_SECRET).update(payloadPart).digest("base64url");
63
+ }
64
+
65
+ function safeEqual(a: string, b: string): boolean {
66
+ const aa = Buffer.from(a);
67
+ const bb = Buffer.from(b);
68
+ return aa.length === bb.length && timingSafeEqual(aa, bb);
69
+ }
70
+
71
+ function isRuntimeMcpContext(value: unknown): value is RuntimeMcpContext {
72
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
73
+ const ctx = value as Partial<RuntimeMcpContext>;
74
+ return (
75
+ typeof ctx.userId === "string" &&
76
+ typeof ctx.topicId === "string" &&
77
+ typeof ctx.topicTitle === "string" &&
78
+ typeof ctx.cwd === "string" &&
79
+ typeof ctx.agent === "string" &&
80
+ isAgentKind(ctx.agent) &&
81
+ (ctx.queryId === undefined || typeof ctx.queryId === "string") &&
82
+ (ctx.model === undefined || typeof ctx.model === "string") &&
83
+ (ctx.currentUserPrompt === undefined || typeof ctx.currentUserPrompt === "string") &&
84
+ (ctx.autoContinue === undefined || typeof ctx.autoContinue === "boolean") &&
85
+ (ctx.visualTools === undefined || typeof ctx.visualTools === "boolean") &&
86
+ (ctx.peerBridge === undefined ||
87
+ (typeof ctx.peerBridge.hubCellId === "string" &&
88
+ typeof ctx.peerBridge.hostTopicId === "string" &&
89
+ typeof ctx.peerBridge.hostQueryId === "string" &&
90
+ typeof ctx.peerBridge.canSpawnSubagents === "boolean"))
91
+ );
92
+ }
93
+
94
+ export function issueRuntimeMcpToken(ctx: RuntimeMcpContext): string {
95
+ const payloadPart = encodeTokenPart({
96
+ v: 1,
97
+ exp: Date.now() + TOKEN_TTL_MS,
98
+ ctx,
99
+ } satisfies RuntimeTokenPayload);
100
+ return `${payloadPart}.${signTokenPayload(payloadPart)}`;
101
+ }
102
+
103
+ export function resolveRuntimeMcpToken(token: string | null): RuntimeMcpContext | null {
104
+ if (!token) return null;
105
+ const [payloadPart, signature, extra] = token.split(".");
106
+ if (!payloadPart || !signature || extra !== undefined) return null;
107
+ if (!safeEqual(signature, signTokenPayload(payloadPart))) return null;
108
+
109
+ try {
110
+ const payload = decodeTokenPart(payloadPart) as Partial<RuntimeTokenPayload>;
111
+ if (payload.v !== 1 || typeof payload.exp !== "number" || payload.exp <= Date.now())
112
+ return null;
113
+ if (!isRuntimeMcpContext(payload.ctx)) return null;
114
+ return payload.ctx;
115
+ } catch {
116
+ return null;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * MCP server spec injected into an agent session's MCP config so the agent
122
+ * connects back to this node's runtime endpoint with its per-turn token.
123
+ */
124
+ export function buildRuntimeMcpSpec(
125
+ agent: AgentKind,
126
+ ctx: RuntimeMcpContext,
127
+ ): Record<string, unknown> {
128
+ const token = issueRuntimeMcpToken(ctx);
129
+ const base = `http://127.0.0.1:${runtimePort}${RUNTIME_MCP_BASE_PATH}`;
130
+ const query = `token=${encodeURIComponent(token)}`;
131
+ if (agent === "codex") return { url: `${base}/mcp?${query}` };
132
+ return {
133
+ type: "sse" as const,
134
+ url: `${base}/sse?${query}`,
135
+ timeout: CLAUDE_MCP_TOOL_TIMEOUT_MS,
136
+ };
137
+ }
@@ -0,0 +1,27 @@
1
+ export interface PeerSessionBridgeIpcConfig {
2
+ url: string;
3
+ token: string;
4
+ }
5
+
6
+ const registrations: Array<{ id: symbol; config: PeerSessionBridgeIpcConfig }> = [];
7
+
8
+ export function registerPeerSessionBridgeIpcConfig(config: PeerSessionBridgeIpcConfig): () => void {
9
+ const registration = { id: Symbol("peer-session-bridge-ipc"), config: { ...config } };
10
+ registrations.push(registration);
11
+ let disposed = false;
12
+ return () => {
13
+ if (disposed) return;
14
+ disposed = true;
15
+ const index = registrations.findIndex((entry) => entry.id === registration.id);
16
+ if (index >= 0) registrations.splice(index, 1);
17
+ };
18
+ }
19
+
20
+ export function peerSessionBridgeIpcEnv(): Record<string, string> | undefined {
21
+ const active = registrations.at(-1)?.config;
22
+ if (!active) return undefined;
23
+ return {
24
+ NEGOTIUM_PEER_SESSION_BRIDGE_URL: active.url,
25
+ NEGOTIUM_PEER_SESSION_BRIDGE_TOKEN: active.token,
26
+ };
27
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Remote-node forwarding adapter hook.
3
+ *
4
+ * Addressing keeps the `"<node>/<topic>"` shape so cross-node session-comm can
5
+ * plug in later (an otium-hub adapter, a negotium peer protocol, …) without
6
+ * changing tool contracts. On a standalone node every remote call fails
7
+ * gracefully and `peerSessionsForUser` contributes nothing to peek listings.
8
+ */
9
+
10
+ export interface PeerForwardArgs {
11
+ action: "tell" | "ask" | "abort";
12
+ toNode: string;
13
+ toTopic: string;
14
+ userId: string;
15
+ fromKey?: string;
16
+ fromTitle?: string;
17
+ fromTopicId?: string;
18
+ message?: string;
19
+ requestId?: string;
20
+ depth?: number;
21
+ fromDepth?: number;
22
+ /** Hub turn authorizing a worker-originated peer call. */
23
+ sourceQueryId?: string;
24
+ }
25
+
26
+ export type PeerForwardResult = { ok: true } | { ok: false; error: string };
27
+
28
+ export interface RemoteReplyRoute {
29
+ nodeName: string;
30
+ nodeCellId: string;
31
+ topicId: string;
32
+ userId: string;
33
+ requestId: string;
34
+ }
35
+
36
+ export interface PeerSessionBridge {
37
+ forward(args: PeerForwardArgs): Promise<PeerForwardResult>;
38
+ sessions(userId: string, sourceQueryId?: string): Promise<PeerSessionsResult>;
39
+ reply(
40
+ route: RemoteReplyRoute,
41
+ sourceTitle: string,
42
+ replyText: string,
43
+ kind: "reply" | "error",
44
+ ): Promise<boolean>;
45
+ }
46
+
47
+ let activeBridge: PeerSessionBridge | null = null;
48
+
49
+ const PEER_BRIDGE_URL_ENV = "NEGOTIUM_PEER_SESSION_BRIDGE_URL";
50
+ const PEER_BRIDGE_TOKEN_ENV = "NEGOTIUM_PEER_SESSION_BRIDGE_TOKEN";
51
+ const PEER_BRIDGE_TIMEOUT_MS = 5_000;
52
+
53
+ type IpcRequest =
54
+ | { action: "forward"; args: PeerForwardArgs }
55
+ | { action: "sessions"; userId: string; sourceQueryId?: string }
56
+ | {
57
+ action: "reply";
58
+ route: RemoteReplyRoute;
59
+ sourceTitle: string;
60
+ replyText: string;
61
+ kind: "reply" | "error";
62
+ };
63
+
64
+ function loopbackBridgeConfig(): { url: string; token: string } | null {
65
+ const rawUrl = process.env[PEER_BRIDGE_URL_ENV]?.trim();
66
+ const token = process.env[PEER_BRIDGE_TOKEN_ENV]?.trim();
67
+ if (!rawUrl || !token) return null;
68
+ try {
69
+ const url = new URL(rawUrl);
70
+ if (url.protocol !== "http:" || !["127.0.0.1", "::1", "localhost"].includes(url.hostname)) {
71
+ return null;
72
+ }
73
+ return { url: url.toString(), token };
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+
79
+ async function callLoopbackBridge<T>(
80
+ request: IpcRequest,
81
+ ): Promise<{ configured: boolean; result: T | null }> {
82
+ const config = loopbackBridgeConfig();
83
+ if (!config) return { configured: false, result: null };
84
+ try {
85
+ const response = await fetch(config.url, {
86
+ method: "POST",
87
+ headers: {
88
+ authorization: `Bearer ${config.token}`,
89
+ "content-type": "application/json",
90
+ },
91
+ body: JSON.stringify(request),
92
+ signal: AbortSignal.timeout(PEER_BRIDGE_TIMEOUT_MS),
93
+ });
94
+ if (!response.ok) return { configured: true, result: null };
95
+ return { configured: true, result: (await response.json()) as T };
96
+ } catch {
97
+ return { configured: true, result: null };
98
+ }
99
+ }
100
+
101
+ export function registerPeerSessionBridge(bridge: PeerSessionBridge): () => void {
102
+ activeBridge = bridge;
103
+ return () => {
104
+ if (activeBridge === bridge) activeBridge = null;
105
+ };
106
+ }
107
+
108
+ export async function forwardToPeer(args: PeerForwardArgs): Promise<PeerForwardResult> {
109
+ if (activeBridge) return activeBridge.forward(args);
110
+ const forwarded = await callLoopbackBridge<PeerForwardResult>({ action: "forward", args });
111
+ if (forwarded.result) return forwarded.result;
112
+ return {
113
+ ok: false,
114
+ error: forwarded.configured
115
+ ? "remote session bridge is configured but unavailable"
116
+ : "remote nodes are not connected on this negotium node (standalone mode)",
117
+ };
118
+ }
119
+
120
+ export interface PeerSessionEntry {
121
+ name: string;
122
+ agent: string | null;
123
+ hasSession: boolean;
124
+ description?: string;
125
+ }
126
+
127
+ export interface PeerSessionsResult {
128
+ ok: boolean;
129
+ nodes?: Array<{ node: string; error?: string; sessions?: PeerSessionEntry[] }>;
130
+ }
131
+
132
+ export async function peerSessionsForUser(
133
+ userId: string,
134
+ sourceQueryId?: string,
135
+ ): Promise<PeerSessionsResult> {
136
+ if (activeBridge) return activeBridge.sessions(userId, sourceQueryId);
137
+ const sessions = await callLoopbackBridge<PeerSessionsResult>({
138
+ action: "sessions",
139
+ userId,
140
+ sourceQueryId,
141
+ });
142
+ if (sessions.result) return sessions.result;
143
+ if (sessions.configured) return { ok: false, nodes: [] };
144
+ return { ok: true, nodes: [] };
145
+ }
146
+
147
+ export async function deliverPeerReply(
148
+ route: RemoteReplyRoute,
149
+ sourceTitle: string,
150
+ replyText: string,
151
+ kind: "reply" | "error",
152
+ ): Promise<boolean> {
153
+ if (activeBridge) return activeBridge.reply(route, sourceTitle, replyText, kind);
154
+ return (
155
+ (
156
+ await callLoopbackBridge<boolean>({
157
+ action: "reply",
158
+ route,
159
+ sourceTitle,
160
+ replyText,
161
+ kind,
162
+ })
163
+ ).result ?? false
164
+ );
165
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Process-bootstrap layer for the session-comm MCP server.
3
+ *
4
+ * Owns CLI-arg parsing, runtime constants, the shared DB helper, and
5
+ * primitive helpers (parseJsonField, isDebugUser) that other layers
6
+ * (`topics.ts`, `topic-config.ts`) compose on top of.
7
+ *
8
+ * Importers should pull *only* what they need from here; the previous
9
+ * `utils.ts` mega-module conflated process state, topic queries, and
10
+ * current-topic mutations in one file.
11
+ */
12
+
13
+ import { homedir } from "node:os";
14
+ import { parseUserIdArg } from "#mcp/mcp-helpers";
15
+ import {
16
+ PROGRESS_DIR as CONFIG_PROGRESS_DIR,
17
+ SESSIONS_DB as CONFIG_SESSIONS_DB,
18
+ DEBUG_FILE,
19
+ FALLBACK_AGENT,
20
+ PLAYWRIGHT_PORTS_DIR,
21
+ SESSION_INBOX_DIR,
22
+ SESSION_WORKSPACE_DIR,
23
+ } from "#platform/config";
24
+ import { readJsonFile } from "#platform/jsonl";
25
+ import { Database } from "#storage/sqlite";
26
+ import type { AgentKind } from "#types";
27
+ // NOTE: import from "#types" — NOT "@/agents". The agents barrel re-exports
28
+ // `maestroProvider`, which transitively pulls in `maestro-agent-sdk`. That SDK
29
+ // runs `bootstrapHostPath()` at module-load and writes `[log] env-bootstrap…`
30
+ // to *stdout* via its default console logger. Because this MCP server speaks
31
+ // JSON-RPC over stdio, any rogue stdout write corrupts the channel and codex's
32
+ // rmcp parser (`serde error expected value at line 1 column 2`) drops every
33
+ // tool on this server. Pulling these symbols straight from `@/types` keeps the
34
+ // SDK out of the import graph.
35
+ import { isAgentKind } from "#types";
36
+
37
+ export { PLAYWRIGHT_PORTS_DIR, SESSION_INBOX_DIR };
38
+
39
+ // --- Constants & CLI args ---
40
+
41
+ export const HOME = homedir();
42
+ export const SESSIONS_DB = CONFIG_SESSIONS_DB;
43
+ export const PROGRESS_DIR = CONFIG_PROGRESS_DIR;
44
+
45
+ const args = process.argv.slice(2);
46
+ export const userId = parseUserIdArg(args);
47
+ export const currentTopic = args.find((a) => a.startsWith("--topic="))?.split("=")[1] || "";
48
+ export const currentTopicId =
49
+ args.find((a) => a.startsWith("--topic-id="))?.slice("--topic-id=".length) || "";
50
+ export const peerHostQueryId =
51
+ args.find((a) => a.startsWith("--peer-host-query-id="))?.slice("--peer-host-query-id=".length) ||
52
+ "";
53
+ export const currentDepth = Number(
54
+ args.find((a) => a.startsWith("--depth="))?.split("=")[1] ?? "0",
55
+ );
56
+ // When true, the session is a silent fork generating an ask_session reply —
57
+ // outbound tools (ask_session/tell_session/abort_session) are not registered.
58
+ export const isReplyOnly = args.includes("--reply-only=true");
59
+ const _agentArg = args.find((a) => a.startsWith("--agent="))?.split("=")[1];
60
+ if (_agentArg !== undefined && !isAgentKind(_agentArg)) {
61
+ throw new Error(`Invalid --agent arg: ${_agentArg}`);
62
+ }
63
+ export const currentAgent: AgentKind = _agentArg ?? FALLBACK_AGENT;
64
+
65
+ // Max tell_session relay depth — env parse/기본값 로직은 @/platform/config 로 이동.
66
+ export { MAX_TELL_DEPTH } from "#platform/config";
67
+ export const MAX_MESSAGE_LENGTH = 10_000;
68
+ export const USER_CWD = SESSION_WORKSPACE_DIR;
69
+
70
+ // --- DB helper ---
71
+
72
+ export function withDb<T>(
73
+ fn: (db: InstanceType<typeof Database>) => T,
74
+ opts?: { write?: boolean },
75
+ ): T {
76
+ const db = new Database(SESSIONS_DB, opts?.write ? undefined : { readonly: true });
77
+ try {
78
+ db.exec(opts?.write ? "PRAGMA busy_timeout = 5000" : "PRAGMA busy_timeout = 3000");
79
+ if (opts?.write) db.exec("PRAGMA journal_mode = WAL");
80
+ return fn(db);
81
+ } finally {
82
+ db.close();
83
+ }
84
+ }
85
+
86
+ export const isManagerTopic = currentTopicId
87
+ ? withDb((db) => {
88
+ const row = db.query("SELECT kind FROM api_topics WHERE id = ?").get(currentTopicId) as
89
+ | { kind?: string }
90
+ | undefined;
91
+ return row?.kind === "manager";
92
+ })
93
+ : false;
94
+
95
+ // --- JSON field parser helper (shared by topics.ts + topic-config.ts) ---
96
+
97
+ export function parseJsonField<T>(value: string | null | undefined, label: string): T | undefined {
98
+ if (!value) return undefined;
99
+ try {
100
+ return JSON.parse(value) as T;
101
+ } catch (e) {
102
+ process.stderr.write(`warn: session-comm: failed to parse ${label}: ${e}\n`);
103
+ return undefined;
104
+ }
105
+ }
106
+
107
+ // --- Debug helper ---
108
+
109
+ let _isDebugUser: boolean | undefined;
110
+ export function isDebugUser(): boolean {
111
+ if (_isDebugUser !== undefined) return _isDebugUser;
112
+ const users = readJsonFile<number[]>(DEBUG_FILE);
113
+ _isDebugUser = !!users?.includes(Number(userId));
114
+ return _isDebugUser;
115
+ }