cli-jaw 2.1.1 → 2.1.3

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 (477) hide show
  1. package/README.ja.md +8 -6
  2. package/README.ko.md +8 -6
  3. package/README.md +18 -11
  4. package/README.zh-CN.md +8 -6
  5. package/dist/bin/cli-jaw.js +19 -3
  6. package/dist/bin/cli-jaw.js.map +1 -1
  7. package/dist/bin/commands/browser.js +1 -1
  8. package/dist/bin/commands/browser.js.map +1 -1
  9. package/dist/bin/commands/chat-search.js +71 -0
  10. package/dist/bin/commands/chat-search.js.map +1 -0
  11. package/dist/bin/commands/chat.js +98 -58
  12. package/dist/bin/commands/chat.js.map +1 -1
  13. package/dist/bin/commands/dispatch.js +130 -5
  14. package/dist/bin/commands/dispatch.js.map +1 -1
  15. package/dist/bin/commands/goal.js +66 -9
  16. package/dist/bin/commands/goal.js.map +1 -1
  17. package/dist/bin/commands/history.js +61 -0
  18. package/dist/bin/commands/history.js.map +1 -0
  19. package/dist/bin/commands/lock.js +104 -0
  20. package/dist/bin/commands/lock.js.map +1 -0
  21. package/dist/bin/commands/memory.js +23 -2
  22. package/dist/bin/commands/memory.js.map +1 -1
  23. package/dist/bin/commands/orchestrate.js.map +1 -1
  24. package/dist/bin/commands/project.js +4 -1
  25. package/dist/bin/commands/project.js.map +1 -1
  26. package/dist/bin/commands/tui/fullscreen-mode.js +237 -0
  27. package/dist/bin/commands/tui/fullscreen-mode.js.map +1 -0
  28. package/dist/bin/commands/tui/input-handler.js +139 -52
  29. package/dist/bin/commands/tui/input-handler.js.map +1 -1
  30. package/dist/bin/commands/tui/overlays.js +177 -70
  31. package/dist/bin/commands/tui/overlays.js.map +1 -1
  32. package/dist/bin/commands/tui/renderer.js +70 -20
  33. package/dist/bin/commands/tui/renderer.js.map +1 -1
  34. package/dist/bin/commands/tui/simple-mode.js +1 -2
  35. package/dist/bin/commands/tui/simple-mode.js.map +1 -1
  36. package/dist/bin/commands/tui/tui-io.js +8 -0
  37. package/dist/bin/commands/tui/tui-io.js.map +1 -0
  38. package/dist/bin/commands/tui/types.js +9 -0
  39. package/dist/bin/commands/tui/types.js.map +1 -1
  40. package/dist/bin/commands/tui/ws-handler.js +115 -23
  41. package/dist/bin/commands/tui/ws-handler.js.map +1 -1
  42. package/dist/bin/commands/worker.js +159 -0
  43. package/dist/bin/commands/worker.js.map +1 -0
  44. package/dist/bin/postinstall.js +0 -76
  45. package/dist/bin/postinstall.js.map +1 -1
  46. package/dist/lib/mcp/format-converters.js +21 -2
  47. package/dist/lib/mcp/format-converters.js.map +1 -1
  48. package/dist/lib/mcp/mcp-registry.js +75 -0
  49. package/dist/lib/mcp/mcp-registry.js.map +1 -0
  50. package/dist/lib/stt.js +1 -1
  51. package/dist/lib/stt.js.map +1 -1
  52. package/dist/lib/upload.js +7 -0
  53. package/dist/lib/upload.js.map +1 -1
  54. package/dist/scripts/fresh-install-smoke.js +1 -1
  55. package/dist/scripts/fresh-install-smoke.js.map +1 -1
  56. package/dist/server.js +151 -22
  57. package/dist/server.js.map +1 -1
  58. package/dist/src/agent/agy-runtime.js +1 -0
  59. package/dist/src/agent/agy-runtime.js.map +1 -1
  60. package/dist/src/agent/agy-transcript-watcher.js +78 -0
  61. package/dist/src/agent/agy-transcript-watcher.js.map +1 -0
  62. package/dist/src/agent/agy-transcript.js +158 -0
  63. package/dist/src/agent/agy-transcript.js.map +1 -0
  64. package/dist/src/agent/args.js +51 -5
  65. package/dist/src/agent/args.js.map +1 -1
  66. package/dist/src/agent/claude-e-runtime.js +1 -1
  67. package/dist/src/agent/claude-e-runtime.js.map +1 -1
  68. package/dist/src/agent/codex-app-client.js +2 -2
  69. package/dist/src/agent/codex-app-client.js.map +1 -1
  70. package/dist/src/agent/cursor-runtime.js +1 -0
  71. package/dist/src/agent/cursor-runtime.js.map +1 -1
  72. package/dist/src/agent/error-classifier.js +6 -1
  73. package/dist/src/agent/error-classifier.js.map +1 -1
  74. package/dist/src/agent/events/claude.js +1 -0
  75. package/dist/src/agent/events/claude.js.map +1 -1
  76. package/dist/src/agent/events/codex.js +0 -44
  77. package/dist/src/agent/events/codex.js.map +1 -1
  78. package/dist/src/agent/events/helpers.js +52 -5
  79. package/dist/src/agent/events/helpers.js.map +1 -1
  80. package/dist/src/agent/events/opencode.js +6 -2
  81. package/dist/src/agent/events/opencode.js.map +1 -1
  82. package/dist/src/agent/events/summary.js +3 -2
  83. package/dist/src/agent/events/summary.js.map +1 -1
  84. package/dist/src/agent/events/tool-labels.js +8 -5
  85. package/dist/src/agent/events/tool-labels.js.map +1 -1
  86. package/dist/src/agent/kiro-auth.js +213 -0
  87. package/dist/src/agent/kiro-auth.js.map +1 -0
  88. package/dist/src/agent/kiro-models.js +79 -0
  89. package/dist/src/agent/kiro-models.js.map +1 -0
  90. package/dist/src/agent/kiro-runtime.js +314 -0
  91. package/dist/src/agent/kiro-runtime.js.map +1 -0
  92. package/dist/src/agent/lifecycle-handler.js +208 -23
  93. package/dist/src/agent/lifecycle-handler.js.map +1 -1
  94. package/dist/src/agent/memory-flush-controller.js +3 -4
  95. package/dist/src/agent/memory-flush-controller.js.map +1 -1
  96. package/dist/src/agent/resume-classifier.js +18 -1
  97. package/dist/src/agent/resume-classifier.js.map +1 -1
  98. package/dist/src/agent/session-persistence.js +2 -2
  99. package/dist/src/agent/session-persistence.js.map +1 -1
  100. package/dist/src/agent/smoke-detector.js +3 -0
  101. package/dist/src/agent/smoke-detector.js.map +1 -1
  102. package/dist/src/agent/spawn/queue.js +11 -5
  103. package/dist/src/agent/spawn/queue.js.map +1 -1
  104. package/dist/src/agent/spawn/resume.js +6 -1
  105. package/dist/src/agent/spawn/resume.js.map +1 -1
  106. package/dist/src/agent/spawn-env.js +1 -1
  107. package/dist/src/agent/spawn-env.js.map +1 -1
  108. package/dist/src/agent/spawn.js +231 -43
  109. package/dist/src/agent/spawn.js.map +1 -1
  110. package/dist/src/agent/watchdog.js +1 -1
  111. package/dist/src/agent/watchdog.js.map +1 -1
  112. package/dist/src/browser/actions.js +0 -8
  113. package/dist/src/browser/actions.js.map +1 -1
  114. package/dist/src/browser/adaptive-fetch/browser-escalation.js +5 -29
  115. package/dist/src/browser/adaptive-fetch/browser-escalation.js.map +1 -1
  116. package/dist/src/browser/adaptive-fetch/browser-runtime.js +5 -14
  117. package/dist/src/browser/adaptive-fetch/browser-runtime.js.map +1 -1
  118. package/dist/src/browser/adaptive-fetch/browser-session.js +14 -28
  119. package/dist/src/browser/adaptive-fetch/browser-session.js.map +1 -1
  120. package/dist/src/browser/adaptive-fetch/challenge-detector.js +4 -26
  121. package/dist/src/browser/adaptive-fetch/challenge-detector.js.map +1 -1
  122. package/dist/src/browser/adaptive-fetch/content-scorer.js +5 -29
  123. package/dist/src/browser/adaptive-fetch/content-scorer.js.map +1 -1
  124. package/dist/src/browser/adaptive-fetch/endpoint-resolvers.js +0 -64
  125. package/dist/src/browser/adaptive-fetch/endpoint-resolvers.js.map +1 -1
  126. package/dist/src/browser/adaptive-fetch/fetcher.js +4 -19
  127. package/dist/src/browser/adaptive-fetch/fetcher.js.map +1 -1
  128. package/dist/src/browser/adaptive-fetch/human-loop.js +0 -26
  129. package/dist/src/browser/adaptive-fetch/human-loop.js.map +1 -1
  130. package/dist/src/browser/adaptive-fetch/index.js +74 -145
  131. package/dist/src/browser/adaptive-fetch/index.js.map +1 -1
  132. package/dist/src/browser/adaptive-fetch/metadata.js +1 -41
  133. package/dist/src/browser/adaptive-fetch/metadata.js.map +1 -1
  134. package/dist/src/browser/adaptive-fetch/output.js +1 -18
  135. package/dist/src/browser/adaptive-fetch/output.js.map +1 -1
  136. package/dist/src/browser/adaptive-fetch/reader-adapters.js +43 -75
  137. package/dist/src/browser/adaptive-fetch/reader-adapters.js.map +1 -1
  138. package/dist/src/browser/adaptive-fetch/safety.js +5 -48
  139. package/dist/src/browser/adaptive-fetch/safety.js.map +1 -1
  140. package/dist/src/browser/adaptive-fetch/third-party-readers.js +0 -11
  141. package/dist/src/browser/adaptive-fetch/third-party-readers.js.map +1 -1
  142. package/dist/src/browser/adaptive-fetch/trace.js +3 -18
  143. package/dist/src/browser/adaptive-fetch/trace.js.map +1 -1
  144. package/dist/src/browser/adaptive-fetch/transforms.js +0 -23
  145. package/dist/src/browser/adaptive-fetch/transforms.js.map +1 -1
  146. package/dist/src/browser/adaptive-fetch/types.js +3 -0
  147. package/dist/src/browser/adaptive-fetch/types.js.map +1 -0
  148. package/dist/src/browser/adaptive-fetch/validators.js +0 -17
  149. package/dist/src/browser/adaptive-fetch/validators.js.map +1 -1
  150. package/dist/src/browser/adaptive-fetch/waf-profiles.js +0 -10
  151. package/dist/src/browser/adaptive-fetch/waf-profiles.js.map +1 -1
  152. package/dist/src/browser/connection.js +1 -1
  153. package/dist/src/browser/connection.js.map +1 -1
  154. package/dist/src/browser/web-ai/chatgpt-response.js +0 -1
  155. package/dist/src/browser/web-ai/chatgpt-response.js.map +1 -1
  156. package/dist/src/browser/web-ai/chatgpt.js +0 -33
  157. package/dist/src/browser/web-ai/chatgpt.js.map +1 -1
  158. package/dist/src/browser/web-ai/diagnostics.js +0 -16
  159. package/dist/src/browser/web-ai/diagnostics.js.map +1 -1
  160. package/dist/src/browser/web-ai/doctor.js.map +1 -1
  161. package/dist/src/browser/web-ai/errors.js +1 -1
  162. package/dist/src/browser/web-ai/errors.js.map +1 -1
  163. package/dist/src/browser/web-ai/grok-live.js +2 -0
  164. package/dist/src/browser/web-ai/grok-live.js.map +1 -1
  165. package/dist/src/cli/claude-models.js +2 -2
  166. package/dist/src/cli/claude-models.js.map +1 -1
  167. package/dist/src/cli/command-context.js.map +1 -1
  168. package/dist/src/cli/commands.js +16 -4
  169. package/dist/src/cli/commands.js.map +1 -1
  170. package/dist/src/cli/compact.js +2 -1
  171. package/dist/src/cli/compact.js.map +1 -1
  172. package/dist/src/cli/handlers/session-handlers.js +34 -0
  173. package/dist/src/cli/handlers/session-handlers.js.map +1 -0
  174. package/dist/src/cli/handlers-project.js.map +1 -1
  175. package/dist/src/cli/handlers-runtime.js +2 -0
  176. package/dist/src/cli/handlers-runtime.js.map +1 -1
  177. package/dist/src/cli/handlers-workflows.js +85 -17
  178. package/dist/src/cli/handlers-workflows.js.map +1 -1
  179. package/dist/src/cli/handlers.js +84 -43
  180. package/dist/src/cli/handlers.js.map +1 -1
  181. package/dist/src/cli/readiness.js +17 -1
  182. package/dist/src/cli/readiness.js.map +1 -1
  183. package/dist/src/cli/registry-live.js +17 -0
  184. package/dist/src/cli/registry-live.js.map +1 -0
  185. package/dist/src/cli/registry.js +51 -10
  186. package/dist/src/cli/registry.js.map +1 -1
  187. package/dist/src/cli/tui/composer.js +135 -5
  188. package/dist/src/cli/tui/composer.js.map +1 -1
  189. package/dist/src/cli/tui/diffview.js +41 -0
  190. package/dist/src/cli/tui/diffview.js.map +1 -0
  191. package/dist/src/cli/tui/editor.js +45 -0
  192. package/dist/src/cli/tui/editor.js.map +1 -0
  193. package/dist/src/cli/tui/file-mention.js +78 -0
  194. package/dist/src/cli/tui/file-mention.js.map +1 -0
  195. package/dist/src/cli/tui/highlight.js +82 -0
  196. package/dist/src/cli/tui/highlight.js.map +1 -0
  197. package/dist/src/cli/tui/keymap.js +21 -0
  198. package/dist/src/cli/tui/keymap.js.map +1 -1
  199. package/dist/src/cli/tui/markdown.js +124 -0
  200. package/dist/src/cli/tui/markdown.js.map +1 -0
  201. package/dist/src/cli/tui/mode.js +26 -0
  202. package/dist/src/cli/tui/mode.js.map +1 -0
  203. package/dist/src/cli/tui/overlay.js +121 -98
  204. package/dist/src/cli/tui/overlay.js.map +1 -1
  205. package/dist/src/cli/tui/render/frame.js +72 -0
  206. package/dist/src/cli/tui/render/frame.js.map +1 -0
  207. package/dist/src/cli/tui/render/layout.js +22 -0
  208. package/dist/src/cli/tui/render/layout.js.map +1 -0
  209. package/dist/src/cli/tui/render/mouse.js +25 -0
  210. package/dist/src/cli/tui/render/mouse.js.map +1 -0
  211. package/dist/src/cli/tui/render/scheduler.js +37 -0
  212. package/dist/src/cli/tui/render/scheduler.js.map +1 -0
  213. package/dist/src/cli/tui/render/viewport.js +82 -0
  214. package/dist/src/cli/tui/render/viewport.js.map +1 -0
  215. package/dist/src/cli/tui/renderers.js +37 -0
  216. package/dist/src/cli/tui/renderers.js.map +1 -1
  217. package/dist/src/cli/tui/stream.js +48 -0
  218. package/dist/src/cli/tui/stream.js.map +1 -0
  219. package/dist/src/cli/tui/text-buffer.js +143 -0
  220. package/dist/src/cli/tui/text-buffer.js.map +1 -0
  221. package/dist/src/cli/tui/theme.js +128 -0
  222. package/dist/src/cli/tui/theme.js.map +1 -0
  223. package/dist/src/cli/tui/transcript.js +4 -0
  224. package/dist/src/cli/tui/transcript.js.map +1 -1
  225. package/dist/src/command-contract/help-renderer.js +1 -1
  226. package/dist/src/command-contract/help-renderer.js.map +1 -1
  227. package/dist/src/core/chat-sessions.js +63 -0
  228. package/dist/src/core/chat-sessions.js.map +1 -0
  229. package/dist/src/core/cli-detection.js +14 -0
  230. package/dist/src/core/cli-detection.js.map +1 -1
  231. package/dist/src/core/compact.js +43 -11
  232. package/dist/src/core/compact.js.map +1 -1
  233. package/dist/src/core/config.js +14 -1
  234. package/dist/src/core/config.js.map +1 -1
  235. package/dist/src/core/db.js +52 -12
  236. package/dist/src/core/db.js.map +1 -1
  237. package/dist/src/core/employees.js +2 -2
  238. package/dist/src/core/employees.js.map +1 -1
  239. package/dist/src/core/main-session.js +3 -2
  240. package/dist/src/core/main-session.js.map +1 -1
  241. package/dist/src/discord/bot.js.map +1 -1
  242. package/dist/src/discord/discord-file.js +1 -1
  243. package/dist/src/discord/discord-file.js.map +1 -1
  244. package/dist/src/discord/send-only-client.js +1 -3
  245. package/dist/src/discord/send-only-client.js.map +1 -1
  246. package/dist/src/goal/heartbeat.js +113 -7
  247. package/dist/src/goal/heartbeat.js.map +1 -1
  248. package/dist/src/goal/store.js +44 -5
  249. package/dist/src/goal/store.js.map +1 -1
  250. package/dist/src/ide/diff.js +37 -0
  251. package/dist/src/ide/diff.js.map +1 -1
  252. package/dist/src/manager/launchd-service.js +1 -1
  253. package/dist/src/manager/launchd-service.js.map +1 -1
  254. package/dist/src/manager/lifecycle-helpers.js +14 -0
  255. package/dist/src/manager/lifecycle-helpers.js.map +1 -1
  256. package/dist/src/manager/lifecycle-store.js.map +1 -1
  257. package/dist/src/manager/lifecycle.js +94 -5
  258. package/dist/src/manager/lifecycle.js.map +1 -1
  259. package/dist/src/manager/notes/routes.js +0 -31
  260. package/dist/src/manager/notes/routes.js.map +1 -1
  261. package/dist/src/manager/notes/ws.js.map +1 -1
  262. package/dist/src/manager/preview-link-policy.js +11 -4
  263. package/dist/src/manager/preview-link-policy.js.map +1 -1
  264. package/dist/src/manager/registry.js.map +1 -1
  265. package/dist/src/manager/routes/dashboard-memory.js.map +1 -1
  266. package/dist/src/manager/scan.js +36 -1
  267. package/dist/src/manager/scan.js.map +1 -1
  268. package/dist/src/manager/server.js +42 -7
  269. package/dist/src/manager/server.js.map +1 -1
  270. package/dist/src/manager/shutdown.js +8 -3
  271. package/dist/src/manager/shutdown.js.map +1 -1
  272. package/dist/src/manager/systemd-service.js +1 -1
  273. package/dist/src/manager/systemd-service.js.map +1 -1
  274. package/dist/src/memory/indexing.js +49 -1
  275. package/dist/src/memory/indexing.js.map +1 -1
  276. package/dist/src/memory/keyword-expand.js +0 -187
  277. package/dist/src/memory/keyword-expand.js.map +1 -1
  278. package/dist/src/memory/runtime.js +3 -2
  279. package/dist/src/memory/runtime.js.map +1 -1
  280. package/dist/src/memory/shared.js.map +1 -1
  281. package/dist/src/messaging/send.js +66 -7
  282. package/dist/src/messaging/send.js.map +1 -1
  283. package/dist/src/orchestrator/collect.js +3 -2
  284. package/dist/src/orchestrator/collect.js.map +1 -1
  285. package/dist/src/orchestrator/distribute.js +9 -3
  286. package/dist/src/orchestrator/distribute.js.map +1 -1
  287. package/dist/src/orchestrator/friction.js +59 -0
  288. package/dist/src/orchestrator/friction.js.map +1 -0
  289. package/dist/src/orchestrator/gateway.js +4 -4
  290. package/dist/src/orchestrator/gateway.js.map +1 -1
  291. package/dist/src/orchestrator/pipeline.js +89 -45
  292. package/dist/src/orchestrator/pipeline.js.map +1 -1
  293. package/dist/src/orchestrator/sanitize.js +69 -0
  294. package/dist/src/orchestrator/sanitize.js.map +1 -0
  295. package/dist/src/orchestrator/seed.js +50 -0
  296. package/dist/src/orchestrator/seed.js.map +1 -0
  297. package/dist/src/orchestrator/state-machine.js +215 -76
  298. package/dist/src/orchestrator/state-machine.js.map +1 -1
  299. package/dist/src/orchestrator/worker-monitor.js +1 -1
  300. package/dist/src/orchestrator/worker-monitor.js.map +1 -1
  301. package/dist/src/orchestrator/worker-progress.js +29 -0
  302. package/dist/src/orchestrator/worker-progress.js.map +1 -0
  303. package/dist/src/orchestrator/worker-registry.js +63 -2
  304. package/dist/src/orchestrator/worker-registry.js.map +1 -1
  305. package/dist/src/prompt/builder.js +190 -115
  306. package/dist/src/prompt/builder.js.map +1 -1
  307. package/dist/src/prompt/templates/a1-system.md +124 -61
  308. package/dist/src/prompt/templates/control-system.md +2 -2
  309. package/dist/src/prompt/templates/employee.md +41 -56
  310. package/dist/src/prompt/templates/orchestration.md +23 -8
  311. package/dist/src/prompt/templates/skills.md +6 -0
  312. package/dist/src/routes/browser.js +3 -3
  313. package/dist/src/routes/browser.js.map +1 -1
  314. package/dist/src/routes/employees.js +4 -4
  315. package/dist/src/routes/employees.js.map +1 -1
  316. package/dist/src/routes/goal.js +48 -4
  317. package/dist/src/routes/goal.js.map +1 -1
  318. package/dist/src/routes/heartbeat.js +1 -1
  319. package/dist/src/routes/heartbeat.js.map +1 -1
  320. package/dist/src/routes/i18n.js +1 -1
  321. package/dist/src/routes/i18n.js.map +1 -1
  322. package/dist/src/routes/jaw-memory.js +1 -1
  323. package/dist/src/routes/jaw-memory.js.map +1 -1
  324. package/dist/src/routes/memory.js.map +1 -1
  325. package/dist/src/routes/messaging.js +8 -3
  326. package/dist/src/routes/messaging.js.map +1 -1
  327. package/dist/src/routes/orchestrate.js +267 -117
  328. package/dist/src/routes/orchestrate.js.map +1 -1
  329. package/dist/src/routes/quota-agy-reverse.js +6 -5
  330. package/dist/src/routes/quota-agy-reverse.js.map +1 -1
  331. package/dist/src/routes/quota-kiro-reverse.js +187 -0
  332. package/dist/src/routes/quota-kiro-reverse.js.map +1 -0
  333. package/dist/src/routes/quota.js +68 -6
  334. package/dist/src/routes/quota.js.map +1 -1
  335. package/dist/src/routes/settings.js +37 -8
  336. package/dist/src/routes/settings.js.map +1 -1
  337. package/dist/src/routes/skills.js.map +1 -1
  338. package/dist/src/security/path-guards.js +0 -3
  339. package/dist/src/security/path-guards.js.map +1 -1
  340. package/dist/src/security/security-audit-log.js.map +1 -1
  341. package/dist/src/shared/shell-command-display.js +51 -0
  342. package/dist/src/shared/shell-command-display.js.map +1 -0
  343. package/dist/src/telegram/bot.js.map +1 -1
  344. package/dist/src/trace/store.js +32 -0
  345. package/dist/src/trace/store.js.map +1 -1
  346. package/dist/src/types/cli-engine.js +1 -0
  347. package/dist/src/types/cli-engine.js.map +1 -1
  348. package/dist/src/workflows/employee-boundary.js +7 -7
  349. package/dist/src/workflows/employee-boundary.js.map +1 -1
  350. package/dist/src/workflows/handoff.js +16 -16
  351. package/dist/src/workflows/handoff.js.map +1 -1
  352. package/dist/src/workflows/scope-sandbox.js +58 -0
  353. package/dist/src/workflows/scope-sandbox.js.map +1 -0
  354. package/package.json +1 -1
  355. package/public/assets/providers/kiro-color.svg +15 -0
  356. package/public/assets/providers/kiro.svg +14 -0
  357. package/public/css/chat.css +27 -0
  358. package/public/css/modals.css +79 -0
  359. package/public/css/orc-state.css +10 -6
  360. package/public/css/sidebar.css +85 -0
  361. package/public/css/variables.css +6 -2
  362. package/public/dist/assets/{Agent-CIUGaUVn.js → Agent-DBKNQ6tp.js} +1 -1
  363. package/public/dist/assets/{DocPanel-C1pGgE-S.js → DocPanel-BU16GUlB.js} +1 -1
  364. package/public/dist/assets/DocPanel-gU-WkgyA.css +1 -0
  365. package/public/dist/assets/Employees-F0ssNuO-.js +1 -0
  366. package/public/dist/assets/{FolderPanel-DSkanaGN.js → FolderPanel-DT0fU8f2.js} +1 -1
  367. package/public/dist/assets/Heartbeat-fsuLzY9c.js +1 -0
  368. package/public/dist/assets/Mcp-_Yq4N3Sk.js +4 -0
  369. package/public/dist/assets/{Memory-BgN8djV4.js → Memory-CRR8kotI.js} +1 -1
  370. package/public/dist/assets/{ModelProvider-DHQ1Zvw0.js → ModelProvider-DQgISbKw.js} +1 -1
  371. package/public/dist/assets/agent-meta-C4mauPL5.js +1 -0
  372. package/public/dist/assets/app-0OQhPpTG.css +1 -0
  373. package/public/dist/assets/app-CSqIyg9A.js +55 -0
  374. package/public/dist/assets/constants-BHMkzpN_.js +1 -0
  375. package/public/dist/assets/{employees-DiNLzEXS.js → employees-CZdWHH-r.js} +7 -7
  376. package/public/dist/assets/idb-cache-0LNMskFB.js +1 -0
  377. package/public/dist/assets/idb-cache-5H89a4l8.js +1 -0
  378. package/public/dist/assets/manager-CGTQ5EIm.js +12 -0
  379. package/public/dist/assets/manager-UBunDqMH.css +1 -0
  380. package/public/dist/assets/{memory-B_nn0A4v.js → memory-B3QSmj_e.js} +9 -9
  381. package/public/dist/assets/memory-DOBqiuPc.js +1 -0
  382. package/public/dist/assets/{provider-icons-XEfJPGTe.js → provider-icons-Crv6yvlG.js} +35 -6
  383. package/public/dist/assets/render-J11oxfnl.js +28 -0
  384. package/public/dist/assets/settings-BV_2Bh0_.js +1 -0
  385. package/public/dist/assets/settings-m_cim0ov.js +151 -0
  386. package/public/dist/assets/sidebar-ZBXdBgF7.js +49 -0
  387. package/public/dist/assets/{skills-COa4iSwL.js → skills-CjaBmIPV.js} +6 -6
  388. package/public/dist/assets/skills-Xm3fl1zr.js +1 -0
  389. package/public/dist/assets/{slash-commands-C_ePvrPi.js → slash-commands-71jc07Wf.js} +1 -1
  390. package/public/dist/assets/slash-commands-BO6ssnAz.js +1 -0
  391. package/public/dist/assets/{trace-drawer-CY4z_Ie8.js → trace-drawer-Czvf2P2o.js} +1 -1
  392. package/public/dist/assets/ui-CverZJnd.js +1 -0
  393. package/public/dist/assets/ui-qKxy-4p9.js +142 -0
  394. package/public/dist/index.html +113 -86
  395. package/public/dist/manager/index.html +2 -2
  396. package/public/index.html +111 -84
  397. package/public/js/constants.ts +34 -11
  398. package/public/js/features/chat-messages.ts +13 -2
  399. package/public/js/features/chat-search.ts +5 -1
  400. package/public/js/features/chat.ts +86 -44
  401. package/public/js/features/employees.ts +7 -2
  402. package/public/js/features/idb-cache.ts +4 -0
  403. package/public/js/features/media-lightbox.ts +40 -0
  404. package/public/js/features/message-history.ts +55 -7
  405. package/public/js/features/pending-queue.ts +28 -4
  406. package/public/js/features/process-block.ts +32 -17
  407. package/public/js/features/process-log-adapter.ts +11 -2
  408. package/public/js/features/settings-cli-status-render.ts +161 -0
  409. package/public/js/features/settings-cli-status.ts +136 -187
  410. package/public/js/features/settings-core.ts +5 -1
  411. package/public/js/features/settings-mcp.ts +513 -7
  412. package/public/js/features/settings-types.ts +1 -0
  413. package/public/js/features/settings.ts +1 -1
  414. package/public/js/features/tool-ui.ts +18 -4
  415. package/public/js/features/transport-status-row.ts +3 -0
  416. package/public/js/features/ui-status.ts +5 -1
  417. package/public/js/main.ts +5 -0
  418. package/public/js/preview-parent-origin.ts +14 -0
  419. package/public/js/provider-icons.ts +6 -1
  420. package/public/js/render/file-links.ts +10 -2
  421. package/public/js/render/markdown.ts +16 -0
  422. package/public/js/ui.ts +8 -1
  423. package/public/js/virtual-scroll.ts +19 -0
  424. package/public/js/ws.ts +151 -15
  425. package/public/locales/en.json +9 -2
  426. package/public/locales/ja.json +9 -2
  427. package/public/locales/ko.json +9 -2
  428. package/public/locales/zh.json +9 -2
  429. package/public/manager/src/App.tsx +13 -70
  430. package/public/manager/src/InstancePreview.tsx +56 -11
  431. package/public/manager/src/SidebarRailRouter.tsx +32 -4
  432. package/public/manager/src/components/InstanceRow.tsx +0 -1
  433. package/public/manager/src/components/WorkbenchHeader.tsx +17 -7
  434. package/public/manager/src/doc-panel/DocPanel.tsx +14 -1
  435. package/public/manager/src/doc-panel/doc-panel.css +16 -0
  436. package/public/manager/src/folder-panel/FolderPanel.tsx +13 -2
  437. package/public/manager/src/hooks/useElectronDroppedPaths.ts +89 -0
  438. package/public/manager/src/hooks/useInstanceMessageEvents.ts +4 -15
  439. package/public/manager/src/manager-components.css +35 -1
  440. package/public/manager/src/manager-polish.css +11 -1
  441. package/public/manager/src/manager-shortcut-runner.ts +107 -0
  442. package/public/manager/src/manager-shortcuts.ts +15 -0
  443. package/public/manager/src/panels/desktop-bridge.ts +13 -0
  444. package/public/manager/src/settings/SettingsSidebar.tsx +1 -1
  445. package/public/manager/src/settings/components/sidebar-filter.ts +2 -0
  446. package/public/manager/src/settings/pages/Mcp.tsx +311 -43
  447. package/public/manager/src/settings/pages/components/McpServerCard.tsx +89 -55
  448. package/public/manager/src/settings/pages/components/agent/agent-meta.ts +32 -9
  449. package/public/manager/src/settings/pages/components/employees-helpers.ts +1 -0
  450. package/public/manager/src/settings/pages/components/heartbeat-helpers.ts +1 -0
  451. package/public/manager/src/settings/pages/mcp-helpers.ts +54 -12
  452. package/public/manager/src/settings/types.ts +1 -0
  453. package/public/manager/src/types.ts +2 -1
  454. package/scripts/fresh-install-smoke.ts +1 -1
  455. package/scripts/smoke/agy-transcript-tail-smoke.mjs +89 -0
  456. package/public/dist/assets/DocPanel-CL1scIfq.css +0 -1
  457. package/public/dist/assets/Employees-BL9MAzzx.js +0 -1
  458. package/public/dist/assets/Heartbeat-D64JCg4t.js +0 -1
  459. package/public/dist/assets/Mcp-wYwJA_61.js +0 -3
  460. package/public/dist/assets/agent-meta-BhEbjy4P.js +0 -1
  461. package/public/dist/assets/app-2Rz848bQ.js +0 -48
  462. package/public/dist/assets/app-BW-Xz2p6.css +0 -1
  463. package/public/dist/assets/constants-ahKI_aEG.js +0 -1
  464. package/public/dist/assets/idb-cache-BnZfG5FD.js +0 -1
  465. package/public/dist/assets/idb-cache-CZ3JdK8r.js +0 -1
  466. package/public/dist/assets/manager-CfyZloIP.js +0 -12
  467. package/public/dist/assets/manager-DyrwJLZr.css +0 -1
  468. package/public/dist/assets/memory-CCjNFWmO.js +0 -1
  469. package/public/dist/assets/render-CgDEBoCX.js +0 -28
  470. package/public/dist/assets/settings-CY_6tGuS.js +0 -1
  471. package/public/dist/assets/settings-DWW4GzNc.js +0 -66
  472. package/public/dist/assets/sidebar-DQKE5IA1.js +0 -23
  473. package/public/dist/assets/skills-DYTlqoke.js +0 -1
  474. package/public/dist/assets/slash-commands-D7CU5oso.js +0 -1
  475. package/public/dist/assets/ui-Car1OUvV.js +0 -1
  476. package/public/dist/assets/ui-DK_9ndj0.js +0 -140
  477. /package/public/dist/assets/{locale-DT1WRaeJ.js → locale-BHMJIzyw.js} +0 -0
@@ -0,0 +1,107 @@
1
+ import type { DashboardDetailTab, DashboardInstance, DashboardShortcutAction, DashboardSidebarMode } from './types';
2
+ import { panelShortcutBus } from './panels/panel-shortcut-bus';
3
+ import { getDesktop } from './panels/desktop-bridge';
4
+
5
+ /**
6
+ * Dependencies the manager keyboard-shortcut runner needs from <App>.
7
+ * Extracted from App.tsx to keep the component under the dashboard line budget;
8
+ * behaviour is identical — App passes current render values on every call.
9
+ */
10
+ export interface ManagerShortcutRunnerDeps {
11
+ selectedInstance: DashboardInstance | null;
12
+ filtered: DashboardInstance[];
13
+ activeDetailTab: DashboardDetailTab;
14
+ setDrawerOpen: (open: boolean) => void;
15
+ handleSidebarModeChange: (mode: DashboardSidebarMode) => void;
16
+ handlePreview: (instance: DashboardInstance) => void;
17
+ selectRelativeInstance: (direction: 1 | -1) => void;
18
+ handleTabChange: (tab: DashboardDetailTab) => void;
19
+ setPreviewRefreshKey: (updater: (key: number) => number) => void;
20
+ handleSidebarToggle: () => void;
21
+ }
22
+
23
+ export function runManagerShortcut(action: DashboardShortcutAction, deps: ManagerShortcutRunnerDeps): void {
24
+ if (panelShortcutBus.dispatch(action)) return;
25
+ if (action === 'focusInstances') {
26
+ deps.handleSidebarModeChange('instances');
27
+ deps.setDrawerOpen(false);
28
+ return;
29
+ }
30
+ if (action === 'focusActiveSession') {
31
+ const target = deps.selectedInstance?.ok
32
+ ? deps.selectedInstance
33
+ : deps.filtered.find(instance => instance.ok) || null;
34
+ if (target) deps.handlePreview(target);
35
+ else deps.handleSidebarModeChange('instances');
36
+ return;
37
+ }
38
+ if (action === 'focusNotes') {
39
+ deps.handleSidebarModeChange('notes');
40
+ deps.setDrawerOpen(false);
41
+ return;
42
+ }
43
+ if (action === 'previousInstance') {
44
+ deps.selectRelativeInstance(-1);
45
+ return;
46
+ }
47
+ if (action === 'nextInstance') {
48
+ deps.selectRelativeInstance(1);
49
+ return;
50
+ }
51
+ if (action === 'closeFocusedTab') {
52
+ const active = document.activeElement;
53
+ if (active?.closest('.browser-webview-stack, .browser-tab-strip')) {
54
+ document.dispatchEvent(new CustomEvent('jaw:shortcut-action', { detail: 'closeBrowserTab' }));
55
+ } else if (active?.closest('.terminal-panel, .xterm')) {
56
+ document.dispatchEvent(new CustomEvent('jaw:shortcut-action', { detail: 'closeTerminalTab' }));
57
+ } else if (active?.closest('.bottom-panel')) {
58
+ panelShortcutBus.dispatch('closeActiveBottomTab');
59
+ } else if (active?.closest('.right-panel')) {
60
+ panelShortcutBus.dispatch('toggleRightPanel');
61
+ }
62
+ return;
63
+ }
64
+ if (action === 'switchTab1') { deps.handleTabChange('overview'); return; }
65
+ if (action === 'switchTab2') { deps.handleTabChange('preview'); return; }
66
+ if (action === 'switchTab3') { deps.handleTabChange('logs'); return; }
67
+ if (action === 'switchTab4') { deps.handleTabChange('settings'); return; }
68
+ if (action === 'previousTab' || action === 'nextTab') {
69
+ const tabs: DashboardDetailTab[] = ['overview', 'preview', 'logs', 'settings'];
70
+ const idx = tabs.indexOf(deps.activeDetailTab);
71
+ const dir = action === 'nextTab' ? 1 : -1;
72
+ deps.handleTabChange(tabs[(idx + dir + tabs.length) % tabs.length]);
73
+ return;
74
+ }
75
+ if (action === 'browserReload' || action === 'browserHardReload' || action === 'browserFocusUrl' || action === 'browserBack' || action === 'browserForward') {
76
+ const el = document.activeElement;
77
+ if (el?.closest('.browser-webview-stack, .browser-tab-strip, .browser-panel')) {
78
+ document.dispatchEvent(new CustomEvent('jaw:shortcut-action', { detail: action }));
79
+ return;
80
+ }
81
+ // Focus is NOT in the browser panel → reload acts on the preview or app.
82
+ // Preview focus refreshes the preview for both soft and hard (the iframe
83
+ // has no separate hard-reload concept).
84
+ if (action === 'browserReload' || action === 'browserHardReload') {
85
+ if (el?.closest('.preview-panel')) {
86
+ deps.setPreviewRefreshKey(key => key + 1);
87
+ } else if (action === 'browserReload') {
88
+ getDesktop()?.reloadWindow?.();
89
+ } else {
90
+ getDesktop()?.hardReloadWindow?.();
91
+ }
92
+ }
93
+ // browserFocusUrl / Back / Forward are browser-only → no-op outside the panel.
94
+ return;
95
+ }
96
+ if (action === 'terminalClear' || action === 'terminalNewTab') {
97
+ const el = document.activeElement;
98
+ if (el?.closest('.terminal-panel, .xterm')) {
99
+ document.dispatchEvent(new CustomEvent('jaw:shortcut-action', { detail: action }));
100
+ }
101
+ return;
102
+ }
103
+ if (action === 'toggleLeftSidebar') {
104
+ deps.handleSidebarToggle();
105
+ return;
106
+ }
107
+ }
@@ -134,12 +134,27 @@ export function shortcutMatches(event: KeyboardEvent, raw: string): boolean {
134
134
  && resolveEventKey(event) === parsed.key;
135
135
  }
136
136
 
137
+ /**
138
+ * Actions whose keyboard shortcut is owned by the Electron application menu
139
+ * accelerator (single source of truth), so the renderer keydown matcher must
140
+ * NOT also match them — otherwise ⌘R fires twice (menu + keydown). The actions
141
+ * stay in MANAGER_SHORTCUT_ACTIONS so the menu can still dispatch them by name;
142
+ * only the renderer-side keyboard binding is suppressed. This also makes a
143
+ * persisted user keymap carrying Meta+R harmless, and preserves the browser's
144
+ * native ⌘R in a pure web build (no menu, no match → no preventDefault).
145
+ */
146
+ export const RENDERER_DISABLED_SHORTCUT_ACTIONS = new Set<DashboardShortcutAction>([
147
+ 'browserReload',
148
+ 'browserHardReload',
149
+ ]);
150
+
137
151
  export function actionForShortcutEvent(
138
152
  event: KeyboardEvent,
139
153
  keymap: unknown,
140
154
  ): DashboardShortcutAction | null {
141
155
  const shortcuts = normalizeManagerShortcutKeymap(keymap);
142
156
  for (const action of MANAGER_SHORTCUT_ACTIONS) {
157
+ if (RENDERER_DISABLED_SHORTCUT_ACTIONS.has(action)) continue;
143
158
  if (shortcutMatches(event, shortcuts[action])) return action;
144
159
  if (MANAGER_SHORTCUT_ALIASES[action]?.some(shortcut => shortcutMatches(event, shortcut))) return action;
145
160
  }
@@ -47,6 +47,16 @@ export type FolderBridgeApi = {
47
47
  onDirChange: (cb: (dirPath: string) => void) => () => void;
48
48
  };
49
49
 
50
+ export type DroppedPathEntry = {
51
+ name: string;
52
+ path: string;
53
+ kind: 'file' | 'directory';
54
+ };
55
+
56
+ export type DragDropBridgeApi = {
57
+ resolveDroppedItems: (files: File[]) => Promise<{ ok: boolean; entries?: DroppedPathEntry[]; rejected?: Array<{ path: string; reason: string }>; error?: string }>;
58
+ };
59
+
50
60
  export type ShortcutBridgeApi = {
51
61
  onAction: (cb: (action: DashboardShortcutAction) => void) => () => void;
52
62
  };
@@ -87,10 +97,13 @@ export type CliJawDesktopApi = {
87
97
  terminal?: TerminalBridgeApi | undefined;
88
98
  diff?: DiffBridgeApi | undefined;
89
99
  folder?: FolderBridgeApi | undefined;
100
+ dragDrop?: DragDropBridgeApi | undefined;
90
101
  clipboard?: ClipboardBridgeApi | undefined;
91
102
  permissions?: PermissionDiagnosticsBridgeApi | undefined;
92
103
  shortcuts?: ShortcutBridgeApi | undefined;
93
104
  browser?: BrowserBridgeApi | undefined;
105
+ reloadWindow?: (() => void) | undefined;
106
+ hardReloadWindow?: (() => void) | undefined;
94
107
  };
95
108
 
96
109
  export function getDesktop(): CliJawDesktopApi | null {
@@ -21,9 +21,9 @@ const CATEGORIES: SidebarEntry[] = [
21
21
  { id: 'channels-discord', label: 'Channels — Discord', group: 'channels' },
22
22
  { id: 'heartbeat', label: 'Heartbeat & schedules', group: 'automation' },
23
23
  { id: 'memory', label: 'Memory', group: 'automation' },
24
+ { id: 'mcp', label: 'MCP servers', group: 'mcp' },
24
25
  { id: 'speech', label: 'Speech & keys', group: 'integrations' },
25
26
  { id: 'prompts', label: 'Prompts', group: 'integrations' },
26
- { id: 'mcp', label: 'MCP servers', group: 'integrations' },
27
27
  { id: 'browser', label: 'Browser / CDP', group: 'integrations' },
28
28
  { id: 'network', label: 'Network', group: 'network-security' },
29
29
  { id: 'permissions', label: 'Permissions', group: 'network-security' },
@@ -14,6 +14,7 @@ export type SidebarEntry = {
14
14
  export const SIDEBAR_GROUP_ORDER: ReadonlyArray<SettingsCategoryGroup> = [
15
15
  'runtime',
16
16
  'identity',
17
+ 'mcp',
17
18
  'channels',
18
19
  'automation',
19
20
  'integrations',
@@ -24,6 +25,7 @@ export const SIDEBAR_GROUP_ORDER: ReadonlyArray<SettingsCategoryGroup> = [
24
25
  export const SIDEBAR_GROUP_LABELS: Record<SettingsCategoryGroup, string> = {
25
26
  runtime: 'Runtime',
26
27
  identity: 'Identity',
28
+ mcp: 'MCP',
27
29
  channels: 'Channels',
28
30
  automation: 'Automation',
29
31
  integrations: 'Integrations',
@@ -1,13 +1,3 @@
1
- // Phase 8 — MCP page: structured editor for server cards plus an Advanced
2
- // JSON view, plus action buttons for sync/install/reset against the
3
- // unified MCP config (`/api/mcp`).
4
- //
5
- // The page owns one synthetic dirty-store key (`mcp.config`) carrying the
6
- // full normalized config. Save PUTs back with `toPersistShape` to keep the
7
- // on-disk JSON minimal. Render-time validation (duplicate names, missing
8
- // commands, malformed env) gates the dirty entry's `valid` flag — invalid
9
- // configs cannot save.
10
-
11
1
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
12
2
  import type { SettingsPageProps } from '../types';
13
3
  import { JsonEditorField } from '../fields';
@@ -21,7 +11,9 @@ import {
21
11
  import { InlineWarn } from './components/InlineWarn';
22
12
  import { McpServerCard } from './components/McpServerCard';
23
13
  import {
14
+ countInstallBundleCandidates,
24
15
  findDuplicateNames,
16
+ getServerTag,
25
17
  makeEmptyServer,
26
18
  newServerName,
27
19
  normalizeMcpConfig,
@@ -46,6 +38,9 @@ type SyncResultsPayload = {
46
38
  servers?: unknown;
47
39
  };
48
40
 
41
+ type ModalTab = 'active' | 'add';
42
+ type AddSubTab = 'local' | 'remote';
43
+
49
44
  export default function Mcp({ port, client, dirty, registerSave }: SettingsPageProps) {
50
45
  const { state, refresh, setData } = usePageSnapshot<unknown>(client, '/api/mcp');
51
46
  const [draft, setDraft] = useState<McpConfig>({ servers: {} });
@@ -55,6 +50,18 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
55
50
  const [actionState, setActionState] = useState<ActionResult>({ kind: 'idle' });
56
51
  const actionRunIdRef = useRef(0);
57
52
 
53
+ const [modalOpen, setModalOpen] = useState(false);
54
+ const [modalTab, setModalTab] = useState<ModalTab>('active');
55
+ const [addSubTab, setAddSubTab] = useState<AddSubTab>('local');
56
+ const [addName, setAddName] = useState('');
57
+ const [addCommand, setAddCommand] = useState('');
58
+ const [addArgs, setAddArgs] = useState('');
59
+ const [addEnv, setAddEnv] = useState('');
60
+ const [addUrl, setAddUrl] = useState('');
61
+ const [addHeaders, setAddHeaders] = useState('');
62
+ const [addError, setAddError] = useState<string | null>(null);
63
+ const [addPending, setAddPending] = useState(false);
64
+
58
65
  const original = useMemo<McpConfig>(
59
66
  () => (state.kind === 'ready' ? normalizeMcpConfig(state.data) : { servers: {} }),
60
67
  [state],
@@ -69,9 +76,7 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
69
76
  }, [state]);
70
77
 
71
78
  useEffect(() => {
72
- return () => {
73
- dirty.remove(DIRTY_KEY);
74
- };
79
+ return () => { dirty.remove(DIRTY_KEY); };
75
80
  }, [dirty]);
76
81
 
77
82
  const names = useMemo(() => Object.keys(draft.servers), [draft]);
@@ -85,6 +90,10 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
85
90
  const hasFieldErrors = validations.some((v) => v.result.kind === 'invalid');
86
91
  const hasDupes = duplicates.size > 0;
87
92
  const isValid = !hasFieldErrors && !hasDupes && advancedValid;
93
+ const bundleCandidates = useMemo(
94
+ () => countInstallBundleCandidates(draft.servers),
95
+ [draft],
96
+ );
88
97
 
89
98
  const writeDirty = useCallback(
90
99
  (next: McpConfig, valid: boolean) => {
@@ -101,7 +110,6 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
101
110
  (next: McpConfig, nextOrder?: string[]) => {
102
111
  setDraft(next);
103
112
  if (nextOrder) setOrder(nextOrder);
104
- // Recompute validity on the next draft, not the stale `isValid`.
105
113
  const ns = nextOrder ?? Object.keys(next.servers);
106
114
  const dupes = findDuplicateNames(ns);
107
115
  const fieldOk = ns.every(
@@ -115,8 +123,6 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
115
123
  const onRenameServer = useCallback(
116
124
  (oldName: string, nextName: string) => {
117
125
  if (oldName === nextName) return;
118
- // Preserve insertion order; keep the same card in place even if the
119
- // new name collides with a later card (validation surfaces the dupe).
120
126
  const nextOrder = order.map((n) => (n === oldName ? nextName : n));
121
127
  const nextServers: Record<string, McpServer> = {};
122
128
  for (const n of nextOrder) {
@@ -185,9 +191,6 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
185
191
  if (!(DIRTY_KEY in bundle)) return;
186
192
  const body = bundle[DIRTY_KEY];
187
193
  const updated = await client.put<unknown>('/api/mcp', body);
188
- // /api/mcp PUT returns `{ ok, servers: string[] }` — re-fetch to get the
189
- // canonical persisted shape rather than reusing the (possibly trimmed)
190
- // request body.
191
194
  dirty.clear();
192
195
  const fresh = await client.get<unknown>('/api/mcp').catch(() => updated);
193
196
  const normalized = normalizeMcpConfig(fresh);
@@ -211,9 +214,7 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
211
214
  !window.confirm(
212
215
  `You have unsaved MCP edits. ${label} will use the on-disk config, not your unsaved changes. Continue?`,
213
216
  )
214
- ) {
215
- return;
216
- }
217
+ ) { return; }
217
218
  }
218
219
  const runId = actionRunIdRef.current + 1;
219
220
  actionRunIdRef.current = runId;
@@ -236,6 +237,94 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
236
237
  [client, dirty],
237
238
  );
238
239
 
240
+ const resetAddForm = useCallback(() => {
241
+ setAddName('');
242
+ setAddCommand('');
243
+ setAddArgs('');
244
+ setAddEnv('');
245
+ setAddUrl('');
246
+ setAddHeaders('');
247
+ setAddError(null);
248
+ setAddPending(false);
249
+ }, []);
250
+
251
+ const openModal = useCallback(() => {
252
+ setModalTab('active');
253
+ resetAddForm();
254
+ setModalOpen(true);
255
+ }, [resetAddForm]);
256
+
257
+ const closeModal = useCallback(() => {
258
+ setModalOpen(false);
259
+ resetAddForm();
260
+ }, [resetAddForm]);
261
+
262
+ const handleAddAndSync = useCallback(async () => {
263
+ const name = addName.trim() || newServerName(Object.keys(original.servers));
264
+ const server: McpServer = addSubTab === 'remote'
265
+ ? { url: addUrl.trim(), ...(addHeaders.trim() ? { headers: parseSimpleKV(addHeaders) } : {}) }
266
+ : {
267
+ command: addCommand.trim(),
268
+ ...(addArgs.trim() ? { args: addArgs.split(/\r?\n|,/).map(s => s.trim()).filter(Boolean) } : {}),
269
+ ...(addEnv.trim() ? { env: parseSimpleKV(addEnv) } : {}),
270
+ };
271
+
272
+ const validation = validateServer(name, server);
273
+ if (validation.kind === 'invalid') {
274
+ setAddError(validation.reason);
275
+ return;
276
+ }
277
+ if (original.servers[name]) {
278
+ setAddError(`Server "${name}" already exists.`);
279
+ return;
280
+ }
281
+
282
+ setAddPending(true);
283
+ setAddError(null);
284
+
285
+ try {
286
+ const merged: McpConfig = {
287
+ ...original,
288
+ servers: { ...original.servers, [name]: server },
289
+ };
290
+ await client.put<unknown>('/api/mcp', toPersistShape(merged));
291
+ await client.post<unknown>('/api/mcp/sync', {});
292
+ dirty.clear();
293
+ const fresh = await client.get<unknown>('/api/mcp').catch(() => merged);
294
+ const normalized = normalizeMcpConfig(fresh);
295
+ setDraft(normalized);
296
+ setOrder(Object.keys(normalized.servers));
297
+ setData(fresh);
298
+ await refresh();
299
+ closeModal();
300
+ } catch (err: unknown) {
301
+ setAddError(err instanceof Error ? err.message : String(err));
302
+ setAddPending(false);
303
+ }
304
+ }, [addName, addSubTab, addUrl, addHeaders, addCommand, addArgs, addEnv, original, client, dirty, refresh, setData, closeModal]);
305
+
306
+ const handleRemoveFromModal = useCallback(async (name: string) => {
307
+ const nextServers = { ...original.servers };
308
+ delete nextServers[name];
309
+ const merged: McpConfig = { ...original, servers: nextServers };
310
+ try {
311
+ await client.put<unknown>('/api/mcp', toPersistShape(merged));
312
+ await client.post<unknown>('/api/mcp/sync', {});
313
+ dirty.clear();
314
+ const fresh = await client.get<unknown>('/api/mcp').catch(() => merged);
315
+ const normalized = normalizeMcpConfig(fresh);
316
+ setDraft(normalized);
317
+ setOrder(Object.keys(normalized.servers));
318
+ setData(fresh);
319
+ await refresh();
320
+ } catch (err: unknown) {
321
+ setActionState({
322
+ kind: 'error',
323
+ message: err instanceof Error ? err.message : String(err),
324
+ });
325
+ }
326
+ }, [original, client, dirty, refresh, setData]);
327
+
239
328
  if (state.kind === 'loading') return <PageLoading />;
240
329
  if (state.kind === 'offline') return <PageOffline port={port} />;
241
330
  if (state.kind === 'error') return <PageError message={state.message} />;
@@ -277,15 +366,6 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
277
366
  );
278
367
  })}
279
368
  </div>
280
- <div className="mcp-servers-actions">
281
- <button
282
- type="button"
283
- className="settings-action"
284
- onClick={onAddServer}
285
- >
286
- Add server
287
- </button>
288
- </div>
289
369
  {hasDupes && (
290
370
  <InlineWarn role="alert">
291
371
  Duplicate server name{dupeNames.length === 1 ? '' : 's'}: {dupeNames.join(', ')}.
@@ -294,7 +374,7 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
294
374
  )}
295
375
  {hasFieldErrors && !hasDupes && (
296
376
  <InlineWarn role="alert">
297
- Some servers have errors (missing command or invalid name).
377
+ Some servers have errors (missing command/URL or invalid name).
298
378
  Saving is blocked until they are fixed.
299
379
  </InlineWarn>
300
380
  )}
@@ -305,6 +385,13 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
305
385
  hint="These act on the saved config. Save edits first if you want them included."
306
386
  >
307
387
  <div className="mcp-action-buttons">
388
+ <button
389
+ type="button"
390
+ className="settings-action"
391
+ onClick={openModal}
392
+ >
393
+ + Add, Set MCP
394
+ </button>
308
395
  <button
309
396
  type="button"
310
397
  className="settings-action"
@@ -316,10 +403,10 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
316
403
  <button
317
404
  type="button"
318
405
  className="settings-action"
319
- disabled={actionState.kind === 'pending'}
406
+ disabled={actionState.kind === 'pending' || bundleCandidates === 0}
320
407
  onClick={() => void runAction('Install bundle', '/api/mcp/install')}
321
408
  >
322
- Install bundle
409
+ Install bundle ({bundleCandidates})
323
410
  </button>
324
411
  <button
325
412
  type="button"
@@ -331,9 +418,7 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
331
418
  !window.confirm(
332
419
  'Reset MCP config to defaults? Your custom servers will be removed.',
333
420
  )
334
- ) {
335
- return;
336
- }
421
+ ) { return; }
337
422
  void runAction('Reset to defaults', '/api/mcp/reset');
338
423
  }}
339
424
  >
@@ -341,14 +426,10 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
341
426
  </button>
342
427
  </div>
343
428
  {actionState.kind === 'pending' && (
344
- <p className="settings-section-hint" role="status">
345
- {actionState.label}…
346
- </p>
429
+ <p className="settings-section-hint" role="status">{actionState.label}…</p>
347
430
  )}
348
431
  {actionState.kind === 'success' && (
349
- <p className="settings-section-hint" role="status">
350
- ✅ {actionState.message}
351
- </p>
432
+ <p className="settings-section-hint" role="status">{actionState.message}</p>
352
433
  )}
353
434
  {actionState.kind === 'error' && (
354
435
  <InlineWarn role="alert">{actionState.message}</InlineWarn>
@@ -382,6 +463,193 @@ export default function Mcp({ port, client, dirty, registerSave }: SettingsPageP
382
463
  </p>
383
464
  )}
384
465
  </SettingsSection>
466
+
467
+ {modalOpen && (
468
+ <div className="mcp-modal-backdrop" onClick={closeModal}>
469
+ <div
470
+ className="mcp-modal"
471
+ role="dialog"
472
+ aria-modal="true"
473
+ aria-label="MCP Server Management"
474
+ onClick={(e) => e.stopPropagation()}
475
+ >
476
+ <div className="mcp-modal-tabs">
477
+ <button
478
+ type="button"
479
+ className={`mcp-modal-tab${modalTab === 'active' ? ' is-active' : ''}`}
480
+ onClick={() => setModalTab('active')}
481
+ >
482
+ Active Servers
483
+ </button>
484
+ <button
485
+ type="button"
486
+ className={`mcp-modal-tab${modalTab === 'add' ? ' is-active' : ''}`}
487
+ onClick={() => { setModalTab('add'); resetAddForm(); }}
488
+ >
489
+ Add New
490
+ </button>
491
+ </div>
492
+
493
+ <div className="mcp-modal-body">
494
+ {modalTab === 'active' && (
495
+ <div className="mcp-active-list">
496
+ {Object.keys(original.servers).length === 0 && (
497
+ <p className="settings-section-hint">No active MCP servers.</p>
498
+ )}
499
+ {Object.entries(original.servers).map(([name, srv]) => {
500
+ const tag = getServerTag(srv);
501
+ return (
502
+ <div key={name} className="mcp-active-row">
503
+ <span className="mcp-active-name">{name}</span>
504
+ <span className="mcp-active-detail">
505
+ {srv.url || [srv.command, ...(srv.args || [])].filter(Boolean).join(' ')}
506
+ </span>
507
+ {tag && <span className="mcp-server-tag" data-tag={tag}>[{tag}]</span>}
508
+ <button
509
+ type="button"
510
+ className="settings-action settings-action-discard mcp-active-remove"
511
+ onClick={() => void handleRemoveFromModal(name)}
512
+ >
513
+ Remove
514
+ </button>
515
+ </div>
516
+ );
517
+ })}
518
+ </div>
519
+ )}
520
+
521
+ {modalTab === 'add' && (
522
+ <div className="mcp-add-form">
523
+ <div className="mcp-add-subtabs">
524
+ <button
525
+ type="button"
526
+ className={`mcp-modal-tab${addSubTab === 'local' ? ' is-active' : ''}`}
527
+ onClick={() => setAddSubTab('local')}
528
+ >
529
+ Local
530
+ </button>
531
+ <button
532
+ type="button"
533
+ className={`mcp-modal-tab${addSubTab === 'remote' ? ' is-active' : ''}`}
534
+ onClick={() => setAddSubTab('remote')}
535
+ >
536
+ Remote
537
+ </button>
538
+ </div>
539
+
540
+ <label className="settings-field settings-field-text">
541
+ <span className="settings-field-label">Name</span>
542
+ <input
543
+ type="text"
544
+ value={addName}
545
+ placeholder="my-server"
546
+ spellCheck={false}
547
+ onChange={(e) => setAddName(e.target.value)}
548
+ />
549
+ </label>
550
+
551
+ {addSubTab === 'local' ? (
552
+ <>
553
+ <label className="settings-field settings-field-text">
554
+ <span className="settings-field-label">Command</span>
555
+ <input
556
+ type="text"
557
+ value={addCommand}
558
+ placeholder="npx"
559
+ spellCheck={false}
560
+ onChange={(e) => setAddCommand(e.target.value)}
561
+ />
562
+ </label>
563
+ <label className="settings-field settings-field-text">
564
+ <span className="settings-field-label">Args (one per line)</span>
565
+ <textarea
566
+ value={addArgs}
567
+ rows={3}
568
+ spellCheck={false}
569
+ placeholder="-y&#10;@upstash/context7-mcp"
570
+ onChange={(e) => setAddArgs(e.target.value)}
571
+ />
572
+ </label>
573
+ <label className="settings-field settings-field-text">
574
+ <span className="settings-field-label">Env (KEY=value per line)</span>
575
+ <textarea
576
+ value={addEnv}
577
+ rows={2}
578
+ spellCheck={false}
579
+ onChange={(e) => setAddEnv(e.target.value)}
580
+ />
581
+ </label>
582
+ </>
583
+ ) : (
584
+ <>
585
+ <label className="settings-field settings-field-text">
586
+ <span className="settings-field-label">URL</span>
587
+ <input
588
+ type="text"
589
+ value={addUrl}
590
+ placeholder="https://mcp.example.com/sse"
591
+ spellCheck={false}
592
+ onChange={(e) => setAddUrl(e.target.value)}
593
+ />
594
+ </label>
595
+ <label className="settings-field settings-field-text">
596
+ <span className="settings-field-label">Headers (KEY=value per line)</span>
597
+ <textarea
598
+ value={addHeaders}
599
+ rows={2}
600
+ spellCheck={false}
601
+ onChange={(e) => setAddHeaders(e.target.value)}
602
+ />
603
+ </label>
604
+ </>
605
+ )}
606
+
607
+ {addError && <InlineWarn role="alert">{addError}</InlineWarn>}
608
+
609
+ <div className="mcp-add-actions">
610
+ <button
611
+ type="button"
612
+ className="settings-action"
613
+ onClick={closeModal}
614
+ >
615
+ Cancel
616
+ </button>
617
+ <button
618
+ type="button"
619
+ className="settings-action"
620
+ disabled={addPending}
621
+ onClick={() => void handleAddAndSync()}
622
+ >
623
+ {addPending ? 'Adding…' : 'Add & Sync'}
624
+ </button>
625
+ </div>
626
+ </div>
627
+ )}
628
+ </div>
629
+
630
+ <button
631
+ type="button"
632
+ className="mcp-modal-close"
633
+ aria-label="Close"
634
+ onClick={closeModal}
635
+ >
636
+ &times;
637
+ </button>
638
+ </div>
639
+ </div>
640
+ )}
385
641
  </form>
386
642
  );
387
643
  }
644
+
645
+ function parseSimpleKV(text: string): Record<string, string> {
646
+ const out: Record<string, string> = {};
647
+ for (const line of text.split(/\r?\n/)) {
648
+ const t = line.trim();
649
+ if (!t || t.startsWith('#')) continue;
650
+ const eq = t.indexOf('=');
651
+ if (eq === -1) continue;
652
+ out[t.slice(0, eq).trim()] = t.slice(eq + 1);
653
+ }
654
+ return out;
655
+ }